GitHub Commit 定时监控
// ==UserScript==
// @name GitHub Commit 定时监控
// @namespace GitHub Commit 定时监控
// @version 0.1.0
// @description 通过脚本猫定时请求GitHub REST API以监控GitHub仓库Commit更新情况
// @author DreamNya
// @grant GM_setValue
// @grant GM_getValue
// @grant CAT_userConfig
// @grant GM_notification
// @grant GM_xmlhttpRequest
// @grant GM_openInTab
// @icon data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxNiAxNiIgd2lkdGg9IjEyOCIgaGVpZ2h0PSIxMjgiPgogICA8Y2lyY2xlIGN4PSI4IiBjeT0iOCIgZmlsbD0iI2ZmZmZmZiIgcj0iOCIvPgogIDxwYXRoIGQ9Im04LDBjNC40MiwwIDgsMy41OCA4LDhhOC4wMSw4LjAxIDAgMCAxIC01LjQ1LDcuNTljLTAuNCwwLjA4IC0wLjU1LC0wLjE3IC0wLjU1LC0wLjM4YzAsLTAuMjcgMC4wMSwtMS4xMyAwLjAxLC0yLjJjMCwtMC43NSAtMC4yNSwtMS4yMyAtMC41NCwtMS40OGMxLjc4LC0wLjIgMy42NSwtMC44OCAzLjY1LC0zLjk1YzAsLTAuODggLTAuMzEsLTEuNTkgLTAuODIsLTIuMTVjMC4wOCwtMC4yIDAuMzYsLTEuMDIgLTAuMDgsLTIuMTJjMCwwIC0wLjY3LC0wLjIyIC0yLjIsMC44MmMtMC42NCwtMC4xOCAtMS4zMiwtMC4yNyAtMiwtMC4yN2MtMC42OCwwIC0xLjM2LDAuMDkgLTIsMC4yN2MtMS41MywtMS4wMyAtMi4yLC0wLjgyIC0yLjIsLTAuODJjLTAuNDQsMS4xIC0wLjE2LDEuOTIgLTAuMDgsMi4xMmMtMC41MSwwLjU2IC0wLjgyLDEuMjggLTAuODIsMi4xNWMwLDMuMDYgMS44NiwzLjc1IDMuNjQsMy45NWMtMC4yMywwLjIgLTAuNDQsMC41NSAtMC41MSwxLjA3Yy0wLjQ2LDAuMjEgLTEuNjEsMC41NSAtMi4zMywtMC42NmMtMC4xNSwtMC4yNCAtMC42LC0wLjgzIC0xLjIzLC0wLjgyYy0wLjY3LDAuMDEgLTAuMjcsMC4zOCAwLjAxLDAuNTNjMC4zNCwwLjE5IDAuNzMsMC45IDAuODIsMS4xM2MwLjE2LDAuNDUgMC42OCwxLjMxIDIuNjksMC45NGMwLDAuNjcgMC4wMSwxLjMgMC4wMSwxLjQ5YzAsMC4yMSAtMC4xNSwwLjQ1IC0wLjU1LDAuMzhhOCw4IDAgMCAxIC01LjQ3LC03LjU5YzAsLTQuNDIgMy41OCwtOCA4LC04eiIvPgo8L3N2Zz4=
// @license MIT
// @crontab 0 */8 * * *
// @connect api.github.com
// ==/UserScript==
/* ==UserConfig==
全局设置:
Timeout:
title: 请求超时时间
description: 默认10000ms,最小1000ms,最大60000ms
type: number
default: 10000
min: 1000
max: 60000
unit: ms
NotifyNoUpdate:
title: 无更新是否通知
default: '通知'
type: select
values: ['通知','不通知']
NotifyTimeout:
title: 通知持续时间
description: 默认5000ms,最小1000ms
type: number
default: 5000
min: 1000
unit: ms
NotifyMessage:
title: 通知内容
default: '通知更新内容'
type: select
values: ['通知更新内容','仅通知更新情况']
NotifyClick:
title: 有新commit点击通知跳转对应commit
default: '跳转'
type: select
values: ['跳转','不跳转']
Authorization:
title: Authorization身份验证(填写可访问更多终结点并获得更高的速率限制)
description: 留空则忽略,格式:Token/Bearer+空格+令牌内容
type: text
---
监控仓库JSON:
Enable:
title: 是否启用JSON配置(启用后忽略所有非JSON配置)
default: '禁用'
type: select
values: ['启用','禁用']
JSON:
title: JSON(须为标准JSON)
description: JSON规则详见脚本页
type: text
---
监控仓库1:
Enable:
title: 是否启用监控仓库1配置(优先级低于JSON)
default: '禁用'
type: select
values: ['启用','禁用']
owner:
title: owner
description: 仓库所有者,必填,留空则忽略本设置
type: text
repo:
title: repo
description: 仓库名称,必填,留空则忽略本设置
type: text
branch:
title: branch
description: 仓库分支,选填,不填则监控整个仓库push,填入则监控具体分支
type: text
---
监控仓库2:
Enable:
title: 是否启用监控仓库2配置(优先级低于JSON)
default: '禁用'
type: select
values: ['启用','禁用']
owner:
title: owner
description: 仓库所有者,必填,留空则忽略本设置
type: text
repo:
title: repo
description: 仓库名称,必填,留空则忽略本设置
type: text
branch:
title: branch
description: 仓库分支,选填,不填则监控整个仓库push,填入则监控具体分支
type: text
---
监控仓库3:
Enable:
title: 是否启用监控仓库3配置(优先级低于JSON)
default: '禁用'
type: select
values: ['启用','禁用']
owner:
title: owner
description: 仓库所有者,必填,留空则忽略本设置
type: text
repo:
title: repo
description: 仓库名称,必填,留空则忽略本设置
type: text
branch:
title: branch
description: 仓库分支,选填,不填则监控整个仓库push,填入则监控具体分支
type: text
==/UserConfig== */
/*
脚本存储规则:
- UserConfig Key 用户配置
- 全局设置
- /监控仓库([1-3]|JSON)$/
- lastSHA Key 监控仓库最后监控Commit SHA
- lastSHA_${UserConfig Key}
*/
/*
监控仓库JSON解析规则:
- 须为可被JSON.parse解析的标准JSON文本
- 深度1的Key为自定义监控仓库别名,不能以/监控仓库([1-3]|JSON)$/为仓库别名
- 深度2的Key仅解析owner\repo\branch(owner\repo任意为空则忽略,branch可为空)忽略其余Key
JSON示例:
{
"scriptcat-vscode": {
"owner": "scriptscat",
"repo": "scriptcat-vscode"
},
"scriptscat": {
"owner": "scriptscat",
"repo": "scriptcat",
"branch": "develop/beta"
}
}
*/
//读取全局设置
const globalSetting = {};
globalSetting.icon = GM_info.script.icon; //自己画的图标……以后再说
globalSetting.Timeout = GM_getValue('全局设置.Timeout', 10000) * 1;
globalSetting.NotifyNoUpdate = GM_getValue('全局设置.NotifyNoUpdate', '通知') == '通知';
globalSetting.NotifyTimeout = GM_getValue('全局设置.NotifyTimeout', 5000) * 1;
globalSetting.NotifyMessage = GM_getValue('全局设置.NotifyMessage', '通知更新内容') == '通知更新内容';
globalSetting.NotifyClick = GM_getValue('全局设置.NotifyClick', '跳转') == '跳转';
globalSetting.Authorization = GM_getValue('全局设置.Authorization', null); //脚本猫不同脚本相同@namespace共享GM_Value,可能存在令牌被窃取隐患?
//主函数
return new Promise(async (resolve) => {
const XHRTask = {};
const JSONSetting = {};
JSONSetting.error = '';
JSONSetting.Enable = GM_getValue('监控仓库JSON.Enable', '禁用');
if (JSONSetting.Enable == '启用') {
try {
const json = JSON.parse(GM_getValue('监控仓库JSON.JSON'))
Object.keys(json).forEach((key) => {
if (/监控仓库([1-3]|JSON)$/.test(key)) throw new Error('JSON不能以/监控仓库([1-3]|JSON)$/为key')
const { owner, repo, branch } = json[key]
if (!owner && !repo) return
XHRTask[key] = { owner, repo, branch, lastSHA: GM_getValue('lastSHA_' + key) }
})
} catch (err) {
JSONSetting.Enable = '禁用'
JSONSetting.error = err
console.log('JSON err', err)
}
}
if (JSONSetting.Enable == '禁用') {
['监控仓库1', '监控仓库2', '监控仓库3'].forEach((key) => {
const Enable = GM_getValue(key + '.Enable', '禁用')
if (Enable != '启用') return
const owner = GM_getValue(key + '.owner')
const repo = GM_getValue(key + '.repo')
const branch = GM_getValue(key + '.branch')
if (!owner && !repo) return
XHRTask[key] = { owner, repo, branch, lastSHA: GM_getValue('lastSHA_' + key) }
})
}
if (Object.keys(XHRTask).length == 0 || JSONSetting.error) {
const errResult = JSONSetting.error ? 'JSON配置错误' + JSONSetting.error : '监控仓库配置错误'
GM_notification({
text: errResult,
title: "Github commit 定时监控",
image: globalSetting.icon
})
resolve((CAT_userConfig(), errResult))
} else {
console.log(XHRTask)
}
const XHRResult = []
for (const task in XHRTask) {
const { owner, repo, branch, lastSHA } = XHRTask[task]
const url = `https://api.github.com/repos/${owner}/${repo}/${branch ? 'branches' : 'commits'}`
const taskUrl = `${owner}/${repo}${branch ? '/tree/' + branch : ''}`
const response = (await GMxhr(url).catch(e => (console.log(e), e.message))).response
if (typeof response == 'string') {
XHRResult.push(`${taskUrl}\n请求失败 ${response}`)
continue
}
const commit = branch ? response.find(i => i.name == branch)?.commit : response[0]
if (!commit) {
XHRResult.push(`${taskUrl}\n请求失败 无此branch`)
continue
}
const { sha: SHA, url: commitUrl } = commit
if (SHA != lastSHA) {
//branch方式不会直接返回commitTitle,需要额外请求一次具体branch
const commitTitle = !globalSetting.NotifyMessage
? null
: (branch
? await GMxhr(commit.url).then(r => r.response).catch(e => (console.log(e), "commitTitle请求失败\n" + e.message))
: commit
).commit.message
const message = `${taskUrl}${taskUrl.length > 30 ? ' ' : '\n'}${lastSHA ? '发现新' : '初始化'}commit`
const githubUrl = `https://github.com/${owner}/${repo}/commit/${SHA}` //待定,通知点击后跳转到具体commit
XHRResult.push({ message, githubUrl, commitTitle })
GM_setValue('lastSHA_' + task, SHA)
} else {
XHRResult.push(`${taskUrl}\n无新commit`)
}
}
console.log(XHRResult)
//GM_notification队列 async to sync
for (const result of XHRResult) {
//无更新不通知
if (!globalSetting.NotifyNoUpdate && result.includes?.('无新commit')) continue
await GMnotify({
text: (globalSetting.NotifyMessage && result.commitTitle) || ' ',
title: result.message || result,
image: globalSetting.icon,
onclick: globalSetting.NotifyClick && result.githubUrl ? () => GM_openInTab(result.githubUrl, { active: true }) : null,
timeout: globalSetting.NotifyTimeout,
})
}
//脚本运行完毕
resolve(XHRResult.map((result) => result.message || result).join('\n'))
});
//全局函数 ↓
//GM_xmlhttpRequest async to sync
function GMxhr(url) {
// https://docs.github.com/zh/rest/overview/authenticating-to-the-rest-api
// 你可以对 REST API 进行身份验证,以访问更多终结点并获得更高的速率限制。
const headers = globalSetting.Authorization
? {
"Authorization": globalSetting.Authorization,
"X-GitHub-Api-Version": "2022-11-28"
}
: null
return new Promise((resolve, reject) => {
GM_xmlhttpRequest({
url,
method: 'GET',
...headers,
responseType: 'json',
timeout: globalSetting.Timeout,
ontimeout: (xhr) => reject({ message: 'Timeout', xhr }),
onerror: (xhr) => reject({ message: 'Error', xhr }),
onload: (xhr) => {
console.log(xhr)
if (xhr.status != 200) reject({ message: xhr.response.message, xhr })
resolve(xhr)
}
})
})
}
//GM_notification队列 async to sync
function GMnotify(config) {
return new Promise((resolve) => {
GM_notification({
...config,
ondone: () => setTimeout(() => resolve(), 500) //上一个通知结束后间隔500ms显示下一个通知
})
})
}