Xin Gallery Puls is a Go-based ACG gallery backend.
It supports ingest from:
- Pixiv bookmarks crawler
- Telegram direct photo/document
- Telegram links (Pixiv / yande.re / X/Twitter / FANBOX )
It stores:
- Preview post in Publish Channel (A)
- Origin file in Storage Channel (B)
- Metadata in Cloudflare D1
- Optional backup to WebDAV/OpenList
PUBLISH_CHANNEL_ID(A): gallery preview postsSTORAGE_CHANNEL_ID(B): origin file storage (document)DISCUSSION_GROUP_ID(optional): linked discussion group for A
Flow per image:
- Send preview photo to A (caption)
- Send origin document to B
- If discussion group configured, post comment in A's thread:
- origin link (to B message)
- source link
- Write record into D1
Twitter link ingest caption style:
- Header:
title(source_url) / artist - Blockquote 1: tweet text
- Blockquote 2: hashtags (if any)
cmd/server/main.go: startup, HTTP, Telegram botinternal/config/config.go: env loadinginternal/app/: ingest/crawler logicinternal/telegram/telegram.go: Telegram send/downloadinternal/database/d1.go: D1 schema + queriesinternal/web/server.go: HTTP pages and APIsschema.sql: canonical D1 schemaweb/: frontend static pages
BOT_TOKENPUBLISH_CHANNEL_IDSTORAGE_CHANNEL_IDCLOUDFLARE_ACCOUNT_IDCLOUDFLARE_API_TOKEND1_DATABASE_IDADMIN_PASSWORD
CHANNEL_ID- backward compatibility fallback
- if
PUBLISH_CHANNEL_IDorSTORAGE_CHANNEL_IDis empty, fallback toCHANNEL_ID
DISCUSSION_GROUP_ID- linked group for channel comments
PIXIV_PHPSESSIDPIXIV_USER_IDPIXIV_TAG(optional, empty = all)PIXIV_REST(showorhide, defaultshow)PIXIV_CRAWL_ORDER(descorasc, defaultdesc)PIXIV_LIMIT(default40)PIXIV_MAX_PAGES(legacy fallback, default0= unlimited)PIXIV_BOOTSTRAP_MAX_PAGES(default-1-> fallback toPIXIV_MAX_PAGES)PIXIV_INCREMENTAL_MAX_PAGES(default2)PIXIV_INTERVAL_MINUTES(default120)
TWITTER_API_DOMAIN(defaultfxtwitter.com)TWITTER_AUTHOR_ENABLED(true/false, defaultfalse)TWITTER_AUTHOR_USERS(comma-separated usernames, e.g.LIMU838,kasuga_iz)TWITTER_RSS_SOURCES(semicolon-separated templates, supports{user})- example:
https://rsshubi.zeabur.app/twitter/user/{user}/includeRts=0&includeReplies=0&count=20
- example:
TWITTER_AUTHOR_INTERVAL_MINUTES(default60)TWITTER_AUTHOR_FETCH_LIMIT(default20)UMAMI_BASE_URLUMAMI_WEBSITE_ID_FRONTENDUMAMI_USERNAMEUMAMI_PASSWORDUMAMI_API_TOKENUMAMI_LOOKBACK_DAYS(default7)
BACKUP_ENABLED(true/false, defaultfalse)BACKUP_WEBDAV_URLBACKUP_WEBDAV_USERNAMEBACKUP_WEBDAV_PASSWORDBACKUP_BASE_PATH(default/MyPixiv)BACKUP_WORKERS(default1)BACKUP_RETRY_MAX(default5)BACKUP_POLL_SECONDS(default8)BACKUP_TASK_TIMEOUT_SECONDS(default120)
LISTEN_ADDR(default:8080)
Use schema.sql.
images now includes extra v2 fields:
source_textpublish_channel_id,publish_message_idstorage_channel_id,storage_message_iddiscussion_group_id,discussion_message_id
Backend EnsureSchema can auto-add missing columns for upgrade.
- Create channel A (publish).
- Create channel B (storage).
- (Optional) Create/choose discussion group and link to channel A.
- Add bot as admin in A and B.
- If using discussion comments, ensure bot can send messages in the linked group.
- Create a new D1 database.
- Run
schema.sql(or let backend auto-create on first start).
At minimum set:
BOT_TOKENPUBLISH_CHANNEL_IDSTORAGE_CHANNEL_IDDISCUSSION_GROUP_ID(optional)CLOUDFLARE_ACCOUNT_IDCLOUDFLARE_API_TOKEND1_DATABASE_IDADMIN_PASSWORD
- Build from Dockerfile and deploy to Zeabur.
- Confirm logs contain
HTTP server listening on :8080.
- Send a test image to bot:
- A gets preview
- B gets origin
- D1 has one row
- Send a Twitter link to bot:
- caption includes title/source/artist + tweet quote + hashtag quote
- discussion comment contains origin link + source link
- Open:
/gallery/admin/upload(Basic Auth)
Pages:
GET /-> redirect/galleryGET /galleryGET /favoritesGET /admin/upload(Basic Auth)
Public APIs:
GET /api/posts?type=all|h|v&offset=0&limit=20GET /api/favorites?type=all|h|v&offset=0&limit=20GET /api/randomGET /api/random?type=hGET /api/random?type=vGET /api/random?format=urlGET /api/random?format=redirectGET /image/{file_id}
Admin APIs:
GET /admin/api/imagesGET /admin/api/images/countPOST /admin/api/images/hidePOST /admin/api/images/favoriteGET /admin/api/umami/summaryGET /admin/api/backup/healthGET /admin/api/backup/statsPOST /admin/api/backup/backfillPOST /admin/api/backup/retry-failedGET /admin/api/backup/failedPOST /admin/api/backup/resolve
- Random image API returns preview only by design.
/image/{file_id}is long-cache friendly (max-age=31536000, immutable).- Keep only one bot instance to avoid Telegram
getUpdates 409 conflict. - Twitter author crawler uses RSS source + existing Twitter single-link ingest flow; state key format:
twitter_author_last_<username>. - If token/password leaked, rotate immediately.