Add hash-lists filtering, format code, prepare API

This commit is contained in:
Kevin Kandlbinder 2022-02-23 15:12:02 +01:00
parent b81af24e50
commit 1b15b12859
11 changed files with 353 additions and 106 deletions

View file

@ -18,7 +18,6 @@
package cmd
import (
"github.com/Unkn0wnCat/matrix-veles/internal/config"
"github.com/spf13/viper"
"log"
"os"
@ -49,13 +48,16 @@ func Execute() {
func init() {
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is ./config.yaml)")
viper.SetDefault("bot.homeserver", "https://matrix.org")
viper.SetDefault("bot.homeserver", "matrix.org")
viper.SetDefault("bot.homeserver_url", "https://matrix.org")
viper.SetDefault("bot.username", "")
viper.SetDefault("bot.password", "")
viper.SetDefault("bot.accessKey", "")
viper.SetDefault("bot.rooms", config.RoomConfigTree{})
viper.SetDefault("bot.mongo.uri", "mongodb://localhost:27017")
viper.SetDefault("bot.mongo.database", "veles")
viper.SetDefault("bot.mongo.collection.entries", "entries")
viper.SetDefault("bot.mongo.collection.lists", "lists")
viper.SetDefault("bot.mongo.collection.rooms", "rooms")
cobra.OnInitialize(loadConfig)
}

80
internal/bot/getState.go Normal file
View file

@ -0,0 +1,80 @@
package bot
import (
"encoding/json"
"fmt"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
type StateEventPL struct {
Type string `json:"type"`
Sender string `json:"sender"`
RoomID string `json:"room_id"`
EventID string `json:"event_id"`
OriginServerTS int64 `json:"origin_server_ts"`
Content StateEventPLContent `json:"content"`
Unsigned struct {
Age int `json:"age"`
} `json:"unsigned"`
}
type StateEventPLContent struct {
Ban int `json:"ban"`
Events map[string]int `json:"events"`
EventsDefault int `json:"events_default"`
Invite int `json:"invite"`
Kick int `json:"kick"`
Notifications map[string]int `json:"notifications"`
Redact int `json:"redact"`
StateDefault int `json:"state_default"`
Users map[string]int `json:"users"`
UsersDefault int `json:"users_default"`
}
func GetRoomState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPLContent, error) {
url := matrixClient.BuildURL("rooms", roomId.String(), "state")
res, err := matrixClient.MakeRequest("GET", url, nil, nil)
if err != nil {
return nil, fmt.Errorf("ERROR: Could request room state - %v", err)
}
var stateEvents []StateEventPL
err = json.Unmarshal(res, &stateEvents)
if err != nil {
return nil, fmt.Errorf("ERROR: Could parse room state - %v", err)
}
var plEventContent StateEventPLContent
found := false
for _, e2 := range stateEvents {
if e2.Type != event.StatePowerLevels.Type {
continue
}
found = true
plEventContent = e2.Content
}
if !found {
return nil, fmt.Errorf("ERROR: Could find room power level - %v", err)
}
if plEventContent.Events == nil {
plEventContent.Events = make(map[string]int)
}
if plEventContent.Notifications == nil {
plEventContent.Notifications = make(map[string]int)
}
if plEventContent.Users == nil {
plEventContent.Users = make(map[string]int)
}
return &plEventContent, nil
}

View file

@ -15,7 +15,6 @@ import (
"log"
"maunium.net/go/mautrix"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)
func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixClient *mautrix.Client) {
@ -86,6 +85,36 @@ func handleHashing(content *event.MessageEventContent, evt *event.Event, matrixC
matrixClient.SendNotice(evt.RoomID, fmt.Sprintf("DEBUG:\n%s", buf.String()))
}
if roomConfig.HashChecker.SubscribedLists == nil {
log.Printf("Room %s is not subscribed to any lists!", roomConfig.RoomID)
return // Not subscribed to any lists
}
subMap := make(map[string]bool)
for _, listId := range roomConfig.HashChecker.SubscribedLists {
subMap[listId.Hex()] = true
}
found := false
log.Printf("%v", subMap)
for _, listId := range hashObj.PartOf {
_, ok := subMap[listId.Hex()]
if ok {
found = true
break
}
}
if !found {
log.Printf("Room %s is not subscribed to any lists of hashobj %s!", roomConfig.RoomID, hashObj.ID.Hex())
return // Not subscribed
}
log.Printf("Illegal content detected in room %s!", roomConfig.RoomID)
handleIllegalContent(evt, matrixClient, hashObj, roomConfig)
}
@ -97,78 +126,6 @@ func redactMessage(evt *event.Event, matrixClient *mautrix.Client, hashObj *mode
}
}
type StateEventPL struct {
Type string `json:"type"`
Sender string `json:"sender"`
RoomID string `json:"room_id"`
EventID string `json:"event_id"`
OriginServerTS int64 `json:"origin_server_ts"`
Content StateEventPLContent `json:"content"`
Unsigned struct {
Age int `json:"age"`
} `json:"unsigned"`
}
type StateEventPLContent struct {
Ban int `json:"ban"`
Events map[string]int `json:"events"`
EventsDefault int `json:"events_default"`
Invite int `json:"invite"`
Kick int `json:"kick"`
Notifications map[string]int `json:"notifications"`
Redact int `json:"redact"`
StateDefault int `json:"state_default"`
Users map[string]int `json:"users"`
UsersDefault int `json:"users_default"`
}
func GetRoomState(matrixClient *mautrix.Client, roomId id.RoomID) (*StateEventPLContent, error) {
url := matrixClient.BuildURL("rooms", roomId.String(), "state")
log.Println(url)
res, err := matrixClient.MakeRequest("GET", url, nil, nil)
if err != nil {
return nil, fmt.Errorf("ERROR: Could request room state - %v", err)
}
var stateEvents []StateEventPL
err = json.Unmarshal(res, &stateEvents)
if err != nil {
return nil, fmt.Errorf("ERROR: Could parse room state - %v", err)
}
var plEventContent StateEventPLContent
found := false
for _, e2 := range stateEvents {
if e2.Type != event.StatePowerLevels.Type {
continue
}
found = true
plEventContent = e2.Content
}
if !found {
return nil, fmt.Errorf("ERROR: Could find room power level - %v", err)
}
if plEventContent.Events == nil {
plEventContent.Events = make(map[string]int)
}
if plEventContent.Notifications == nil {
plEventContent.Notifications = make(map[string]int)
}
if plEventContent.Users == nil {
plEventContent.Users = make(map[string]int)
}
return &plEventContent, nil
}
func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
plEventContent, err := GetRoomState(matrixClient, evt.RoomID)
if err != nil {
@ -186,6 +143,15 @@ func muteUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBE
}
func banUser(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry) {
req := mautrix.ReqBanUser{
Reason: fmt.Sprintf("Veles has detected an hash-map-match! Tags: %s, ID: %s", hashObj.Tags, hashObj.ID.Hex()),
UserID: evt.Sender,
}
matrixClient.BanUser(evt.RoomID, &req)
}
func postNotice(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
local, server, err := evt.Sender.Parse()
if err != nil {
@ -198,18 +164,39 @@ If you believe this action was an accident, please contact an room administrator
}
/*func msgMods(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
local, server, err := evt.Sender.Parse()
if err != nil {
return
}
SendAlert(matrixClient, evt.RoomID.String(), fmt.Sprintf(
`Veles Triggered: The message by %s (on %s) was flagged for containing material used by spammers or trolls! (Reference: %s)`, local, server, hashObj.ID.Hex()))
}*/
func handleIllegalContent(evt *event.Event, matrixClient *mautrix.Client, hashObj *model.DBEntry, roomConfig config.RoomConfig) {
switch roomConfig.HashCheckMode {
switch roomConfig.HashChecker.HashCheckMode {
case 0:
postNotice(evt, matrixClient, hashObj, roomConfig)
break
case 1:
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
case 2:
muteUser(evt, matrixClient, hashObj)
redactMessage(evt, matrixClient, hashObj)
postNotice(evt, matrixClient, hashObj, roomConfig)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
case 3:
banUser(evt, matrixClient, hashObj)
redactMessage(evt, matrixClient, hashObj)
if roomConfig.HashChecker.NoticeToChat {
postNotice(evt, matrixClient, hashObj, roomConfig)
}
break
}

47
internal/bot/sendAlert.go Normal file
View file

@ -0,0 +1,47 @@
package bot
/*func SendAlert(matrixClient *mautrix.Client, room string, message string) {
roomConfig, err := config.GetRoomConfigByRoomID(room)
if err != nil {
log.Printf("Failed to get room config - %v", err)
return
}
if roomConfig.AlertChannel == nil {
roomPLState, err := GetRoomState(matrixClient, id.RoomID(room))
if err != nil {
log.Printf("Failed to get room power levels - %v", err)
return
}
var mods []id.UserID
for member, level := range roomPLState.Users {
if level >= roomConfig.HashChecker.NotificationPowerLevel {
mods = append(mods, id.UserID(member))
}
}
req := mautrix.ReqCreateRoom{
Name: "Veles Alert Channel",
Topic: "Veles Alerts",
Invite: mods,
IsDirect: true,
Visibility: "private",
}
resp, err := matrixClient.CreateRoom(&req)
if err != nil {
log.Printf("Failed to create alert room - %v", err)
return
}
str := resp.RoomID.String()
roomConfig.AlertChannel = &str
config.SaveRoomConfig(roomConfig)
}
matrixClient.SendNotice(id.RoomID(*roomConfig.AlertChannel), message)
}*/

View file

@ -42,7 +42,7 @@ func SetRoomConfigActive(id string, active bool) {
roomConfigWg.Add(1)
roomConfig := GetRoomConfig(id)
roomConfig.Active = true
roomConfig.Active = active
err := SaveRoomConfig(&roomConfig)
if err != nil {
@ -67,9 +67,9 @@ func GetRoomConfig(id string) RoomConfig {
// RoomConfigInitialUpdate updates all RoomConfig entries to set activity and create blank configs
func RoomConfigInitialUpdate(ids []id.RoomID) {
db := db.DbClient.Database(viper.GetString("bot.mongo.database"))
database := db.DbClient.Database(viper.GetString("bot.mongo.database"))
cursor, err := db.Collection("rooms").Find(context.TODO(), bson.D{}, nil)
cursor, err := database.Collection("rooms").Find(context.TODO(), bson.D{}, nil)
if err != nil {
log.Panicf("Error querying room configs: %v", err)
}
@ -105,7 +105,8 @@ func AddRoomConfig(id string) RoomConfig {
roomConfigWg.Wait()
roomConfigWg.Add(1)
config := RoomConfig{ID: primitive.NewObjectID(), Active: true, RoomID: id}
config := GetDefaultRoomConfig()
config.RoomID = id
err := SaveRoomConfig(&config)
if err != nil {
@ -119,26 +120,26 @@ func AddRoomConfig(id string) RoomConfig {
}
func SaveRoomConfig(roomConfig *RoomConfig) error {
db := db.DbClient.Database(viper.GetString("bot.mongo.database"))
database := db.DbClient.Database(viper.GetString("bot.mongo.database"))
opts := options.Replace().SetUpsert(true)
filter := bson.D{{"room_id", roomConfig.RoomID}}
_, err := db.Collection("rooms").ReplaceOne(context.TODO(), filter, roomConfig, opts)
_, err := database.Collection(viper.GetString("bot.mongo.collection.rooms")).ReplaceOne(context.TODO(), filter, roomConfig, opts)
return err
}
func GetRoomConfigByRoomID(id string) (*RoomConfig, error) {
db := db.DbClient.Database(viper.GetString("bot.mongo.database"))
database := db.DbClient.Database(viper.GetString("bot.mongo.database"))
res := db.Collection("rooms").FindOne(context.TODO(), bson.D{{"room_id", id}})
res := database.Collection(viper.GetString("bot.mongo.collection.rooms")).FindOne(context.TODO(), bson.D{{"room_id", id}})
if res.Err() != nil {
return nil, res.Err()
}
object := RoomConfig{}
object := GetDefaultRoomConfig()
err := res.Decode(&object)
if err != nil {
@ -147,3 +148,18 @@ func GetRoomConfigByRoomID(id string) (*RoomConfig, error) {
return &object, nil
}
func GetDefaultRoomConfig() RoomConfig {
return RoomConfig{
ID: primitive.NewObjectID(),
Active: true,
RoomID: "",
Debug: false,
AdminPowerLevel: 100,
HashChecker: HashCheckerConfig{
NoticeToChat: true,
NotificationPowerLevel: 50,
HashCheckMode: 1,
},
}
}

View file

@ -35,14 +35,28 @@ type RoomConfig struct {
// Debug specifies if the bot shall run in dry run mode
Debug bool `yaml:"debug" bson:"debug"`
AlertChannel *string `bson:"alert_channel"`
AdminPowerLevel int `bson:"admin_power_level"`
HashChecker HashCheckerConfig `bson:"hash_checker"`
}
type HashCheckerConfig struct {
NoticeToChat bool `bson:"chat_notice"`
NotificationPowerLevel int `yaml:"notification_level" bson:"notification_level"`
/*
HashCheckMode specifies the mode the bot should operate under in this room
HashCheck-Modes:
0. Silent Mode (Don't do anything)
1. Notify Mode (Message Room Admins & Mods)
2. Mute Mode (Remove message, notify admin & mute user)
3. Ban Mode (Remove message, notify admin & ban user)
0. Notice Mode (Post notice)
1. Delete Mode (Remove message, post notice)
2. Mute Mode (Remove message, post notice & mute user)
3. Ban Mode (Remove message, post notice & ban user)
*/
HashCheckMode uint8 `yaml:"mode" bson:"hash_check_mode"`
SubscribedLists []*primitive.ObjectID `bson:"subscribed_lists" json:"subscribed_lists"`
}

View file

@ -5,6 +5,7 @@ import (
"github.com/Unkn0wnCat/matrix-veles/internal/db/model"
"github.com/spf13/viper"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/mongo"
"go.mongodb.org/mongo-driver/mongo/options"
"log"
@ -37,15 +38,15 @@ func SaveEntry(entry *model.DBEntry) error {
filter := bson.D{{"_id", entry.ID}}
_, err := db.Collection("entries").ReplaceOne(context.TODO(), filter, entry, opts)
_, err := db.Collection(viper.GetString("bot.mongo.collection.entries")).ReplaceOne(context.TODO(), filter, entry, opts)
return err
}
func GetEntryByHash(hash string) (*model.DBEntry, error) {
func GetEntryByID(id primitive.ObjectID) (*model.DBEntry, error) {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
res := db.Collection("entries").FindOne(context.TODO(), bson.D{{"hash_value", hash}})
res := db.Collection(viper.GetString("bot.mongo.collection.entries")).FindOne(context.TODO(), bson.D{{"_id", id}})
if res.Err() != nil {
return nil, res.Err()
}
@ -59,3 +60,51 @@ func GetEntryByHash(hash string) (*model.DBEntry, error) {
return &object, nil
}
func GetEntryByHash(hash string) (*model.DBEntry, error) {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
res := db.Collection(viper.GetString("bot.mongo.collection.entries")).FindOne(context.TODO(), bson.D{{"hash_value", hash}})
if res.Err() != nil {
return nil, res.Err()
}
object := model.DBEntry{}
err := res.Decode(&object)
if err != nil {
return nil, err
}
return &object, nil
}
func SaveList(list *model.DBHashList) error {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
opts := options.Replace().SetUpsert(true)
filter := bson.D{{"_id", list.ID}}
_, err := db.Collection(viper.GetString("bot.mongo.collection.lists")).ReplaceOne(context.TODO(), filter, list, opts)
return err
}
func GetListByID(id primitive.ObjectID) (*model.DBHashList, error) {
db := DbClient.Database(viper.GetString("bot.mongo.database"))
res := db.Collection(viper.GetString("bot.mongo.collection.lists")).FindOne(context.TODO(), bson.D{{"_id", id}})
if res.Err() != nil {
return nil, res.Err()
}
object := model.DBHashList{}
err := res.Decode(&object)
if err != nil {
return nil, err
}
return &object, nil
}

View file

@ -0,0 +1,8 @@
package model
import "go.mongodb.org/mongo-driver/bson/primitive"
type DBComment struct {
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"`
Content string `bson:"content" json:"content"`
}

View file

@ -6,16 +6,12 @@ import (
)
type DBEntry struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Tags []string `bson:"tags" json:"tags"`
HashValue string `bson:"hash_value" json:"hash"`
FileURL string `bson:"file_url" json:"file_url"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"`
Comments []*DBEntryComment `bson:"comments" json:"comments"`
}
type DBEntryComment struct {
CommentedBy *primitive.ObjectID `bson:"commented_by" json:"commented_by"`
Content string `bson:"content" json:"content"`
ID primitive.ObjectID `bson:"_id" json:"id"`
Tags []string `bson:"tags" json:"tags"`
PartOf []*primitive.ObjectID `bson:"part_of" json:"part_of"`
HashValue string `bson:"hash_value" json:"hash"`
FileURL string `bson:"file_url" json:"file_url"`
Timestamp time.Time `bson:"timestamp" json:"timestamp"`
AddedBy *primitive.ObjectID `bson:"added_by" json:"added_by"`
Comments []*DBComment `bson:"comments" json:"comments"`
}

10
internal/db/model/list.go Normal file
View file

@ -0,0 +1,10 @@
package model
import "go.mongodb.org/mongo-driver/bson/primitive"
type DBHashList struct {
ID primitive.ObjectID `bson:"_id" json:"id"`
Tags []string `bson:"tags" json:"tags"`
Comments []*DBComment `bson:"comments" json:"comments"`
Maintainers []*primitive.ObjectID `bson:"maintainers" json:"maintainers"`
}

38
internal/db/model/user.go Normal file
View file

@ -0,0 +1,38 @@
package model
import (
"encoding/base64"
"golang.org/x/crypto/bcrypt"
)
type DBUser struct {
Username string `bson:"username" json:"username"`
HashedPassword string `bson:"password" json:"password"`
Password *string `bson:"-" json:"-"`
}
func (usr *DBUser) HashPassword() error {
if usr.Password == nil {
return nil
}
hash, err := bcrypt.GenerateFromPassword([]byte(*usr.Password), 14)
if err != nil {
return err
}
usr.HashedPassword = base64.StdEncoding.EncodeToString(hash)
return nil
}
func (usr *DBUser) CheckPassword(password string) error {
hash, err := base64.StdEncoding.DecodeString(usr.HashedPassword)
if err != nil {
return err
}
err = bcrypt.CompareHashAndPassword(hash, []byte(password))
return err
}