Documentation
¶
Index ¶
- Variables
- func DecompressMediaBytes(r io.Reader, compression protocol.MediaCompression) (io.ReadCloser, error)
- func SetMediaDebugLogs(enabled bool)
- type Config
- type DNSServer
- type Feed
- func (f *Feed) AfterFetchCycle(ctx context.Context)
- func (f *Feed) ChannelNames() []string
- func (f *Feed) GetBlock(channel, block int) ([]byte, error)
- func (f *Feed) GitHubRelay() *GitHubRelay
- func (f *Feed) IsPrivateChannel(channelNum int) bool
- func (f *Feed) MediaCache() *MediaCache
- func (f *Feed) MergeProfilePics(pics map[string][]byte) int
- func (f *Feed) ProfilePicsBundle() protocol.ProfilePicsBundle
- func (f *Feed) SetChannelDisplayName(channelNum int, displayName string)
- func (f *Feed) SetChannels(channels []string)
- func (f *Feed) SetChatInfo(channelNum int, chatType protocol.ChatType, canSend bool)
- func (f *Feed) SetGitHubRelay(r *GitHubRelay)
- func (f *Feed) SetLatestVersion(v string)
- func (f *Feed) SetMediaCache(c *MediaCache)
- func (f *Feed) SetNextFetch(ts uint32)
- func (f *Feed) SetProfilePics(pics map[string][]byte) int
- func (f *Feed) SetTelegramLoggedIn(loggedIn bool)
- func (f *Feed) UpdateChannel(channelNum int, msgs []protocol.Message)
- type GitHubRelay
- func (g *GitHubRelay) Domain() string
- func (g *GitHubRelay) Flush(ctx context.Context) error
- func (g *GitHubRelay) Has(size int64, crc uint32) bool
- func (g *GitHubRelay) MaxBytes() int64
- func (g *GitHubRelay) PruneStale(ctx context.Context, cutoff time.Time) (int, error)
- func (g *GitHubRelay) Repo() string
- func (g *GitHubRelay) Run(ctx context.Context)
- func (g *GitHubRelay) TTL() time.Duration
- func (g *GitHubRelay) Touch(size int64, crc uint32)
- func (g *GitHubRelay) Upload(ctx context.Context, body []byte) error
- type GitHubRelayConfig
- type MediaCache
- func (c *MediaCache) GetBlock(channel, block uint16) ([]byte, error)
- func (c *MediaCache) Lookup(cacheKey string) (protocol.MediaMeta, bool)
- func (c *MediaCache) LookupByChannel(channel uint16) (mime, filename string, ok bool)
- func (c *MediaCache) MaxAcceptableBytes() int64
- func (c *MediaCache) SetGitHubRelay(g *GitHubRelay)
- func (c *MediaCache) Stats() MediaCacheStats
- func (c *MediaCache) Store(cacheKey, tag string, content []byte, mimeType, filename string) (protocol.MediaMeta, error)
- func (c *MediaCache) StoreWithOptions(cacheKey, tag string, content []byte, mimeType, filename string, ...) (protocol.MediaMeta, error)
- func (c *MediaCache) Sweep() int
- func (c *MediaCache) TouchRelayEntries()
- type MediaCacheConfig
- type MediaCacheStats
- type MediaCacheStoreOptions
- type PublicReader
- type Server
- type TelegramConfig
- type TelegramReader
- func (tr *TelegramReader) RequestRefresh()
- func (tr *TelegramReader) Run(ctx context.Context) error
- func (tr *TelegramReader) SendMessage(ctx context.Context, channelNum int, text string) error
- func (tr *TelegramReader) SetFetchInterval(d time.Duration)
- func (tr *TelegramReader) UpdateChannels(channels []string)
- type XPublicReader
Constants ¶
This section is empty.
Variables ¶
var ErrCacheFull = errors.New("no free media channel slot")
ErrCacheFull is returned by Store when no media channel slot is available. In practice this requires either MediaChannelEnd-Start+1 simultaneously pinned files or a TTL too generous for the workload.
var ErrTooLarge = errors.New("media file exceeds configured max-size")
ErrTooLarge is returned by Store when content exceeds MaxFileBytes.
Functions ¶
func DecompressMediaBytes ¶ added in v0.13.0
func DecompressMediaBytes(r io.Reader, compression protocol.MediaCompression) (io.ReadCloser, error)
DecompressMediaBytes is the inverse of compressMediaBytes; exposed for the HTTP layer (which receives a stream of compressed bytes after the header is stripped) and tests.
func SetMediaDebugLogs ¶ added in v0.13.0
func SetMediaDebugLogs(enabled bool)
SetMediaDebugLogs enables or disables the media debug log channel.
Types ¶
type Config ¶
type Config struct {
ListenAddr string
Domain string
Passphrase string
ChannelsFile string
XAccountsFile string
XRSSInstances string
MaxPadding int
MsgLimit int // max messages per channel (0 = default 15)
NoTelegram bool // if true, fetch public channels without Telegram login
AllowManage bool // if true, remote channel management and sending via DNS is allowed
Debug bool // if true, log every decoded DNS query
// DNSMediaEnabled toggles the slow DNS-relay path. When false the
// server still ingests media bytes (so other relays can serve them)
// but the wire-format DNS flag is unset for clients.
DNSMediaEnabled bool
DNSMediaMaxSize int64 // per-file cap for the DNS relay (0 = no cap)
DNSMediaCacheTTL int // DNS-relay TTL in minutes
DNSMediaCompression string // DNS-relay compression: none|gzip|deflate
FetchInterval time.Duration // 0 = default 10m; floor enforced by main
GitHubRelay GitHubRelayConfig
Telegram TelegramConfig
}
Config holds server configuration.
type DNSServer ¶
type DNSServer struct {
// contains filtered or unexported fields
}
DNSServer serves feed data over DNS TXT queries.
func NewDNSServer ¶
func NewDNSServer(listenAddr, domain string, feed *Feed, queryKey, responseKey [protocol.KeySize]byte, maxPadding int, reader *TelegramReader, allowManage bool, channelsFile string, xAccounts []string, debug bool) *DNSServer
NewDNSServer creates a DNS server for the given domain.
func (*DNSServer) AddRefresher ¶ added in v0.7.0
func (s *DNSServer) AddRefresher(channelCtl channelRefresher)
AddRefresher adds an additional source refresher (e.g., X reader) for admin refresh.
func (*DNSServer) ListenAndServe ¶
ListenAndServe starts the DNS server on UDP, shutting down when ctx is cancelled.
func (*DNSServer) SetChannelRefresher ¶ added in v0.7.0
func (s *DNSServer) SetChannelRefresher(channelCtl channelRefresher)
SetChannelRefresher allows wiring a non-Telegram channel source (e.g. public reader) for admin update/refresh operations.
func (*DNSServer) SetXReader ¶ added in v0.9.0
func (s *DNSServer) SetXReader(xr *XPublicReader)
SetXReader stores the XPublicReader so baseCh can be updated on channel changes.
type Feed ¶
type Feed struct {
// contains filtered or unexported fields
}
Feed manages the block data for all channels.
func (*Feed) AfterFetchCycle ¶ added in v0.13.0
AfterFetchCycle: touch live media → flush pending → prune stale. Touch must come first so files referenced by skipped fetches don't age out.
func (*Feed) ChannelNames ¶
ChannelNames returns the configured channel names.
func (*Feed) GitHubRelay ¶ added in v0.13.0
func (f *Feed) GitHubRelay() *GitHubRelay
GitHubRelay returns the configured relay, or nil.
func (*Feed) IsPrivateChannel ¶
IsPrivateChannel returns true if the channel has chatType == ChatTypePrivate.
func (*Feed) MediaCache ¶ added in v0.13.0
func (f *Feed) MediaCache() *MediaCache
MediaCache returns the configured MediaCache or nil.
func (*Feed) MergeProfilePics ¶ added in v0.16.0
MergeProfilePics is SetProfilePics that retains the existing bundle's entries (re-extracted and re-verified) and overlays pics on top. Used by readers that only know a subset of channels (Telegram-only, X-only) so each one contributes without wiping the others.
Serialised so two readers merging from the same prior state can't lose each other's writes.
func (*Feed) ProfilePicsBundle ¶ added in v0.16.0
func (f *Feed) ProfilePicsBundle() protocol.ProfilePicsBundle
ProfilePicsBundle returns a copy of the current directory.
func (*Feed) SetChannelDisplayName ¶ added in v0.11.0
SetChannelDisplayName stores a human-readable title for a channel (1-indexed). It never mutates the handle in f.channels, which remains the stable identifier.
func (*Feed) SetChannels ¶
SetChannels replaces the channel list and rebuilds metadata.
func (*Feed) SetChatInfo ¶
SetChatInfo stores the chat type and send capability for a channel.
func (*Feed) SetGitHubRelay ¶ added in v0.13.0
func (f *Feed) SetGitHubRelay(r *GitHubRelay)
SetGitHubRelay attaches the GitHub fast relay. Safe to call once at startup. nil disables.
func (*Feed) SetLatestVersion ¶ added in v0.7.0
SetLatestVersion stores latest known release version for the dedicated version channel.
func (*Feed) SetMediaCache ¶ added in v0.13.0
func (f *Feed) SetMediaCache(c *MediaCache)
SetMediaCache attaches a MediaCache to this Feed. Pass nil to disable media serving (the default for backward compat). Safe to call once at startup before any DNS query is served.
func (*Feed) SetNextFetch ¶
SetNextFetch sets the unix timestamp of the next server-side fetch.
func (*Feed) SetProfilePics ¶ added in v0.16.0
SetProfilePics replaces the profile-pic bundle with the given username → image-bytes map. Other usernames currently in the bundle are dropped; use MergeProfilePics for additive behaviour. Empty values are skipped. Requires SetMediaCache. Returns the number of avatars in the resulting bundle.
func (*Feed) SetTelegramLoggedIn ¶
SetTelegramLoggedIn sets the flag indicating whether the server has a Telegram session.
type GitHubRelay ¶ added in v0.13.0
type GitHubRelay struct {
// contains filtered or unexported fields
}
GitHubRelay uploads encrypted media to a GitHub repo. Domain and object names are HMAC'd; blobs are AES-256-GCM. Uploads are batched into one Git Data API commit per flush.
func NewGitHubRelay ¶ added in v0.13.0
func NewGitHubRelay(cfg GitHubRelayConfig, domain, passphrase string) *GitHubRelay
NewGitHubRelay returns nil when the config is incomplete.
func (*GitHubRelay) Domain ¶ added in v0.13.0
func (g *GitHubRelay) Domain() string
Domain is the HMAC'd path segment used inside the relay repo.
func (*GitHubRelay) Flush ¶ added in v0.13.0
func (g *GitHubRelay) Flush(ctx context.Context) error
Flush forces an immediate commit of any pending uploads. Safe to call from tests or graceful shutdown; does nothing if the queue is empty.
func (*GitHubRelay) Has ¶ added in v0.13.0
func (g *GitHubRelay) Has(size int64, crc uint32) bool
Has reports whether the file is committed or queued for the next commit.
func (*GitHubRelay) MaxBytes ¶ added in v0.13.0
func (g *GitHubRelay) MaxBytes() int64
MaxBytes is the per-file cap. 0 means no cap.
func (*GitHubRelay) PruneStale ¶ added in v0.13.0
PruneStale removes every file in `known` whose lastSeen is older than cutoff. Selection happens INSIDE commitMu so concurrent prunes from different readers can't pick the same files and race the resulting commits (which used to produce 422 BadObjectState).
func (*GitHubRelay) Repo ¶ added in v0.13.0
func (g *GitHubRelay) Repo() string
Repo returns the configured "owner/repo" so the discovery channel can expose it to clients without leaking the token.
func (*GitHubRelay) Run ¶ added in v0.13.0
func (g *GitHubRelay) Run(ctx context.Context)
Run waits for shutdown and flushes any remaining pending uploads on the way out. Flush + prune during normal operation are driven by Feed.AfterFetchCycle so they line up with the natural cadence of upstream fetches. A best-effort backstop tick handles the case where nothing has fetched in a long time (e.g. all channels were skipped from cache).
func (*GitHubRelay) TTL ¶ added in v0.13.0
func (g *GitHubRelay) TTL() time.Duration
TTL returns the configured object lifetime.
func (*GitHubRelay) Touch ¶ added in v0.13.0
func (g *GitHubRelay) Touch(size int64, crc uint32)
Touch refreshes the lastSeen timestamp without re-uploading. Used when upstream re-delivers a file that's already in the relay.
type GitHubRelayConfig ¶ added in v0.13.0
type GitHubRelayConfig struct {
Enabled bool
Token string
Repo string
Branch string // default branch to commit to; "" → "main"
StatePath string // file used to persist lastSeen across restarts
MaxBytes int64
TTLMinutes int
}
GitHubRelayConfig configures the GitHub fast relay. Active() requires Enabled + Token + Repo.
func (GitHubRelayConfig) Active ¶ added in v0.13.0
func (g GitHubRelayConfig) Active() bool
type MediaCache ¶ added in v0.13.0
type MediaCache struct {
// contains filtered or unexported fields
}
MediaCache stores binary media blobs (images, files, ...) keyed by an upstream-stable identifier (Telegram file_id, image URL, ...). Each entry occupies one channel number drawn from the [MediaChannelStart, MediaChannelEnd] range, plus a precomputed list of fixed-size raw blocks served via the regular DNS TXT path.
The cache is safe for concurrent use. Hot-path operations (Store, GetBlock) are O(log n) at worst and typically O(1) with the help of two side maps.
func NewMediaCache ¶ added in v0.13.0
func NewMediaCache(cfg MediaCacheConfig) *MediaCache
NewMediaCache constructs a cache with the given configuration. A zero MaxFileBytes disables the size cap; a zero TTL means entries never expire (not recommended in production).
func (*MediaCache) GetBlock ¶ added in v0.13.0
func (c *MediaCache) GetBlock(channel, block uint16) ([]byte, error)
GetBlock returns one block of cached media for serving over DNS. Returns an error if the channel isn't a media channel, the entry has expired, or the block index is out of range. Increments the served-query counter.
func (*MediaCache) Lookup ¶ added in v0.13.0
func (c *MediaCache) Lookup(cacheKey string) (protocol.MediaMeta, bool)
Lookup returns the metadata for an entry by cache key, refreshing TTL on hit. Returns ok=false if not present.
func (*MediaCache) LookupByChannel ¶ added in v0.13.0
func (c *MediaCache) LookupByChannel(channel uint16) (mime, filename string, ok bool)
LookupByChannel returns the cached entry's transport metadata (mime, filename) for a serving channel. Returns ok=false if no entry is mapped. Used by the HTTP layer to pick a sensible Content-Type/Content-Disposition for clients that didn't provide one in the query string.
func (*MediaCache) MaxAcceptableBytes ¶ added in v0.13.0
func (c *MediaCache) MaxAcceptableBytes() int64
MaxAcceptableBytes returns the largest file size any enabled relay would accept. Callers use it as the "should we even fetch this?" gate so that files which fit GitHub but not DNS still get pulled. 0 means "no cap".
func (*MediaCache) SetGitHubRelay ¶ added in v0.13.0
func (c *MediaCache) SetGitHubRelay(g *GitHubRelay)
SetGitHubRelay attaches the GitHub fast relay. Store calls (and Lookup hits) will then surface RelayGitHub when the relay has the bytes.
func (*MediaCache) Stats ¶ added in v0.13.0
func (c *MediaCache) Stats() MediaCacheStats
Stats returns a snapshot of cache counters. Lock-free for the per-counter fields; Entries and Bytes are also atomic.
func (*MediaCache) Store ¶ added in v0.13.0
func (c *MediaCache) Store(cacheKey, tag string, content []byte, mimeType, filename string) (protocol.MediaMeta, error)
Store inserts (or refreshes) a media blob into the cache and returns metadata that the caller can embed in a feed message.
cacheKey is an upstream-stable identifier (e.g. Telegram file_id, image URL). When the same key is stored again, the existing entry's TTL is refreshed and the same channel/blocks are returned without copying the contents — callers should rely on this for the "fetch every 10 min" duplicate-handling case described in the design.
tag is the protocol media tag (MediaImage, MediaFile, ...); mimeType and filename are optional and stored for the HTTP layer to surface to the client. content is the raw file bytes; the caller may pass a slice it continues to use after the call (Store copies into block-sized chunks).
func (*MediaCache) StoreWithOptions ¶ added in v0.16.0
func (c *MediaCache) StoreWithOptions(cacheKey, tag string, content []byte, mimeType, filename string, opts MediaCacheStoreOptions) (protocol.MediaMeta, error)
StoreWithOptions is Store with selective relay control.
func (*MediaCache) Sweep ¶ added in v0.13.0
func (c *MediaCache) Sweep() int
Sweep evicts entries whose TTL has elapsed. Returns the number evicted. Safe to call from a periodic goroutine.
func (*MediaCache) TouchRelayEntries ¶ added in v0.13.0
func (c *MediaCache) TouchRelayEntries()
TouchRelayEntries refreshes relay lastSeen for every cached file so files referenced by skipped-fetch cycles aren't pruned.
type MediaCacheConfig ¶ added in v0.13.0
type MediaCacheConfig struct {
MaxFileBytes int64
TTL time.Duration
Compression protocol.MediaCompression
Logf func(format string, args ...interface{})
DNSRelayEnabled bool // controls Relays[RelayDNS] on the wire
}
MediaCacheConfig configures a new MediaCache.
type MediaCacheStats ¶ added in v0.13.0
type MediaCacheStats struct {
Entries int64 `json:"entries"`
Bytes int64 `json:"bytes"`
Queries uint64 `json:"queries"`
StoreHits uint64 `json:"storeHits"`
StoreMisses uint64 `json:"storeMisses"`
StoreRejected uint64 `json:"storeRejected"`
Evictions uint64 `json:"evictions"`
MaxFileBytes int64 `json:"maxFileBytes"`
TTLSeconds int64 `json:"ttlSeconds"`
}
MediaCacheStats is a snapshot of cache counters.
type MediaCacheStoreOptions ¶ added in v0.16.0
type MediaCacheStoreOptions struct {
SkipGitHub bool
}
MediaCacheStoreOptions toggles relay paths for a single Store call. Zero value = both DNS channel and (if a relay is configured) GitHub upload. SkipGitHub keeps the DNS allocation but skips the upload — used when many small siblings share one bundled GitHub upload.
type PublicReader ¶
type PublicReader struct {
// contains filtered or unexported fields
}
PublicReader fetches recent posts from public Telegram channels via the web view.
func NewPublicReader ¶
func NewPublicReader(channelUsernames []string, feed *Feed, msgLimit int, baseCh int) *PublicReader
NewPublicReader creates a reader for public channels without Telegram login.
func (*PublicReader) RequestRefresh ¶ added in v0.7.0
func (pr *PublicReader) RequestRefresh()
RequestRefresh signals the fetch loop to re-fetch immediately.
func (*PublicReader) Run ¶
func (pr *PublicReader) Run(ctx context.Context) error
Run starts the periodic public-channel fetch loop.
func (*PublicReader) SetFetchInterval ¶ added in v0.13.0
func (pr *PublicReader) SetFetchInterval(d time.Duration)
SetFetchInterval overrides the default 10m fetch cadence. Caller must invoke before Run starts.
func (*PublicReader) UpdateChannels ¶ added in v0.7.0
func (pr *PublicReader) UpdateChannels(channels []string)
UpdateChannels replaces the channel list and updates Feed metadata.
type Server ¶
type Server struct {
// contains filtered or unexported fields
}
Server orchestrates the DNS server and Telegram reader.
type TelegramConfig ¶
type TelegramConfig struct {
APIID int
APIHash string
Phone string
Password string // 2FA password, empty if not used
SessionPath string
LoginOnly bool // if true, authenticate and exit
CodePrompt func(ctx context.Context) (string, error)
}
TelegramConfig holds Telegram API credentials.
type TelegramReader ¶
type TelegramReader struct {
// contains filtered or unexported fields
}
TelegramReader fetches messages from Telegram channels.
func NewTelegramReader ¶
func NewTelegramReader(cfg TelegramConfig, channelUsernames []string, feed *Feed, msgLimit int, baseCh int) *TelegramReader
NewTelegramReader creates a reader for the given channel usernames.
func (*TelegramReader) RequestRefresh ¶
func (tr *TelegramReader) RequestRefresh()
RequestRefresh signals the fetch loop to re-fetch immediately.
func (*TelegramReader) Run ¶
func (tr *TelegramReader) Run(ctx context.Context) error
Run starts the Telegram client, authenticates, and periodically fetches messages.
func (*TelegramReader) SendMessage ¶
SendMessage sends a text message to the given channel/chat (1-indexed).
func (*TelegramReader) SetFetchInterval ¶ added in v0.13.0
func (tr *TelegramReader) SetFetchInterval(d time.Duration)
SetFetchInterval overrides the default 10m fetch cadence.
func (*TelegramReader) UpdateChannels ¶
func (tr *TelegramReader) UpdateChannels(channels []string)
UpdateChannels replaces the channel list and updates the Feed accordingly.
type XPublicReader ¶ added in v0.7.0
type XPublicReader struct {
// contains filtered or unexported fields
}
XPublicReader fetches public posts for X usernames via Nitter RSS endpoints.
func NewXPublicReader ¶ added in v0.7.0
func (*XPublicReader) RequestRefresh ¶ added in v0.7.0
func (xr *XPublicReader) RequestRefresh()
func (*XPublicReader) SetBaseCh ¶ added in v0.9.0
func (xr *XPublicReader) SetBaseCh(baseCh int)
SetBaseCh updates the base channel number when Telegram channels are added/removed.
func (*XPublicReader) SetFetchInterval ¶ added in v0.13.0
func (xr *XPublicReader) SetFetchInterval(d time.Duration)
SetFetchInterval overrides the default 10m fetch cadence.
func (*XPublicReader) UpdateChannels ¶ added in v0.7.0
func (xr *XPublicReader) UpdateChannels(_ []string)