// ==UserScript==
// @name Script Finder
// @namespace http://tampermonkey.net/
// @version 0.1.2
// @description Script Finder allows you to find userscripts from greasyfork on any website.
// @author shiquda
// @namespace https://github.com/shiquda/shiquda_UserScript
// @supportURL https://github.com/shiquda/shiquda_UserScript/issues
// @match *://*/*
// @grant GM_xmlhttpRequest
// @grant GM_addStyle
// @license AGPL-3.0
// ==/UserScript==
(function () {
const domainParts = window.location.hostname.split('.').slice(-2);
const domain = domainParts.join('.');
const errorMessage = "Failed to retrieve script information or there are no available scripts for this domain.";
function getScriptsInfo(domain) {
var url = `https://gf.qytechs.cn/scripts/by-site/${domain}?filter_locale=0`;
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: (response) => {
const parser = new DOMParser();
const doc = parser.parseFromString(response.responseText, "text/html");
const scripts = doc.querySelector("#browse-script-list")?.querySelectorAll('[data-script-id]');
let scriptsInfo = [];
if (!scripts) {
scriptsInfo = errorMessage
} else {
for (var i = 0; i < scripts.length; i++) {
scriptsInfo.push(parseScriptInfo(scripts[i]));
}
}
console.log(scriptsInfo);
showScriptsInfo(scriptsInfo); // 显示脚本信息
},
onerror: () => {
console.log("Some error!");
const scriptsInfo = errorMessage
showScriptsInfo(scriptsInfo)
}
});
// oneClickInstall();
}
// 解析脚本信息
function parseScriptInfo(script) {
return {
id: script.getAttribute('data-script-id'),
name: script.getAttribute('data-script-name'),
author: script.querySelector("dd.script-list-author").textContent,
description: script.querySelector(".script-description").textContent,
version: script.getAttribute('data-script-version'),
url: 'https://gf.qytechs.cn/scripts/' + script.getAttribute('data-script-id'),
// createDate: script.getAttribute('data-script-created-date'),
// updateDate: script.getAttribute('data-script-updated-date'),
installs: script.getAttribute('data-script-total-installs'),
// dailyInstalls: script.getAttribute('data-script-daily-installs'),
ratingScore: script.getAttribute('data-script-rating-score')
};
}
function showScriptsInfo(scriptsInfo) {
GM_addStyle(`
button.script-button {
position: fixed;
bottom: 50%;
right: -50px;
transform: translateY(50%);
padding: 12px;
font-size: 16px;
border: none;
border-radius: 4px;
background-color: #1e90ff;
color: #ffffff;
cursor: pointer;
transition: right 0.3s;
}
div.info-container {
display: none;
position: fixed;
top: 10%;
right: 100px;
width: 600px;
padding: 12px;
background-color: #ffffff;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3);
border-radius: 4px;
opacity: 0;
transition: opacity 0.3s;
z-index: 9999;
max-height: 80vh;
overflow-y: auto;
}
ul.info-list {
list-style: none;
margin: 0;
padding: 0;
}
li.info-item {
margin-bottom: 10px;
border: 1px solid #ddd;
padding: 10px;
display: flex;
flex-direction: column;
}
.div.script-container {
display: flex;
flex-direction: column;
}
a.script-link {
font-size: 18px !important;
font-weight: bold !important;
margin-bottom: 5px !important;
color: #1e90ff !important;
}
p.script-description {
color: black !important;
margin-top: 2px;
margin-bottom: 5px;
}
div.details-container {
font-size: 18px ;
font-weight: bold;
display: flex;
justify-content: space-between;
margin-bottom: 15px;
}
span.script-details {
color: black !important;
flex-grow: 1;
text-align: left;
}
div.table-header {
color: #1e90ff !important;
font-size: 25px;
}
input.script-search-input {
width: 96% !important;
padding: 10px !important;
font-size: 18px !important;
border: 1px solid #ddd !important;
border-radius: 4px !important;
box-shadow: 0 2px 5px rgba(0, 0, 0, 0.3) !important;
margin-bottom: 15px !important;
margin-top: 20px !important;
}
a.install-button {
font-size: 25px;
background-color: green;
color: white;
padding: 10px;
}
button.to-greasyfork {
position: absolute;
top: 10px;
right: 10px;
border-radius: 4px;
padding: 8px;
font-size: 16px;
border: none;
background-color: #1e90ff;
color: #ffffff;
cursor: pointer;
}
`);
// 创建打开列表按钮
var button = document.createElement('button');
button.className = 'script-button';
button.innerText = 'Scripts';
// 创建脚本容器
var infoContainer = document.createElement('div');
infoContainer.className = 'info-container';
// 创建搜索框
var searchInput = document.createElement('input');
searchInput.type = 'text';
searchInput.placeholder = 'Search scripts...';
searchInput.className = 'script-search-input';
// 创建指向greasyfork的链接
var toGreasyfork = document.createElement('button');
toGreasyfork.className = 'to-greasyfork';
toGreasyfork.innerText = 'View on Greasyfork';
// 创建表头
var tableHeader = document.createElement('div');
tableHeader.className = 'table-header';
tableHeader.appendChild(document.createTextNode('Script Finder'));
tableHeader.appendChild(searchInput);
tableHeader.appendChild(toGreasyfork);
// 创建脚本列表
var infoList = document.createElement('ul');
infoList.className = 'info-list';
// 插入脚本
if (scriptsInfo === errorMessage) {
infoList.innerHTML = errorMessage;
} else {
for (var i = 0; i < scriptsInfo.length; i++) {
var script = scriptsInfo[i];
var listItem = document.createElement('li');
listItem.className = 'info-item';
var scriptContainer = document.createElement('div');
scriptContainer.className = 'script-container';
var nameElement = document.createElement('a');
nameElement.className = 'script-link';
nameElement.innerText = script.name;
nameElement.href = script.url;
nameElement.target = '_blank';
var descriptionElement = document.createElement('p');
descriptionElement.className = 'script-description';
descriptionElement.innerHTML = script.description;
var detailsContainer = document.createElement('div');
detailsContainer.className = 'details-container';
// 创建一键安装按钮
var installButton = document.createElement('a');
installButton.className = 'install-button';
installButton.innerText = `Install ${script.version}`;
installButton.href = `https://gf.qytechs.cn/scripts/${script.id}/code/script.user.js`;
var authorElement = document.createElement('span');
authorElement.className = 'script-details';
authorElement.innerText = 'Author: ' + script.author;
var installsElement = document.createElement('span');
installsElement.className = 'script-details';
installsElement.innerText = 'Installs: ' + script.installs;
var ratingElement = document.createElement('span');
ratingElement.className = 'script-details';
ratingElement.innerText = 'Rating: ' + script.ratingScore;
detailsContainer.appendChild(authorElement);
detailsContainer.appendChild(installsElement);
detailsContainer.appendChild(ratingElement);
scriptContainer.appendChild(nameElement);
scriptContainer.appendChild(descriptionElement);
scriptContainer.appendChild(detailsContainer);
scriptContainer.appendChild(installButton);
listItem.appendChild(scriptContainer);
listItem.scriptId = script.id;
infoList.appendChild(listItem);
}
}
infoContainer.appendChild(tableHeader)
infoContainer.appendChild(infoList);
var timeout;
button.addEventListener('mouseenter', function () {
clearTimeout(timeout);
button.style.right = '10px';
});
button.addEventListener('mouseleave', function () {
timeout = setTimeout(function () {
button.style.right = '-50px';
}, 500);
});
button.addEventListener('click', function (event) {
event.stopPropagation();
button.style.right = '10px';
infoContainer.style.display = "block"
infoContainer.style.opacity = 1
});
infoContainer.addEventListener('click', function (event) {
event.stopPropagation();
});
searchInput.addEventListener('input', () => {
searchScript()
})
toGreasyfork.addEventListener('click', function () {
window.open(`https://gf.qytechs.cn/scripts/by-site/${domain}?q=${searchInput.value}`)
})
document.body.addEventListener('click', function () {
clearTimeout(timeout);
button.style.right = '-50px';
infoContainer.style.opacity = 0
infoContainer.style.display = "none"
});
document.body.appendChild(button);
document.body.appendChild(infoContainer);
}
function searchScript() {
const searchWord = document.querySelector('.script-search-input').value;
const scriptList = document.querySelectorAll('.info-item')
for (let i = 0; i < scriptList.length; i++) {
if (scriptList[i].innerText.includes(searchWord)) {
scriptList[i].style.display = 'block'
} else {
scriptList[i].style.display = 'none'
}
}
}
// 一键安装 此处参考tampermonkey versioncheck.js 但是不知道为什么别的网站没有这个对象
/*
function oneClickInstall() {
const scriptList = document.querySelectorAll('.script-container');
// 获取tamppermonkey脚本信息
let tm = window.external?.Tampermonkey
let vm = window.external?.ViolentMonkey
function getInstalledVersion(name, namespace) {
return new Promise(function (resolve, reject) {
if (tm) {
tm.isInstalled(name, namespace, function (i) {
if (i.installed) {
resolve(i.version);
} else {
resolve(null);
}
});
return;
}
if (vm) {
vm.isInstalled(name, namespace).then(resolve);
return;
};
reject()
});
}
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/version/format
function compareVersions(a, b) {
if (a == b) {
return 0;
}
let aParts = a.split('.');
let bParts = b.split('.');
for (let i = 0; i < aParts.length; i++) {
let result = compareVersionPart(aParts[i], bParts[i]);
if (result != 0) {
return result;
}
}
// If all of a's parts are the same as b's parts, but b has additional parts, b is greater.
if (bParts.length > aParts.length) {
return -1;
}
return 0;
}
function compareVersionPart(partA, partB) {
let partAParts = parseVersionPart(partA);
let partBParts = parseVersionPart(partB);
for (let i = 0; i < partAParts.length; i++) {
// "A string-part that exists is always less than a string-part that doesn't exist"
if (partAParts[i].length > 0 && partBParts[i].length == 0) {
return -1;
}
if (partAParts[i].length == 0 && partBParts[i].length > 0) {
return 1;
}
if (partAParts[i] > partBParts[i]) {
return 1;
}
if (partAParts[i] < partBParts[i]) {
return -1;
}
}
return 0;
}
// It goes number, string, number, string. If it doesn't exist, then
// 0 for numbers, empty string for strings.
function parseVersionPart(part) {
if (!part) {
return [0, "", 0, ""];
}
let partParts = /([0-9]*)([^0-9]*)([0-9]*)([^0-9]*)/.exec(part)
return [
partParts[1] ? parseInt(partParts[1]) : 0,
partParts[2],
partParts[3] ? parseInt(partParts[3]) : 0,
partParts[4]
];
}
function handleInstallResult(installedVersion, version) {
if (installedVersion == null) {
// Not installed
return `Install { version } `;
}
// installButton.removeAttribute("data-ping-url")
switch (compareVersions(installedVersion, version)) {
// Upgrade
case -1:
return `Upgrade ${version} `;
break;
// Downgrade
case 1:
return `Downgrade ${version} `;
break;
// Equal
case 0:
return `Reinstall ${version} `;
break;
}
}
for (let i = 0; i < scriptList.length; i++) {
let script = scriptList[i];
let id = script.scriptId
GM_xmlhttpRequest({
url: `https://gf.qytechs.cn/scripts/${id}.json`,
method: "GET",
onload: (response) => {
const data = JSON.parse(response.responseText)
let latestVersion = data.version
let namespace = data.namespace
let installedVersion = getInstalledVersion(id, namespace)
let versionInfo = handleInstallResult(installedVersion, latestVersion)
script.querySelector('.install-button').innerText = versionInfo
},
onerror: (error) => {
console.log(`An error occured when fetching script ${id}: ${error}`)
}
})
}
}
*/
getScriptsInfo(domain);
})();