From 84bb8e31540644d5258794328db738f6e88d9d93 Mon Sep 17 00:00:00 2001 From: fade Date: Thu, 1 Sep 2022 07:31:27 -0400 Subject: [PATCH] add duplicate protection --- README.md | 9 +- bot.go | 43 +++++--- config.go | 7 +- config.json | 5 +- limits.go | 106 +++++++++++++++----- main.go | 4 +- services/openrc/mastodon-group-bot | 2 +- services/systemd/mastodon-group-bot.service | 2 +- 8 files changed, 126 insertions(+), 52 deletions(-) diff --git a/README.md b/README.md index 78f2128..a0f76a9 100644 --- a/README.md +++ b/README.md @@ -5,13 +5,13 @@ This is a bot which implements group functionality in Mastodon. * Repost toots * Welcome message of new members * Limit of toots per hour +* Duplicate protection +* Logging * Admin commands ### Admin commands * unboost \ * delete \ -* block \ -* unblock \ # Configuration The bot is configured in a JSON file that looks like this: @@ -22,8 +22,9 @@ The bot is configured in a JSON file that looks like this: "ClientSecret": "0000000000000000000000000000000000000000000", "AccessToken": "0000000000000000000000000000000000000000000", "WelcomeMessage": "We have a new member in our group. Please love and favor" - "Max_toots": 1, - "Toots_interval": 24, + "Max_toots": 2, + "Toots_interval": 12, + "Duplicate_buf": 10, "Admins": ["admin@example.com"] } ``` diff --git a/bot.go b/bot.go index 7f6c4f8..4305836 100644 --- a/bot.go +++ b/bot.go @@ -2,6 +2,7 @@ package main import ( "context" + "crypto/sha512" "fmt" "regexp" "strings" @@ -9,7 +10,7 @@ import ( "github.com/mattn/go-mastodon" ) -func RunBot(Conf Config) { +func RunBot() { logger_init() c := mastodon.NewClient(&mastodon.Config{ @@ -59,7 +60,7 @@ func RunBot(Conf Config) { if !followed(acct) { // Add to db and post welcome message InfoLogger.Printf("%s followed", acct) - add_to_db(acct, Conf.Max_toots) + add_to_db(acct) InfoLogger.Printf("%s added to database", acct) var message = fmt.Sprintf("%s @%s", Conf.WelcomeMessage, acct) @@ -74,31 +75,49 @@ func RunBot(Conf Config) { // Read message if notif.Type == "mention" { acct := notif.Status.Account.Acct + content := notif.Status.Content + tooturl := notif.Status.URL + for i := 0; i < len(followers); i++ { if acct == string(followers[i].Acct) { // Follow check if notif.Status.Visibility == "public" { // Reblog toot if notif.Status.InReplyToID == nil { // Not boost replies - if !followed(acct) { // Add to db if needed - add_to_db(acct, Conf.Max_toots) + // Duplicate protection + content_hash := sha512.New() + content_hash.Write([]byte(content)) + hash := fmt.Sprintf("%x", content_hash.Sum(nil)) + + if !check_msg_hash(hash) { + save_msg_hash(hash) + InfoLogger.Printf("Hash of %s added to database", tooturl) + } else { + WarnLogger.Printf("%s is a duplicate and not boosted", tooturl) + break + } + + // Add to db if needed + if !followed(acct) { + add_to_db(acct) InfoLogger.Printf("%s added to database", acct) } - if check_ticket(acct, Conf.Max_toots, Conf.Toots_interval) > 0 { // Limit + + // Message limit + if check_ticket(acct) > 0 { take_ticket(acct) InfoLogger.Printf("Ticket of %s was taken", acct) c.Reblog(ctx, notif.Status.ID) - InfoLogger.Printf("Toot %s of %s was rebloged", notif.Status.URL, acct) + InfoLogger.Printf("Toot %s of %s was rebloged", tooturl, acct) } else { WarnLogger.Printf("%s haven't tickets", acct) } } else { - WarnLogger.Printf("%s is reply and not boosted", notif.Status.URL) + WarnLogger.Printf("%s is reply and not boosted", tooturl) } } else if notif.Status.Visibility == "direct" { // Admin commands for y := 0; y < len(Conf.Admins); y++ { if acct == Conf.Admins[y] { - text := notif.Status.Content recmd := regexp.MustCompile(`<.*?> `) - command := recmd.ReplaceAllString(text, "") + command := recmd.ReplaceAllString(content, "") args := strings.Split(command, " ") mID := mastodon.ID((args[1])) @@ -108,10 +127,6 @@ func RunBot(Conf Config) { c.Unreblog(ctx, mID) case "delete": c.DeleteStatus(ctx, mID) - case "block": - c.AccountBlock(ctx, mID) - case "unblock": - c.AccountUnblock(ctx, mID) } } } else { @@ -119,7 +134,7 @@ func RunBot(Conf Config) { } } } else { - WarnLogger.Printf("%s is not public toot and not boosted", notif.Status.URL) + WarnLogger.Printf("%s is not public toot and not boosted", tooturl) break } } diff --git a/config.go b/config.go index d6d930f..712bc39 100644 --- a/config.go +++ b/config.go @@ -9,8 +9,10 @@ import ( var ( ConfPath = flag.String("config", "config.json", "Path to config") - DBPath = flag.String("db", "limits.db", "Path to database") + DBPath = flag.String("db", "mastodon-group-bot.db", "Path to database") LogPath = flag.String("log", "mastodon-group-bot.log", "Path to log") + + Conf = ReadConfig() ) type Config struct { @@ -21,10 +23,11 @@ type Config struct { WelcomeMessage string `json:"WelcomeMessage"` Max_toots uint16 `json:"Max_toots"` Toots_interval uint16 `json:"Toots_interval"` + Duplicate_buf int `json:"Duplicate_buf"` Admins []string `json:"Admins"` } -func ReadConf() Config { +func ReadConfig() Config { flag.Parse() data, err := os.ReadFile(*ConfPath) diff --git a/config.json b/config.json index 5b95271..d9c3c43 100644 --- a/config.json +++ b/config.json @@ -4,7 +4,8 @@ "ClientSecret": "0000000000000000000000000000000000000000000", "AccessToken": "0000000000000000000000000000000000000000000", "WelcomeMessage": "We have a new member in our group. Please love and favor", - "Max_toots": 1, - "Toots_interval": 24, + "Max_toots": 2, + "Toots_interval": 12, + "Duplicate_buf": 10, "Admins": ["admin@example.com"] } \ No newline at end of file diff --git a/limits.go b/limits.go index 3722f2b..407e1d4 100644 --- a/limits.go +++ b/limits.go @@ -15,25 +15,97 @@ func init_limit_db() *sql.DB { if err != nil { ErrorLogger.Println("Open database") } - cmd := `CREATE TABLE IF NOT EXISTS Limits (id INTEGER PRIMARY KEY AUTOINCREMENT, acct TEXT, ticket INTEGER, time TEXT)` - stat, err := db.Prepare(cmd) + + cmd1 := `CREATE TABLE IF NOT EXISTS Limits (id INTEGER PRIMARY KEY AUTOINCREMENT, acct TEXT, ticket INTEGER, time TEXT)` + cmd2 := `CREATE TABLE IF NOT EXISTS MsgHashs (message_hash TEXT)` + + stat1, err := db.Prepare(cmd1) if err != nil { ErrorLogger.Println("Create database") } - stat.Exec() + stat1.Exec() + + stat2, err := db.Prepare(cmd2) + if err != nil { + ErrorLogger.Println("Create database") + } + stat2.Exec() return db } // Add account to database -func add_to_db(acct string, limit uint16) { +func add_to_db(acct string) { db := init_limit_db() cmd := `INSERT INTO Limits (acct, ticket) VALUES (?, ?)` stat, err := db.Prepare(cmd) if err != nil { ErrorLogger.Println("Add account to databse") } - stat.Exec(acct, limit) + stat.Exec(acct, Conf.Max_toots) +} + +// Save message hash +func save_msg_hash(hash string) { + db := init_limit_db() + + cmd1 := `SELECT COUNT(*) FROM MsgHashs` + cmd2 := `DELETE FROM MsgHashs WHERE ROWID IN (SELECT ROWID FROM MsgHashs LIMIT 1)` + cmd3 := `INSERT INTO MsgHashs (message_hash) VALUES (?)` + + var rows int + + db.QueryRow(cmd1).Scan(&rows) + + if rows >= Conf.Duplicate_buf { + superfluous := rows - Conf.Duplicate_buf + + for i := 0; i <= superfluous; i++ { + stat2, err := db.Prepare(cmd2) + if err != nil { + ErrorLogger.Println("Delete message hash from database") + } + stat2.Exec() + } + } + + stat1, err := db.Prepare(cmd3) + if err != nil { + ErrorLogger.Println("Add message hash to database") + } + stat1.Exec(hash) +} + +// Check followed once +func followed(acct string) bool { + db := init_limit_db() + cmd := `SELECT acct FROM Limits WHERE acct = ?` + err := db.QueryRow(cmd, acct).Scan(&acct) + if err != nil { + if err != sql.ErrNoRows { + InfoLogger.Println("Check followed") + } + + return false + } + + return true +} + +// Check message hash +func check_msg_hash(hash string) bool { + db := init_limit_db() + cmd := `SELECT message_hash FROM MsgHashs WHERE message_hash = ?` + err := db.QueryRow(cmd, hash).Scan(&hash) + if err != nil { + if err != sql.ErrNoRows { + InfoLogger.Println("Check message hash in database") + } + + return false + } + + return true } // Take ticket for tooting @@ -59,24 +131,8 @@ func take_ticket(acct string) { stat.Exec(ticket, last_toot_at, acct) } -// Check followed once -func followed(acct string) bool { - db := init_limit_db() - cmd := `SELECT acct FROM Limits WHERE acct = ?` - err := db.QueryRow(cmd, acct).Scan(&acct) - if err != nil { - if err != sql.ErrNoRows { - ErrorLogger.Println("Check followed") - } - - return false - } - - return true -} - // Check ticket availability -func check_ticket(acct string, ticket uint16, toots_interval uint16) uint16 { +func check_ticket(acct string) uint16 { db := init_limit_db() cmd1 := `SELECT ticket FROM Limits WHERE acct = ?` cmd2 := `SELECT time FROM Limits WHERE acct = ?` @@ -90,7 +146,7 @@ func check_ticket(acct string, ticket uint16, toots_interval uint16) uint16 { lastT, _ := time.Parse("2006/01/02 15:04:05 MST", lastS) since := time.Since(lastT) - limit := fmt.Sprintf("%dh", toots_interval) + limit := fmt.Sprintf("%dh", Conf.Toots_interval) interval, _ := time.ParseDuration(limit) if since >= interval { @@ -99,9 +155,9 @@ func check_ticket(acct string, ticket uint16, toots_interval uint16) uint16 { if err != nil { ErrorLogger.Println("Check ticket availability") } - stat.Exec(ticket, acct) + stat.Exec(Conf.Max_toots, acct) - return ticket + return Conf.Max_toots } return tickets diff --git a/main.go b/main.go index 7f5823e..c980332 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,5 @@ package main func main() { - config := ReadConf() - - RunBot(config) + RunBot() } diff --git a/services/openrc/mastodon-group-bot b/services/openrc/mastodon-group-bot index 1040f76..99c5c4f 100644 --- a/services/openrc/mastodon-group-bot +++ b/services/openrc/mastodon-group-bot @@ -2,7 +2,7 @@ name=$RC_SVCNAME command="/usr/bin/$name" command_arg1="-config /etc/$name/config.json" -command_arg2="-db /var/lib/$name/limits.db" +command_arg2="-db /var/lib/$name/$name.db" command_arg3="-log /var/log/$name/$name.log" pidfile="/run/$name.pid" user="nobody" diff --git a/services/systemd/mastodon-group-bot.service b/services/systemd/mastodon-group-bot.service index 64d950b..05a664b 100644 --- a/services/systemd/mastodon-group-bot.service +++ b/services/systemd/mastodon-group-bot.service @@ -6,7 +6,7 @@ Wants=network-online.target [Service] Type=simple User=nobody -ExecStart=/usr/bin/mastodon-group-bot -config /etc/mastodon-group-bot/config.json -db /var/lib/mastodon-group-bot/limits.db -log /var/log/mastodon-group-bot/mastodon-group-bot.log +ExecStart=/usr/bin/mastodon-group-bot -config /etc/mastodon-group-bot/config.json -db /var/lib/mastodon-group-bot/mastodon-group-bot.db -log /var/log/mastodon-group-bot/mastodon-group-bot.log [Install] WantedBy=multi-user.target \ No newline at end of file