浏览代码

Bot doing basic raid detection

tags/1.0
Ian Albert 4 年前
父节点
当前提交
7dcec0e7a8
共有 4 个文件被更改,包括 305 次插入9 次删除
  1. 6
    0
      .gitignore
  2. 11
    0
      config.py.sample
  3. 二进制
      rocketbot.db.sample
  4. 288
    9
      rocketbot.py

+ 6
- 0
.gitignore 查看文件

@@ -114,3 +114,9 @@ dmypy.json
114 114
 # Pyre type checker
115 115
 .pyre/
116 116
 
117
+# Mac!!
118
+.DS_Store
119
+
120
+# Contains secrets so exclude
121
+config.py
122
+rocketbot.db

+ 11
- 0
config.py.sample 查看文件

@@ -0,0 +1,11 @@
1
+# Copy this file to config.py and fill in necessary values
2
+config = {
3
+	'clientToken': 'token',
4
+	'dbHost': 'localhost',
5
+	'dbUser': 'username',
6
+	'dbPassword': 'password',
7
+	'dbDatabase': 'databasename',
8
+	'joinWarningCount': 5,
9
+	'joinWarningSeconds': 5,
10
+	'commandPrefix': '$',
11
+}

二进制
rocketbot.db.sample 查看文件


+ 288
- 9
rocketbot.py 查看文件

@@ -1,17 +1,296 @@
1
+from config import config
2
+from datetime import datetime, timedelta
1 3
 import discord
4
+from discord.ext import commands
5
+from discord.ext.commands.context import Context
6
+import sqlite3
7
+import sys
2 8
 
3
-client = discord.Client()
9
+# -- Database ---------------------------------------------------------------
4 10
 
5
-@client.event
11
+def runSQLBatch(batchFunction):
12
+    dbConnection = sqlite3.connect('rocketbot.db')
13
+    dbCursor = dbConnection.cursor()
14
+    batchFunction(dbConnection, dbCursor)
15
+    dbConnection.commit()
16
+    dbConnection.close()
17
+
18
+def loadGuildSettings():
19
+    def load(con, cur):
20
+        global config
21
+        for row in cur.execute("""SELECT * FROM guilds"""):
22
+            id = row[0]
23
+            g = getOrCreateGuildContext(id, save=False)
24
+            g.warningChannelId = row[1]
25
+            g.warningMentionId = row[2]
26
+            g.joinWarningCount = row[3] or config['joinWarningCount']
27
+            g.joinWarningSeconds = row[4] or config['joinWarningSeconds']
28
+            print('Guild {0} channel id is {1}'.format(id, g.warningChannelId))
29
+    runSQLBatch(load)
30
+
31
+def createTables():
32
+    def makeTables(con, cur):
33
+        cur.execute("""CREATE TABLE guilds (
34
+            id INTEGER,
35
+            warningChannelId INTEGER,
36
+            warningUserId INTEGER,
37
+            joinWarningCount INTEGER,
38
+            joinWarningSeconds INTEGER,
39
+            PRIMARY KEY(id ASC))""")
40
+    runSQLBatch(makeTables)
41
+
42
+def saveGuildContext(gc):
43
+    def save(con, cur):
44
+        print('Saving guild context with id {0}'.format(gc.id))
45
+        cur.execute('SELECT id FROM guilds WHERE id=?', (gc.id,))
46
+        channelId = gc.warningChannel.id if gc.warningChannel != None else gc.warningChannelId
47
+        userId = gc.warningMention.id if gc.warningMention != None else gc.warningMentionId
48
+        exists = cur.fetchone() != None
49
+        print('Record exists' if exists else 'Record does not exist')
50
+        if exists:
51
+            cur.execute("""UPDATE guilds SET
52
+                warningChannelId=?,
53
+                warningUserId=?,
54
+                joinWarningCount=?,
55
+                joinWarningSeconds=?
56
+                WHERE id=?""", (
57
+                    channelId,
58
+                    userId,
59
+                    gc.joinWarningCount,
60
+                    gc.joinWarningSeconds,
61
+                    gc.id
62
+                ))
63
+        else:
64
+            cur.execute("""INSERT INTO guilds (
65
+                id,
66
+                warningChannelId,
67
+                warningUserId,
68
+                joinWarningCount,
69
+                joinWarningSeconds)
70
+                VALUES (?, ?, ?, ?, ?)""", (
71
+                    gc.id,
72
+                    channelId,
73
+                    userId,
74
+                    gc.joinWarningCount,
75
+                    gc.joinWarningSeconds
76
+                ))
77
+    runSQLBatch(save)
78
+
79
+# -- Classes ----------------------------------------------------------------
80
+
81
+class JoinRecord:
82
+    def __init__(self, member):
83
+        self.member = member
84
+        self.joinTime = member.joined_at or datetime.now()
85
+        self.isKicked = False
86
+        self.isBanned = False
87
+    def ageSeconds(self, referenceTime=datetime.now()):
88
+        a = referenceTime - self.joinTime
89
+        return a.total_seconds()
90
+
91
+class GuildContext:
92
+    def __init__(self, id):
93
+        global config
94
+        self.id = id
95
+        self.guild = None
96
+        self.warningChannelId = None
97
+        self.warningChannel = None
98
+        self.warningMentionId = None
99
+        self.warningMention = None
100
+        self.joins = []
101
+        self.joinWarningCount = config['joinWarningCount']
102
+        self.joinWarningSeconds = config['joinWarningSeconds']
103
+        self.isJoinRaidInProgress = False
104
+        self.lastWarningMessage = None
105
+
106
+    async def handleJoin(self, member):
107
+        print('{0.guild.name}: {0.name} joined'.format(member))
108
+        self.joins.append(JoinRecord(member))
109
+        await self.__checkJoins()
110
+
111
+    async def handleSetWarningChannel(self, context):
112
+        print('{0.guild.name}: Warning channel set to {0.channel.name}'.format(context))
113
+        self.warningChannel = context.channel
114
+        self.warningChannelId = context.channel.id
115
+        saveGuildContext(self)
116
+
117
+    async def handleSetWarningMention(self, context):
118
+        if len(context.args) < 1:
119
+            return
120
+        roleArg = context.args[0]
121
+        print('{0.guild.name}: {1}'.format(context, roleArg))
122
+
123
+    async def handleSetRaidWarningRate(self, context, count, seconds):
124
+        self.joinWarningCount = count
125
+        self.joinWarningSeconds = seconds
126
+        print('{0.name}: Set rate as {1} joins per {2} seconds'.format(self.guild, self.joinWarningCount, self.joinWarningSeconds))
127
+        saveGuildContext(self)
128
+
129
+    async def __checkJoins(self):
130
+        now = datetime.now()
131
+        recentJoinCount = self.__countRecentJoins(now)
132
+        print('{0} join(s) in the past {1} seconds'.format(recentJoinCount, self.joinWarningSeconds))
133
+        if recentJoinCount >= self.joinWarningCount:
134
+            # In join raid
135
+            if not self.isJoinRaidInProgress:
136
+                # Raid just started
137
+                self.isJoinRaidInProgress = True
138
+                await self.__onJoinRaidBegin()
139
+            else:
140
+                # Continuing existing join raid
141
+                await self.__onJoinRaidUpdated()
142
+        else:
143
+            # No join raid
144
+            if self.isJoinRaidInProgress:
145
+                # Join raid just ended
146
+                self.isJoinRaidInProgress = False
147
+                await self.__onJoinRaidEnd()
148
+            self.__cullOldJoins(now)
149
+
150
+    def __countRecentJoins(self, now=datetime.now()):
151
+        recentJoinCount = 0
152
+        for joinRecord in self.joins:
153
+            if joinRecord.ageSeconds(now) <= self.joinWarningSeconds:
154
+                recentJoinCount += 1
155
+        return recentJoinCount
156
+
157
+    def __cullOldJoins(self, now=datetime.now()):
158
+        i = 0
159
+        while i < len(self.joins):
160
+            if self.joins[i].ageSeconds(now) > self.joinWarningSeconds:
161
+                self.joins.pop(i)
162
+            else:
163
+                i += 1
164
+
165
+    async def __onJoinRaidBegin(self):
166
+        print('A join raid has begun!')
167
+        if self.warningChannel == None:
168
+            print('No warning channel set')
169
+            return
170
+        # TODO: Mention mod role
171
+        message = 'A join raid has been detected! It includes these users:'
172
+        for join in self.joins:
173
+            message += '\n• ' + join.member.mention
174
+        self.lastWarningMessage = await self.warningChannel.send(message)
175
+
176
+    async def __onJoinRaidUpdated(self):
177
+        print('Join raid still occurring')
178
+        if self.lastWarningMessage == None:
179
+            return
180
+        message = 'A join raid has been detected! It includes these users:'
181
+        for join in self.joins:
182
+            message += '\n• ' + join.member.mention
183
+        await self.lastWarningMessage.edit(content=message)
184
+
185
+    async def __onJoinRaidEnd(self):
186
+        print('Join raid has ended')
187
+        pass
188
+
189
+guildIdToGuildContext = {}
190
+
191
+def getOrCreateGuildContext(obj, save=True):
192
+    gid = None
193
+    if obj == None:
194
+        return None
195
+    if isinstance(obj, int):
196
+        gid = obj
197
+    elif isinstance(obj, GuildContext):
198
+        return obj
199
+    elif isinstance(obj, discord.Guild):
200
+        gid = obj.id
201
+    elif isinstance(obj, (discord.Message, discord.Member, discord.TextChannel, discord.ext.commands.context.Context)):
202
+        gid = obj.guild.id
203
+    if gid == None:
204
+        print('Unhandled datatype', type(obj))
205
+        return None
206
+    lookedUp = guildIdToGuildContext.get(gid)
207
+    if lookedUp != None:
208
+        return lookedUp
209
+    g = GuildContext(gid)
210
+    guildIdToGuildContext[gid] = g
211
+    if save:
212
+        saveGuildContext(g)
213
+    return g
214
+
215
+loadGuildSettings()
216
+
217
+intents = discord.Intents.default()
218
+intents.members = True # To get join/leave events
219
+bot = commands.Bot(command_prefix='$', intents=intents)
220
+
221
+# -- Bot commands -----------------------------------------------------------
222
+
223
+@commands.command()
224
+async def hello(ctx):
225
+    message = ctx.message
226
+    print('Got message from {0.author.name} in {0.channel.id}'.format(message))
227
+    await message.channel.send('Hello, {0.author.mention}!'.format(message))
228
+    print('Replied "Hello!"')
229
+bot.add_command(hello)
230
+
231
+@commands.command()
232
+@commands.has_permissions(manage_messages=True)
233
+async def setraidwarningrate(ctx, count: int, seconds: int):
234
+    g = getOrCreateGuildContext(ctx)
235
+    if g == None:
236
+        return
237
+    await g.handleSetRaidWarningRate(ctx, count, seconds)
238
+bot.add_command(setraidwarningrate)
239
+
240
+@commands.command()
241
+@commands.has_permissions(manage_messages=True)
242
+async def setwarningchannel(ctx):
243
+    g = getOrCreateGuildContext(ctx)
244
+    if g == None:
245
+        return
246
+    await g.handleSetWarningChannel(ctx)
247
+bot.add_command(setwarningchannel)
248
+
249
+@commands.command()
250
+@commands.has_permissions(manage_messages=True)
251
+async def setwarningrole(ctx):
252
+    g = getOrCreateGuildContext(ctx)
253
+    if g == None:
254
+        return
255
+    await g.handleSetWarningMention(ctx)
256
+bot.add_command(setwarningrole)
257
+
258
+# -- Bot events -------------------------------------------------------------
259
+
260
+@bot.listen()
261
+async def on_connect():
262
+    global bot
263
+    global guildIdToGuildContext
264
+    for guild in bot.guilds:
265
+        g = guildIdToGuildContext.get(guild.id)
266
+        if g == None:
267
+            print('No record for', guild.id)
268
+            continue
269
+        g.guild = guild
270
+        if g.warningChannelId != None:
271
+            g.warningChannel = guild.get_channel(g.warningChannelId)
272
+            print('Recovered warning channel', g.warningChannel)
273
+        if g.warningMentionId != None:
274
+            g.warningMention = guild.get_role(g.warningMentionId)
275
+            print('Recovered warning mention', g.warningMention)
276
+
277
+@bot.listen()
6 278
 async def on_ready():
7
-    print('We have logged in as {0.user}'.format(client))
279
+    pass
8 280
 
9
-@client.event
10
-async def on_message(message):
11
-    if message.author == client.user:
281
+@bot.listen()
282
+async def on_member_join(member):
283
+    print('User {0.name} joined'.format(member))
284
+    g = getOrCreateGuildContext(member)
285
+    if g == None:
286
+        print('No GuildContext for guild {0.guild.name}'.format(member))
12 287
         return
288
+    await g.handleJoin(member)
13 289
 
14
-    if message.content.startswith('$hello'):
15
-        await message.channel.send('Hello!')
290
+@bot.listen()
291
+async def on_member_remove(member):
292
+    print('User {0.name} left'.format(member))
16 293
 
17
-client.run('bot token')
294
+print('Starting bot')
295
+bot.run(config['clientToken'])
296
+print('Bot done')

正在加载...
取消
保存