-
Notifications
You must be signed in to change notification settings - Fork 0
/
BMBanNotifier.py
405 lines (346 loc) · 17.6 KB
/
BMBanNotifier.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
#!/usr/bin/env python3
"""
DiscordBotBattlemetricsNotifier
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
created by lukeg3 forked from alexemanuelol/Discord-BOT-Battlemetrics-Ban-Notifier
This program uses the discord.py python wrapper for the Discord API to create a Discord bot
that reads and responds to user commands in a specific channel or with specific user definitions.
The program queries the Battlemetrics API in response to user commands in discord as well as at
a regular interval defined by BM_POLLING_INTERVAL.
After each interval of the BM_POLLING_INTERVAL the Battlemetrics RCON API is queried and any new
bans made since the last query are made into embeds and posted in the designated discord channel.
See the README.md for how to configure the config.ini file prior to running this program.
"""
# import neccesary modules
import configparser
from http import client
from lib2to3.pgen2.token import NOTEQUAL
from pickle import NONE
import discord
import json
import requests
import os
import threading
from enum import Enum
# Read configuration file
config = configparser.ConfigParser()
config.read(os.path.abspath(__file__).replace(os.path.basename(__file__), "config.ini"))
"""Values pulled from config file loaded in"""
PREFIX = config["General"]["prefix"] #discord command prefix
DC_ADMINS = config["Discord"]["admins"].replace(" ", "").split(",") #discord admins list
DC_TOKEN = config["Discord"]["discordToken"] #discord Oauth token
DC_TEXT_CHANNEL_ID = int(config["Discord"]["discordTextChannelId"]) #discord channel identifier
DC_TEXT_CHANNEL_NAME = config["Discord"]["discordTextChannelName"].replace(" ", "").split(",")
BM_TOKEN = config["Battlemetrics"]["battlemetricsToken"] #battlemetric api token
BM_BANLIST_ID = config["Battlemetrics"]["banListId"] #battlemetrics ban list id
BM_POLLING_INTERVAL = int(config["Battlemetrics"]["pollingInterval"]) #how often the api is polled (default 10 minutes)
HEADERS = {"Authorization" : "Bearer " + BM_TOKEN}
BANLISTURL = "https://api.battlemetrics.com/bans?filter[banList]=" + BM_BANLIST_ID + "&include=user,server"
"""Define class used for storing ban information"""
class BanInfo(Enum):
PLAYER_NAME = 0
STEAM_ID = 1
REASON = 2
NOTE = 3
TIME_BANNED = 4
TIME_UNBANNED = 5
SERVER = 6
ADMIN_NAME = 7
"""Define class used for storing player information"""
class PlayerInfo:
PLAYER_NAME = 0
STEAM_ID = 1
NUM_ACTIVE = 2
NUM_EXPIRED = 3
MOST_RECENT = 4
MOST_RECENT_NOTE= 5
BMLINK = 6
COMMBANLINK = 7
def __init__(self, name, sid, na, ne, mr, mrn, bm, cbl):
self.PLAYER_NAME = name
self.STEAM_ID = sid
self.NUM_ACTIVE = na
self.NUM_EXPIRED = ne
self.MOST_RECENT = mr
self.MOST_RECENT_NOTE = mrn
self.BMLINK = bm
self.COMMBANLINK = cbl
def name(self):
return self.PLAYER_NAME
def steamID(self):
return self.STEAM_ID
def numActive(self):
return self.NUM_ACTIVE
def numExpired(self):
return self.NUM_EXPIRED
def mostRecent(self):
return self.MOST_RECENT
def mostRecentNote(self):
return self.MOST_RECENT_NOTE
def bmLink(self):
return self.BMLINK
def communityBanLink(self):
return self.COMMBANLINK
class battlemetricsDiscordBot(discord.Client):
""" Discord Ban Bot """
def __init__(self, **options):
""" Initialize """
super().__init__(**options)
self.prevList = None
self.event = threading.Event()
self.thread = threading.Thread(target=self.polling_thread, args=(self.event,))
self.thread.start()
client = discord.Client()
async def on_ready(self):
""" on_ready. """
print('Logged on as', self.user)
async def on_message(self, message):
""" Whenever there is a new message in the discord channel. """
messageUpper = message.content.upper()
if message.author == self.user: #if its our bots message, the bot won't respond
return
print(str(message.author) + ": " + str(message.content))
"""Define Discord channel commands"""
"""
if you want anyone who has access to a Discord channel to be able to execute all the commands change the below
if statement to:
if message.content.startswith(PREFIX) and str(message.channel) in DC_TEXT_CHANNEL_NAME:
if you want only admins to be able to use it in any channel
if str(message.author) in DC_ADMINS and message.content.startswith(PREFIX):
if you want only admins to be able to use it in a specific channel
if str(message.author) in DC_ADMINS and message.content.startswith(PREFIX) and str(message.channel) in DC_TEXT_CHANNEL_NAME:
if you want anyone to use the commands anywhere
if message.content.startswith(PREFIX):
"""
if message.content.startswith(PREFIX) and str(message.channel) in DC_TEXT_CHANNEL_NAME:
command = messageUpper[len(PREFIX):]
if command == "MANUALBANLISTPOLL": #command refreshs from api manually
print("Running manual poll")
await message.author.send("Manually pulled ban list")
self.update()
elif command == "LASTBAN": #command DMs the user that executes the command the last ban
print("Pulling last ban")
banList = get_banlist(BANLISTURL, HEADERS)
if banList != []:
await message.author.send(embed=self.create_embed_of_ban(banList[0]))
elif command == "HELP": #command DMs the bot commands to the user that executes the command
print("Messaging help information")
await message.author.send(embed=self.create_help_embed())
elif "USER" in command: #command get users ban information from battlemetrics with their steamid and posts in channel
try:
steamId = command.strip(" USER") #get just the steamid from the command
if len(steamId) != 17:
print("Invalid SteamID")
await message.author.send("Invalid SteamID")
else:
print("Pulling playerid")
playerId = get_playerID(steamId, HEADERS)
if playerId != None:
if len(playerId) == 9:
playerInfoURL = "https://api.battlemetrics.com/bans?filter[player]=" + playerId + "&include=user,server"
print("Making user embed to channel")
await message.channel.send(embed=self.create_player_embed(self, playerId, playerInfoURL, HEADERS))
else:
print("PlayerID not of correct length")
await message.author.send("No User Profile Found, check battlemetrics")
else:
await message.author.send("No User Profile Found, check battlemetrics")
except Exception as e:
print("User command exception",e)
return[]
def polling_thread(self, event):
""" Polling thread that runs every UPDATE_TIMER second """
while True:
self.update()
event.wait(BM_POLLING_INTERVAL) # Wait for next poll
def update(self):
""" Poll from Battlemetrics API and if there is new data, display it in the discord text channel. """
print("Polling from Battlemetrics API...\nURL: " + str(BANLISTURL))
banList = get_banlist(BANLISTURL, HEADERS)
if banList: # If poll was successful
print("Poll was successful.")
diff = self.get_banlist_difference(banList)
if len(diff) > 0: #there is something new when diff length >0
print("New bans detected!\n" + str(diff))
self.update_text_channel(self, diff)
else:
print("Nothing new...")
else:
print("Poll was not successful...")
def get_banlist_difference(self, newList):
""" Returns a list of the difference between newList and self.prevList. """
if self.prevList == None: #if its the first time, fill prevlist with the current list
self.prevList = newList
return []
difference = [item for item in newList if item not in self.prevList] #holds the differences between the last poll and current poll
self.prevList = newList
return difference
def update_text_channel(temporary, self, newBans):
""" Update text channel with the new bans. """
print("Transmit new ban information to the discord text channel...")
try:
for ban in newBans:
embedVar = self.create_embed_of_ban(ban)
self.send_embed_to_text_channel(embedVar)
except Exception as e:
print("update_text_channel exception:",e)
return
print("Successfully sent to text channel")
def create_help_embed(self):
""" Create help embed for this bot. """
embedVar = discord.Embed(title="Discord Command List", color=0x00ff00)
embedVar.add_field(name="!help", value="Displays this help message", inline=False)
embedVar.add_field(name="!manualbanlistpoll", value="Manually refreshes ban list and checks for changes", inline=False)
embedVar.add_field(name="!lastban", value="DMs you the last ban made and its information", inline=False)
embedVar.add_field(name="!user 'steamid'", value="Searches for a players ban history by SteamID. Replace 'steamid' with the players SteamID", inline=False)
return embedVar
def create_embed_of_ban(self, ban):
""" Creates an embed of a ban. """
embedVar = discord.Embed(title="New Ban Information", color=0x00ff00)
embedVar.add_field(name="PLAYER NAME", value=ban[BanInfo.PLAYER_NAME.value], inline=False)
embedVar.add_field(name="STEAMID", value=ban[BanInfo.STEAM_ID.value], inline=False)
embedVar.add_field(name="REASON", value=ban[BanInfo.REASON.value], inline=False)
if BanInfo.NOTE.value != None:
embedVar.add_field(name="NOTE", value=ban[BanInfo.NOTE.value], inline=False)
else:
embedVar.add_field(name="NOTE", value="No ban notes", inline=False)
embedVar.add_field(name="DATE", value=ban[BanInfo.TIME_BANNED.value], inline=False)
embedVar.add_field(name="EXPIRES", value=ban[BanInfo.TIME_UNBANNED.value], inline=False)
embedVar.add_field(name="SERVER", value=ban[BanInfo.SERVER.value], inline=False)
embedVar.add_field(name="ADMIN NAME", value=ban[BanInfo.ADMIN_NAME.value], inline=False)
return embedVar
def send_embed_to_text_channel(self, embedVar):
""" Send embed to text channel. """
self.loop.create_task(self.get_channel(DC_TEXT_CHANNEL_ID).send(embed=embedVar))
def create_player_embed(trash,self, id, url, headers):
"""Creates embed of a players ban history and information"""
card = self.make_playercard(self, id, url, headers)
embedVar = discord.Embed(title="Player Information", color=0x00ff00)
embedVar.add_field(name="Player Name", value=PlayerInfo.name(card), inline=False)
embedVar.add_field(name="SteamID", value=PlayerInfo.steamID(card), inline=False)
embedVar.add_field(name="Number of active bans:", value=PlayerInfo.numActive(card), inline=False)
embedVar.add_field(name="Number of expired bans:", value=PlayerInfo.numExpired(card), inline=False)
embedVar.add_field(name="Most recent ban reason:", value=PlayerInfo.mostRecent(card), inline=False)
embedVar.add_field(name="Most recent ban note:", value=PlayerInfo.mostRecentNote(card), inline=False)
embedVar.add_field(name="Battlemetrics Link:", value=PlayerInfo.bmLink(card), inline=False)
embedVar.add_field(name="Community Ban List Link:", value=PlayerInfo.communityBanLink(card), inline=False)
return embedVar
def make_playercard(trash, self, id, url, headers):
"""Polls battlemetrics api to get the player information
to make the player embed"""
try:
response = requests.get(url, headers=headers)
except Exception as e:
print("make_playercard json exception",e)
return []
card = response.json()
playerNames, steamIds, numact, numexp, recent, note, bmurl, cblink = ([] for i in range(8))
try:
playerNames.append(card["data"][0]["meta"]["player"])
except Exception as e:
print("Unknown Player Name",e)
playerNames.append("Unknown Player")
for i in range(10):
if card["data"][0]["attributes"]["identifiers"][i]["type"]=="steamID":
steamIds.append(card["data"][0]["attributes"]["identifiers"][i]["identifier"])
break
numact.append(card["meta"]["active"])
numexp.append(card["meta"]["expired"])
try:
if card["data"][0]["type"] == "ban":
recent.append(card["data"][0]["attributes"]["reason"])
if card["data"][0]["attributes"]["note"] == "":
note.append("No Ban Note")
else:
note.append(card["data"][0]["attributes"]["note"])
else:
recent.append("No recent bans")
note.append("No Ban Note")
except Exception as e:
print("make_playercard recent ban exception",e)
if recent[0] == None:
recent.append("No recent bans")
if note[0] == None:
note.append("No ban note attached")
bmurl.append("https://www.battlemetrics.com/rcon/players/"+id)
cblink.append("https://communitybanlist.com/search/"+steamIds[0])
returnList = PlayerInfo(playerNames[0], steamIds[0], numact[0], numexp[0], recent[0], note[0], bmurl[0], cblink[0])
return returnList
def get_banlist(url, headers):
""" Returns a list of the most recent banned players, default 10 players.
Returns an empty list if request went wrong.
"""
try:
response = requests.get(url, headers=headers)
except Exception as e:
print("get_banlist exception",e)
return []
banList = response.json()
tempServer, tempBanner = dict(), dict()
for include in banList["included"]:
if include["type"] == "server": #list of server names
tempServer[include["id"]] = include["attributes"]["name"]
elif include["type"] == "user": #list of admin names
tempBanner[include["id"]] = include["attributes"]["nickname"]
playerNames, steamIds, banReasons, note, timeBanned, timeUnbanned, server, banner = ([] for i in range(8))
for ban in banList["data"]: #fill all the fields for the embed
try:
playerNames.append(ban["meta"]["player"])
except Exception as e:
print("Unknown Player Name",e)
playerNames.append("Unknown Player")
for i in range(10):
if ban["attributes"]["identifiers"][i]["type"]=="steamID":
steamIds.append(ban["attributes"]["identifiers"][i]["identifier"])
break
banReasons.append(ban["attributes"]["reason"].replace(" ({{duration}} ban) - Expires in {{timeLeft}}.", ""))
note.append(ban["attributes"]["note"])
timeBanned.append(ban["attributes"]["timestamp"].replace("T", " ")[:-5])
expires = ban["attributes"]["expires"]
timeUnbanned.append(expires.replace("T", " ")[:-5] if expires != None else "Indefinitely")
server.append(tempServer[ban["relationships"]["server"]["data"]["id"]])
banner.append(tempBanner[ban["relationships"]["user"]["data"]["id"]])
returnList = []
for l in list(zip(playerNames, steamIds, banReasons, note, timeBanned, timeUnbanned, server, banner)):
returnList.append(dict(zip([0,1,2,3,4,5,6,7], l)))
return returnList
def get_playerID(steamID, headers):
"""Returns the battlemetrics player id number
from an api request searching with a players steamID"""
url = "https://api.battlemetrics.com/players?filter[search]=" + steamID
try:
response = requests.get(url, headers=headers)
except Exception as e:
print("get_playerID exception", e)
return []
playerInfo = response.json()
playerID = None
for player in playerInfo["data"]:
playerID = player["id"]
if playerID==None:
playerID = "Unknown Player"
return playerID
def config_check():
""" Verify that config is set. """
cfg = config["General"]["prefix"]
if cfg == "None":
raise Exception("Discord command prefix is not set.")
cfg = config["Discord"]["discordToken"]
if cfg == "None":
raise Exception("Discord token is not set.")
cfg = config["Discord"]["discordTextChannelId"]
if cfg == "None":
raise Exception("Discord text channel id is not set.")
cfg = config["Battlemetrics"]["battlemetricsToken"]
if cfg == "None":
raise Exception("Battlemetrics token is not set.")
cfg = config["Battlemetrics"]["banListId"]
if cfg == "None":
raise Exception("Battlemetrics banlist id is not set.")
cfg = config["Battlemetrics"]["pollingInterval"]
if cfg == "None":
raise Exception("Battlemetrics polling interval is not set.")
if __name__ == "__main__":
config_check()
bot = battlemetricsDiscordBot()
bot.run(DC_TOKEN)