✨ Add server (#262)
parent
120d5667b0
commit
21fca698b3
|
@ -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'
|
||||||
|
|
|
@ -0,0 +1,4 @@
|
||||||
|
# For couldflare workers create a file named `.dev.vars`
|
||||||
|
|
||||||
|
TG_BOT_TOKEN=
|
||||||
|
TG_GROUP_ID=
|
|
@ -0,0 +1,8 @@
|
||||||
|
```
|
||||||
|
npm install
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
npm run deploy
|
||||||
|
```
|
|
@ -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("<", "<")
|
||||||
|
.replaceAll(">", ">")
|
||||||
|
.replaceAll("&", "&");
|
||||||
|
}
|
|
@ -0,0 +1,4 @@
|
||||||
|
export type ENV = {
|
||||||
|
TG_BOT_TOKEN: string;
|
||||||
|
TG_GROUP_ID: string;
|
||||||
|
};
|
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
},
|
||||||
|
}
|
|
@ -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 = ""
|
|
@ -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"
|
||||||
|
|
784
pnpm-lock.yaml
784
pnpm-lock.yaml
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
||||||
|
packages:
|
||||||
|
- functions
|
Loading…
Reference in New Issue