server

package
v0.17.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 9, 2026 License: MIT Imports: 36 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
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.

View Source
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

func (s *DNSServer) ListenAndServe(ctx context.Context) error

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 NewFeed

func NewFeed(channels []string) *Feed

NewFeed creates a new Feed with the given channel names.

func (*Feed) AfterFetchCycle added in v0.13.0

func (f *Feed) AfterFetchCycle(ctx context.Context)

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

func (f *Feed) ChannelNames() []string

ChannelNames returns the configured channel names.

func (*Feed) GetBlock

func (f *Feed) GetBlock(channel, block int) ([]byte, error)

GetBlock returns the block data for a given channel and block number.

func (*Feed) GitHubRelay added in v0.13.0

func (f *Feed) GitHubRelay() *GitHubRelay

GitHubRelay returns the configured relay, or nil.

func (*Feed) IsPrivateChannel

func (f *Feed) IsPrivateChannel(channelNum int) bool

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

func (f *Feed) MergeProfilePics(pics map[string][]byte) int

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

func (f *Feed) SetChannelDisplayName(channelNum int, displayName string)

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

func (f *Feed) SetChannels(channels []string)

SetChannels replaces the channel list and rebuilds metadata.

func (*Feed) SetChatInfo

func (f *Feed) SetChatInfo(channelNum int, chatType protocol.ChatType, canSend bool)

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

func (f *Feed) SetLatestVersion(v string)

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

func (f *Feed) SetNextFetch(ts uint32)

SetNextFetch sets the unix timestamp of the next server-side fetch.

func (*Feed) SetProfilePics added in v0.16.0

func (f *Feed) SetProfilePics(pics map[string][]byte) int

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

func (f *Feed) SetTelegramLoggedIn(loggedIn bool)

SetTelegramLoggedIn sets the flag indicating whether the server has a Telegram session.

func (*Feed) UpdateChannel

func (f *Feed) UpdateChannel(channelNum int, msgs []protocol.Message)

UpdateChannel replaces the messages for a channel, re-serializing into blocks.

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

func (g *GitHubRelay) PruneStale(ctx context.Context, cutoff time.Time) (int, error)

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.

func (*GitHubRelay) Upload added in v0.13.0

func (g *GitHubRelay) Upload(ctx context.Context, body []byte) error

Upload encrypts body and queues it for the next batched commit. ErrTooLarge if body exceeds the configured cap.

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.

func New

func New(cfg Config) (*Server, error)

New creates a new Server.

func (*Server) Run

func (s *Server) Run(ctx context.Context) error

Run starts both the DNS server and the 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

func (tr *TelegramReader) SendMessage(ctx context.Context, channelNum int, text string) error

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 NewXPublicReader(accounts []string, feed *Feed, msgLimit int, baseCh int, instancesCSV string) *XPublicReader

func (*XPublicReader) RequestRefresh added in v0.7.0

func (xr *XPublicReader) RequestRefresh()

func (*XPublicReader) Run added in v0.7.0

func (xr *XPublicReader) Run(ctx context.Context) error

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)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL