Skip to content

Commit 5286fcc

Browse files
committed
feat: 添加群验证
1 parent e979e35 commit 5286fcc

8 files changed

Lines changed: 236 additions & 9 deletions

File tree

external/approve/.gitattributes

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
* text eol=lf
2+
3+
*.png -text
4+
*.jpg -text
5+
*.ico -text
6+
*.gif -text
7+
*.webp -text

external/approve/.gitignore

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
lib
2+
dist
3+
external
4+
5+
node_modules
6+
npm-debug.log
7+
yarn-debug.log
8+
yarn-error.log
9+
tsconfig.tsbuildinfo
10+
11+
.eslintcache
12+
.DS_Store
13+
.idea
14+
.vscode
15+
*.suo
16+
*.ntvs*
17+
*.njsproj
18+
*.sln

external/approve/package.json

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
{
2+
"name": "@leafbot/koishi-plugin-approve",
3+
"description": "",
4+
"version": "0.0.1",
5+
"main": "lib/index.js",
6+
"typings": "lib/index.d.ts",
7+
"files": [
8+
"lib",
9+
"dist"
10+
],
11+
"license": "MIT",
12+
"keywords": [
13+
"chatbot",
14+
"koishi",
15+
"plugin"
16+
],
17+
"dependencies": {
18+
"@octokit/rest": "^21.0.0"
19+
},
20+
"peerDependencies": {
21+
"koishi": "^4.18.11"
22+
}
23+
}

external/approve/readme.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# @leafbot/koishi-plugin-approve
2+
3+
[![npm](https://img.shields.io/npm/v/@leafbot/koishi-plugin-approve?style=flat-square)](https://www.npmjs.com/package/@leafbot/koishi-plugin-approve)
4+
5+

external/approve/src/index.ts

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
import { Octokit } from "@octokit/rest";
2+
import { RequestError } from "@octokit/request-error";
3+
import type { Context, Session } from "koishi";
4+
import { Schema } from "koishi";
5+
6+
export const name = "approve";
7+
8+
export interface Config {}
9+
10+
export const Config: Schema<Config> = Schema.object({});
11+
12+
const repoOwner = "Winds-Studio";
13+
const repoName = "Leaf";
14+
const octokit = new Octokit();
15+
const cacheTtlMs = 60_000;
16+
17+
type CacheEntry<T> = {
18+
expiresAt: number;
19+
value: T;
20+
etag?: string;
21+
lastModified?: string;
22+
};
23+
24+
const createCachedFetcher = <T>(
25+
fetcher: (
26+
headers: Record<string, string>,
27+
) => Promise<{ value: T; etag?: string; lastModified?: string }>,
28+
) => {
29+
let cache: CacheEntry<T> | null = null;
30+
let inflight: Promise<CacheEntry<T>> | null = null;
31+
32+
return async () => {
33+
if (cache && Date.now() < cache.expiresAt) {
34+
return cache;
35+
}
36+
37+
if (inflight) {
38+
return inflight;
39+
}
40+
41+
inflight = (async () => {
42+
const headers: Record<string, string> = {};
43+
if (cache?.etag) {
44+
headers["If-None-Match"] = cache.etag;
45+
}
46+
if (cache?.lastModified) {
47+
headers["If-Modified-Since"] = cache.lastModified;
48+
}
49+
50+
try {
51+
const result = await fetcher(headers);
52+
cache = {
53+
expiresAt: Date.now() + cacheTtlMs,
54+
value: result.value,
55+
etag: result.etag,
56+
lastModified: result.lastModified,
57+
};
58+
return cache;
59+
} catch (error: unknown) {
60+
if (error instanceof RequestError && error.status === 304 && cache) {
61+
cache = {
62+
...cache,
63+
expiresAt: Date.now() + cacheTtlMs,
64+
};
65+
return cache;
66+
}
67+
throw error;
68+
} finally {
69+
inflight = null;
70+
}
71+
})();
72+
73+
return inflight;
74+
};
75+
};
76+
77+
const getDefaultBranch = createCachedFetcher(async (headers) => {
78+
const repoInfo = await octokit.rest.repos.get({
79+
owner: repoOwner,
80+
repo: repoName,
81+
headers,
82+
});
83+
84+
return {
85+
value: repoInfo.data.default_branch,
86+
etag: repoInfo.headers.etag,
87+
lastModified: repoInfo.headers["last-modified"],
88+
};
89+
});
90+
91+
type CommitInfo = {
92+
latestSha: string;
93+
shortSha: string;
94+
defaultBranch: string;
95+
};
96+
97+
const getLatestCommit = createCachedFetcher(async (headers) => {
98+
const { value: defaultBranch } = await getDefaultBranch();
99+
const commitInfo = await octokit.rest.repos.getCommit({
100+
owner: repoOwner,
101+
repo: repoName,
102+
ref: defaultBranch,
103+
headers,
104+
});
105+
const latestSha = commitInfo.data.sha;
106+
107+
return {
108+
value: {
109+
latestSha,
110+
shortSha: latestSha.substring(0, 7),
111+
defaultBranch,
112+
} satisfies CommitInfo,
113+
etag: commitInfo.headers.etag,
114+
lastModified: commitInfo.headers["last-modified"],
115+
};
116+
});
117+
118+
const handleEvent = async (ctx: Context, session: Session) => {
119+
if (!session.messageId) {
120+
return;
121+
}
122+
123+
const logger = ctx.logger;
124+
125+
try {
126+
const { value } = await getLatestCommit();
127+
const { latestSha, shortSha } = value;
128+
129+
const applyMessage = (session.content || "").trim();
130+
131+
if (applyMessage === latestSha || applyMessage === shortSha) {
132+
await session.bot.handleGuildMemberRequest(session.messageId, true);
133+
logger.info(`已通过 ${session.userId} 的加群请求,SHA: ${applyMessage}`);
134+
} else {
135+
await session.bot.handleGuildMemberRequest(session.messageId, false);
136+
logger.info(
137+
`已拒绝 ${session.userId} 的加群请求。错误 SHA: ${applyMessage},期望: ${shortSha}${latestSha}`,
138+
);
139+
}
140+
} catch (error) {
141+
logger.warn(`获取 GitHub 最新 commit 失败: ${error}`);
142+
}
143+
};
144+
145+
export function apply(ctx: Context, _config: Config) {
146+
ctx.on("guild-member-request", async (session) => {
147+
await handleEvent(ctx, session);
148+
});
149+
}

external/approve/tsconfig.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"compilerOptions": {
3+
"rootDir": "src",
4+
"outDir": "lib",
5+
"target": "es2022",
6+
"module": "ESNext",
7+
"moduleDetection": "force",
8+
"noImplicitThis": true,
9+
"verbatimModuleSyntax": true,
10+
"useDefineForClassFields": true,
11+
"declaration": true,
12+
"noUncheckedIndexedAccess": true,
13+
"forceConsistentCasingInFileNames": true,
14+
"libReplacement": false,
15+
"composite": true,
16+
"incremental": true,
17+
"skipLibCheck": true,
18+
"esModuleInterop": true,
19+
"moduleResolution": "bundler",
20+
"strict": true,
21+
"jsx": "react-jsx",
22+
"jsxImportSource": "@satorijs/element",
23+
"types": ["node", "yml-register/types"]
24+
},
25+
"include": ["src"]
26+
}

external/notice/.editorconfig

Lines changed: 0 additions & 9 deletions
This file was deleted.

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1536,6 +1536,14 @@ __metadata:
15361536
languageName: node
15371537
linkType: hard
15381538

1539+
"@leafbot/koishi-plugin-approve@workspace:external/approve":
1540+
version: 0.0.0-use.local
1541+
resolution: "@leafbot/koishi-plugin-approve@workspace:external/approve"
1542+
peerDependencies:
1543+
koishi: ^4.18.11
1544+
languageName: unknown
1545+
linkType: soft
1546+
15391547
"@leafbot/koishi-plugin-notice@workspace:external/notice":
15401548
version: 0.0.0-use.local
15411549
resolution: "@leafbot/koishi-plugin-notice@workspace:external/notice"

0 commit comments

Comments
 (0)