-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathbot.py
466 lines (395 loc) · 16.5 KB
/
bot.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
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
import discord
from discord.ext import commands, tasks
import websocket
import json
import threading
import requests
import asyncio
from collections import deque
import time
from config import *
import logging
import coloredlogs
LAST_MESSAGE_TIME = None
global ws_sub_kill_and_loss, ws_sub_only_loss
ws_sub_kill_and_loss = None
ws_sub_only_loss = None
bot = commands.Bot(command_prefix="!", intents=discord.Intents.all())
processed_hashes = deque(maxlen=hash_limit)
info_logger = logging.getLogger("Discord")
coloredlogs.install(level="INFO", logger=info_logger)
debug_logger = logging.getLogger("WebSocket")
coloredlogs.install(level="DEBUG", logger=debug_logger)
error_logger = logging.getLogger("Error")
coloredlogs.install(level="ERROR", logger=error_logger)
"""
Description: This function is used to send message to discord channel
@:param ws: websocket
@:param message: message received from websocket
@:param track_kill: True if kill is to be tracked
@:return: None
"""
def on_ws_message(ws, message, track_kill):
global LAST_MESSAGE_TIME
data = json.loads(message)
info_logger.info("Received killmail")
kill_hash = data.get("hash")
url = data.get("url")
ship_id = data.get("ship_type_id")
if kill_hash and kill_hash not in processed_hashes:
processed_hashes.append(
kill_hash
) # Adds new hash and may remove the oldest if limit is reached
# Process the message, e.g., send to Discord channel
channel = bot.get_channel(channel_id)
# Send url only if track_kill is True or ship_id is in only_loss
if channel and (track_kill or ship_id in only_loss.values()):
info_logger.info(f"Sending message to channel {channel.name}")
asyncio.run_coroutine_threadsafe(channel.send(url), bot.loop)
else:
error_logger.error("Channel not found")
else:
info_logger.info("Duplicate message received, ignoring.")
LAST_MESSAGE_TIME = time.time()
"""
Description: This function is used to send message to discord channel
@:param ws: websocket
@:param dic: dictionary
"""
def on_ws_open(ws, dic):
def run(*args):
if dic == kill_and_loss:
debug_logger.debug("Subscribing to all kills")
with open("subscriptions.json", "r") as file:
subscriptions = json.load(file)
for subscription in subscriptions:
ws.send(json.dumps(subscription))
debug_logger.debug(f"Subscribed to {subscription}")
for ship in dic:
ws.send(
json.dumps(
{
"action": "sub",
"channel": f"ship:{dic[ship]}",
}
)
)
debug_logger.debug(f"Subscribed to {ship}")
debug_logger.debug("Subscribed to all kills")
debug_logger.debug(f"Opened WebSocket for {dic}")
threading.Thread(target=run).start()
def start_websocket_kill_and_loss():
global LAST_MESSAGE_TIME
ws = websocket.WebSocketApp(
"wss://zkillboard.com/websocket/",
on_open=lambda ws: on_ws_open(ws, dic=kill_and_loss),
on_message=lambda ws, message: on_ws_message(ws, message, track_kill=True),
)
ws_thread = threading.Thread(target=ws.run_forever)
ws_thread.start()
return ws, ws_thread
def start_websocket_only_loss():
global LAST_MESSAGE_TIME
ws = websocket.WebSocketApp(
"wss://zkillboard.com/websocket/",
on_open=lambda ws: on_ws_open(ws, dic=only_loss),
on_message=lambda ws, message: on_ws_message(ws, message, False),
)
ws_thread = threading.Thread(target=ws.run_forever)
ws_thread.start()
return ws, ws_thread
async def manage_websocket():
global LAST_MESSAGE_TIME
LAST_MESSAGE_TIME = time.time()
global ws_sub_kill_and_loss, ws_sub_only_loss
ws_sub_kill_and_loss, ws_thread_kill_and_loss = start_websocket_kill_and_loss()
ws_sub_only_loss, ws_thread_only_loss = start_websocket_only_loss()
while True:
await asyncio.sleep(10) # Check every 10 seconds
if (time.time() - LAST_MESSAGE_TIME) > inactivity_threshold:
debug_logger.debug("WebSocket inactive, attempting to reconnect...")
# Close the WebSocket
ws_sub_kill_and_loss.close()
ws_sub_kill_and_loss = None
ws_sub_only_loss.close()
ws_sub_only_loss = None
# Check if the thread is still alive and join it if it is
if ws_thread_kill_and_loss.is_alive():
ws_thread_kill_and_loss.join(
timeout=10
) # Wait for the thread to finish with a timeout
# Reinitialize the WebSocket and thread
(
ws_sub_kill_and_loss,
ws_thread_kill_and_loss,
) = start_websocket_kill_and_loss()
ws_sub_only_loss, ws_thread_only_loss = start_websocket_only_loss()
LAST_MESSAGE_TIME = time.time()
async def tq_status():
try:
server_status = requests.get(
"https://esi.evetech.net/latest/status/?datasource=tranquility"
)
# fetch player count
player_count = server_status.json()["players"]
info_logger.info(f"Currently {player_count} players online")
# set bot status as player count
await bot.change_presence(
activity=discord.Activity(
type=discord.ActivityType.watching,
name=f"Tq, {player_count} players online",
)
)
info_logger.info("Updated bot status")
except Exception as e:
info_logger.error(f"Error fetching player count: {str(e)}")
@tasks.loop(seconds=600)
async def status_update_loop():
await tq_status()
@bot.event
async def on_ready():
info_logger.info(f"Logged in as {bot.user}")
bot.loop.create_task(manage_websocket())
info_logger.info(f"Started WebSocket {ws_sub_kill_and_loss}")
status_update_loop.start()
info_logger.info("Started status update loop")
# Below are added
# This function add new item to subs.json file
def add_subscription(sub_type, sub_id, filename="subscriptions.json"):
global ws_sub_kill_and_loss
# Construct the new subscription item
new_sub = {"action": "sub", "channel": f"{sub_type}:{sub_id}"}
ws_sub_kill_and_loss.send(json.dumps(new_sub))
# Read the current data from the file
try:
with open(filename, "r", encoding="utf-8") as file:
subs = json.load(file)
except FileNotFoundError:
subs = []
except json.JSONDecodeError:
print("Error reading the subscriptions file. Is it empty or malformed?")
return False
# Add the new subscription to the list
subs.append(new_sub)
# Write the updated list back to the file
try:
with open(filename, "w", encoding="utf-8") as file:
json.dump(subs, file, indent=4)
return True
except Exception as e:
print(f"Error writing to the JSON file: {e}")
return False
# This function delete the item if in subs.json file
def delete_subscription(sub_type, sub_id, filename="subscriptions.json"):
# Construct the subscription item to be deleted
global ws_sub_kill_and_loss
target_sub = {"action": "sub", "channel": f"{sub_type}:{sub_id}"}
ws_sub_kill_and_loss.send(json.dumps(target_sub))
try:
# Read the current data from the file
with open(filename, "r", encoding="utf-8") as file:
subs = json.load(file)
# Check if the subscription is in the list and remove it
if target_sub in subs:
subs.remove(target_sub)
else:
print("Subscription not found in the file.")
return False
# Write the updated list back to the file
with open(filename, "w", encoding="utf-8") as file:
json.dump(subs, file, indent=4)
return True
except FileNotFoundError:
print("File not found. Please check the file path.")
return False
except json.JSONDecodeError:
print("Error reading the JSON file. Is it malformed?")
return False
except Exception as e:
print(f"Error handling the JSON file: {e}")
return False
# !sub (char/ship/corp/system) name
@bot.command(name="sub")
async def sub(ctx, entity_type: str, *, entity_name: str):
# Check if the entity_type is valid
if entity_type.lower() not in ["char", "ship", "system", "corp"]:
await ctx.send(
"Invalid search type. Please select from **char**, **ship**, **system** or **corp**."
)
return
url = "https://esi.evetech.net/latest/universe/ids/?datasource=tranquility&language=en"
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=[entity_name], headers=headers)
if response.status_code != 200:
await ctx.send("Failed to communicate with the EVE Online API.")
return
data = response.json()
# Check if the desired type is in the response
if entity_type.lower() == "char" and "characters" in data:
# Logic to handle character type
characters = data["characters"]
# Further logic to display characters or use the information
await ctx.send(
f'Character found: {", ".join([char["name"] for char in characters])}'
)
add_subscription("character", f'{characters[0]["id"]}')
elif entity_type.lower() == "ship" and "inventory_types" in data:
# Logic to handle inventory type
inventory_types = data["inventory_types"]
# Further logic to display inventory types or use the information
await ctx.send(
f'Ship found: {", ".join([item["name"] for item in inventory_types])}'
)
add_subscription("ship", f'{inventory_types[0]["id"]}')
elif entity_type.lower() == "system" and "systems" in data:
# Logic to handle inventory type
system = data["systems"]
# Further logic to display inventory types or use the information
await ctx.send(f'System found: {", ".join([item["name"] for item in system])}')
add_subscription("system", f'{system[0]["id"]}')
elif entity_type.lower() == "corp" and "corporations" in data:
# Logic to handle inventory type
corporation = data["corporations"]
# Further logic to display inventory types or use the information
await ctx.send(
f'Corporation found: {", ".join([item["name"] for item in corporation])}'
)
add_subscription("corporation", f'{corporation[0]["id"]}')
else:
await ctx.send("No matching data found for the given type and name.")
# !unsub (char/ship/corp/system) name
@bot.command(name="unsub")
async def unsub(ctx, entity_type: str, *, entity_name: str):
# Check if the entity_type is valid
if entity_type.lower() not in ["char", "ship", "system", "corp"]:
await ctx.send(
"Invalid search type. Please select from **char**, **ship**, **system** or **corp**."
)
return
url = "https://esi.evetech.net/latest/universe/ids/?datasource=tranquility&language=en"
headers = {"Content-Type": "application/json"}
response = requests.post(url, json=[entity_name], headers=headers)
if response.status_code != 200:
await ctx.send("Failed to communicate with the EVE Online API.")
return
data = response.json()
# Check if the desired type is in the response
if entity_type.lower() == "char" and "characters" in data:
# Logic to handle character type
characters = data["characters"]
# Further logic to display characters or use the information
await ctx.send(
f'Character found: {", ".join([char["name"] for char in characters])}'
)
delete_subscription("character", f'{characters[0]["id"]}')
elif entity_type.lower() == "ship" and "inventory_types" in data:
# Logic to handle inventory type
inventory_types = data["inventory_types"]
# Further logic to display inventory types or use the information
await ctx.send(
f'Ship found: {", ".join([item["name"] for item in inventory_types])}'
)
delete_subscription("ship", f'{inventory_types[0]["id"]}')
elif entity_type.lower() == "system" and "systems" in data:
# Logic to handle inventory type
system = data["systems"]
# Further logic to display inventory types or use the information
await ctx.send(f'System found: {", ".join([item["name"] for item in system])}')
delete_subscription("system", f'{system[0]["id"]}')
elif entity_type.lower() == "corp" and "corporations" in data:
# Logic to handle inventory type
corporation = data["corporations"]
# Further logic to display inventory types or use the information
await ctx.send(
f'Corporation found: {", ".join([item["name"] for item in corporation])}'
)
delete_subscription("corporation", f'{corporation[0]["id"]}')
else:
await ctx.send("No matching data found for the given type and name.")
# !list list currently subscribed characters, ships, corps and systems
@bot.command(name="list")
async def list(ctx):
filename = "subscriptions.json"
characters = []
corps = []
groups = []
ships = []
systems = []
try:
# Read the current data from the file
with open(filename, "r", encoding="utf-8") as file:
subs = json.load(file)
# Iterate through subscriptions and sort IDs into respective lists
for sub in subs:
channel = sub.get("channel", "")
if ":" in channel:
type, id = channel.split(":")
if type == "character":
response = requests.get(
f"https://esi.evetech.net/latest/characters/{id}"
)
if response.status_code == 200:
data = response.json()
name = data.get("name")
if name:
characters.append(name)
elif type == "corporation":
response = requests.get(
f"https://esi.evetech.net/latest/corporations/{id}"
)
if response.status_code == 200:
data = response.json()
name = data.get("name")
if name:
corps.append(name)
elif type == "group":
response = requests.get(
f"https://esi.evetech.net/latest/universe/groups/{id}"
)
if response.status_code == 200:
data = response.json()
name = data.get("name")
if name:
groups.append(name)
elif type == "ship":
response = requests.get(
f"https://esi.evetech.net/latest/universe/types/{id}"
)
if response.status_code == 200:
data = response.json()
name = data.get("name")
if name:
ships.append(name)
elif type == "system":
response = requests.get(
f"https://esi.evetech.net/latest/universe/systems/{id}"
)
if response.status_code == 200:
data = response.json()
name = data.get("name")
if name:
systems.append(name)
except FileNotFoundError:
print("File not found. Please check the file path.")
return {}
except json.JSONDecodeError:
print("Error reading the JSON file. Is it malformed?")
return {}
except Exception as e:
print(f"Error handling the JSON file: {e}")
return {}
result = "**Currently subscribing - **\n\n"
if characters:
result += "**characters:**\n" + "\n".join(characters) + "\n\n"
if corps:
result += "**corporations:**\n" + "\n".join(corps) + "\n\n"
if groups:
result += "**groups:**\n" + "\n".join(groups) + "\n\n"
if ships:
result += "**ships:**\n" + "\n".join(ships) + "\n\n"
if systems:
result += "**systems:**\n" + "\n".join(systems) + "\n"
# Trim any extra newline characters from the end of the string
await ctx.send(result.strip())
# End of new code
bot.run(token)