Greasy Fork镜像 API

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

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/445697/1244619/Greasy%20Fork%20API.js

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

QingJ © 2025

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