package main import ( "bufio" "context" "fmt" "os" "path" "regexp" "strings" "time" "github.com/joho/godotenv" log "github.com/sirupsen/logrus" "github.com/UA-Fediland/go-mastodon" "gorm.io/driver/postgres" "gorm.io/gorm" "maunium.net/go/mautrix" m_event "maunium.net/go/mautrix/event" m_id "maunium.net/go/mautrix/id" ) type Post struct { ID int PostId string MessageId string } func parseRegexFile(path string) ([]regexp.Regexp, error) { file, err := os.Open(path) if err != nil { return nil, err } file_scanner := bufio.NewScanner(file) file_scanner.Split(bufio.ScanLines) var result []regexp.Regexp for file_scanner.Scan() { text := file_scanner.Text() if strings.Trim(text, " ")[0] != '#' { regex, err := regexp.Compile(text) if err != nil { return nil, err } result = append(result, *regex) } } return result, nil } func main() { ctx := context.Background() err := godotenv.Load() ROOM_ID := os.Getenv("MATRIX_ROOM_ID") log.SetLevel(log.DebugLevel) if err != nil { log.Fatal(err) } ex, err := os.Getwd() if err != nil { log.Fatal(err) } blacklistRegexes, err := parseRegexFile(path.Join(ex, "blacklist.txt")) if err != nil { log.Fatal(err) } whitelistRegexes, err := parseRegexFile(path.Join(ex, "whitelist.txt")) if err != nil { log.Fatal(err) } // log.Info(nicknameRegexes, domainRegexes) db, err := gorm.Open(postgres.Open(fmt.Sprintf("host=%s user=%s password=%s dbname=%s port=%s", os.Getenv("POSTGRES_HOST"), os.Getenv("POSTGRES_USER"), os.Getenv("POSTGRES_PASSWORD"), os.Getenv("POSTGRES_DB"), os.Getenv("POSTGRES_PORT"))), &gorm.Config{}) db.AutoMigrate(&Post{}) if err != nil { log.Fatal(err) } masto_client := mastodon.NewClient(&mastodon.Config{Server: os.Getenv("MASTODON_HOMESERVER"), ClientID: os.Getenv("MASTODON_CLIENT_ID"), ClientSecret: os.Getenv("MASTODON_CLIENT_SECRET"), AccessToken: os.Getenv("MASTODON_ACCESS_TOKEN"), }) masto_user, err := masto_client.GetAccountCurrentUser(ctx) if err != nil { log.Fatal(err) } var subscribers []*mastodon.Account var pg mastodon.Pagination log.Debug("get followers list") for { fs, err := masto_client.GetAccountFollowers(context.Background(), masto_user.ID, &pg) if err != nil { log.Fatal(err) } subscribers = append(subscribers, fs...) // log.Debug(pg) // log.Debug(pg.MaxID) if pg.MaxID == "" { break } time.Sleep(200 * time.Millisecond) } log.Debugf("subscribers count: %v", len(subscribers)) // for _, subscriber := range subscribers { // log.Debug(subscriber.Acct) // } matrix_client, err := mautrix.NewClient(os.Getenv("MATRIX_HOMESERVER"), m_id.UserID(os.Getenv("MATRIX_USERNAME")), os.Getenv("MATRIX_ACCESS_TOKEN")) matrix_client.Store = mautrix.NewAccountDataStore("ua.in.fediland.uabot", matrix_client) syncer := matrix_client.Syncer.(*mautrix.DefaultSyncer) if err != nil { log.Fatal(err) } log.Info("starting...") matrix_client.SendText(m_id.RoomID(ROOM_ID), "Бот запущений!") syncer.OnEventType(m_event.EventReaction, func(source mautrix.EventSource, ev *m_event.Event) { log.Debug("reactions") // if ev.RoomID == ROOM_ID && ev.Content != nil && content := ev.Content.AsReaction() key := []rune(content.RelatesTo.Key) log.Debug(content.RelatesTo.Key) if ev.RoomID.String() == ROOM_ID && (key[0] == '👍' || key[0] == '👎') { log.Debug("reacted well") var post Post if err := db.First(&post, "message_id = ?", content.RelatesTo.EventID.String()).Error; err != nil { log.Error(err) return } status, err := masto_client.GetStatus(ctx, mastodon.ID(post.PostId)) if err != nil { log.Error(err) return } if key[0] == '👍' { _, err = masto_client.Reblog(ctx, mastodon.ID(post.PostId)) if err != nil { log.Error(err) return } // matrix_client.SendMessageEvent(ROOM_ID, "m.room.message", interface{}) body := fmt.Sprintf(`#%d: Схвалено %s `, post.ID, status.URL) _, err := matrix_client.SendMessageEvent(m_id.RoomID(ROOM_ID), m_event.EventMessage, &m_event.MessageEventContent{ Body: body, MsgType: m_event.MsgText, NewContent: &m_event.MessageEventContent{ MsgType: m_event.MsgText, Body: body}, RelatesTo: &m_event.RelatesTo{ Type: m_event.RelReplace, EventID: m_id.EventID(post.MessageId), }, }) if err != nil { log.Error(err) } } else { body := fmt.Sprintf(`#%d: Відмовлено %s `, post.ID, status.URL) _, err := matrix_client.SendMessageEvent(m_id.RoomID(ROOM_ID), m_event.EventMessage, &m_event.MessageEventContent{ Body: body, MsgType: m_event.MsgText, NewContent: &m_event.MessageEventContent{ MsgType: m_event.MsgText, Body: body, }, RelatesTo: &m_event.RelatesTo{ Type: m_event.RelReplace, EventID: m_id.EventID(post.MessageId), }, }) if err != nil { log.Error(err) } } } }) events, err := masto_client.StreamingUser(ctx) if err != nil { log.Fatal(err) } go func() { for { if err := matrix_client.Sync(); err != nil { log.Error(fmt.Errorf("Sync() returned %s", err)) } } }() notif_loop: for { notif_event, ok := (<-events).(*mastodon.NotificationEvent) if !ok { continue } notif := notif_event.Notification // log.Println(notif) if notif.Type == "follow" { // acct_parsed := strings.Split(notif.Account.Acct, "@") for _, regex := range blacklistRegexes { if regex.MatchString(notif.Account.Acct) { _, err := masto_client.AccountBlock(ctx, notif.Account.ID) if err == nil { matrix_client.SendText(m_id.RoomID(ROOM_ID), fmt.Sprintf("%s заблокований автоматично регулярним виразом: %s", notif.Account.Acct, regex.String())) } continue notif_loop } } subscribers = append(subscribers, ¬if.Account) masto_client.PostStatus(ctx, &mastodon.Toot{ Status: fmt.Sprintf("Вітаємо у нашій спільноті! @%s", notif.Account.Acct), Visibility: mastodon.VisibilityUnlisted, }) } if notif.Type == "mention" { log.Debugf("post %v mentioned bot", notif.Status.ID) } if notif.Type == "mention" && notif.Status.InReplyToID == nil && notif.Status.InReplyToAccountID == nil { log.Debugf(`post passed reply check (%v)`, notif.Status.ID) ok := false for _, subscriber := range subscribers { if subscriber.ID == notif.Account.ID { ok = true break } } if ok && notif.Status.Visibility == "public" { log.Debugf("post %v is public and user has subscription", notif.Status.ID) for _, regex := range whitelistRegexes { if regex.MatchString(notif.Account.Acct) { _, err = masto_client.Reblog(ctx, notif.Status.ID) if err != nil { log.Error(err) } log.Debugf("post %v belongs to whitelisted user (%v) by regex: %v", notif.Status.ID, notif.Account.Acct, regex.String()) continue notif_loop } } if err := db.Create(&Post{ PostId: string(notif.Status.ID), }).Error; err != nil { log.Error(err) continue } var post Post if err := db.First(&post, "post_id = ?", string(notif.Status.ID)).Error; err != nil { log.Error(err) continue } message, err := matrix_client.SendText(m_id.RoomID(ROOM_ID), fmt.Sprintf(`#%d: На модерації %s 👍 - Так 👎 - Ні `, post.ID, notif.Status.URL)) if err != nil { log.Fatal(err) db.Delete(&post, post.ID) } post.MessageId = message.EventID.String() db.Save(&post) } if ok && notif.Status.Visibility == "direct" && notif.Status.Content == "ping" { masto_client.PostStatus(ctx, &mastodon.Toot{ Status: "meow", InReplyToID: notif.Status.ID, Visibility: "direct", }) } } } }