- Add defer/recover in getMP4Meta to catch panics from gomedia library - Implement fallback to ffprobe when MP4 parsing fails - Fixes "no vosdata" panic when processing certain MP4 files Also add CLAUDE.md to provide architecture guidance for AI coding assistants. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
7.3 KiB
7.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
SaveAny-Bot is a Telegram bot written in Go that saves files from Telegram and external websites to various storage backends (Local, Alist, S3, WebDAV, Telegram). It supports bypassing "restrict saving content" media, batch downloads, auto-organization, and JS parser plugins for extracting content from websites.
Commands
Development
# Run the bot directly
go run ./cmd
# Run with specific config file
go run ./cmd --config /path/to/config.toml
# Generate i18n keys after modifying locale files
go generate ./...
# Run tests
go test ./...
# Run specific package tests
go test ./storage/s3
go test ./pkg/queue
Docker
# Build and run locally
docker-compose -f docker-compose.local.yml up
# Production deployment
docker-compose up -d
Architecture
Application Initialization Flow (CRITICAL)
The application follows a strict initialization sequence in cmd/run.go::initAll:
- Config (
config.Init) - Load config.toml using viper - Cache (
common/cache) - Initialize in-memory cache - i18n (
common/i18n) - Load localization files fromcommon/i18n/locale/ - Database (
database.Init) - Connect to SQLite, auto-migrate models - Storage (
storage.LoadStorages) - Initialize all configured storage backends - Parsers (
parsers.LoadPlugins) - Load JS parser plugins if enabled - Userbot (optional) - Login to Telegram userbot if configured
- Bot (
client/bot.Init) - Initialize Telegram bot client - Core (
core.Run) - Start task queue workers
When adding new initialization steps, add them to initAll in this order.
Core Components
Task Queue System (core/)
- Central abstraction:
core.Executableinterface withType(),Title(),TaskID(),Execute(ctx) - Task queue:
pkg/queue.TaskQueue[Executable]managed bycore.Run - Worker pool size controlled by
config.Workers - Task implementations in
core/tasks/:tfile/- Telegram file downloadsbatchtfile/- Batch downloadstelegraph/- Telegraph page savingparsed/- Content from parser pluginsdirectlinks/- Direct URL downloads
- Tasks support lifecycle hooks:
TaskBeforeStart,TaskSuccess,TaskFail,TaskCancel(configured viaconfig.Hook.Exec)
Telegram Client (client/)
- Bot:
client/bot/bot.gousesgotgproto.NewClient, handlers inclient/bot/handlers/ - Userbot:
client/user/for bypassing restricted content (optional) - Middleware:
client/middleware/handles flood wait, recovery, retry - Handler registration: All handlers registered in
handlers.Register(), command list inhandlers.CommandHandlers
Storage Backends (storage/)
- Abstract interface:
StoragewithInit(),Save(),Exists(),JoinStoragePath() - Implementations:
local/,alist/,s3/,minio/,webdav/,telegram/ - Storage registry:
storage.Storagesmap, constructed viastorageConstructors - Add new storage: Define enum in
pkg/enums/storage, config inconfig/storage/, implementation instorage/, register instorageConstructors
Parser Plugins (parsers/)
- Native parsers:
parsers/native/twitter/,parsers/native/kemono/ - JS plugin runtime:
parsers/js/usinggojaVM andplaywright-go - Plugin interface:
registerParser({ metadata, canHandle, parse })in JS - Plugins return
parser.ItemwithResources[], converted tocore/tasks/parsedtasks - Plugin docs:
plugins/README.md
Configuration (config/)
- Main config:
config/viper.go::Configloaded viaviper - Storage configs:
config/storage/factory.go::LoadStorageConfigswith type-specific validation - Environment override: Prefix
SAVEANY_, dots become underscores (e.g.,SAVEANY_TELEGRAM_TOKEN) - Important:
config.C()returns a copy, don't modify its fields; modify viaviperinconfig.Init
Database (database/)
- SQLite with GORM
- Models:
User,Dir,Rule,WatchChat - User sync:
database.syncUserssyncsconfig.Usersto DB (creates/deletes users based on config) - Don't create/delete users manually; use config file
Internationalization (common/i18n/)
- Locale files:
common/i18n/locale/*.yaml - Key generation:
go generate ./...runscmd/geni18n/main.goto generatecommon/i18n/i18nk/keys.go - Usage:
i18n.T(i18nk.KeyName, map[string]any{"Param": value}) - Add new strings: Edit YAML → run
go generate→ use new key in code
Logging
- Uses
github.com/charmbracelet/log - Logger injected into context in
cmd/run.go::Runvialog.WithContext - Prefer
log.FromContext(ctx)over global logger when context is available
Data Flow
- User sends message/file to Telegram bot
- Handler in
client/bot/handlers/processes update - Handler creates task (implements
core.Executable) and callscore.AddTask() - Task enters
pkg/queue.TaskQueue - Worker goroutine picks up task, calls
Execute(ctx) - Task downloads content (from Telegram or parser) and saves to storage backend via
Storage.Save() - Task completion triggers hooks, sends result message back to user
For parsed content (URLs):
- Handler extracts URL, calls
parsers.ParseWithContext() - Parser (native or JS plugin) returns
parser.ItemwithResources[] core/tasks/parsed.Taskcreated with resources- Task downloads each resource and saves to storage
Project-Specific Guidelines
Adding New Storage Backend
- Define enum in
pkg/enums/storage/storage.go - Create config struct in
config/storage/yourtype.goimplementingStorageConfigwithValidate() - Implement storage in
storage/yourtype/yourtype.gowithStorageinterface - Register in
config/storage/factory.go::storageFactoriesandstorage/storage.go::storageConstructors - Update
config.example.tomlwith example configuration
Adding New Task Type
- Create package in
core/tasks/yourtype/ - Define struct implementing
core.Executableinterface - Implement
Type(),Title(),TaskID(),Execute(ctx) - Add task type enum in
pkg/enums/tasktype/ - Create task from handler and call
core.AddTask(ctx, task)
Adding New Bot Command
- Create handler file in
client/bot/handlers/yourcommand.go - Implement handler function with signature matching gotgproto patterns
- Register in
handlers.Register()function - Add to
handlers.CommandHandlersslice for command list - Add i18n strings for command description and responses
Adding i18n Strings
- Edit locale files in
common/i18n/locale/(e.g.,zh-Hans.yaml,en.yaml) - Run
go generate ./...to generatecommon/i18n/i18nk/keys.go - Use in code:
i18n.T(i18nk.YourNewKey, map[string]any{"Variable": value})
Working with Middleware
New cross-cutting concerns (logging, metrics, rate limiting) should be implemented as middleware in client/middleware/:
- Create middleware file with function signature matching gotgproto middleware
- Add to
middleware.NewDefaultMiddlewares()inclient/bot/bot.go
Parser Plugin Development
- JS plugins go in directories specified by
config.parser.plugin_dirs(default:./plugins/) - See
plugins/README.mdfor plugin API documentation - Plugins have access to
ghttpfor HTTP requests andplaywrightfor browser automation - Test plugins in
./testplugins/directory