Skip to content

Commit 079ad8f

Browse files
committed
feat: Refactor moderation into strategies and add tests
Refactor the moderation into strategies to be customized. Add a bunch of tests.
1 parent 42e4729 commit 079ad8f

File tree

11 files changed

+191
-21
lines changed

11 files changed

+191
-21
lines changed

Gemfile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,14 @@ source "https://rubygems.org"
44

55
ruby "2.7.7"
66

7+
# discord api for ruby
78
gem "discordrb"
89

10+
# load environment variables from .env file
11+
gem 'dotenv'
12+
13+
# redis is used as backend
914
gem "redis"
1015

16+
# testing
1117
gem "rspec", group: :test

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ GEM
1313
rest-client (>= 2.1.0.rc1)
1414
domain_name (0.5.20190701)
1515
unf (>= 0.0.5, < 1.0.0)
16+
dotenv (2.8.1)
1617
event_emitter (0.2.6)
1718
ffi (1.15.5)
1819
http-accept (1.7.0)
@@ -59,6 +60,7 @@ PLATFORMS
5960

6061
DEPENDENCIES
6162
discordrb
63+
dotenv
6264
redis
6365
rspec
6466

bot.rb

Lines changed: 17 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
require "./lib/discord"
66
require "./lib/discord/permission"
77
require "./lib/backend"
8+
require "./lib/moderation_strategy"
89

910
# setup logging
1011
$logger = Logger.new(STDOUT)
@@ -24,6 +25,11 @@
2425

2526
initialize_backend()
2627

28+
strategies = []
29+
strategies << WatchListStrategy.new(self)
30+
strategies << RemoveMessageStrategy.new(self)
31+
# strategies << RewriteMessageStrategy.new
32+
2733
# bot commands
2834
bot.message do |event|
2935
$logger.info("Message from #{event.user.name} (#{event.user.id})")
@@ -55,20 +61,10 @@
5561
end
5662
end
5763
else
58-
# watched users loop
59-
if get_watch_list_users(event.server.id.to_i).include?(event.user.id.to_i)
60-
case analysed = sentiment_analysis(event.message.content)
61-
when /Positive/i
62-
$logger.info("Sentiment Analysis: Positive")
63-
when /Negative/i
64-
$logger.info("Sentiment Analysis: Negative")
65-
edited = moderation_rewrite(event.message.content)
66-
$logger.info(edited)
67-
reason = "Moderation (rewriting due to negative sentiment)"
68-
event.message.delete(reason)
69-
event.respond("~~#{event.message.content}~~" + "\n" + edited)
70-
else
71-
$logger.info("Sentiment Analysis: Neutral")
64+
# execute enabled strategies
65+
strategies.each do |strategy|
66+
if strategy.condition(event)
67+
strategy.execute(event)
7268
end
7369
end
7470
end
@@ -96,7 +92,10 @@
9692
end
9793
end
9894

99-
# This method call has to be put at the end of your script, it is what makes the bot actually connect to Discord. If you
100-
# leave it out (try it!) the script will simply stop and the bot will not appear online.
101-
at_exit { bot.stop }
102-
bot.run
95+
begin
96+
at_exit { bot.stop }
97+
bot.run
98+
rescue Interrupt
99+
$logger.info("Exiting...")
100+
exit
101+
end

environment.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
require "dotenv"
2+
Dotenv.load
3+
14
# Environment variables
25
OPENAI_API_KEY = ENV["OPENAI_API_KEY"]
36
DISCORD_BOT_TOKEN = ENV["DISCORD_BOT_TOKEN"]

lib/backend.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
module Backend
44
def initialize_backend()
55
@redis ||= Redis.new(url: REDIS_URL)
6+
raise "Redis connection failed" unless @redis.ping == "PONG"
67
end
78

89
def add_user_to_watch_list(server_id, user_id)

lib/moderation_strategy.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
class ModerationStrategy
2+
def initialize(bot)
3+
@bot = bot
4+
end
5+
6+
def condition(event)
7+
false
8+
end
9+
10+
def execute(event)
11+
nil
12+
end
13+
end
14+
15+
class RemoveMessageStrategy < ModerationStrategy
16+
def condition(event)
17+
case analysed = sentiment_analysis(event.message.content)
18+
when /Positive/i
19+
$logger.info("Sentiment Analysis: Positive")
20+
false
21+
when /Negative/i
22+
$logger.info("Sentiment Analysis: Negative")
23+
true
24+
else
25+
$logger.info("Sentiment Analysis: Neutral")
26+
false
27+
end
28+
end
29+
30+
def execute(event)
31+
reason = "Moderation (removing message)"
32+
event.message.delete(reason)
33+
end
34+
end
35+
36+
class WatchListStrategy < ModerationStrategy
37+
def condition(event)
38+
# watched users loop
39+
if @bot.get_watch_list_users(event.server.id.to_i).include?(event.user.id.to_i)
40+
case analysed = sentiment_analysis(event.message.content)
41+
when /Positive/i
42+
$logger.info("Sentiment Analysis: Positive")
43+
false
44+
when /Negative/i
45+
$logger.info("Sentiment Analysis: Negative")
46+
true
47+
else
48+
$logger.info("Sentiment Analysis: Neutral")
49+
false
50+
end
51+
end
52+
end
53+
54+
def execute(event)
55+
edited = moderation_rewrite(event.message.content)
56+
$logger.info(edited)
57+
reason = "Moderation (rewriting due to negative sentiment)"
58+
event.message.delete(reason)
59+
event.respond("~~<@#{event.user.id}>: #{event.message.content}~~" + "\n" + edited)
60+
end
61+
end

lib/open_ai.rb

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,19 @@ def query(url, params)
1111
request["Content-Type"] = "application/json"
1212
request["Authorization"] = "Bearer #{OPENAI_API_KEY}"
1313
request.body = params.to_json
14-
$logger.info(request.body)
14+
$logger.debug(request.body)
1515

1616
response = http.request(request)
17-
$logger.info(response.body)
17+
$logger.debug(response.body)
1818

1919
ret = JSON.parse(response.body)
20-
2120
if ret.include?("error")
2221
ret["error"]
2322
else
2423
ret["choices"][0]["text"].strip
2524
end
25+
rescue Net::ReadTimeout
26+
"Timeout"
2627
end
2728

2829
# query OpenAI completions API for sentiment analysis
@@ -38,6 +39,18 @@ def sentiment_analysis(text)
3839
})
3940
end
4041

42+
def qualify_toxicity(text)
43+
query("https://api.openai.com/v1/completions", {
44+
model: "text-davinci-003",
45+
prompt: "Is the statement : \"#{text}\" is toxic or non-toxic?",
46+
temperature: 0.7,
47+
max_tokens: 256,
48+
top_p: 1,
49+
frequency_penalty: 0,
50+
presence_penalty: 0,
51+
})
52+
end
53+
4154
# moderation request
4255
def moderation_rewrite(text)
4356
query("https://api.openai.com/v1/completions", {

spec/backend_spec.rb

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
require "backend"
2+
3+
describe Backend do
4+
include Backend
5+
6+
let(:server_id) { 123 }
7+
let(:user_id) { 456 }
8+
9+
before do
10+
@redis = Redis.new(url: REDIS_URL)
11+
@redis.flushall
12+
end
13+
14+
describe "#add_user_to_watch_list" do
15+
it "adds a user to the watch list" do
16+
add_user_to_watch_list(server_id, user_id)
17+
expect(@redis.smembers("server_#{server_id}_users")).to include(user_id.to_s)
18+
end
19+
end
20+
21+
describe "#remove_user_from_watch_list" do
22+
it "removes a user from the watch list" do
23+
add_user_to_watch_list(server_id, user_id)
24+
remove_user_from_watch_list(server_id, user_id)
25+
expect(@redis.smembers("server_#{server_id}_users")).not_to include(user_id.to_s)
26+
end
27+
end
28+
29+
describe "#get_watch_list_users" do
30+
it "returns the watch list users" do
31+
add_user_to_watch_list(server_id, user_id)
32+
expect(get_watch_list_users(server_id)).to include(user_id)
33+
end
34+
end
35+
36+
describe "#add_server" do
37+
it "adds a server" do
38+
add_server(server_id)
39+
expect(@redis.smembers("servers")).to include(server_id.to_s)
40+
end
41+
end
42+
43+
describe "#remove_server" do
44+
it "removes a server" do
45+
add_server(server_id)
46+
remove_server(server_id)
47+
expect(@redis.smembers("servers")).not_to include(server_id.to_s)
48+
end
49+
end
50+
51+
describe "#get_servers" do
52+
it "returns the servers" do
53+
add_server(server_id)
54+
expect(get_servers).to include(server_id)
55+
end
56+
end
57+
58+
after do
59+
@redis.flushall
60+
end
61+
end

spec/discord/permission_spec.rb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
require "discord"
2+
require "discord/permission"
3+
4+
describe Discord::Permission do
5+
describe "#ADMINISTRATOR" do
6+
it "returns the administrator permission" do
7+
expect(Discord::Permission::ADMINISTRATOR).to eq(8)
8+
end
9+
end
10+
end

spec/discord_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
require "discord"
2+
3+
describe Discord do
4+
end

0 commit comments

Comments
 (0)