Add server (#262)

pull/286/head
北雁云依 2024-03-04 19:36:56 +08:00 committed by Rizumu Ayaka
parent 120d5667b0
commit 21fca698b3
11 changed files with 918 additions and 70 deletions

View File

@ -29,9 +29,9 @@ jobs:
with: with:
fetch-depth: 0 fetch-depth: 0
- uses: pnpm/action-setup@v2 - uses: pnpm/action-setup@v3
with: with:
version: '8.10.5' version: '8.15.4'
- uses: actions/setup-node@v4 - uses: actions/setup-node@v4
with: with:
@ -48,3 +48,4 @@ jobs:
accountId: ${{ secrets.CLOUDFLARE_PAGES_ACCOUNT }} accountId: ${{ secrets.CLOUDFLARE_PAGES_ACCOUNT }}
projectName: rle-wiki projectName: rle-wiki
directory: docs/.vitepress/dist directory: docs/.vitepress/dist
wranglerVersion: '3'

View File

@ -0,0 +1,4 @@
# For couldflare workers create a file named `.dev.vars`
TG_BOT_TOKEN=
TG_GROUP_ID=

View File

@ -0,0 +1,8 @@
```
npm install
npm run dev
```
```
npm run deploy
```

View File

@ -0,0 +1,124 @@
import { Bot, InputFile, InputMediaBuilder } from "grammy";
import { InputMediaPhoto } from "grammy/types";
import { Hono } from "hono";
import { env } from "hono/adapter";
import { handle } from "hono/cloudflare-pages";
import { ENV } from "./types";
const IP_HEADER = "CF-Connecting-IP";
const newSuccess = (message = "success") => ({ code: 0, message });
const newError500 = (message = "server internal error") => ({
code: 500.001,
message,
});
const newErrorFormat400 = (
message = "The data format of the request is invalid. Please check and use the correct data format."
) => ({ code: 400.001, message });
const app = new Hono();
app.onError((err, c) => {
console.error(String(err));
return c.json(newError500(), 500);
});
app.get("/", (c) => {
return c.text("Hello, Project Trans SuggestionBox!");
});
app.post("/api/v1/suggestion", async (c) => {
const { TG_BOT_TOKEN, TG_GROUP_ID } = env<ENV>(c);
const bot = new Bot(TG_BOT_TOKEN);
let metaUA = "";
let metaIP = "";
let metaReferrer = "";
let contactContent = "";
let textContent = "";
const reqImages: File[] = [];
const msgImages: InputMediaPhoto[] = [];
try {
const form = await c.req.formData();
metaIP = c.req.header(IP_HEADER) || "";
metaReferrer = c.req.header("Referer") || "";
metaUA = c.req.header("User-Agent") || "";
textContent = form.get("textContent") || "";
contactContent = form.get("contactContent") || "";
reqImages.push(...(form.getAll("images") as unknown as File[]));
for (const image of reqImages) {
const buffer = new Uint8Array(await image.arrayBuffer());
const tgInputFile = new InputFile(buffer, image.name);
msgImages.push(InputMediaBuilder.photo(tgInputFile));
}
} catch (error) {
// TODO log error
console.error(error);
return c.json(newErrorFormat400(), 400);
}
const msgs = [`<b>意见箱收到新消息</b>\n`];
msgs.push(`${replaceHtmlTag(textContent)}\n`);
contactContent &&
msgs.push(
`<b>联系方式</b>\n<blockquote><code>${replaceHtmlTag(
contactContent
)}</code></blockquote>`
);
metaReferrer &&
msgs.push(
`<b>Referrer</b>\n<blockquote>${replaceHtmlTag(
metaReferrer
)}</blockquote>`
);
if (metaIP) {
msgs.push(
`<b>IP</b> <i><a href="https://ip.sb/ip/${encodeURIComponent(
metaIP
)}">View in Web</a></i>\n<blockquote><code>${replaceHtmlTag(
metaIP
)}</code></blockquote>`
);
}
if (metaUA) {
msgs.push(
`<b>UA</b> <i><a href="https://uaparser.js.org/?ua=${encodeURIComponent(
metaUA
)}">View in Web</a></i>\n<pre><code>${replaceHtmlTag(
metaUA
)}</code></pre>`
);
}
const message = msgs.join("\n");
if (msgImages.length) {
msgImages[0].caption = message;
msgImages[0].parse_mode = "HTML";
}
try {
if (msgImages.length) {
await bot.api.sendMediaGroup(TG_GROUP_ID, msgImages);
} else {
await bot.api.sendMessage(TG_GROUP_ID, message, { parse_mode: "HTML" });
}
return c.json(newSuccess());
} catch (error) {
// TODO handle error
// TODO log error
console.error(error);
return c.json(newError500(), 500);
}
});
export const onRequest = handle(app);
function replaceHtmlTag(str: string) {
return str
.replaceAll("<", "&lt;")
.replaceAll(">", "&gt;")
.replaceAll("&", "&amp;");
}

View File

@ -0,0 +1,4 @@
export type ENV = {
TG_BOT_TOKEN: string;
TG_GROUP_ID: string;
};

View File

@ -0,0 +1,16 @@
{
"name": "server",
"private": true,
"scripts": {
"dev": "wrangler dev src/index.ts",
"deploy": "wrangler deploy --minify src/index.ts"
},
"dependencies": {
"grammy": "^1.21.1",
"hono": "^4.0.8"
},
"devDependencies": {
"@cloudflare/workers-types": "^4.20240208.0",
"wrangler": "^3.25.0"
}
}

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ESNext",
"module": "ESNext",
"moduleResolution": "Bundler",
"strict": true,
"lib": [
"ESNext"
],
"types": [
"@cloudflare/workers-types"
],
"jsx": "preserve",
"strictNullChecks": true,
"noImplicitAny": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"skipLibCheck": true,
"skipDefaultLibCheck": true
},
}

View File

@ -0,0 +1,18 @@
name = "server"
compatibility_date = "2023-12-01"
# [vars]
# MY_VARIABLE = "production_value"
# [[kv_namespaces]]
# binding = "MY_KV_NAMESPACE"
# id = "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
# [[r2_buckets]]
# binding = "MY_BUCKET"
# bucket_name = "my-bucket"
# [[d1_databases]]
# binding = "DB"
# database_name = "my-database"
# database_id = ""

View File

@ -14,6 +14,7 @@
"update-package": "pnpm dlx vp-update" "update-package": "pnpm dlx vp-update"
}, },
"devDependencies": { "devDependencies": {
"@cloudflare/workers-types": "^4.20240222.0",
"@iconify-json/octicon": "^1.1.52", "@iconify-json/octicon": "^1.1.52",
"@nolebase/vitepress-plugin-enhanced-readabilities": "^1.22.2", "@nolebase/vitepress-plugin-enhanced-readabilities": "^1.22.2",
"@nolebase/vitepress-plugin-git-changelog": "^1.22.2", "@nolebase/vitepress-plugin-git-changelog": "^1.22.2",
@ -35,6 +36,7 @@
"vuepress-theme-hope": "2.0.0-rc.0" "vuepress-theme-hope": "2.0.0-rc.0"
}, },
"dependencies": { "dependencies": {
"hono": "^4.0.9",
"katex": "^0.16.9", "katex": "^0.16.9",
"pjts-suggestion-box": "^0.0.3", "pjts-suggestion-box": "^0.0.3",
"vuepress-plugin-md-enhance": "2.0.0-rc.0" "vuepress-plugin-md-enhance": "2.0.0-rc.0"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,2 @@
packages:
- functions