refactor(SuggestionBox): ♻️ remove suggestion box server
parent
887a183acc
commit
c767f065ff
|
@ -0,0 +1 @@
|
||||||
|
/api* https://suggestion-box.project-trans.org/api/:splat
|
|
@ -1,4 +0,0 @@
|
||||||
# For couldflare workers create a file named `.dev.vars`
|
|
||||||
|
|
||||||
TG_BOT_TOKEN=
|
|
||||||
TG_GROUP_ID=
|
|
|
@ -1,10 +0,0 @@
|
||||||
# Run
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh
|
|
||||||
npm run deploy
|
|
||||||
```
|
|
|
@ -1,143 +0,0 @@
|
||||||
import type { InputMediaPhoto } from 'grammy/types'
|
|
||||||
import type { ENV } from './types'
|
|
||||||
import { Bot, InputFile, InputMediaBuilder } from 'grammy'
|
|
||||||
import { Hono } from 'hono'
|
|
||||||
import { env } from 'hono/adapter'
|
|
||||||
import { handle } from 'hono/cloudflare-pages'
|
|
||||||
import { cors } from 'hono/cors'
|
|
||||||
import { customAlphabet } from 'nanoid'
|
|
||||||
|
|
||||||
const nanoid = customAlphabet('abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890', 8)
|
|
||||||
|
|
||||||
const IP_HEADER = 'CF-Connecting-IP'
|
|
||||||
|
|
||||||
const newSuccess = (message = 'success') => ({ code: 0, message })
|
|
||||||
function newError500(message = 'server internal error') {
|
|
||||||
return {
|
|
||||||
code: 500.001,
|
|
||||||
message,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function newErrorFormat400(message = 'The data format of the request is invalid. Please check and use the correct data format.') {
|
|
||||||
return { code: 400.001, message }
|
|
||||||
}
|
|
||||||
|
|
||||||
const app = new Hono()
|
|
||||||
|
|
||||||
app.onError((err, c) => {
|
|
||||||
console.error(String(err))
|
|
||||||
return c.json(newError500(), 500)
|
|
||||||
})
|
|
||||||
|
|
||||||
app.use('/api/*', cors())
|
|
||||||
|
|
||||||
app.get('/api', (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)
|
|
||||||
if (!TG_BOT_TOKEN) {
|
|
||||||
throw new Error('TG_BOT_TOKEN is not set')
|
|
||||||
}
|
|
||||||
if (!TG_GROUP_ID) {
|
|
||||||
throw new Error('TG_GROUP_ID is not set')
|
|
||||||
}
|
|
||||||
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 = form.get('referrer') || 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 ticketNumber = `#TN-${nanoid()}`
|
|
||||||
|
|
||||||
const msgs = [`<b>意见箱收到新消息</b> ${ticketNumber}\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(ticketNumber))
|
|
||||||
}
|
|
||||||
catch (error) {
|
|
||||||
// TODO handle error
|
|
||||||
// TODO log error
|
|
||||||
console.error(error)
|
|
||||||
throw error
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
export const onRequest = handle(app)
|
|
||||||
|
|
||||||
function replaceHtmlTag(str: string) {
|
|
||||||
return str
|
|
||||||
.replaceAll('<', '<')
|
|
||||||
.replaceAll('>', '>')
|
|
||||||
.replaceAll('&', '&')
|
|
||||||
}
|
|
|
@ -1,4 +0,0 @@
|
||||||
export type ENV = {
|
|
||||||
TG_BOT_TOKEN: string;
|
|
||||||
TG_GROUP_ID: string;
|
|
||||||
};
|
|
|
@ -1,16 +0,0 @@
|
||||||
{
|
|
||||||
"name": "server",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"dev": "wrangler dev src/index.ts",
|
|
||||||
"deploy": "wrangler deploy --minify src/index.ts"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"grammy": "^1.35.0",
|
|
||||||
"hono": "^4.7.2",
|
|
||||||
"nanoid": "^5.1.0"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@cloudflare/workers-types": "^4.20250214.0"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"target": "ESNext",
|
|
||||||
"jsx": "preserve",
|
|
||||||
"lib": [
|
|
||||||
"ESNext"
|
|
||||||
],
|
|
||||||
"module": "ESNext",
|
|
||||||
"moduleResolution": "Bundler",
|
|
||||||
"types": [
|
|
||||||
"@cloudflare/workers-types"
|
|
||||||
],
|
|
||||||
"strict": true,
|
|
||||||
"strictNullChecks": true,
|
|
||||||
"noImplicitAny": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"skipDefaultLibCheck": true,
|
|
||||||
"skipLibCheck": true
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,18 +0,0 @@
|
||||||
name = "server"
|
|
||||||
compatibility_date = "2025-02-14"
|
|
||||||
|
|
||||||
# [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 = ""
|
|
|
@ -1,2 +0,0 @@
|
||||||
packages:
|
|
||||||
- functions
|
|
Loading…
Reference in New Issue