Documentation
¶
Index ¶
- Variables
- func ConfigURI(domain string, extraDomains []string, passphrase string, ...) string
- func DecompressMediaBytes(r io.Reader, compression protocol.MediaCompression) (io.ReadCloser, error)
- func LoadOrCreateServerEncKey(dataDir string) (*ecdh.PrivateKey, error)
- func LoadOrCreateServerKey(dataDir string) (ed25519.PrivateKey, error)
- func LoadPrivateInvites(path string) ([]string, error)
- func ParseInviteHash(link string) (string, error)
- func SaveServerEncKey(dataDir string, key *ecdh.PrivateKey) error
- func ServerPublicKeyString(priv ed25519.PrivateKey) string
- func SetMediaDebugLogs(enabled bool)
- type ChatInboxEntry
- type ChatService
- func (c *ChatService) Domains() []string
- func (c *ChatService) HandleCell(selector [protocol.ChatSelectorSize]byte, counter uint32, payload []byte, ...) []byte
- func (c *ChatService) Info() protocol.ChatInfo
- func (c *ChatService) RotateEk(now time.Time, grace time.Duration) ([]byte, error)
- func (c *ChatService) RunEkRotation(ctx context.Context, republish func())
- func (c *ChatService) RunSweeper(ctx context.Context)
- func (c *ChatService) SetEkPersist(save func(*ecdh.PrivateKey) error)
- func (c *ChatService) StatsSnapshot() map[string]int64
- type ChatStore
- func (s *ChatStore) AccountCount() int
- func (s *ChatStore) Ack(addr, peer [protocol.AddressSize]byte, upToSeq uint32, receipt []byte, ...) (status byte, err error)
- func (s *ChatStore) Close() error
- func (s *ChatStore) CommitMessage(src, dst [protocol.AddressSize]byte, seq uint32, envelope []byte, ...) (status byte, lastAccepted uint32, remaining uint16, resetUnix uint32, ...)
- func (s *ChatStore) EnablePeriodicSync(interval time.Duration)
- func (s *ChatStore) FetchBlock(addr, src [protocol.AddressSize]byte, seq uint32, block uint8, now time.Time) ([]byte, bool, error)
- func (s *ChatStore) InboxStatus(addr [protocol.AddressSize]byte, now time.Time) ([]ChatInboxEntry, error)
- func (s *ChatStore) Keys(addr [protocol.AddressSize]byte, now time.Time) (identityPub, encPub []byte, ok bool, err error)
- func (s *ChatStore) Limits() protocol.ChatLimits
- func (s *ChatStore) OpenSession(addr [protocol.AddressSize]byte, ts uint32, now time.Time, commit bool) (identityPub []byte, lastAck uint32, status byte, err error)
- func (s *ChatStore) PairState(owner, peer [protocol.AddressSize]byte, now time.Time) (accepted, delivered uint32, receipt []byte, ok bool, err error)
- func (s *ChatStore) PrecheckMessage(src, dst [protocol.AddressSize]byte, now time.Time) (status byte, remaining uint16, resetUnix uint32, err error)
- func (s *ChatStore) Register(rec *protocol.RegisterEnvelope, raw []byte, now time.Time) error
- func (s *ChatStore) RegisterRecord(addr [protocol.AddressSize]byte, now time.Time) ([]byte, bool, error)
- func (s *ChatStore) ResolvePeerHandle(owner [protocol.AddressSize]byte, handle [protocol.ChatPeerHandleSize]byte, ...) (peer [protocol.AddressSize]byte, ok bool, err error)
- func (s *ChatStore) RunSync(ctx context.Context)
- func (s *ChatStore) SendQuota(addr [protocol.AddressSize]byte, now time.Time) (remaining uint16, resetUnix uint32, ok bool, err error)
- func (s *ChatStore) SetAccountTTL(d time.Duration)
- func (s *ChatStore) SetMaxAccounts(n int)
- func (s *ChatStore) Sweep(now time.Time) (expiredMsgs, deletedAccounts int, err error)
- type Config
- type DNSServer
- func (s *DNSServer) AddRefresher(channelCtl channelRefresher)
- func (s *DNSServer) ListenAndServe(ctx context.Context) error
- func (s *DNSServer) SetChannelRefresher(channelCtl channelRefresher)
- func (s *DNSServer) SetChatService(chat *ChatService) error
- func (s *DNSServer) SetExtraDomains(extra []string)
- func (s *DNSServer) SetReportFile(path string) error
- func (s *DNSServer) SetXReader(xr *XPublicReader)
- 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) SetChatAvailable(v bool)
- func (f *Feed) SetChatInfo(channelNum int, chatType protocol.ChatType, canSend bool)
- func (f *Feed) SetChatInfoPayload(payload []byte)
- 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) SetSigningKey(priv ed25519.PrivateKey)
- 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 ErrChatAccountsFull = fmt.Errorf("chat: account store full")
ErrChatAccountsFull is returned by Register when the store is at its configured maxAccounts cap.
var ErrTooLarge = errors.New("media file exceeds configured max-size")
ErrTooLarge is returned by Store when content exceeds MaxFileBytes.
Functions ¶
func ConfigURI ¶ added in v0.25.9
func ConfigURI(domain string, extraDomains []string, passphrase string, priv ed25519.PrivateKey) string
ConfigURI builds the thefeed:// import URI advertising this server's main domain (path), passphrase, pinned signing public key (sk=), any extra sub-domains (d=, comma-separated), and two bootstrap resolvers so a freshly-imported client can reach DNS immediately.
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 LoadOrCreateServerEncKey ¶ added in v0.25.9
func LoadOrCreateServerEncKey(dataDir string) (*ecdh.PrivateKey, error)
LoadOrCreateServerEncKey returns the server's x25519 encryption key, reading it from <dataDir>/server_x25519.key or generating and persisting a new one if the file is absent.
func LoadOrCreateServerKey ¶ added in v0.25.9
func LoadOrCreateServerKey(dataDir string) (ed25519.PrivateKey, error)
LoadOrCreateServerKey returns the server's ed25519 private key, reading it from <dataDir>/server_ed25519.key or generating and persisting a new one if the file is absent. The accompanying public key (base64url) is what the operator pins in configs — print it with ServerPublicKeyString.
The file format is base64-std of the 32-byte seed. A raw 32-byte seed is also accepted, so an operator can drop in a key produced elsewhere.
func LoadPrivateInvites ¶ added in v0.20.0
LoadPrivateInvites reads invite links from path (one per line, `#` comments allowed) and returns the parsed hashes. Missing file is not an error — the feature is opt-in.
func ParseInviteHash ¶ added in v0.20.0
ParseInviteHash extracts the invite hash from any supported invite-link shape: t.me/+HASH, t.me/joinchat/HASH, tg://join?invite=HASH, or bare HASH (with or without leading +). Public-username URLs (t.me/foo without + or joinchat/) are rejected.
func SaveServerEncKey ¶ added in v0.25.9
func SaveServerEncKey(dataDir string, key *ecdh.PrivateKey) error
SaveServerEncKey overwrites the persisted x25519 chat key. Used on ek rotation so a restart loads the rotated key, not the pre-rotation one. The previous key lives on only in RAM (its grace window), which is what gives the session layer forward secrecy once it expires.
func ServerPublicKeyString ¶ added in v0.25.9
func ServerPublicKeyString(priv ed25519.PrivateKey) string
ServerPublicKeyString returns the base64url (no padding) encoding of the server public key — the value pinned in configs and bundled defaults.
func SetMediaDebugLogs ¶ added in v0.13.0
func SetMediaDebugLogs(enabled bool)
SetMediaDebugLogs enables or disables the media debug log channel.
Types ¶
type ChatInboxEntry ¶ added in v0.25.9
ChatInboxEntry describes one waiting message for INBOX_STATUS.
type ChatService ¶ added in v0.25.9
type ChatService struct {
// contains filtered or unexported fields
}
ChatService handles chat requests on the dedicated chat domain(s). Every query is one uniform sealed cell. A per-connection eph↔ek handshake yields a session (Ksession + a server-assigned selector); all later ops are sealed under Ksession, so the op, the destination and the read metadata are all encrypted. Sessions, uploads and handshake reassemblies are RAM-only and bounded; accounts/inboxes live in the ChatStore.
func NewChatService ¶ added in v0.25.9
func NewChatService(store *ChatStore, ek *ecdh.PrivateKey, queryKey [protocol.KeySize]byte, limits protocol.ChatLimits, domains []string) *ChatService
NewChatService creates the chat handler. domains are the dedicated chat sub-domains (already validated against feed domains by the DNS layer).
func (*ChatService) Domains ¶ added in v0.25.9
func (c *ChatService) Domains() []string
Domains returns the chat sub-domains.
func (*ChatService) HandleCell ¶ added in v0.25.9
func (c *ChatService) HandleCell(selector [protocol.ChatSelectorSize]byte, counter uint32, payload []byte, domain string, now time.Time) []byte
HandleCell processes one decoded chat cell and returns the response bytes (to be wrapped by EncodeResponse). An empty slice means "incomplete handshake, keep going"; ChatSessionLostResp means "re-handshake".
func (*ChatService) Info ¶ added in v0.25.9
func (c *ChatService) Info() protocol.ChatInfo
Info returns the ChatInfo payload advertised (signed) on the feed metadata path. EkPub is the current key; on rotation the caller re-publishes Info().
func (*ChatService) RotateEk ¶ added in v0.25.9
RotateEk swaps in a fresh current key, retires the old one into the grace window, persists the new key (if a saver is set), and returns the new public key so the caller can re-publish (re-sign) ChatInfo. Live sessions keep working; clients pick up the new key on their next ChatInfo refresh.
func (*ChatService) RunEkRotation ¶ added in v0.25.9
func (c *ChatService) RunEkRotation(ctx context.Context, republish func())
RunEkRotation rotates the server encryption key on chatEkRotatePeriod, then re-publishes (re-signs) ChatInfo so clients learn the new key. Runs until ctx is done.
func (*ChatService) RunSweeper ¶ added in v0.25.9
func (c *ChatService) RunSweeper(ctx context.Context)
RunSweeper periodically expires sessions, handshakes, old messages, and idle accounts until ctx is done.
func (*ChatService) SetEkPersist ¶ added in v0.25.9
func (c *ChatService) SetEkPersist(save func(*ecdh.PrivateKey) error)
SetEkPersist registers how a freshly rotated current key is saved to disk, so a restart loads the new key (not the pre-rotation one). Optional — tests leave it nil and rotate in RAM only.
func (*ChatService) StatsSnapshot ¶ added in v0.25.9
func (c *ChatService) StatsSnapshot() map[string]int64
StatsSnapshot returns the per-window counters for the hourly report and resets them. `accounts` and `sessions` are point-in-time gauges.
type ChatStore ¶ added in v0.25.9
type ChatStore struct {
// contains filtered or unexported fields
}
ChatStore persists chat accounts: a hot RAM cache in front of a bbolt file (pure Go, single mmap'd file). Two durability modes:
- write-through (default): every mutation is committed to disk before the op returns, so a crash never loses committed state. Concurrent writes go through bbolt's Batch (coalesced fsyncs).
- periodic (EnablePeriodicSync): RAM is authoritative; mutations only mark the account dirty and RunSync writes all dirty accounts + fsyncs every interval. The hot path never touches disk, so ops stop convoying on the batch commit — a crash can lose up to ~interval of state.
The RAM cache + its lock are SHARDED by address so commits to different accounts proceed in parallel. Per account, operations still serialize on that account's shard, so ordering and correctness are preserved.
func OpenChatStore ¶ added in v0.25.9
func OpenChatStore(path string, limits protocol.ChatLimits) (*ChatStore, error)
OpenChatStore opens (or creates) the chat account store at path.
func (*ChatStore) AccountCount ¶ added in v0.25.9
AccountCount returns the number of stored accounts.
func (*ChatStore) Ack ¶ added in v0.25.9
func (s *ChatStore) Ack(addr, peer [protocol.AddressSize]byte, upToSeq uint32, receipt []byte, now time.Time) (status byte, err error)
Ack frees delivered messages: removes peer→addr messages with seq ≤ upToSeq and bumps last_delivered. receipt is the recipient's opaque E2E proof for upToSeq (stored verbatim, relayed to the sender via PairState). Idempotent (the cell seal authenticated the caller).
func (*ChatStore) Close ¶ added in v0.25.9
Close writes any dirty accounts (periodic mode) and closes the file.
func (*ChatStore) CommitMessage ¶ added in v0.25.9
func (s *ChatStore) CommitMessage(src, dst [protocol.AddressSize]byte, seq uint32, envelope []byte, now time.Time) (status byte, lastAccepted uint32, remaining uint16, resetUnix uint32, err error)
CommitMessage stores a fully-verified envelope into dst's inbox, enforcing the authoritative quota/replay checks, and writes both accounts in one batch.
func (*ChatStore) EnablePeriodicSync ¶ added in v0.25.9
EnablePeriodicSync trades per-commit durability for a periodic flush every interval: ops mutate only the RAM cache (high throughput, CPU/RAM-bound rather than disk-bound) and RunSync writes the dirty accounts + fsyncs on the interval. A crash can lose up to ~interval of just-received messages. Call once after OpenChatStore, before serving, then start RunSync.
func (*ChatStore) FetchBlock ¶ added in v0.25.9
func (s *ChatStore) FetchBlock(addr, src [protocol.AddressSize]byte, seq uint32, block uint8, now time.Time) ([]byte, bool, error)
FetchBlock returns one slice of a stored envelope by (src, seq, block index). src disambiguates the sender — seq is per-pair, so the same seq can exist from two different senders in one inbox; matching seq alone would return the wrong envelope.
func (*ChatStore) InboxStatus ¶ added in v0.25.9
func (s *ChatStore) InboxStatus(addr [protocol.AddressSize]byte, now time.Time) ([]ChatInboxEntry, error)
InboxStatus lists the waiting messages for addr.
func (*ChatStore) Keys ¶ added in v0.25.9
func (s *ChatStore) Keys(addr [protocol.AddressSize]byte, now time.Time) (identityPub, encPub []byte, ok bool, err error)
Keys returns the registered identity and encryption keys for addr.
func (*ChatStore) Limits ¶ added in v0.25.9
func (s *ChatStore) Limits() protocol.ChatLimits
Limits returns the store's configured limits.
func (*ChatStore) OpenSession ¶ added in v0.25.9
func (s *ChatStore) OpenSession(addr [protocol.AddressSize]byte, ts uint32, now time.Time, commit bool) (identityPub []byte, lastAck uint32, status byte, err error)
OpenSession validates a strictly-increasing OPEN timestamp for addr and persists it. Retained for completeness (the v2 handshake checks skew inline).
func (*ChatStore) PairState ¶ added in v0.25.9
func (s *ChatStore) PairState(owner, peer [protocol.AddressSize]byte, now time.Time) (accepted, delivered uint32, receipt []byte, ok bool, err error)
PairState returns (last_accepted, last_delivered, receipt) for messages owner←peer. receipt is the recipient's E2E proof for last_delivered (nil if none yet), relayed to the sender for offline verification.
func (*ChatStore) PrecheckMessage ¶ added in v0.25.9
func (s *ChatStore) PrecheckMessage(src, dst [protocol.AddressSize]byte, now time.Time) (status byte, remaining uint16, resetUnix uint32, err error)
PrecheckMessage runs the advisory checks for a src→dst message.
func (*ChatStore) Register ¶ added in v0.25.9
Register creates or refreshes the account for a verified registration record.
func (*ChatStore) RegisterRecord ¶ added in v0.25.9
func (s *ChatStore) RegisterRecord(addr [protocol.AddressSize]byte, now time.Time) ([]byte, bool, error)
RegisterRecord returns the raw signed registration record for addr.
func (*ChatStore) ResolvePeerHandle ¶ added in v0.25.9
func (s *ChatStore) ResolvePeerHandle(owner [protocol.AddressSize]byte, handle [protocol.ChatPeerHandleSize]byte, now time.Time) (peer [protocol.AddressSize]byte, ok bool, err error)
ResolvePeerHandle maps a 4-byte peer handle to a full peer address within owner's known pairs. Returns ok=false if there is no unique match.
func (*ChatStore) RunSync ¶ added in v0.25.9
RunSync writes dirty accounts and fsyncs every syncEvery until ctx is done, with a final flush on shutdown. No-op unless EnablePeriodicSync was called.
func (*ChatStore) SendQuota ¶ added in v0.25.9
func (s *ChatStore) SendQuota(addr [protocol.AddressSize]byte, now time.Time) (remaining uint16, resetUnix uint32, ok bool, err error)
SendQuota returns how many messages addr may still send this hour.
func (*ChatStore) SetAccountTTL ¶ added in v0.25.9
SetAccountTTL sets how long an idle account is kept before deletion. 0 (the default) never deletes accounts. Call once after OpenChatStore.
func (*ChatStore) SetMaxAccounts ¶ added in v0.25.9
SetMaxAccounts caps total stored accounts (0 = unlimited). Call once after OpenChatStore.
type Config ¶
type Config struct {
ListenAddr string
Domain string // main domain; canonical for relay path derivation
ExtraDomains []string // additional sub-domains the server also answers feed queries on
Passphrase string
DataDir string // server state dir; holds the signing key
ChannelsFile string
PrivateChannelsFile string // optional: invite links for private channels
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
// Chat (standalone messenger). Configured when ChatDomains is non-empty;
// the domains are dedicated chat sub-domains, distinct from the feed
// domains. Zero limits fall back to protocol defaults.
ChatDomains []string
ChatEnabled bool // serve chat (default true when domains set); false advertises chat-but-disabled
ChatSendPerHour int
ChatInboxCap int
ChatPerPairCap int
ChatMaxMsgBytes int
ChatTTLHours int // inbox-message TTL (hours)
ChatAccountTTLDays int // delete idle accounts after N days (0 = never)
ChatMaxAccounts int // cap on stored accounts (0 = unlimited)
// ChatSyncSeconds controls durability vs throughput for the chat store.
// 0 = fsync every committed message (max durability, lowest throughput).
// N>0 = flush to disk every N seconds instead (much higher throughput; a
// crash can lose up to ~N seconds of just-received messages — acceptable
// since chat is E2E and senders can resend).
ChatSyncSeconds int
}
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) SetChatService ¶ added in v0.25.9
func (s *DNSServer) SetChatService(chat *ChatService) error
SetChatService attaches the chat handler on its dedicated sub-domains. Chat domains must not equal any feed domain (nesting under one is fine — longest-suffix matching routes them correctly). Call before ListenAndServe.
func (*DNSServer) SetExtraDomains ¶ added in v0.25.9
SetExtraDomains adds sub-domains the server also answers feed queries on. The main domain (from NewDNSServer) stays canonical for relay path derivation. Blanks, the main domain, and duplicates are ignored. Call before ListenAndServe.
func (*DNSServer) SetReportFile ¶ added in v0.25.9
SetReportFile enables appending each hourly report as a JSON line to a rotating file (the canonical source for the `--report` dashboard). Empty path disables it. Call before ListenAndServe.
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) SetChatAvailable ¶ added in v0.25.9
SetChatAvailable sets the metadata flag advertising that this server has the messenger configured. Call before SetChatInfoPayload.
func (*Feed) SetChatInfo ¶
SetChatInfo stores the chat type and send capability for a channel.
func (*Feed) SetChatInfoPayload ¶ added in v0.25.9
SetChatInfoPayload publishes (or clears, with empty payload) the chat capability data on ChatInfoChannel. Block 0 is prefixed with a uint16 total-block count (same convention as titles); the channel is signed like any other when a signing key is set. Call after SetSigningKey.
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) SetSigningKey ¶ added in v0.25.9
func (f *Feed) SetSigningKey(priv ed25519.PrivateKey)
SetSigningKey enables ExtraBlock signing with the server key and builds the signed blocks for every channel already populated. Call once at startup, after NewFeed. nil leaves signing disabled.
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, privateInviteHashes []string, feed *Feed, msgLimit int, baseCh int) *TelegramReader
NewTelegramReader creates a reader for the given channel usernames and private-channel invite hashes. privateInviteHashes is the list of base64url-ish invite codes (see ParseInviteHash) — empty when no private channels are configured.
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)