Skip to content

Commit

Permalink
Added handy buttons for auto ban messages (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
Szer committed Jul 22, 2024
1 parent 3cd64b3 commit 39f4322
Show file tree
Hide file tree
Showing 16 changed files with 593 additions and 254 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ ML_SPAM_DELETION_ENABLED=false
ML_TRAIN_BEFORE_DATE=2021-01-01
ML_TRAINING_SET_FRACTION=0.2
ML_SPAM_THRESHOLD=0.5
ML_WARNING_THRESHOLD=0.0
ML_STOP_WORDS_IN_CHATS={"-123":["word1","word2"]}
54 changes: 54 additions & 0 deletions src/VahterBanBot.Tests/BanTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,58 @@ type BanTests(fixture: VahterTestContainers) =
Assert.False msgNotBanned
}

[<Fact>]
let ``Vahter can unban user`` () = task {
// record a message
let msgUpdate = Tg.quickMsg(chat = fixture.ChatsToMonitor[0])
let! _ = fixture.SendMessage msgUpdate

// send the ban message
let! banResp =
Tg.replyMsg(msgUpdate.Message, "/ban", fixture.AdminUsers[0])
|> fixture.SendMessage
Assert.Equal(HttpStatusCode.OK, banResp.StatusCode)

// assert that the message got banned
let! msgBanned = fixture.MessageBanned msgUpdate.Message
Assert.True msgBanned

// send the unban message from another vahter
let! banResp =
Tg.quickMsg($"/unban {msgUpdate.Message.From.Id}", chat = fixture.ChatsToMonitor[0], from = fixture.AdminUsers[1])
|> fixture.SendMessage
Assert.Equal(HttpStatusCode.OK, banResp.StatusCode)

// assert that the message no longer banned
let! msgBanned = fixture.MessageBanned msgUpdate.Message
Assert.False msgBanned
}

[<Fact>]
let ``Only Vahter can unban user`` () = task {
// record a message
let msgUpdate = Tg.quickMsg(chat = fixture.ChatsToMonitor[0])
let! _ = fixture.SendMessage msgUpdate

// send the ban message
let! banResp =
Tg.replyMsg(msgUpdate.Message, "/ban", fixture.AdminUsers[0])
|> fixture.SendMessage
Assert.Equal(HttpStatusCode.OK, banResp.StatusCode)

// assert that the message got banned
let! msgBanned = fixture.MessageBanned msgUpdate.Message
Assert.True msgBanned

// send the unban message from a random user
let! banResp =
Tg.quickMsg($"/unban {msgUpdate.Message.From.Id}", chat = fixture.ChatsToMonitor[0])
|> fixture.SendMessage
Assert.Equal(HttpStatusCode.OK, banResp.StatusCode)

// assert that the message still banned
let! msgBanned = fixture.MessageBanned msgUpdate.Message
Assert.True msgBanned
}

interface IAssemblyFixture<VahterTestContainers>
28 changes: 28 additions & 0 deletions src/VahterBanBot.Tests/ContainerTestBase.fs
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,34 @@ type VahterTestContainers() =
let! count = conn.QuerySingleAsync<int>(sql, {| chatId = msg.Chat.Id; messageId = msg.MessageId |})
return count > 0
}

member _.GetCallbackId(msg: Message) (caseName: string) = task {
use conn = new NpgsqlConnection(publicConnectionString)
//language=postgresql
let sql = """
SELECT id
FROM callback
WHERE data ->> 'Case' = @caseName
AND data -> 'Fields' -> 0 -> 'message' ->> 'message_id' = @messageId::TEXT
AND data -> 'Fields' -> 0 -> 'message' -> 'chat' ->> 'id' = @chatId::TEXT
"""
return! conn.QuerySingleAsync<Guid>(
sql, {| chatId = msg.Chat.Id
messageId = msg.MessageId
caseName = caseName |})
}

member _.IsMessageFalsePositive(msg: Message) = task {
use conn = new NpgsqlConnection(publicConnectionString)
//language=postgresql
let sql = """
SELECT COUNT(*) FROM false_positive_messages
WHERE chat_id = @chatId
AND message_id = @messageId
"""
let! result = conn.QuerySingleAsync<int>(sql, {| chatId = msg.Chat.Id; messageId = msg.MessageId |})
return result > 0
}

// workaround to wait for ML to be ready
type MlAwaitFixture() =
Expand Down
62 changes: 60 additions & 2 deletions src/VahterBanBot.Tests/MLBanTests.fs
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
module VahterBanBot.Tests.MLBanTests

open System.Net
open System.Threading.Tasks
open VahterBanBot.Tests.ContainerTestBase
open VahterBanBot.Tests.TgMessageUtils
open VahterBanBot.Types
open Xunit
open Xunit.Extensions.AssemblyFixture

Expand All @@ -21,6 +20,19 @@ type MLBanTests(fixture: VahterTestContainers, _unused: MlAwaitFixture) =
Assert.True msgBanned
}

[<Fact>]
let ``Message is NOT autobanned if it looks like a spam BUT vahter sent it`` () = task {
// record a message, where 2 is in a training set as spam word
// ChatsToMonitor[0] doesn't have stopwords
// but it was sent by vahter
let msgUpdate = Tg.quickMsg(chat = fixture.ChatsToMonitor[0], text = "2", from = fixture.AdminUsers[0])
let! _ = fixture.SendMessage msgUpdate

// assert that the message got auto banned
let! msgBanned = fixture.MessageIsAutoBanned msgUpdate.Message
Assert.False msgBanned
}

[<Fact>]
let ``Message is NOT autobanned if it has a stopword in specific chat`` () = task {
// record a message, where 2 is in a training set as spam word
Expand Down Expand Up @@ -54,6 +66,52 @@ type MLBanTests(fixture: VahterTestContainers, _unused: MlAwaitFixture) =
let! msgBanned = fixture.MessageIsAutoBanned msgUpdate.Message
Assert.True msgBanned
}

[<Fact>]
let ``If message got auto-deleted we can mark it as false-positive with a button click`` () = task {
// record a message, where 2 is in a training set as spam word
// ChatsToMonitor[0] doesn't have stopwords
let msgUpdate = Tg.quickMsg(chat = fixture.ChatsToMonitor[0], text = "2")
let! _ = fixture.SendMessage msgUpdate

// assert that the message got auto banned
let! msgBanned = fixture.MessageIsAutoBanned msgUpdate.Message
Assert.True msgBanned
// assert it is not false-positive
let! isFalsePositive = fixture.IsMessageFalsePositive msgUpdate.Message
Assert.False isFalsePositive

// send a callback to mark it as false-positive
let! callbackId = fixture.GetCallbackId msgUpdate.Message "NotASpam"
let msgCallback = Tg.callback(string callbackId, from = fixture.AdminUsers[0])
let! _ = fixture.SendMessage msgCallback

// assert it is false-positive
let! isFalsePositive = fixture.IsMessageFalsePositive msgUpdate.Message
Assert.True isFalsePositive
}

[<Fact>]
let ``Only vahter can press THE BUTTON(s)`` () = task {
// record a message, where 2 is in a training set as spam word
// ChatsToMonitor[0] doesn't have stopwords
let msgUpdate = Tg.quickMsg(chat = fixture.ChatsToMonitor[0], text = "2")
let! _ = fixture.SendMessage msgUpdate

// assert that the message got auto banned
let! msgBanned = fixture.MessageIsAutoBanned msgUpdate.Message
Assert.True msgBanned

// send a callback to mark it as false-positive
// we are sending this as a usual user
let! callbackId = fixture.GetCallbackId msgUpdate.Message (nameof CallbackMessage.NotASpam)
let msgCallback = Tg.callback(string callbackId)
let! _ = fixture.SendMessage msgCallback

// assert it is still NOT a false-positive
let! isFalsePositive = fixture.IsMessageFalsePositive msgUpdate.Message
Assert.False isFalsePositive
}

interface IAssemblyFixture<VahterTestContainers>
interface IClassFixture<MlAwaitFixture>
15 changes: 14 additions & 1 deletion src/VahterBanBot.Tests/TgMessageUtils.fs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,20 @@ type Tg() =
Id = (id |> Option.defaultValue (nextInt64())),
Username = (username |> Option.defaultValue null)
)
static member quickMsg (?text: string, ?chat: Chat, ?from: User, ?date: DateTime) =

static member callback(data: string, ?from: User) =
Update(
Id = next(),
Message = null,
CallbackQuery = CallbackQuery(
Id = Guid.NewGuid().ToString(),
Data = data,
From = (from |> Option.defaultValue (Tg.user())),
ChatInstance = Guid.NewGuid().ToString()
)
)

static member quickMsg (?text: string, ?chat: Chat, ?from: User, ?date: DateTime, ?callback: CallbackQuery) =
Update(
Id = next(),
Message =
Expand Down
66 changes: 33 additions & 33 deletions src/VahterBanBot.Tests/test_seed.sql
Original file line number Diff line number Diff line change
Expand Up @@ -130,31 +130,31 @@ VALUES (-666, 10001, 1001, '2021-01-01 00:00:00', 'a', '{}'), -- false positive
(-42, 10099, 1006, '2021-01-01 00:00:09', '2', '{}'),
-- this is not spam
(-666, 10100, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10101, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10102, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10103, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10104, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10105, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10106, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10107, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10108, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10109, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10110, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10111, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10112, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10113, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10114, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10115, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10116, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10117, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10118, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10119, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10120, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10121, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10122, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10123, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10124, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10125, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10101, 1001, '2021-01-01 00:00:01', 'b', '{}'),
(-666, 10102, 1001, '2021-01-01 00:00:01', 'c', '{}'),
(-666, 10103, 1001, '2021-01-01 00:00:01', 'd', '{}'),
(-666, 10104, 1001, '2021-01-01 00:00:01', 'e', '{}'),
(-666, 10105, 1001, '2021-01-01 00:00:01', 'f', '{}'),
(-666, 10106, 1001, '2021-01-01 00:00:01', 'g', '{}'),
(-666, 10107, 1001, '2021-01-01 00:00:01', 'h', '{}'),
(-666, 10108, 1001, '2021-01-01 00:00:01', 'i', '{}'),
(-666, 10109, 1001, '2021-01-01 00:00:01', 'j', '{}'),
(-666, 10110, 1001, '2021-01-01 00:00:01', 'k', '{}'),
(-666, 10111, 1001, '2021-01-01 00:00:01', 'l', '{}'),
(-666, 10112, 1001, '2021-01-01 00:00:01', 'm', '{}'),
(-666, 10113, 1001, '2021-01-01 00:00:01', 'n', '{}'),
(-666, 10114, 1001, '2021-01-01 00:00:01', 'o', '{}'),
(-666, 10115, 1001, '2021-01-01 00:00:01', 'p', '{}'),
(-666, 10116, 1001, '2021-01-01 00:00:01', 'q', '{}'),
(-666, 10117, 1001, '2021-01-01 00:00:01', 'r', '{}'),
(-666, 10118, 1001, '2021-01-01 00:00:01', 's', '{}'),
(-666, 10119, 1001, '2021-01-01 00:00:01', 't', '{}'),
(-666, 10120, 1001, '2021-01-01 00:00:01', 'u', '{}'),
(-666, 10121, 1001, '2021-01-01 00:00:01', 'v', '{}'),
(-666, 10122, 1001, '2021-01-01 00:00:01', 'w', '{}'),
(-666, 10123, 1001, '2021-01-01 00:00:01', 'x', '{}'),
(-666, 10124, 1001, '2021-01-01 00:00:01', 'y', '{}'),
(-666, 10125, 1001, '2021-01-01 00:00:01', 'z', '{}'),
(-666, 10126, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10127, 1001, '2021-01-01 00:00:01', 'a', '{}'),
(-666, 10128, 1001, '2021-01-01 00:00:01', 'a', '{}'),
Expand Down Expand Up @@ -232,12 +232,12 @@ VALUES (-666, 10001, 1001, '2021-01-01 00:00:00', 'a', '{}'), -- false positive

-- to enforce false-negative appearance
(-666, 10200, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10201, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10202, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10203, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10204, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10205, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10206, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10201, 1001, '2021-01-01 00:00:01', '4', '{}'),
(-666, 10202, 1001, '2021-01-01 00:00:01', '5', '{}'),
(-666, 10203, 1001, '2021-01-01 00:00:01', '6', '{}'),
(-666, 10204, 1001, '2021-01-01 00:00:01', '7', '{}'),
(-666, 10205, 1001, '2021-01-01 00:00:01', '8', '{}'),
(-666, 10206, 1001, '2021-01-01 00:00:01', '9', '{}'),
(-666, 10207, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10208, 1001, '2021-01-01 00:00:01', '3', '{}'),
(-666, 10209, 1001, '2021-01-01 00:00:01', '3', '{}'),
Expand Down Expand Up @@ -341,8 +341,8 @@ VALUES (100001, 10001, 'a', 1001, '2021-01-01 00:00:00', -666, 'pro.hell', 34),
INSERT INTO public.false_positive_users(user_id)
VALUES (1001);

INSERT INTO public.false_positive_messages(id)
VALUES (100002);
INSERT INTO public.false_positive_messages(chat_id, message_id)
VALUES (-666, 10008);

INSERT INTO public.false_negative_messages(chat_id, message_id)
VALUES (-42, 10008),
Expand Down
Loading

0 comments on commit 39f4322

Please sign in to comment.