mirror of
https://github.com/TheFunny/TelegramTwitterMediaBot.git
synced 2026-09-23 23:32:05 +00:00
docs: add .env.example as the deployment template
`cp .env.example .env` is now the documented starting point: the tracked
template carries every variable (grouped required / sites / bot behaviour /
network / webhook / reverse proxy) with the defaults the code would use
anyway, and the compose comment plus both READMEs point at it. The proxy note
is spelled out where it matters — teloxide panics on a blank `TELOXIDE_PROXY`,
and inside a container the proxy host must be `host.docker.internal`.
Two follow-ups the template exposed:
- `RUST_LOG=` (present but blank, which `.env` makes easy) silenced the log
again: "unset" was handled, "empty" was not. A blank value now falls back to
the same default. Verified: blank and unset both produce the full startup
sequence.
- `TWITTER_AUTH_TOKEN` was in the README prose but missing from the env table
(both languages).
Verified: `docker compose --env-file .env.example config -q` resolves, and a
script comparing the compose's `${VAR}` references against the template's keys
finds none missing.
This commit is contained in:
@@ -0,0 +1,76 @@
|
||||
# Copy to `.env` (gitignored) and fill in:
|
||||
#
|
||||
# cp .env.example .env
|
||||
#
|
||||
# `docker compose` reads it for the `${VAR}` substitutions in
|
||||
# docker-compose.yml, and `cargo run` reads it through dotenv. Every variable is
|
||||
# described in README.md ("环境变量说明" / "Environment variables") — this file
|
||||
# only shows the shape, with the defaults the code would use anyway.
|
||||
|
||||
# --- required -------------------------------------------------------------
|
||||
# Token from @BotFather. Without it the bot exits at startup.
|
||||
TELOXIDE_TOKEN=
|
||||
|
||||
# --- sites (all optional) -------------------------------------------------
|
||||
# Pixiv: refresh token. Unset = pixiv links answer "support is disabled".
|
||||
PIXIV_REFRESH_TOKEN=
|
||||
# Twitter/X: the `auth_token` cookie of a logged-in session, used only for
|
||||
# NSFW tweets that the public syndication endpoint withholds.
|
||||
TWITTER_AUTH_TOKEN=
|
||||
# bilibili: the whole cookie string; only needed when the egress IP stays
|
||||
# risk-controlled (device cookies are fetched automatically).
|
||||
BILIBILI_COOKIE=
|
||||
|
||||
# --- bot behaviour --------------------------------------------------------
|
||||
# Admin chat IDs, comma-separated: start/stop notices, admin-only commands.
|
||||
BOT_ADMIN=
|
||||
# Log level. Leave the line commented out for the default
|
||||
# (`info,hyper_util=warn,reqwest=warn`); do not set it to an empty value.
|
||||
# RUST_LOG=info,xmedia_bot=debug,x_media=debug
|
||||
# Edit-before-forward record TTL (seconds).
|
||||
EDIT_MESSAGE_TTL_SECONDS=86400
|
||||
# Link-result cache TTL (seconds).
|
||||
LINK_CACHE_TTL_SECONDS=604800
|
||||
# Wrap a post's text in a collapsible blockquote from this many characters on;
|
||||
# 0 disables the wrap.
|
||||
CAPTION_QUOTE_TEXT_CHARS=200
|
||||
# State directory (local runs only — the container uses /app/data).
|
||||
DATA_DIR=data
|
||||
|
||||
# --- network --------------------------------------------------------------
|
||||
# HTTP proxy for the Bot API and site fetches. Two traps: teloxide panics on a
|
||||
# *blank* value, so comment the line out rather than leaving it empty; and
|
||||
# inside a container the proxy must be reachable from there (use
|
||||
# host.docker.internal, not 127.0.0.1 — that is the container itself).
|
||||
# docker-compose.yml does not pass this variable unless you add it to the bot
|
||||
# service's `environment:` block.
|
||||
# TELOXIDE_PROXY=http://127.0.0.1:10808
|
||||
|
||||
# --- webhook deployment (docker-compose.yml) ------------------------------
|
||||
# false = long polling (no public URL needed). true = webhook behind the
|
||||
# bundled nginx-proxy — and then WEBHOOK_LISTEN/PORT/URL are required.
|
||||
WEBHOOK=false
|
||||
# WEBHOOK_LISTEN=0.0.0.0
|
||||
# WEBHOOK_PORT=8443
|
||||
# WEBHOOK_URL=https://your.domain/
|
||||
# Validation token Telegram echoes back as X-Telegram-Bot-Api-Secret-Token.
|
||||
# WEBHOOK_SECRET_TOKEN=
|
||||
# Self-signed certificate path, used only for Telegram-side validation (TLS is
|
||||
# terminated by the reverse proxy); unneeded with acme-companion. Not passed by
|
||||
# docker-compose.yml — add the line there if this deployment needs it.
|
||||
# WEBHOOK_CERT=/app/cert/cert.pem
|
||||
|
||||
# --- reverse proxy (docker-compose.yml) -----------------------------------
|
||||
# Public domain or IP that nginx-proxy routes for; empty = do not route.
|
||||
VIRTUAL_HOST=
|
||||
# Port inside the bot container nginx-proxy forwards to.
|
||||
VIRTUAL_PORT=8443
|
||||
# Certificate notification address for acme-companion.
|
||||
DEFAULT_EMAIL=
|
||||
# UID the container runs as; it must be able to write ./data on the host.
|
||||
LOCAL_USER_ID=1000
|
||||
# Uncomment (here and the matching line in docker-compose.yml) to have
|
||||
# acme-companion issue the certificate for VIRTUAL_HOST.
|
||||
# ACME_HOST=
|
||||
# Send requests with an unknown Host to this vhost (needed for plain-IP access).
|
||||
# DEFAULT_HOST=
|
||||
@@ -98,7 +98,7 @@ Docker: `docker build -t tgxmb .` then `docker run --rm -d --name tgxmb --env-fi
|
||||
- Package manager: **Cargo** (workspace with path dep `x-media` ← `xmedia-bot`). No `[workspace.package]`/shared deps — each crate lists deps independently.
|
||||
- **TLS is rustls end-to-end** (no native-tls/openssl in the tree, no libssl in the Docker runtime image): `teloxide` is declared `default-features = false` with `["webhooks-axum", "macros", "rustls", "ctrlc_handler"]` (the removed `default` also carried `native-tls` and `ctrlc_handler` — the latter must stay); x-media's reqwest is `default-features = false` with `["json", "rustls-tls"]` (webpki-roots baked in, so the image ships no CA bundle). One reqwest 0.12.28 in the lock.
|
||||
- **Versioning**: bump the version in all three places (`crates/x-media/Cargo.toml`, `crates/xmedia-bot/Cargo.toml`, `Cargo.lock`) and **keep `README.md`, `README.en.md` and `AGENTS.md` in sync with the code on every bump**, then commit (`chore: bump version to X.Y.Z`), create an annotated tag `vX.Y.Z`, and push branch + tag (the tag push triggers the Docker Hub build). The tag must equal both crate versions: `.github/workflows/docker.yml` verifies that before building, and `--locked` verifies the lock file.
|
||||
- Config is **environment-variable driven** (dotenv loads `.env`, gitignored; no `.env.example` exists). Key vars: `TELOXIDE_TOKEN` (required), `PIXIV_REFRESH_TOKEN`, `TWITTER_AUTH_TOKEN` (optional; x.com `auth_token` cookie — enables the logged-in GraphQL fallback that fetches NSFW tweets syndication withholds), `BILIBILI_COOKIE` (optional; whole bilibili cookie string — bilibili dynamics fetch anonymously and add their own device cookies, this only rescues an egress IP that bilibili has hard-flagged with `-352`/412), `BOT_ADMIN` (comma-separated ids), `EDIT_MESSAGE_TTL_SECONDS` (default 86400), `LINK_CACHE_TTL_SECONDS` (default 604800), `CAPTION_QUOTE_TEXT_CHARS` (default 200; a post whose text — the `title` plus `content` joined, see `site::compose_text` — reaches this length gets that text wrapped in an expandable blockquote inside its caption, the URL and author line staying outside; `0` disables it. Applied at the send boundary in `send::quote_long_caption`, which locates the text as what follows the author link, so a `/set_format` that moves `{title}`/`{content}` elsewhere and pixiv's title-inside-a-link layout opt out; `copy_messages` forwards and queued retries inherit the wrap, while the edit-before-forward rewrite stays unquoted by design), `DATA_DIR` (default `data`, CWD-relative; the SQLite dir, auto-created), `WEBHOOK`/`WEBHOOK_URL`/`WEBHOOK_LISTEN`/`WEBHOOK_PORT`/`WEBHOOK_CERT`/`WEBHOOK_SECRET_TOKEN` (webhook mode requires URL/listen/port, `.expect`ed; `WEBHOOK_CERT` is Telegram-facing self-signed validation only — TLS must be terminated by a reverse proxy), `RUST_LOG`, `TELOXIDE_PROXY`, `LOCAL_USER_ID` (entrypoint only).
|
||||
- Config is **environment-variable driven** (dotenv loads `.env`, which is gitignored; `.env.example` is the tracked template — `cp .env.example .env` — and is also the file `docker compose` substitutes `${VAR}` from, so every variable the compose passes must be documented there). Key vars: `TELOXIDE_TOKEN` (required), `PIXIV_REFRESH_TOKEN`, `TWITTER_AUTH_TOKEN` (optional; x.com `auth_token` cookie — enables the logged-in GraphQL fallback that fetches NSFW tweets syndication withholds), `BILIBILI_COOKIE` (optional; whole bilibili cookie string — bilibili dynamics fetch anonymously and add their own device cookies, this only rescues an egress IP that bilibili has hard-flagged with `-352`/412), `BOT_ADMIN` (comma-separated ids), `EDIT_MESSAGE_TTL_SECONDS` (default 86400), `LINK_CACHE_TTL_SECONDS` (default 604800), `CAPTION_QUOTE_TEXT_CHARS` (default 200; a post whose text — the `title` plus `content` joined, see `site::compose_text` — reaches this length gets that text wrapped in an expandable blockquote inside its caption, the URL and author line staying outside; `0` disables it. Applied at the send boundary in `send::quote_long_caption`, which locates the text as what follows the author link, so a `/set_format` that moves `{title}`/`{content}` elsewhere and pixiv's title-inside-a-link layout opt out; `copy_messages` forwards and queued retries inherit the wrap, while the edit-before-forward rewrite stays unquoted by design), `DATA_DIR` (default `data`, CWD-relative; the SQLite dir, auto-created), `WEBHOOK`/`WEBHOOK_URL`/`WEBHOOK_LISTEN`/`WEBHOOK_PORT`/`WEBHOOK_CERT`/`WEBHOOK_SECRET_TOKEN` (webhook mode requires URL/listen/port, `.expect`ed; `WEBHOOK_CERT` is Telegram-facing self-signed validation only — TLS must be terminated by a reverse proxy), `RUST_LOG`, `TELOXIDE_PROXY`, `LOCAL_USER_ID` (entrypoint only).
|
||||
- SQLite via `rusqlite` with `bundled` feature (no system libsqlite needed). DB file `$DATA_DIR/task_queue.db` (default `data/task_queue.db`, CWD-relative — run from the workspace root, or `/app` in Docker; set `DATA_DIR` to pin state anywhere). Mount `./data` and `./cert` volumes.
|
||||
- `.gitattributes` enforces LF for `*.sh` (CRLF breaks shebangs in containers). `.gitignore`: `.env`, `data/`, `cert/`, `nginx-*` (proxy state), `/target`, `.idea/` (the compose file is tracked; only `.env` carries the deployment's own values).
|
||||
- Docs are in Chinese (README, AGENTS.md); user-facing bot strings are in English. Keep that split when editing user-facing strings and docs.
|
||||
|
||||
@@ -30,8 +30,11 @@ cargo run -p xmedia-bot
|
||||
Docker deployment (`docker-compose.yml` in this repo is the orchestration; instance values live in the `.env` next to it, and compose substitutes every `${VAR}` from there):
|
||||
|
||||
```bash
|
||||
cp .env.example .env # fill in TELOXIDE_TOKEN and the rest; every line is commented
|
||||
docker build -t tgxmb .
|
||||
docker run --rm -d --name tgxmb --env-file .env -v ./data:/app/data tgxmb
|
||||
# or use the bundled orchestration (nginx-proxy + acme-companion):
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Environment variables: `TELOXIDE_TOKEN` (required), `PIXIV_REFRESH_TOKEN`, `BOT_ADMIN`, `EDIT_MESSAGE_TTL_SECONDS`, `LINK_CACHE_TTL_SECONDS`, `RUST_LOG`, `TELOXIDE_PROXY`, `WEBHOOK*`, `TWITTER_AUTH_TOKEN` (optional), `BILIBILI_COOKIE` (optional).
|
||||
@@ -87,6 +90,7 @@ Telegram only accepts ports 443/80/88/8443.
|
||||
|---|---|
|
||||
| `TELOXIDE_TOKEN` | Bot token (required) |
|
||||
| `PIXIV_REFRESH_TOKEN` | Pixiv refresh token; Pixiv is disabled without it (a pixiv link then gets an explicit "site not enabled" reply instead of silence) |
|
||||
| `TWITTER_AUTH_TOKEN` | Optional; the `auth_token` cookie of a logged-in x.com session, used only to fetch NSFW tweets' media |
|
||||
| `BILIBILI_COOKIE` | Optional bilibili cookie string (`SESSDATA=…; bili_jct=…`); only needed when the egress IP stays risk-controlled (device cookies are fetched automatically) |
|
||||
| `BOT_ADMIN` | Admin chat IDs, comma-separated; receives start/stop notifications |
|
||||
| `EDIT_MESSAGE_TTL_SECONDS` | Edit-before-forward record expiry in seconds, default 86400; once lapsed the prompt is rewritten in place to "expired — nothing was forwarded" (no extra message) |
|
||||
|
||||
@@ -30,8 +30,11 @@ cargo run -p xmedia-bot
|
||||
Docker 部署(编排见仓库里的 `docker-compose.yml`,实例相关的值写在同目录的 `.env`,compose 会自动替换其中的 `${VAR}`):
|
||||
|
||||
```bash
|
||||
cp .env.example .env # 填 TELOXIDE_TOKEN 等,逐项都有注释
|
||||
docker build -t tgxmb .
|
||||
docker run --rm -d --name tgxmb --env-file .env -v ./data:/app/data tgxmb
|
||||
# 或者用仓库里的编排(含 nginx-proxy + acme-companion):
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
环境变量:`TELOXIDE_TOKEN`(必填)、`PIXIV_REFRESH_TOKEN`、`BOT_ADMIN`、`EDIT_MESSAGE_TTL_SECONDS`、`LINK_CACHE_TTL_SECONDS`、`RUST_LOG`、`TELOXIDE_PROXY`、`WEBHOOK*`、`TWITTER_AUTH_TOKEN`(可选)、`BILIBILI_COOKIE`(可选)。
|
||||
@@ -87,6 +90,7 @@ Telegram 只接受 443/80/88/8443 端口。
|
||||
|---|---|
|
||||
| `TELOXIDE_TOKEN` | Bot token(必填) |
|
||||
| `PIXIV_REFRESH_TOKEN` | Pixiv 刷新令牌;未设置则禁用 Pixiv(此时收到 pixiv 链接会明确回复「站点未启用」,不会静默忽略) |
|
||||
| `TWITTER_AUTH_TOKEN` | 可选;登录 x.com 后浏览器 Cookie 里的 `auth_token`,仅在遇到 NSFW 推文时以登录态获取媒体 |
|
||||
| `BILIBILI_COOKIE` | 可选的 B 站 Cookie 串(`SESSDATA=…; bili_jct=…`),仅在出口 IP 被持续风控时才需要(设备 cookie 由 bot 自动获取) |
|
||||
| `BOT_ADMIN` | 管理员聊天 ID,逗号分隔;接收启动/停止通知 |
|
||||
| `EDIT_MESSAGE_TTL_SECONDS` | 转发前编辑记录过期秒数,默认 86400;过期后提示消息会被就地改写为「已过期,未转发」(不额外发消息打扰) |
|
||||
|
||||
@@ -97,12 +97,15 @@ async fn main() {
|
||||
// with no logs; and at `debug` the HTTP client's own lines (hyper_util,
|
||||
// reqwest) outnumbered the bot's by two to one. The timed builder adds
|
||||
// the timestamp the plain `init` omitted, so a line can be compared with
|
||||
// a user's report. An explicit RUST_LOG still wins outright.
|
||||
// a user's report. An explicit RUST_LOG still wins outright — but a blank
|
||||
// one (`RUST_LOG=` in `.env`, which is not "unset") must not silence the
|
||||
// log the way its absence used to.
|
||||
let filter = std::env::var("RUST_LOG")
|
||||
.ok()
|
||||
.filter(|value| !value.trim().is_empty())
|
||||
.unwrap_or_else(|| "info,hyper_util=warn,reqwest=warn".to_string());
|
||||
pretty_env_logger::formatted_timed_builder()
|
||||
.parse_filters(
|
||||
&std::env::var("RUST_LOG")
|
||||
.unwrap_or_else(|_| "info,hyper_util=warn,reqwest=warn".to_string()),
|
||||
)
|
||||
.parse_filters(&filter)
|
||||
.init();
|
||||
log::info!("Starting bot");
|
||||
|
||||
|
||||
Reference in New Issue
Block a user