// ==UserScript==
// @name Greasy Fork镜像 API
// @namespace -
// @version 1.0.0
// @description Parse information on gf.qytechs.cn
// @author NotYou
// @license LGPL-3.0
// @grant none
// ==/UserScript==
/*
"Program" is a set of instructions that a computer uses to perform a specific function.
"Library" is a program that is embedded in other programs.
"This library" refers to "Greasy Fork镜像 API" library.
"Account" is an arrangement in which a person uses the Internet or email services of a particular company, organization or individual person's web resource.
"I" refers to individual person that owns internet account "NotYou" at web resource "gf.qytechs.cn".
I does not responsible for any
damage caused by this library
*/
/*
TODO:
- [x] Feedback
- [ ] Convesations
- [ ] Versions
*/
class GreasyFork {
error(errorable, message) {
if(!errorable) throw new Error(message)
}
languages() {
return [
'ar', 'bg', 'cs', 'da', 'de', 'el', 'en', 'eo', 'es', 'fi', 'fr', 'fr-CA', 'he', 'hu', 'id', 'it', 'ja', 'ko', 'nb', 'nl', 'pl', 'pt-BR', 'ro', 'ru', 'sk', 'sr', 'sv', 'th', 'tr', 'uk', 'ug', 'vi', 'zh-CN', 'zh-TW'
]
}
parseScriptElement(i) {
if(!i||!i.dataset.scriptAuthors) return null
let _ = null,
data = i.dataset,
rating = i.children[0].children[1].children[7] ? i.children[0].children[1].children[7].children[0].innerText.split('\n').join(' ').split(' ') : _,
scriptLink = i.children[0].children[0].children[0] ? i.children[0].children[0].children[0].href : _,
source = !scriptLink ? _ : data.scriptType == 'library' ? scriptLink.slice(0, scriptLink.indexOf('-'))+'/code/index.js' : scriptLink.slice(0, scriptLink.indexOf('-'))+'/code/script.user.js'
return {
name: data.scriptName,
id: +(data.scriptId),
version: data.scriptVersion,
lang: data.scriptLanguage,
installs: +(data.scriptTotalInstalls),
dailyInstalls: +(data.scriptDailyInstalls),
rating: +(data.scriptRatingScore),
ratingGood: rating ? rating[0] == '0' ? 0 : +(rating[0]) : _,
ratingOk: rating ? rating[1] == '0' ? 0 : +(rating[1]) : _,
ratingBad: rating ? rating[2] == '0' ? 0 : +(rating[2]) : _,
authors: JSON.parse(data.scriptAuthors) || data.scriptAuthors.replaceAll('"', '"'),
createDate: data.scriptCreatedDate,
updateDate: data.scriptUpdatedDate,
type: data.scriptType,
cssAsJs: data.cssAvailableAsJs == 'true' ? true : false,
sensitive: data.sensitive == 'true' ? true : false,
element: i,
url: scriptLink,
source: source
}
}
parseScriptCodeMeta(code) {
if(typeof code != 'string'||!code.match(/\/\/ ==UserScript==/)) return null
var arr = code.replaceAll('\n', '').match(/\/\/ ==UserScript==.*\/\/ ==\/UserScript==/)[0].split('// ')
for (let i = 0; i < 2; i++) arr.shift()
arr.pop()
let result = []
for (let i of arr) {
let data = i.replace(/ . +/, ' ').split(' ')
let name = data[0]
data.shift()
let value = data.join(' ')
result.push({
meta: name,
value: value
})
}
return result
}
ls() {
function ls(id) {
if(document.querySelector(`#${id}`)) {
let result = []
for (let i of document.querySelector(`#${id}`).children) {
result.push(new GreasyFork().parseScriptElement(i))
}
return result
} return null
}
return {
scripts: ls('user-script-list'),
browsed: ls('browse-script-list'),
libraries: ls('user-library-script-list')
}
}
get(q) {
let parser = new DOMParser(),
error = new GreasyFork().error,
langs = new GreasyFork().languages(),
str = str => str.replaceAll('\n', '').replaceAll(' ', ''),
_ = null
return {
script(query = q) {
let errMsg = 'Argument 1 is not defined'
return {
async asJSON(id = query || q) {
error(id, errMsg)
return fetch(`https://gf.qytechs.cn/scripts/${id}.json`).then((r) =>
r.json()
).then((c) => {
return c
})
},
async code(id = query || q) {
error(id, errMsg)
return fetch(`https://gf.qytechs.cn/scripts/${id}/code`).then((r) =>
r.text()
).then((c) => {
return parser.parseFromString(c, 'text/html').querySelector('pre').innerText
})
},
async history(id = query || q) {
error(id, errMsg)
return fetch(`https://gf.qytechs.cn/scripts/${id}/versions`).then((r) =>
r.text()
).then((c) => {
let list = parser.parseFromString(c, 'text/html').querySelectorAll('.history_versions li'), result = []
for (let i of list) {
let ver = i.children[1].children[0],
time = i.children[2],
log = i.children[3]
result.push({
version: {
id: +(ver.href.match(/\?version=\d.+/)[0].replace('?version=', '')),
text: ver.innerText,
url: ver.href
},
time: {
text: time.innerText,
iso: time.attributes.datetime.value
},
changelog: log ? {
html: log.innerHTML,
text: log.innerText
} : _
})
}
return result
})
},
async feedback(id = query || q) {
error(id, errMsg)
return fetch(`https://gf.qytechs.cn/scripts/${id}/feedback`).then((r) =>
r.text()
).then((c) => {
let list = parser.parseFromString(c, 'text/html').querySelectorAll('.discussion-list-container'), result = []
for (let i of list) {
let di = i.children[0],
ti = di.children[1],
mt = di.children[0],
mt0 = mt.children[0],
mt1 = mt.children[1],
a0 = mt0.children[0],
a1 = mt1.children[0].children[0],
url = ti.href,
last = mt1.children[0].children
result.push({
id: +(url.match(/ns\/\d.+/)[0].replace('ns/', '')),
url: url,
rating: ti.children[0].classList.contains('rating-icon') ? ti.children[0].innerText : _,
text: ti.children[1] ? ti.children[1].innerText.replace('\n ', '').replace('\n ', '') : ti.children[0].innerText.replace('\n ', '').replace('\n ', ''),
meta: {
first: {
id: +(a0.href.match(/\d.+-/)[0].replace(/-.+/, '').replace('-', '')),
url: a0.href,
nickname: a0.innerText
},
last: {
id: +(a1.href.match(/\d.+-/)[0].replace(/-.+/, '').replace('-', '')),
url: a1.href,
nickname: a1.innerText
},
time: {
first: {
iso: mt0.children[1].attributes.datetime.value,
text: mt0.children[1].innerText
},
last: last[last.length-1].attributes.datetime ? {
iso: last[last.length-1].attributes.datetime.value,
text: last[last.length-1].innerText
} : _
}
}
})
}
return result.length > 1 ? result : _
})
},
async stats(id = query || q) {
error(id, errMsg)
return fetch(`https://gf.qytechs.cn/scripts/${id}/stats.json`).then((r) =>
r.json()
).then((c) => {
return c
})
},
async info(id = query || q, options = {}) {
error(id, errMsg)
return fetch(str(`https://gf.qytechs.cn/
${options.locale && langs.includes(options.locale) ? options.locale : 'en'}
/scripts/${id}${options.locale ? `&locale_override=1` : ''}`)).then((r) =>
r.text()
).then((c) => {
let doc = parser.parseFromString(c, 'text/html'),
screenshots = _,
addInfo = _,
appliesTo = [],
comp = _,
support = doc.querySelector('#script-feedback-suggestion').children.length == 3 ? support = doc.querySelector('#script-feedback-suggestion').children[0].href : _,
getInfo = value => doc.querySelector(`dd.script-${value}`),
license = getInfo('show-license'),
d_installs = getInfo('show-daily-installs') ? +(getInfo('show-daily-installs').innerText.replaceAll(',', '')) : _,
installs = getInfo('show-total-installs') ? +(getInfo('show-total-installs').innerText.replaceAll(',', '')) : _,
r_g = doc.querySelector('.good-rating-count') ? +(doc.querySelector('.good-rating-count').innerText) : _,
r_o = doc.querySelector('.ok-rating-count') ? +(doc.querySelector('.ok-rating-count').innerText) : _,
r_b = doc.querySelector('.bad-rating-count') ? +(doc.querySelector('.bad-rating-count').innerText) : _,
c_date_text = getInfo('show-created-date').innerText,
c_date_iso = doc.querySelector('dd.script-show-created-date').children[0].children[0].attributes.datetime.value,
u_date_text = getInfo('show-updated-date').innerText,
u_date_iso = doc.querySelector('dd.script-show-updated-date').children[0].children[0].attributes.datetime.value,
global = doc.querySelector('#script-info header'),
install = doc.querySelector('#install-area') ? doc.querySelectorAll('#install-area *:not(.install-help-link)') : _,
data = install ? install[0].dataset : _,
yours = doc.querySelector('#script-links').children,
require = doc.querySelector('#script-content p code') ? doc.querySelector('#script-content p code').innerText.replace('// @require ', '') : _
if(doc.querySelector('.user-screenshots')) {
screenshots = []
doc.querySelectorAll('.user-screenshots a').forEach((e) => {
screenshots.push({
source: e.href,
thumbnail: e.children[0].src
})
})
}
doc.querySelectorAll('dd.script-show-applies-to li').forEach((e) => {
appliesTo.push(e.innerText)
})
if(doc.querySelector('.script-show-compatibility')) {
comp = []
doc.querySelectorAll('.script-show-compatibility img').forEach((e) => {
comp.push({
browser: e.alt.replace('Compatible with ', ''),
value: e.title.replaceAll('\n', ' '),
})
})
}
if(doc.querySelector('#additional-info')) addInfo = doc.querySelector('#additional-info')
return {
id: +(id),
name: global.children[0].innerText,
desc: global.children[1].innerText,
version: getInfo('show-version').innerText,
isYour: yours[yours.length-1].children[0].href.match(/\/admin/) ? true : false,
isPrevVersion: data ? data.isPreviousVersion == 'true' ? true : false : _,
installs: {
daily: d_installs,
total: installs,
},
rating: {
good: r_g,
ok: r_o,
bad: r_b,
},
date: {
created: {
text: c_date_text,
iso: c_date_iso,
},
updated: {
text: u_date_text,
iso: u_date_iso,
}
},
license: {
name: license.innerText,
url: license.children[0].children[0] ? license.children[0].children[0].href : _
},
screenshots: screenshots,
additionalInfo: addInfo ? [
{
html: addInfo.innerHTML,
text: addInfo.innerText
}
] : _,
appliesTo: appliesTo,
compatibility: comp,
supportUrl: support,
requireUrl: require
}
})
},
async set(id = query || q, options = {}) {
error(id, errMsg)
return id && fetch(str(`https://gf.qytechs.cn/
${options.locale && langs.includes(options.locale) ? options.locale : 'en'}
/scripts?set=${id}
${options.sort ? `&sort=${options.sort}` : ''}
${options.page ? `&page=${options.page}` : ''}
${options.localeFilter ? `&filter_locale=${options.localeFilter}` : ''}
${options.locale ? `&locale_override=1` : ''}`)).then(r =>
r.text()
).then((c) => {
let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-script-list li')
for (let i of list) {
result.push(new GreasyFork().parseScriptElement(i))
}
return result
})
},
}
},
async user(id = q) {
error(id, 'Argument 1 is not defined')
return fetch(`https://gf.qytechs.cn/users/${id}`).then((r) =>
r.text()
).then((c) => {
let doc = parser.parseFromString(c, 'text/html'), url = new URL(doc.baseURI)
return {
nickname: doc.querySelector('#about-user h2').firstChild.data,
id: +(id),
isMod: doc.querySelector('#about-user > h2 .badge-moderator') ? true : false,
scripts: (() => {
if(doc.querySelector('#user-script-list')) {
let result = []
for (let i of doc.querySelector('#user-script-list').children) {
result.push(new GreasyFork().parseScriptElement(i))
}
return result
} return _
})(),
libraries: (() => {
if(doc.querySelector('#user-library-script-list')) {
let result = []
for (let i of doc.querySelector('#user-library-script-list').children) {
result.push(new GreasyFork().parseScriptElement(i))
}
return result
} return _
})(),
scriptSets: (() => {
if(doc.querySelector('#user-script-sets')) {
let result = []
for (let i of doc.querySelector('#user-script-sets').children) {
if(i.firstChild.data === 'Favorites ') return _
let data = i.firstChild.data.split(':')
result.push({
name: data[0],
desc: data[1].replace(' ', '').split('').reverse().join('').replace(' ', '').split('').reverse().join(''),
id: i.children[0].href.replace(/.*?\/scripts\?set=/, '')
})
}
return result
} return _
})(),
recentComments: (() => {
if(doc.querySelectorAll('#user-discussions section ul li a')) {
let result = []
for (let i of doc.querySelectorAll('#user-discussions section ul li a')) {
result.push({
title: i.lastChild.innerText,
id: !i.href.match(/ns\/\d+/) ? +(i.href.match(/\d+/)[0]) : +(i.href.match(/ns\/\d+/)[0].replace('ns/', '')),
action: i.firstChild.data.replace(': ', ''),
URL: i.href
})
}
return result.length > 1 ? result : _
} return _
})()
}
})
},
async search(query = q, type, options = {}) {
if(typeof query != 'string') throw new Error('Argument 1 is not string')
function searchURL(path) {
return str(`https://gf.qytechs.cn/
${options.locale && langs.includes(options.locale) ? options.locale : 'en'}/
${path}${options.asJSON == true ? '.json' : ''}?q=${query ? query : ''}
${options.sort ? `&sort=${options.sort}` : ''}
${options.page ? `&page=${options.page}` : ''}
${options.locale ? `&locale_override=1` : ''}`)
}
if(type === 'script') {
return fetch(searchURL('scripts')).then((r) =>
options.asJSON == true ? r.json() : r.text()
).then((c) => {
if(options.asJSON == true) return c
let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-script-list li:not(.ad-entry)')
for (let i of list) {
result.push(new GreasyFork().parseScriptElement(i))
}
return result
})
}
if(type === 'library') {
return fetch(searchURL('scripts/libraries')).then((r) =>
options.asJSON == true ? r.json() : r.text()
).then((c) => {
if(options.asJSON == true) return c
let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-script-list li')
for (let i of list) {
result.push(new GreasyFork().parseScriptElement(i))
}
return result
})
} if(type === 'user') {
return fetch(searchURL('users')).then((r) =>
options.asJSON == true ? r.json() : r.text()
).then((c) => {
if(options.asJSON == true) return c
let result = [], list = parser.parseFromString(c, 'text/html').querySelectorAll('#browse-user-list li')
for (let i of list) {
result.push({
nickname: i.children[0].innerText,
id: +(i.children[0].href.replace(/.*?\/users\//, '').split('-')[0]),
badge: (i.children[1] ? i.children[1].innerText.toLowerCase() : 'none'),
url: i.children[0].href,
scripts: +(i.innerHTML.match(/- .+/, '')[0].match(/\d/)[0])
})
}
return result
})
} if(type === 'list') {
return [
'script',
'library',
'user',
'list'
]
} else throw new Error(`Argument 1 ${type} is not defined`)
},
async modlog(options = {}) {
return fetch(str(`https://gf.qytechs.cn/
${options.locale && langs.includes(options.locale) ? options.locale : 'en'}
/moderator_actions
${options.page ? `?page=${options.page}` : '?page=1'}
${options.locale ? `&locale_override=1` : ''}`)).then(r =>
r.text()
).then(c => {
let trs = parser.parseFromString(c, 'text/html').querySelectorAll('.log-table > tbody > tr'), result = []
for (let i of trs) {
let time = i.children[0].children[0],
userUrl = i.children[1].children[0].href,
item = i.children[2].children[0],
res = i.children[4]
result.push({
time: {
text: time.innerText,
iso: time.attributes.datetime.value
},
mod: {
id: userUrl.match(/\d.+-/) ? +(userUrl.match(/\d.+-/)[0].replace('-', '')) : userUrl.match(/\d-/) ? +(userUrl.match(/\d-/)[0].replace('-', '')) : _,
url: userUrl,
nickname: i.children[1].children[0].innerText
},
item: i.children[2] ? {
name: item ? item.innerText : _,
url: item ? item.href : _,
type: item ? str(item.previousSibling.data.replace(': ', '')) : _
} : _,
action: i.children[3].innerText,
reason: {
name: res.innerText.replaceAll(' ', '').replaceAll('\n', ''),
url: res.children[0].children[0] ? res.children[0].children[0].href : _
}
})
}
return result
})
},
}
}
async action(action, options = {}) {
let error = new GreasyFork().error,
parser = new DOMParser()
if(action === 'install') {
error(options.id, 'Argument 2 { id } is not defined')
setTimeout(() => {
window.open(`https://gf.qytechs.cn/scripts/${options.id}/code/source.user.${options.lang === 'css' ? 'css' : 'js'}`, '_top')
}, options.timeout * 1e3 || 0)
}
if(action === 'signout') {
setTimeout(() => {
fetch('https://gf.qytechs.cn/users/sign_out')
}, options.timeout * 1e3 || 0)
} if(action === 'list') {
return [
'install',
'signout',
'list'
]
}
else throw new Error(`Argument 1 ${action} is not defined`)
}
version() {
return '1.0.0'
}
}