Greasy Fork API

Get information from Greasy Fork and do actions in it.

当前为 2023-09-02 提交的版本,查看 最新版本

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