From 225d5b015e8ac6e664e2f19f25433ddbfafcb982 Mon Sep 17 00:00:00 2001 From: RhenCloud Date: Sun, 2 Aug 2026 07:12:54 +0800 Subject: [PATCH] feat(legal): serve Terms of Service and Privacy Policy pages Add bilingual (zh/en) /terms and /privacy pages served by the Worker for configuring the Discord application's Terms of Service and Privacy Policy URLs. Contact line is configurable via optional LEGAL_CONTACT env var. --- src/legal-routes.ts | 247 ++++++++++++++++++++++++++++++++++++++++++++ src/server.ts | 2 + src/types.ts | 1 + 3 files changed, 250 insertions(+) create mode 100644 src/legal-routes.ts diff --git a/src/legal-routes.ts b/src/legal-routes.ts new file mode 100644 index 0000000..defd2e2 --- /dev/null +++ b/src/legal-routes.ts @@ -0,0 +1,247 @@ +import { Hono } from "hono"; +import type { Env } from "./types"; + +type Lang = "zh" | "en"; + +const CONTACT_FALLBACK = "the repository maintainer (open an issue on the project repository)"; +const CONTACT_FALLBACK_ZH = "项目维护者(在项目仓库提交 issue)"; + +function pickLang(raw: string | undefined): Lang { + return raw === "en" ? "en" : "zh"; +} + +function layout(opts: { + lang: Lang; + active: "terms" | "privacy"; + title: string; + updated: string; + body: string; +}): string { + const { lang, active, title, updated, body } = opts; + const altLang: Lang = lang === "zh" ? "en" : "zh"; + const t = (zh: string, en: string): string => (lang === "zh" ? zh : en); + const q = (p: string): string => `${p}?lang=${lang}`; + const langLabel = t("English", "中文"); + return ` + + + + + +${title} · WebHooker + + + +
+
+WebHooker + +
+
+

${title}

+

${t("最后更新", "Last updated")}: ${updated}

+${body} +
+ +
+ +`; +} + +function termsBody(lang: Lang, contact: string): string { + if (lang === "en") { + return ` +

These Terms of Service ("Terms") govern your use of the WebHooker Discord application and bot ("the Service"), which forwards GitHub webhook events to Discord channels and lets you comment on GitHub from Discord using your own linked GitHub account.

+

1. Acceptance

+

By adding the bot to a Discord server, using its slash commands, or linking your GitHub account, you agree to these Terms. If you do not agree, do not use the Service.

+

2. The Service

+ +

3. Acceptable Use

+ +

4. Account Linking & Revocation

+

Linking is optional and initiated by you via /gh login. You may unlink at any time with /gh logout, or revoke access from your GitHub account settings under authorized OAuth applications.

+

5. Availability & Warranty

+

The Service is provided "as is" and "as available", without warranties of any kind. It may be modified, suspended, or discontinued at any time without notice.

+

6. Limitation of Liability

+

To the maximum extent permitted by law, the operator shall not be liable for any indirect, incidental, or consequential damages arising from your use of the Service.

+

7. Changes

+

These Terms may be updated. Continued use after changes constitutes acceptance of the revised Terms.

+

8. Contact

+

Questions about these Terms can be directed to ${contact}.

`; + } + return ` +

本《服务条款》(下称"条款")适用于你对 WebHooker Discord 应用与机器人(下称"本服务")的使用。本服务用于将 GitHub 的 webhook 事件转发到 Discord 频道,并允许你在 Discord 中以你本人绑定的 GitHub 账号对 GitHub 进行评论。

+

1. 接受条款

+

当你将机器人添加到 Discord 服务器、使用其斜杠命令,或绑定你的 GitHub 账号时,即表示你同意本条款。如不同意,请勿使用本服务。

+

2. 服务内容

+ +

3. 可接受使用

+ +

4. 账号绑定与解除

+

绑定为可选,由你通过 /gh login 主动发起。你可随时使用 /gh logout 解除绑定,或在 GitHub 账号设置的已授权 OAuth 应用中撤销授权。

+

5. 可用性与免责声明

+

本服务按"现状"和"现有可用"基础提供,不作任何形式的保证。服务可能随时被修改、暂停或终止,恕不另行通知。

+

6. 责任限制

+

在法律允许的最大范围内,运营者对因使用本服务而产生的任何间接、附带或后果性损害不承担责任。

+

7. 条款变更

+

本条款可能会更新。变更后继续使用即视为接受修订后的条款。

+

8. 联系方式

+

关于本条款的问题可联系${contact}。

`; +} + +function privacyBody(lang: Lang, contact: string): string { + if (lang === "en") { + return ` +

This Privacy Policy explains what data the WebHooker Discord application ("the Service") processes, why, and how it is stored.

+

1. Data We Process

+ +

2. How Data Is Stored

+ +

3. How Data Is Used

+

Solely to operate the Service: forwarding notifications and performing GitHub actions (comment create/edit/delete) that you explicitly request. We do not sell your data or use it for advertising.

+

4. Sharing

+

Data is shared only with the platforms required to deliver the Service — GitHub and Discord — through their official APIs, and with Cloudflare as the hosting/storage provider. No other third-party sharing occurs.

+

5. Your Choices

+ +

6. Data Retention

+

We retain data only as long as needed for the Service. Transient data expires automatically as described above; account links and tokens are removed when you unlink or revoke access.

+

7. Children

+

The Service is not directed to individuals under the age required by Discord's Terms of Service, and we do not knowingly collect their data.

+

8. Changes

+

This policy may be updated. Material changes will be reflected by the "Last updated" date above.

+

9. Contact

+

For privacy questions or data-removal requests, contact ${contact}.

`; + } + return ` +

本《隐私政策》说明 WebHooker Discord 应用(下称"本服务")处理哪些数据、为何处理以及如何存储。

+

1. 我们处理的数据

+ +

2. 数据如何存储

+ +

3. 数据如何使用

+

仅用于运行本服务:转发通知,以及执行你明确请求的 GitHub 操作(评论的创建/编辑/删除)。我们不出售你的数据,也不用于广告。

+

4. 数据共享

+

数据仅通过官方 API 与提供本服务所必需的平台共享——即 GitHub 与 Discord——以及作为托管/存储提供方的 Cloudflare。不存在其他第三方共享。

+

5. 你的选择

+ +

6. 数据留存

+

我们仅在提供本服务所需的期间内保留数据。临时数据按上文自动过期;账号绑定与令牌在你解除绑定或撤销授权时被删除。

+

7. 未成年人

+

本服务不面向低于 Discord 服务条款所要求年龄的个人,我们也不会有意收集其数据。

+

8. 政策变更

+

本政策可能会更新。重大变更将通过上方"最后更新"日期体现。

+

9. 联系方式

+

如有隐私问题或数据删除请求,请联系${contact}。

`; +} + +const UPDATED = "2026-08-01"; + +export function createLegalRoutes(): Hono<{ Bindings: Env }> { + const app = new Hono<{ Bindings: Env }>(); + + app.get("/terms", (c) => { + const lang = pickLang(c.req.query("lang")); + const contact = c.env.LEGAL_CONTACT ?? (lang === "zh" ? CONTACT_FALLBACK_ZH : CONTACT_FALLBACK); + return c.html( + layout({ + lang, + active: "terms", + title: lang === "zh" ? "服务条款" : "Terms of Service", + updated: UPDATED, + body: termsBody(lang, contact), + }), + ); + }); + + app.get("/privacy", (c) => { + const lang = pickLang(c.req.query("lang")); + const contact = c.env.LEGAL_CONTACT ?? (lang === "zh" ? CONTACT_FALLBACK_ZH : CONTACT_FALLBACK); + return c.html( + layout({ + lang, + active: "privacy", + title: lang === "zh" ? "隐私政策" : "Privacy Policy", + updated: UPDATED, + body: privacyBody(lang, contact), + }), + ); + }); + + return app; +} diff --git a/src/server.ts b/src/server.ts index d4457fb..2f538d0 100644 --- a/src/server.ts +++ b/src/server.ts @@ -5,6 +5,7 @@ import { dispatchEvent } from "./discord"; import { createOAuthRoutes } from "./oauth-routes"; import { createActionRoutes } from "./action-routes"; import { createAdminRoutes } from "./admin-routes"; +import { createLegalRoutes } from "./legal-routes"; import { log } from "./log"; const MAX_BODY_SIZE = 1024 * 1024; @@ -16,6 +17,7 @@ export function createServer(): Hono<{ Bindings: Env }> { app.route("/auth", createOAuthRoutes()); app.route("/", createActionRoutes()); + app.route("/", createLegalRoutes()); app.route("/admin", createAdminRoutes()); app.post("/webhook", async (c) => { diff --git a/src/types.ts b/src/types.ts index 07a7b3c..3e80f48 100644 --- a/src/types.ts +++ b/src/types.ts @@ -9,6 +9,7 @@ export interface Env { BASE_URL?: string; ADMIN_USER_IDS?: string; DISCORD_GATEWAY_ENABLED?: string; + LEGAL_CONTACT?: string; ASSETS?: Fetcher; KV: KVNamespace; DISCORD_GATEWAY: DurableObjectNamespace;