Documentation
¶
Overview ¶
Package watch holds the watcher registry, per-watcher state, the send logs and the gate that decides whether a watcher may send one reply.
Index ¶
- Constants
- func AddressedToMe(ctx context.Context, t tgclient.Telegram, p *tgclient.Peer, ...) (bool, error)
- func AppendJSONL(path string, v any) error
- func ReadJSONL[T any](path string) (out []T, bad int, err error)
- func Refuse(rule, format string, a ...any) *output.Error
- type Answer
- type Gate
- func (g *Gate) CheckChat(ctx context.Context, t tgclient.Telegram, p *tgclient.Peer, ...) (*Plan, error)
- func (g *Gate) Limits(chat int64, l *Ledger) error
- func (g *Gate) LoadLedger() (*Ledger, error)
- func (g *Gate) Recheck(ctx context.Context, t tgclient.Telegram, p *tgclient.Peer, ...) error
- func (g *Gate) Record(plan *Plan, sent *tgclient.Message, sendErr error) error
- func (g *Gate) Reload() error
- func (g *Gate) TextLength(text string) error
- type Ledger
- type Plan
- type Registry
- type Send
- type Watcher
Constants ¶
const ( RulePausedAll = "paused_all" RuleNotRegistered = "not_registered" RuleInvalid = "invalid_registration" RulePaused = "paused" RuleSession = "session_mismatch" RuleTextLength = "text_length" RuleRateWatcher = "rate_watcher" RuleRateGlobal = "rate_global" RuleChatSpacing = "chat_spacing" RuleChatType = "chat_type" RuleScope = "scope" RuleEmptyChat = "empty_chat" RuleLatestFromMe = "latest_from_me" RuleOwnerBackoff = "owner_backoff" RuleUnverifiable = "unverifiable" RuleReplyToRequired = "reply_to_required" RuleTargetMissing = "target_missing" RuleTargetFromMe = "target_from_me" RuleAlreadyAnswered = "already_answered" RuleAlreadyReplied = "already_replied" RuleNotMentioned = "not_mentioned" )
Refusal rule ids, reported as error.details.rule.
const ( ScopeListed = "listed" // chats in the registry entry ScopeContacts = "contacts" // mutual contacts, or listed chats ScopeDMFirst = "dm-first" // 1:1 chats where the other side wrote first )
Scopes: which chats a watcher may answer.
const ( DefaultScope = ScopeListed DefaultMentionsOnly = true DefaultMaxChars = 300 DefaultPerHour = 20 DefaultPerChatSeconds = 20 DefaultBackoffMinutes = 15 )
Defaults for omitted registry fields.
const ( PauseFile = "PAUSE" LogFile = "sends.jsonl" )
File names: PAUSE in the watcher's own dir or its data-dir dir; the send log in the data dir.
const RegistryHelp = `` /* 1591-byte string literal not displayed */
RegistryHelp documents watchers.json for --help.
Variables ¶
This section is empty.
Functions ¶
func AddressedToMe ¶
func AddressedToMe(ctx context.Context, t tgclient.Telegram, p *tgclient.Peer, known []tgclient.Message, m *tgclient.Message) (bool, error)
AddressedToMe: m mentions the user (Telegram's flag) or replies to one of their messages. known saves a fetch when it holds the parent.
func AppendJSONL ¶
AppendJSONL appends one line (0600, parent dir created 0700) and syncs it. Callers hold the watch lock.
Types ¶
type Answer ¶
type Answer struct {
T float64 `json:"t"`
At string `json:"at"`
Watcher string `json:"watcher"`
ChatID int64 `json:"chat_id"`
MsgID int `json:"msg_id"`
SentID int `json:"sent_id"`
}
Answer is one answered.jsonl line: a message some watcher answered, and the reply.
type Gate ¶
type Gate struct {
Paths config.Paths
Name string
Caller string // the caller's session id; never printed
Registry *Registry
W *Watcher
GlobalPerHour int
Now func() time.Time
}
Gate runs the checks for one watcher's send. Everything it trusts comes from the user-owned registry and the data dir, re-read by Reload.
func Open ¶
func Open(p config.Paths, name, caller string, globalPerHour int, now func() time.Time) (*Gate, error)
Open loads the watcher and checks pauses, registration and the session binding.
func (*Gate) CheckChat ¶
func (g *Gate) CheckChat(ctx context.Context, t tgclient.Telegram, p *tgclient.Peer, self *tgclient.User, l *Ledger, replyTo int, text string) (*Plan, error)
CheckChat runs the chat-type, scope and conversation rules against the live chat.
func (*Gate) Limits ¶
Limits checks the per-watcher and global hourly limits and per-chat spacing. Every logged attempt counts, failed ones too.
func (*Gate) LoadLedger ¶
LoadLedger reads the shared ledger.
func (*Gate) Recheck ¶
func (g *Gate) Recheck(ctx context.Context, t tgclient.Telegram, p *tgclient.Peer, self *tgclient.User, text string) error
Recheck runs right before the send: the registry is re-read and the entry, binding, text length, chat type and scope must still allow this reply.
func (*Gate) Record ¶
Record appends the attempt to the data-dir send log and, on success, the answer to the ledger. Callers hold the watch lock.
func (*Gate) Reload ¶
Reload re-reads watchers.json and re-checks, in order: PAUSE-ALL, registration, a valid entry, the watcher's PAUSE files, and the caller's session binding.
func (*Gate) TextLength ¶
TextLength checks the reply length against the current entry.
type Ledger ¶
type Ledger struct {
Answers []Answer
}
Ledger is answered.jsonl, read once per send.
type Plan ¶
type Plan struct {
Chat tgclient.ChatRef `json:"chat"`
ReplyTo int `json:"reply_to,omitempty"`
Answers int `json:"answers"` // the incoming message being answered
Group bool `json:"group"`
Text string `json:"text"`
Watcher string `json:"watcher"`
}
Plan is what a passing check would send.
type Registry ¶
Registry is watchers.json; entries are decoded on demand so one bad entry does not hide the others.
func LoadRegistry ¶
LoadRegistry reads the data dir's watchers.json; a missing file is an empty registry.
type Send ¶
type Send struct {
T float64 `json:"t"`
At string `json:"at"`
Watcher string `json:"watcher"`
ChatID int64 `json:"chat_id"`
ReplyTo int `json:"reply_to,omitempty"`
Answered int `json:"answered,omitempty"`
Text string `json:"text"`
OK bool `json:"ok"`
MsgID int `json:"msg_id,omitempty"`
Err string `json:"err,omitempty"`
}
Send is one sends.jsonl line: an attempt that reached Telegram.
type Watcher ¶
type Watcher struct {
Name string `json:"name"`
Session string `json:"-"`
Chats map[int64]string `json:"-"`
Dir string `json:"dir,omitempty"`
Scope string `json:"scope"`
Groups bool `json:"groups"`
MentionsOnly bool `json:"mentions_only"`
MaxChars int `json:"max_chars"`
PerHour int `json:"per_hour"`
PerChatSeconds int `json:"per_chat_seconds"`
BackoffMinutes int `json:"backoff_minutes"`
Approved string `json:"approved"`
// contains filtered or unexported fields
}
Watcher is a registry entry with defaults applied. Session is never printed.
func (*Watcher) PausePaths ¶
PausePaths are the PAUSE files that pause this watcher.