Add initial files
This commit is contained in:
parent
a733e15eb0
commit
c0b0301cb4
3 changed files with 658 additions and 0 deletions
507
bot.go
Normal file
507
bot.go
Normal file
|
|
@ -0,0 +1,507 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"regexp"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gotd/td/session"
|
||||||
|
"github.com/gotd/td/telegram"
|
||||||
|
"github.com/gotd/td/telegram/auth"
|
||||||
|
"github.com/gotd/td/telegram/updates"
|
||||||
|
"github.com/gotd/td/tg"
|
||||||
|
"golang.org/x/term"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type YAMLScamPattern struct {
|
||||||
|
Name string `yaml:"name"`
|
||||||
|
Pattern string `yaml:"pattern"`
|
||||||
|
Weight float64 `yaml:"weight"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
APIId int `yaml:"api_id"`
|
||||||
|
APIHash string `yaml:"api_hash"`
|
||||||
|
MonitoredChat int64 `yaml:"monitored_chat"`
|
||||||
|
AlertChat int64 `yaml:"alert_chat"`
|
||||||
|
NtfyToken string `yaml:"ntfy_token"`
|
||||||
|
NtfyTopic string `yaml:"ntfy_topic"`
|
||||||
|
SessionPath string `yaml:"session_path"`
|
||||||
|
YAMLPatterns []YAMLScamPattern `yaml:"scam_patterns"`
|
||||||
|
Patterns []scamPattern `yaml:"-"` // Compiled patterns, not from YAML
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadConfig(path string) (*Config, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// Create default config file
|
||||||
|
defaultConfig := `api_id: 0
|
||||||
|
api_hash: ""
|
||||||
|
monitored_chat: 0
|
||||||
|
alert_chat: 0
|
||||||
|
ntfy_token: ""
|
||||||
|
ntfy_topic: ""
|
||||||
|
session_path: "antiscam.session"
|
||||||
|
scam_patterns: []
|
||||||
|
`
|
||||||
|
if err := os.WriteFile(path, []byte(defaultConfig), 0644); err != nil {
|
||||||
|
return nil, fmt.Errorf("creating default config file: %w", err)
|
||||||
|
}
|
||||||
|
log.Printf("Created default config file at %s. Please fill in the required values.", path)
|
||||||
|
data = []byte(defaultConfig)
|
||||||
|
} else {
|
||||||
|
return nil, fmt.Errorf("reading config file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var cfg Config
|
||||||
|
if err := yaml.Unmarshal(data, &cfg); err != nil {
|
||||||
|
return nil, fmt.Errorf("parsing config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate required fields
|
||||||
|
if cfg.APIId == 0 {
|
||||||
|
return nil, fmt.Errorf("api_id is not set in config")
|
||||||
|
}
|
||||||
|
if cfg.APIHash == "" {
|
||||||
|
return nil, fmt.Errorf("api_hash is not set in config")
|
||||||
|
}
|
||||||
|
if cfg.MonitoredChat == 0 {
|
||||||
|
return nil, fmt.Errorf("monitored_chat is not set in config")
|
||||||
|
}
|
||||||
|
if cfg.SessionPath == "" {
|
||||||
|
return nil, fmt.Errorf("session_path is not set in config")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Compile patterns from YAML
|
||||||
|
if len(cfg.YAMLPatterns) > 0 {
|
||||||
|
cfg.Patterns = make([]scamPattern, len(cfg.YAMLPatterns))
|
||||||
|
for i, p := range cfg.YAMLPatterns {
|
||||||
|
re, err := regexp.Compile(p.Pattern)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("compiling pattern %q: %w", p.Name, err)
|
||||||
|
}
|
||||||
|
cfg.Patterns[i] = scamPattern{
|
||||||
|
name: p.Name,
|
||||||
|
re: re,
|
||||||
|
weight: p.Weight,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
log.Printf("✓ Loaded %d scam patterns from YAML config:", len(cfg.Patterns))
|
||||||
|
for _, p := range cfg.Patterns {
|
||||||
|
log.Printf(" - %s (%s) (weight: %.1f)", p.name, p.re, p.weight)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &cfg, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type scamPattern struct {
|
||||||
|
name string
|
||||||
|
re *regexp.Regexp
|
||||||
|
weight float64
|
||||||
|
}
|
||||||
|
|
||||||
|
type matchResult struct {
|
||||||
|
score float64
|
||||||
|
matchedKeywords map[string][]string
|
||||||
|
}
|
||||||
|
|
||||||
|
var scamPatterns = []scamPattern{
|
||||||
|
{
|
||||||
|
name: "seeking",
|
||||||
|
re: regexp.MustCompile(`(?i)(?:(?:several\s+people|seeking|looking\s+for)\s+(?:partner|(\d+\s*[-–]\s*\d+|\d+)|two)?)|(new\s+project)`),
|
||||||
|
weight: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "job_opportunity",
|
||||||
|
re: regexp.MustCompile(`(?i)(?:remote|earning|in\s+a)\s+(?:collaboration|growing\s+field|opportunity|work)`),
|
||||||
|
weight: 0.4,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "money",
|
||||||
|
re: regexp.MustCompile(`(?i)(?:\d+[\s,]*(?:\$|€|₹|£)|(?:\$|€|₹|£)\s*\d+,?\d+)(\s+per\s+(week|day))?`),
|
||||||
|
weight: 0.6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "contact",
|
||||||
|
re: regexp.MustCompile(`(?i)(?:\bdm\b|(send|write).*(?:private)?\s+.?\+.?|get\s+in\s+touch)|@\w+`),
|
||||||
|
weight: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "links",
|
||||||
|
re: regexp.MustCompile(`https://t\.me/\+\S+|https://t\.me/[a-z0-9_]+`),
|
||||||
|
weight: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "call_to_action",
|
||||||
|
re: regexp.MustCompile(`(?i)[⬇👇👆⬆💬🪂🎯⤵⤴📍]+|(?:click|tap|join|link|check)\s+(?:here|now|below|out)`),
|
||||||
|
weight: 0.5,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "extended_latin",
|
||||||
|
re: regexp.MustCompile(`(?i)[äöüß]+`),
|
||||||
|
weight: 0.2,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func keywordMatchScore(input string, patterns []scamPattern) matchResult {
|
||||||
|
matched := make(map[string][]string)
|
||||||
|
totalScore := 0.0
|
||||||
|
|
||||||
|
for _, p := range patterns {
|
||||||
|
var matches []string
|
||||||
|
for _, m := range p.re.FindAllString(input, -1) {
|
||||||
|
m = strings.TrimSpace(m)
|
||||||
|
if m != "" {
|
||||||
|
matches = append(matches, m)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(matches) > 0 {
|
||||||
|
matched[p.name] = matches
|
||||||
|
totalScore += p.weight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
score := math.Min(totalScore, 1.0)
|
||||||
|
// Round to 2 decimal places
|
||||||
|
score = math.Round(score*100) / 100
|
||||||
|
|
||||||
|
return matchResult{score: score, matchedKeywords: matched}
|
||||||
|
}
|
||||||
|
|
||||||
|
func escapeMarkdown(text string) string {
|
||||||
|
escapeChars := `\_*[]()~` + "`" + `>#+-=|{}.!`
|
||||||
|
var result strings.Builder
|
||||||
|
for _, r := range text {
|
||||||
|
if strings.ContainsRune(escapeChars, r) {
|
||||||
|
result.WriteRune('\\')
|
||||||
|
}
|
||||||
|
result.WriteRune(r)
|
||||||
|
}
|
||||||
|
return result.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func notify(message, topic, title string, priority int, token string) {
|
||||||
|
url := "https://ntfy.zio.sh/" + topic
|
||||||
|
req, err := http.NewRequest("POST", url, bytes.NewBufferString(message))
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("notify: creating request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if title != "" {
|
||||||
|
req.Header.Set("Title", title)
|
||||||
|
}
|
||||||
|
req.Header.Set("Priority", fmt.Sprintf("%d", priority))
|
||||||
|
req.Header.Set("Markdown", "yes")
|
||||||
|
if token != "" {
|
||||||
|
// ntfy uses basic auth with empty username and token as password
|
||||||
|
encoded := base64.StdEncoding.EncodeToString([]byte(":" + token))
|
||||||
|
req.Header.Set("Authorization", "Basic "+encoded)
|
||||||
|
}
|
||||||
|
resp, err := http.DefaultClient.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("notify: sending request: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveAlertPeer(ctx context.Context, api *tg.Client, cfg *Config) (*tg.InputPeerChannel, error) {
|
||||||
|
result, err := api.MessagesGetDialogs(ctx, &tg.MessagesGetDialogsRequest{
|
||||||
|
OffsetPeer: &tg.InputPeerEmpty{},
|
||||||
|
Limit: 100,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var chats []tg.ChatClass
|
||||||
|
|
||||||
|
switch v := result.(type) {
|
||||||
|
case *tg.MessagesDialogs:
|
||||||
|
chats = v.Chats
|
||||||
|
case *tg.MessagesDialogsSlice:
|
||||||
|
chats = v.Chats
|
||||||
|
case *tg.MessagesDialogsNotModified:
|
||||||
|
return nil, fmt.Errorf("dialogs not modified")
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("unexpected dialogs type: %T", result)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, chat := range chats {
|
||||||
|
if channel, ok := chat.(*tg.Channel); ok {
|
||||||
|
if channel.ID == cfg.AlertChat {
|
||||||
|
return &tg.InputPeerChannel{
|
||||||
|
ChannelID: channel.ID,
|
||||||
|
AccessHash: channel.AccessHash,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, fmt.Errorf("alert channel %d not found", cfg.AlertChat)
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleNewMessage(ctx context.Context, api *tg.Client, alertPeer *tg.InputPeerChannel, cfg *Config, entities tg.Entities, update any) error {
|
||||||
|
// Extract message from either UpdateNewMessage or UpdateNewChannelMessage
|
||||||
|
var msg *tg.Message
|
||||||
|
switch u := update.(type) {
|
||||||
|
case *tg.UpdateNewMessage:
|
||||||
|
m, ok := u.Message.(*tg.Message)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg = m
|
||||||
|
case *tg.UpdateNewChannelMessage:
|
||||||
|
m, ok := u.Message.(*tg.Message)
|
||||||
|
if !ok {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
msg = m
|
||||||
|
default:
|
||||||
|
log.Printf("DEBUG: unknown update type %T", update)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only process messages sent by the bot (Out=true)
|
||||||
|
if msg.Out {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if from monitored supergroup (channels in gotd terminology)
|
||||||
|
peerChannel, ok := msg.PeerID.(*tg.PeerChannel)
|
||||||
|
if !ok || int64(peerChannel.ChannelID) != cfg.MonitoredChat {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
log.Printf("✓ Processing message in chat %v: %s", msg.PeerID, msg.Message)
|
||||||
|
chatID := int64(peerChannel.ChannelID)
|
||||||
|
|
||||||
|
// Use YAML patterns if available, otherwise use defaults
|
||||||
|
patterns := cfg.Patterns
|
||||||
|
if len(patterns) == 0 {
|
||||||
|
patterns = scamPatterns
|
||||||
|
log.Printf("Using hardcoded scam patterns (%d patterns)", len(patterns))
|
||||||
|
} else {
|
||||||
|
log.Printf("Using YAML-loaded scam patterns (%d patterns)", len(patterns))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Score the message
|
||||||
|
result := keywordMatchScore(msg.Message, patterns)
|
||||||
|
|
||||||
|
if extended_latin, ok := result.matchedKeywords["extended_latin"]; ok {
|
||||||
|
for range extended_latin {
|
||||||
|
result.score += 0.1
|
||||||
|
if result.score >= 1.0 {
|
||||||
|
result.score = 1.0
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.score < 1.0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Printf("Matched message with score %.2f", result.score)
|
||||||
|
for key, values := range result.matchedKeywords {
|
||||||
|
for _, value := range values {
|
||||||
|
log.Printf(" %s: %s", key, value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Delete the message from supergroup
|
||||||
|
channel, ok := entities.Channels[chatID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("channel %d not found in entities", chatID)
|
||||||
|
}
|
||||||
|
_, err := api.ChannelsDeleteMessages(ctx, &tg.ChannelsDeleteMessagesRequest{
|
||||||
|
Channel: &tg.InputChannel{
|
||||||
|
ChannelID: chatID,
|
||||||
|
AccessHash: channel.AccessHash,
|
||||||
|
},
|
||||||
|
ID: []int{msg.ID},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to delete message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get sender info
|
||||||
|
var senderID int64
|
||||||
|
if fromID, ok := msg.FromID.(*tg.PeerUser); ok {
|
||||||
|
senderID = int64(fromID.UserID)
|
||||||
|
} else {
|
||||||
|
return fmt.Errorf("could not determine sender")
|
||||||
|
}
|
||||||
|
|
||||||
|
user, ok := entities.Users[senderID]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("user %d not found in entities", senderID)
|
||||||
|
}
|
||||||
|
|
||||||
|
displayName := user.FirstName
|
||||||
|
if user.LastName != "" {
|
||||||
|
displayName += " " + user.LastName
|
||||||
|
}
|
||||||
|
displayName = strings.TrimSpace(displayName)
|
||||||
|
|
||||||
|
username := "no username"
|
||||||
|
if user.Username != "" {
|
||||||
|
username = "@" + user.Username
|
||||||
|
}
|
||||||
|
|
||||||
|
// Restrict sender in supergroup
|
||||||
|
_, err = api.ChannelsEditBanned(ctx, &tg.ChannelsEditBannedRequest{
|
||||||
|
Channel: &tg.InputChannel{
|
||||||
|
ChannelID: chatID,
|
||||||
|
AccessHash: channel.AccessHash,
|
||||||
|
},
|
||||||
|
Participant: &tg.InputPeerUser{
|
||||||
|
UserID: senderID,
|
||||||
|
AccessHash: user.AccessHash,
|
||||||
|
},
|
||||||
|
BannedRights: tg.ChatBannedRights{
|
||||||
|
SendMessages: true,
|
||||||
|
SendMedia: true,
|
||||||
|
SendStickers: true,
|
||||||
|
SendGifs: true,
|
||||||
|
UntilDate: 0,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to restrict user %d: %v", senderID, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get supergroup name
|
||||||
|
chatName := channel.Title
|
||||||
|
|
||||||
|
// Build and send alert message
|
||||||
|
matchMessage := fmt.Sprintf("🚨 Matched\n**Score**: %.2f\n**Chat**: %s (ID: %d)\n**User**: %s (%s) (ID: %d)\n",
|
||||||
|
result.score, escapeMarkdown(chatName), chatID, escapeMarkdown(displayName+" ("+username+")"), username, senderID)
|
||||||
|
|
||||||
|
// Send ntfy notification if config set
|
||||||
|
if cfg.NtfyToken != "" || cfg.NtfyTopic != "" {
|
||||||
|
notify(matchMessage, cfg.NtfyTopic, fmt.Sprintf("Scam Alert: %s", chatName), 5, cfg.NtfyToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send alert message to alert chat
|
||||||
|
_, err = api.MessagesSendMessage(ctx, &tg.MessagesSendMessageRequest{
|
||||||
|
Peer: alertPeer,
|
||||||
|
Message: matchMessage,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
log.Printf("Failed to send alert message: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctx := context.Background()
|
||||||
|
|
||||||
|
// Load configuration
|
||||||
|
configPath := "config.yaml"
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
configPath = os.Args[1]
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg, err := loadConfig(configPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("Failed to load config: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatcher := tg.NewUpdateDispatcher()
|
||||||
|
gapManager := updates.New(updates.Config{Handler: dispatcher})
|
||||||
|
|
||||||
|
client := telegram.NewClient(cfg.APIId, cfg.APIHash, telegram.Options{
|
||||||
|
UpdateHandler: gapManager,
|
||||||
|
SessionStorage: &session.FileStorage{Path: cfg.SessionPath},
|
||||||
|
})
|
||||||
|
|
||||||
|
err = client.Run(ctx, func(ctx context.Context) error {
|
||||||
|
// Check if session exists; if not, authenticate
|
||||||
|
_, err := os.Stat(cfg.SessionPath)
|
||||||
|
if err != nil {
|
||||||
|
// Session doesn't exist, prompt for credentials
|
||||||
|
fmt.Print("Enter phone number: ")
|
||||||
|
var phone string
|
||||||
|
fmt.Scanln(&phone)
|
||||||
|
|
||||||
|
fmt.Print("Enter 2FA password: ")
|
||||||
|
pwBytes, err := term.ReadPassword(0)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("reading password: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println()
|
||||||
|
password := string(pwBytes)
|
||||||
|
|
||||||
|
flow := auth.NewFlow(
|
||||||
|
auth.Constant(
|
||||||
|
phone,
|
||||||
|
password,
|
||||||
|
auth.CodeAuthenticatorFunc(func(ctx context.Context, sentCode *tg.AuthSentCode) (string, error) {
|
||||||
|
fmt.Print("Enter code: ")
|
||||||
|
var code string
|
||||||
|
fmt.Scanln(&code)
|
||||||
|
return code, nil
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
auth.SendCodeOptions{},
|
||||||
|
)
|
||||||
|
|
||||||
|
if err := client.Auth().IfNecessary(ctx, flow); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api := client.API()
|
||||||
|
|
||||||
|
// Resolve alert channel peer at startup
|
||||||
|
alertPeer, err := resolveAlertPeer(ctx, api, cfg)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("resolving alert peer: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register message handler for private/group messages
|
||||||
|
dispatcher.OnNewMessage(func(ctx context.Context, entities tg.Entities, update *tg.UpdateNewMessage) error {
|
||||||
|
return handleNewMessage(ctx, api, alertPeer, cfg, entities, update)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Register handler for channel messages (supergroups)
|
||||||
|
dispatcher.OnNewChannelMessage(func(ctx context.Context, entities tg.Entities, update *tg.UpdateNewChannelMessage) error {
|
||||||
|
return handleNewMessage(ctx, api, alertPeer, cfg, entities, update)
|
||||||
|
})
|
||||||
|
|
||||||
|
// Get self ID for updates manager
|
||||||
|
status, err := client.Auth().Status(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
user := status.User
|
||||||
|
username := ""
|
||||||
|
if user.Username != "" {
|
||||||
|
username = " (@" + user.Username + ")"
|
||||||
|
}
|
||||||
|
displayName := user.FirstName
|
||||||
|
if user.LastName != "" {
|
||||||
|
displayName += " " + user.LastName
|
||||||
|
}
|
||||||
|
log.Printf("✓ Logged in as: %s%s (ID: %d)", displayName, username, user.ID)
|
||||||
|
log.Printf("✓ Bot running, monitoring chat %d", cfg.MonitoredChat)
|
||||||
|
return gapManager.Run(ctx, api, status.User.ID, updates.AuthOptions{})
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
47
go.mod
Normal file
47
go.mod
Normal file
|
|
@ -0,0 +1,47 @@
|
||||||
|
module git.zio.sh/astra/telegram-antiscam
|
||||||
|
|
||||||
|
go 1.25.3
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/gotd/td v0.139.0
|
||||||
|
golang.org/x/term v0.40.0
|
||||||
|
gopkg.in/yaml.v3 v3.0.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 // indirect
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 // indirect
|
||||||
|
github.com/coder/websocket v1.8.14 // indirect
|
||||||
|
github.com/dlclark/regexp2 v1.11.5 // indirect
|
||||||
|
github.com/fatih/color v1.18.0 // indirect
|
||||||
|
github.com/ghodss/yaml v1.0.0 // indirect
|
||||||
|
github.com/go-faster/errors v0.7.1 // indirect
|
||||||
|
github.com/go-faster/jx v1.2.0 // indirect
|
||||||
|
github.com/go-faster/xor v1.0.0 // indirect
|
||||||
|
github.com/go-faster/yaml v0.4.6 // indirect
|
||||||
|
github.com/google/uuid v1.6.0 // indirect
|
||||||
|
github.com/gotd/ige v0.2.2 // indirect
|
||||||
|
github.com/gotd/neo v0.1.5 // indirect
|
||||||
|
github.com/klauspost/compress v1.18.3 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.14 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/ogen-go/ogen v1.16.0 // indirect
|
||||||
|
github.com/segmentio/asm v1.2.1 // indirect
|
||||||
|
github.com/shopspring/decimal v1.4.0 // indirect
|
||||||
|
go.opentelemetry.io/otel v1.40.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/metric v1.40.0 // indirect
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0 // indirect
|
||||||
|
go.uber.org/atomic v1.11.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
go.uber.org/zap v1.27.1 // indirect
|
||||||
|
golang.org/x/crypto v0.47.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 // indirect
|
||||||
|
golang.org/x/mod v0.32.0 // indirect
|
||||||
|
golang.org/x/net v0.49.0 // indirect
|
||||||
|
golang.org/x/sync v0.19.0 // indirect
|
||||||
|
golang.org/x/sys v0.41.0 // indirect
|
||||||
|
golang.org/x/text v0.33.0 // indirect
|
||||||
|
golang.org/x/tools v0.41.0 // indirect
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 // indirect
|
||||||
|
rsc.io/qr v0.2.0 // indirect
|
||||||
|
)
|
||||||
104
go.sum
Normal file
104
go.sum
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8=
|
||||||
|
github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs=
|
||||||
|
github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
|
||||||
|
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
|
||||||
|
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dlclark/regexp2 v1.11.5 h1:Q/sSnsKerHeCkc/jSTNq1oCm7KiVgUMZRDUoRu0JQZQ=
|
||||||
|
github.com/dlclark/regexp2 v1.11.5/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
|
||||||
|
github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM=
|
||||||
|
github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU=
|
||||||
|
github.com/ghodss/yaml v1.0.0 h1:wQHKEahhL6wmXdzwWG11gIVCkOv05bNOh+Rxn0yngAk=
|
||||||
|
github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
|
||||||
|
github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg=
|
||||||
|
github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo=
|
||||||
|
github.com/go-faster/jx v1.2.0 h1:T2YHJPrFaYu21fJtUxC9GzmluKu8rVIFDwwGBKTDseI=
|
||||||
|
github.com/go-faster/jx v1.2.0/go.mod h1:UWLOVDmMG597a5tBFPLIWJdUxz5/2emOpfsj9Neg0PE=
|
||||||
|
github.com/go-faster/xor v0.3.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
|
||||||
|
github.com/go-faster/xor v1.0.0 h1:2o8vTOgErSGHP3/7XwA5ib1FTtUsNtwCoLLBjl31X38=
|
||||||
|
github.com/go-faster/xor v1.0.0/go.mod h1:x5CaDY9UKErKzqfRfFZdfu+OSTfoZny3w5Ak7UxcipQ=
|
||||||
|
github.com/go-faster/yaml v0.4.6 h1:lOK/EhI04gCpPgPhgt0bChS6bvw7G3WwI8xxVe0sw9I=
|
||||||
|
github.com/go-faster/yaml v0.4.6/go.mod h1:390dRIvV4zbnO7qC9FGo6YYutc+wyyUSHBgbXL52eXk=
|
||||||
|
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
|
||||||
|
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
|
||||||
|
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
|
||||||
|
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
|
||||||
|
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
|
||||||
|
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
|
||||||
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/gotd/ige v0.2.2 h1:XQ9dJZwBfDnOGSTxKXBGP4gMud3Qku2ekScRjDWWfEk=
|
||||||
|
github.com/gotd/ige v0.2.2/go.mod h1:tuCRb+Y5Y3eNTo3ypIfNpQ4MFjrnONiL2jN2AKZXmb0=
|
||||||
|
github.com/gotd/neo v0.1.5 h1:oj0iQfMbGClP8xI59x7fE/uHoTJD7NZH9oV1WNuPukQ=
|
||||||
|
github.com/gotd/neo v0.1.5/go.mod h1:9A2a4bn9zL6FADufBdt7tZt+WMhvZoc5gWXihOPoiBQ=
|
||||||
|
github.com/gotd/td v0.139.0 h1:3viuXqNdC0+mmd5GerDFp/rlII/QcZSzh/pjuG56NSU=
|
||||||
|
github.com/gotd/td v0.139.0/go.mod h1:nBietiOYxaXEo6PmRp73LL64upWlk9rcFEZSJu6VieY=
|
||||||
|
github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
|
||||||
|
github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
|
||||||
|
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
|
||||||
|
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
|
||||||
|
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/ogen-go/ogen v1.16.0 h1:fKHEYokW/QrMzVNXId74/6RObRIUs9T2oroGKtR25Iw=
|
||||||
|
github.com/ogen-go/ogen v1.16.0/go.mod h1:s3nWiMzybSf8fhxckyO+wtto92+QHpEL8FmkPnhL3jI=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0=
|
||||||
|
github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs=
|
||||||
|
github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k=
|
||||||
|
github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME=
|
||||||
|
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
|
||||||
|
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64=
|
||||||
|
go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y=
|
||||||
|
go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms=
|
||||||
|
go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g=
|
||||||
|
go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g=
|
||||||
|
go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw=
|
||||||
|
go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA=
|
||||||
|
go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE=
|
||||||
|
go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0=
|
||||||
|
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
|
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
|
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||||
|
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
|
||||||
|
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
|
||||||
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090 h1:Di6/M8l0O2lCLc6VVRWhgCiApHV8MnQurBnFSHsQtNY=
|
||||||
|
golang.org/x/exp v0.0.0-20230725093048-515e97ebf090/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
|
||||||
|
golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c=
|
||||||
|
golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU=
|
||||||
|
golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o=
|
||||||
|
golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8=
|
||||||
|
golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||||
|
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
|
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
|
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg=
|
||||||
|
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM=
|
||||||
|
golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
|
||||||
|
golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
|
||||||
|
golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc=
|
||||||
|
golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
nhooyr.io/websocket v1.8.17 h1:KEVeLJkUywCKVsnLIDlD/5gtayKp8VoCkksHCGGfT9Y=
|
||||||
|
nhooyr.io/websocket v1.8.17/go.mod h1:rN9OFWIUwuxg4fR5tELlYC04bXYowCP9GX47ivo2l+c=
|
||||||
|
rsc.io/qr v0.2.0 h1:6vBLea5/NRMVTz8V66gipeLycZMl/+UlFmk8DvqQ6WY=
|
||||||
|
rsc.io/qr v0.2.0/go.mod h1:IF+uZjkb9fqyeF/4tlBoynqmQxUoPfWEKh921coOuXs=
|
||||||
Loading…
Add table
Add a link
Reference in a new issue