Experimental Discord bot written in Python
Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.

rocketbot.py 9.9KB

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