Global code ovehaul - remove CAS and other, add 15 minute greet check
- Remove CAS, as it no longer works for us - Remove other checks - Implement 15 minute greet timeout check - Fix chat logos - Ditch thirdparty logger, use stdlib log facility wrapper for logging
This commit is contained in:
130
internal/moon/entityGuard.go
Normal file
130
internal/moon/entityGuard.go
Normal file
@@ -0,0 +1,130 @@
|
||||
package moon
|
||||
|
||||
import (
|
||||
"bunny-bot/internal/mylog"
|
||||
|
||||
"github.com/NicoNex/echotron/v3"
|
||||
"github.com/davecgh/go-spew/spew"
|
||||
)
|
||||
|
||||
// The Bot API always returns a User/Chat carrying its id, and echotron validates
|
||||
// nothing: lclient.dispatch (network.go:138) is json.Unmarshal plus a check() that
|
||||
// reads only the ok/error_code envelope (helpers.go:37). An entity that arrives
|
||||
// without an id therefore reaches us as a silent zero, with no layer below to
|
||||
// complain about it. These predicates are the whole of our defence.
|
||||
//
|
||||
// They are SILENT. A refusal is a fact, not an incident: reporting happens once, at
|
||||
// the ingress, where the offending update can be dumped whole. A predicate that logs
|
||||
// would make one broken update produce one line per entity that happens to meet it.
|
||||
|
||||
// usableUser reports whether a user entity is one we can actually address.
|
||||
//
|
||||
// The test is against zero and not against zero-or-less on purpose: a chat id is
|
||||
// legitimately negative for a group or supergroup, so a positivity check would
|
||||
// refuse every group chat in the project.
|
||||
func usableUser(u *echotron.User) bool {
|
||||
return u != nil && u.ID != 0
|
||||
}
|
||||
|
||||
// usableChat reports whether a chat entity is one we can actually address, with the
|
||||
// same reasoning and the same zero-not-positive rule.
|
||||
func usableChat(c *echotron.Chat) bool {
|
||||
return c != nil && c.ID != 0
|
||||
}
|
||||
|
||||
// usableUpdate reports whether an update is worth dispatching at all.
|
||||
//
|
||||
// Its branches mirror telegramMsgParser's, in the same order and under the same
|
||||
// conditions, so a branch added to one and not the other is visible by reading. That
|
||||
// is not tidiness: the alternative is a validator shaped differently from the
|
||||
// dispatcher, whose coverage nobody can check by eye.
|
||||
//
|
||||
// nil is tolerated only where absence is normal. A message with no author is a
|
||||
// channel post or a service message, and refusing those would disable chat logging
|
||||
// for channels. A nil element inside NewChatMembers is different — the array asserts
|
||||
// a newcomer exists — and so is a nil NewChatMember.User, which asserts who changed.
|
||||
// A nil OldChatMember.User is tolerated: some transitions have no previous member.
|
||||
func usableUpdate(u *echotron.Update) bool {
|
||||
if u == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
switch {
|
||||
case u.ChatMember != nil:
|
||||
return usableChatMember(u.ChatMember)
|
||||
case u.Message != nil:
|
||||
return usableMessage(u.Message)
|
||||
case u.EditedMessage != nil:
|
||||
return usableMessage(u.EditedMessage)
|
||||
default:
|
||||
// Nothing we act on. The dispatcher's spew fallback is where this belongs.
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// usableChatMember checks a membership change.
|
||||
//
|
||||
// Its entities are values, not pointers: ChatMemberUpdated.Chat, .From and both
|
||||
// ChatMember structs can never be nil, so there is no nil test to fall back on and
|
||||
// the id check is the only thing that can catch a malformed one.
|
||||
func usableChatMember(cm *echotron.ChatMemberUpdated) bool {
|
||||
return usableChat(&cm.Chat) &&
|
||||
usableUser(cm.NewChatMember.User) &&
|
||||
(cm.OldChatMember.User == nil || usableUser(cm.OldChatMember.User)) &&
|
||||
usableUser(&cm.From)
|
||||
}
|
||||
|
||||
// usableMessage checks a message, or an edit of one — the two carry the same fields
|
||||
// and are treated the same way.
|
||||
//
|
||||
// Message.Chat is a value: a zero chat id would otherwise reach elog, which builds
|
||||
// chat_logs/<id>/yyyy/mm/dd.txt from it and would silently create a chat_logs/0
|
||||
// directory and write every malformed update into it.
|
||||
func usableMessage(m *echotron.Message) bool {
|
||||
if !usableChat(&m.Chat) {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.From != nil && !usableUser(m.From) {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.LeftChatMember != nil && !usableUser(m.LeftChatMember) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, member := range m.NewChatMembers {
|
||||
if !usableUser(member) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// One level deep, and the nested chat only. The nested author is never rendered,
|
||||
// so a zero there is cosmetic; the level bound exists to keep the work constant.
|
||||
if m.PinnedMessage != nil && !usableChat(&m.PinnedMessage.Chat) {
|
||||
return false
|
||||
}
|
||||
|
||||
if m.ReplyToMessage != nil && !usableChat(&m.ReplyToMessage.Chat) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// reportBrokenUpdate records an update we are refusing, in the terms it deserves:
|
||||
// this is not a user being difficult and not a transient API error, it is an
|
||||
// upstream that broke its own contract.
|
||||
//
|
||||
// The whole update is dumped, once. The dump is the point — the condition is
|
||||
// unreproducible and unqueryable, since there is no id to ask Telegram about — and a
|
||||
// partial dump of one field out of four would not be evidence of anything.
|
||||
func reportBrokenUpdate(u *echotron.Update) {
|
||||
mylog.Error(
|
||||
"Telegram sent an update with an entity carrying no id, refusing it: nothing in it can be greeted, recorded or banned, and an API that sends one is broken",
|
||||
)
|
||||
|
||||
mylog.Error(spew.Sdump(u))
|
||||
}
|
||||
|
||||
/* vim: set ft=go noet ai ts=4 sw=4 sts=4: */
|
||||
Reference in New Issue
Block a user