Greasy Fork镜像 API

Get information from Greasy Fork镜像 and do actions in it.

当前为 2023-08-19 提交的版本,查看 最新版本

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.gf.qytechs.cn/scripts/445697/1237548/Greasy%20Fork%20API.js

  1. // ==UserScript==
  2. // @name Greasy Fork镜像 API
  3. // @namespace -
  4. // @version 2.0.0
  5. // @description Get information from Greasy Fork镜像 and do actions in it.
  6. // @author NotYou
  7. // @license LGPL-3.0
  8. // @connect gf.qytechs.cn
  9. // @grant GM_xmlhttpRequest
  10. // @grant GM_openInTab
  11. // ==/UserScript==
  12.  
  13. class GreasyFork {
  14. constructor() {
  15. if(location.host === 'gf.qytechs.cn' || location.host === 'sleazyfork.org') {
  16. this.host = location.host
  17. return
  18. }
  19.  
  20. throw new Error('Invalid instance initialization location, host is not valid.')
  21. }
  22.  
  23. static get INVALID_ARGUMENT_ERROR() {
  24. return 'Argument "{0}" is not valid'
  25. }
  26.  
  27. static get PARSING_ERROR() {
  28. return 'Unexpected parsing error, "{0}"'
  29. }
  30.  
  31. static get INVALID_PAGE_ERROR() {
  32. return 'Current page is not valid'
  33. }
  34.  
  35. static __format(str, ...args) {
  36. let result = str
  37.  
  38. for (let i = 0; i < args.length; i++) {
  39. const arg = args[i]
  40.  
  41. result = result.replace(new RegExp(`\\{${i}\\}`, 'g'), arg)
  42. }
  43.  
  44. return result
  45. }
  46.  
  47. static __isId(id) {
  48. return typeof id === 'string' && /^\d+$/.test(id)
  49. }
  50.  
  51. static get languages() {
  52. return [
  53. 'ar', 'bg', 'cs', 'da', 'de', 'el', 'en', 'eo', 'es', 'fi', 'fr', 'fr-CA', 'he', 'hu', 'id', 'it', 'ja', 'ka', 'ko', 'nb', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sr', 'sv', 'th', 'tr', 'uk', 'ug', 'vi', 'zh-CN', 'zh-TW'
  54. ]
  55. }
  56.  
  57. static get version() {
  58. return '2.0.0'
  59. }
  60.  
  61. static parseScriptNode(node) {
  62. if (!(node instanceof HTMLElement) || !node.dataset.scriptId) {
  63. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'node'))
  64. }
  65.  
  66. const {
  67. scriptId,
  68. scriptName,
  69. scriptAuthors,
  70. scriptDailyInstalls,
  71. scriptTotalInstalls,
  72. scriptRatingScore,
  73. scriptCreatedDate,
  74. scriptUpdatedDate,
  75. scriptType,
  76. scriptVersion,
  77. sensitive,
  78. scriptLanguage,
  79. cssAvailableAsJs
  80. } = node.dataset
  81.  
  82. const ratingsNode = node.querySelector('dd.script-list-ratings')
  83. let ratings = {}
  84.  
  85. if(ratingsNode) {
  86. const ratingsGood = Number(ratingsNode.querySelector('.good-rating-count').textContent)
  87. const ratingsOk = Number(ratingsNode.querySelector('.ok-rating-count').textContent)
  88. const ratingsBad = Number(ratingsNode.querySelector('.bad-rating-count').textContent)
  89.  
  90. ratings = {
  91. ratingsGood,
  92. ratingsOk,
  93. ratingsBad
  94. }
  95. }
  96.  
  97. return Object.assign({
  98. scriptId,
  99. scriptName,
  100. scriptAuthors: JSON.parse(scriptAuthors),
  101. scriptDailyInstalls: Number(scriptDailyInstalls),
  102. scriptTotalInstalls: Number(scriptTotalInstalls),
  103. scriptRatingScore: Number(scriptRatingScore),
  104. scriptCreatedDate,
  105. scriptUpdatedDate,
  106. scriptType,
  107. scriptVersion,
  108. sensitive: sensitive === 'true',
  109. scriptLanguage,
  110. cssAvailableAsJs: cssAvailableAsJs === 'true',
  111. node
  112. }, ratings)
  113. }
  114.  
  115. static parseScriptMetadata(code) {
  116. if (typeof code !== 'string') {
  117. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'code'))
  118. }
  119.  
  120. const reScriptMetadata = /\/\/ ==UserScript==\n(.*?[\s\S]+)\n\/\/ ==\/UserScript==/
  121. const matched = code.match(reScriptMetadata)
  122.  
  123. if (!Boolean(matched)) {
  124. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'code'))
  125. }
  126.  
  127. const metadataResponse = {}
  128. const metadata = matched[1]
  129.  
  130. const metadataChunks = metadata.split('\n')
  131.  
  132. for (let i = 0; i < metadataChunks.length; i++) {
  133. const metadataChunk = metadataChunks[i]
  134.  
  135. try {
  136. const { metaKey, metaValue } = metadataChunk.match(/\/\/ @(?<metaKey>[a-zA-Z\-\d\:]+)\s+(?<metaValue>.+)/).groups
  137.  
  138. metadataResponse[metaKey] = metaValue
  139. } catch(error) {
  140. throw new Error(GreasyFork.__format(GreasyFork.PARSING_ERROR, error))
  141. }
  142. }
  143.  
  144. return metadataResponse
  145. }
  146.  
  147. static getScriptData(id) {
  148. if (!GreasyFork.__isId(id)) {
  149. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  150. }
  151.  
  152. return new Promise((res, rej) => {
  153. GM_xmlhttpRequest({
  154. url: `https://gf.qytechs.cn/scripts/${id}.json`,
  155. onload: response => {
  156. const data = JSON.parse(response.responseText)
  157.  
  158. return res(data)
  159. },
  160. onerror: err => {
  161. return rej(err)
  162. }
  163. })
  164. })
  165. }
  166.  
  167. static getScriptCode(id) {
  168. if (!GreasyFork.__isId(id)) {
  169. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  170. }
  171.  
  172. return new Promise((res, rej) => {
  173. GM_xmlhttpRequest({
  174. url: `https://gf.qytechs.cn/scripts/${id}/code/userscript.user.js`,
  175. onload: response => {
  176. const code = response.responseText
  177.  
  178. return res(code)
  179. },
  180. onerror: err => {
  181. return rej(err)
  182. }
  183. })
  184. })
  185. }
  186.  
  187. static getScriptHistory(id) {
  188. if (!GreasyFork.__isId(id)) {
  189. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  190. }
  191.  
  192. return new Promise((res, rej) => {
  193. GM_xmlhttpRequest({
  194. url: `https://gf.qytechs.cn/scripts/${id}/versions.json`,
  195. onload: response => {
  196. const data = JSON.parse(response.responseText)
  197.  
  198. return res(data)
  199. },
  200. onerror: err => {
  201. return rej(err)
  202. }
  203. })
  204. })
  205. }
  206.  
  207. static getScriptStats(id) {
  208. if (!GreasyFork.__isId(id)) {
  209. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  210. }
  211.  
  212. return new Promise((res, rej) => {
  213. GM_xmlhttpRequest({
  214. url: `https://gf.qytechs.cn/scripts/${id}/stats.json`,
  215. onload: response => {
  216. const data = JSON.parse(response.responseText)
  217.  
  218. return res(data)
  219. },
  220. onerror: err => {
  221. return rej(err)
  222. }
  223. })
  224. })
  225. }
  226.  
  227. static getScriptSet(id, page = 1) {
  228. if (!GreasyFork.__isId(id)) {
  229. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  230. }
  231.  
  232. if (typeof page !== 'number') {
  233. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'page'))
  234. }
  235.  
  236. return new Promise((res, rej) => {
  237. GM_xmlhttpRequest({
  238. url: `https://gf.qytechs.cn/scripts.json?set=${id}&page=${page}&filter_locale=0`,
  239. onload: response => {
  240. const data = JSON.parse(response.responseText)
  241.  
  242. return res(data)
  243. },
  244. onerror: err => {
  245. return rej(err)
  246. }
  247. })
  248. })
  249. }
  250.  
  251. static getUserData(id) {
  252. if (!GreasyFork.__isId(id)) {
  253. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  254. }
  255.  
  256. return new Promise((res, rej) => {
  257. GM_xmlhttpRequest({
  258. url: `https://gf.qytechs.cn/users/${id}.json`,
  259. onload: response => {
  260. const data = JSON.parse(response.responseText)
  261.  
  262. return res(data)
  263. },
  264. onerror: err => {
  265. return rej(err)
  266. }
  267. })
  268. })
  269. }
  270.  
  271. static searchScripts(query, page = 1) {
  272. if (typeof query !== 'string') {
  273. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'query'))
  274. }
  275.  
  276. if (typeof page !== 'number') {
  277. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'page'))
  278. }
  279.  
  280. return new Promise((res, rej) => {
  281. GM_xmlhttpRequest({
  282. url: `https://gf.qytechs.cn/scripts.json?q=${query}&page=${page}`,
  283. onload: response => {
  284. const data = JSON.parse(response.responseText)
  285.  
  286. return res(data)
  287. },
  288. onerror: err => {
  289. console.error(err)
  290. return rej([])
  291. }
  292. })
  293. })
  294. }
  295.  
  296. static searchUsers(query, page = 1) {
  297. if (typeof query !== 'string') {
  298. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'query'))
  299. }
  300.  
  301. if (typeof page !== 'number') {
  302. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'page'))
  303. }
  304.  
  305. return new Promise((res, rej) => {
  306. GM_xmlhttpRequest({
  307. url: `https://gf.qytechs.cn/users.json?q=${query}&page=${page}`,
  308. onload: response => {
  309. const data = JSON.parse(response.responseText)
  310.  
  311. return res(data)
  312. },
  313. onerror: err => {
  314. console.error(err)
  315. return rej([])
  316. }
  317. })
  318. })
  319. }
  320.  
  321. static installScript(id, type = 'js') {
  322. if (!GreasyFork.__isId(id)) {
  323. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'id'))
  324. }
  325.  
  326. if (type !== 'js' && type !== 'css') {
  327. throw new Error(GreasyFork.__format(GreasyFork.INVALID_ARGUMENT_ERROR, 'type'))
  328. }
  329.  
  330. const URL = `https://gf.qytechs.cn/scripts/${id}/code/userscript.user.${type}`
  331.  
  332. GM_openInTab(URL)
  333. }
  334.  
  335. listScripts() {
  336. const scriptList = document.querySelector('.script-list')
  337.  
  338. if (scriptList === null) {
  339. throw new Error(GreasyFork.INVALID_PAGE_ERROR)
  340. }
  341.  
  342. const userScripts = scriptList.querySelectorAll('[data-script-id]')
  343. const result = []
  344. const typeMap = {
  345. 'browse-script-list': 'browse',
  346. 'user-script-list': 'user'
  347. }
  348. const type = typeMap[scriptList.id] || 'unknown'
  349.  
  350. for (let i = 0; i < userScripts.length; i++) {
  351. const userScript = userScripts[i]
  352.  
  353. result.push(
  354. GreasyFork.parseScriptNode(userScript)
  355. )
  356. }
  357.  
  358. return {
  359. type,
  360. list: result
  361. }
  362. }
  363.  
  364. signOut() {
  365. GM_xmlhttpRequest({
  366. url: `https://${this.host}/users/sign_out`
  367. })
  368. }
  369. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址