aa-killtracker 0.17.0__py3-none-any.whl → 0.18.0a2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
killtracker/tasks.py CHANGED
@@ -1,8 +1,9 @@
1
1
  """Tasks for killtracker."""
2
2
 
3
- from datetime import timedelta
3
+ import time
4
4
 
5
- from celery import chain, shared_task
5
+ import dhooks_lite
6
+ from celery import Task, chain, shared_task
6
7
 
7
8
  from django.db import IntegrityError
8
9
  from django.utils.timezone import now
@@ -11,58 +12,81 @@ from eveuniverse.tasks import update_unresolved_eve_entities
11
12
 
12
13
  from allianceauth.services.hooks import get_extension_logger
13
14
  from allianceauth.services.tasks import QueueOnce
14
- from app_utils.caching import cached_queryset
15
15
  from app_utils.esi import retry_task_if_esi_is_down
16
16
  from app_utils.logging import LoggerAddTag
17
17
 
18
- from . import APP_NAME, __title__
19
- from .app_settings import (
18
+ from killtracker import __title__
19
+ from killtracker.app_settings import (
20
20
  KILLTRACKER_DISCORD_SEND_DELAY,
21
21
  KILLTRACKER_GENERATE_MESSAGE_MAX_RETRIES,
22
22
  KILLTRACKER_GENERATE_MESSAGE_RETRY_COUNTDOWN,
23
23
  KILLTRACKER_MAX_KILLMAILS_PER_RUN,
24
24
  KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS,
25
+ KILLTRACKER_RUN_TIMEOUT,
25
26
  KILLTRACKER_STORING_KILLMAILS_ENABLED,
26
27
  KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
27
28
  KILLTRACKER_TASKS_TIMEOUT,
28
- KILLTRACKER_ZKB_REQUEST_DELAY,
29
29
  )
30
- from .core.killmails import Killmail
31
- from .exceptions import WebhookTooManyRequests
32
- from .models import EveKillmail, Tracker, Webhook
30
+ from killtracker.core import worker_shutdown
31
+ from killtracker.core.discord_messages import DiscordMessage
32
+ from killtracker.core.killmails import (
33
+ Killmail,
34
+ KillmailDoesNotExist,
35
+ ZKBTooManyRequestsError,
36
+ )
37
+ from killtracker.exceptions import WebhookTooManyRequests
38
+ from killtracker.models import EveKillmail, Tracker, Webhook
33
39
 
34
40
  logger = LoggerAddTag(get_extension_logger(__name__), __title__)
35
41
 
36
42
 
37
- @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
38
- def run_killtracker(runs: int = 0) -> None:
39
- """Main task for running the Killtracker.
43
+ @shared_task(bind=True, base=QueueOnce, timeout=KILLTRACKER_TASKS_TIMEOUT)
44
+ def run_killtracker(self: Task) -> int:
45
+ """Try to fetch new killmails from ZKB API and start trackers.
40
46
 
41
- Will fetch new killmails from ZKB and start running trackers for them
47
+ This is the main periodic task for running Killtracker.
42
48
  """
43
49
  if not is_esi_online():
44
50
  logger.warning("ESI is currently offline. Aborting")
45
- return
51
+ return 0
46
52
 
47
- if runs == 0:
48
- logger.info("Killtracker run started...")
49
- qs = cached_queryset(
50
- Webhook.objects.filter(is_enabled=True),
51
- key=f"{APP_NAME}_enabled_webhooks",
52
- timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
53
- )
54
- for webhook in qs:
55
- webhook.reset_failed_messages()
53
+ for webhook in Webhook.objects.filter(is_enabled=True):
54
+ webhook.reset_failed_messages()
55
+
56
+ started = time.time()
57
+
58
+ def is_timed_out() -> bool:
59
+ elapsed = time.time() - started
60
+ return KILLTRACKER_RUN_TIMEOUT - elapsed <= 0
61
+
62
+ killmails_count = 0
63
+ for _ in range(KILLTRACKER_MAX_KILLMAILS_PER_RUN):
64
+ if is_timed_out():
65
+ break
66
+
67
+ if worker_shutdown.is_shutting_down(self):
68
+ logger.debug("Aborting due to worker shutdown")
69
+ break
70
+
71
+ killmail = None
72
+ try:
73
+ killmail = Killmail.create_from_zkb_redisq()
74
+ except ZKBTooManyRequestsError as ex:
75
+ seconds = (ex.retry_at - now()).total_seconds()
76
+ if seconds < 0:
77
+ break
78
+
79
+ logger.warning(
80
+ "Killtracker has been baned from ZKB API for %f seconds", seconds
81
+ )
82
+ raise self.retry(countdown=seconds)
83
+
84
+ if not killmail:
85
+ break
56
86
 
57
- killmail = Killmail.create_from_zkb_redisq()
58
- if killmail:
87
+ killmails_count += 1
59
88
  killmail.save()
60
- qs = cached_queryset(
61
- Tracker.objects.filter(is_enabled=True),
62
- key=f"{APP_NAME}_enabled_trackers",
63
- timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
64
- )
65
- for tracker in qs:
89
+ for tracker in Tracker.objects.filter(is_enabled=True):
66
90
  run_tracker.delay(tracker_pk=tracker.pk, killmail_id=killmail.id)
67
91
 
68
92
  if KILLTRACKER_STORING_KILLMAILS_ENABLED:
@@ -71,59 +95,64 @@ def run_killtracker(runs: int = 0) -> None:
71
95
  update_unresolved_eve_entities.si(),
72
96
  ).delay()
73
97
 
74
- total_killmails = runs + (1 if killmail else 0)
75
- if killmail and total_killmails < KILLTRACKER_MAX_KILLMAILS_PER_RUN:
76
- run_killtracker.apply_async(
77
- kwargs={"runs": runs + 1},
78
- eta=now() + timedelta(milliseconds=KILLTRACKER_ZKB_REQUEST_DELAY),
79
- )
80
- else:
81
- if (
82
- KILLTRACKER_STORING_KILLMAILS_ENABLED
83
- and KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS > 0
84
- ):
85
- delete_stale_killmails.delay()
98
+ elapsed = time.time() - started
99
+ logger.info(
100
+ "Killtracker processed %d new killmails from ZKB in %f seconds",
101
+ killmails_count,
102
+ elapsed,
103
+ )
86
104
 
87
- logger.info(
88
- "Killtracker runs completed. %d killmails received from ZKB",
89
- total_killmails,
90
- )
105
+ if (
106
+ KILLTRACKER_STORING_KILLMAILS_ENABLED
107
+ and KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS > 0
108
+ ):
109
+ delete_stale_killmails.delay()
110
+
111
+ return killmails_count
91
112
 
92
113
 
93
114
  @shared_task(bind=True, max_retries=None)
94
115
  def run_tracker(
95
- self, tracker_pk: int, killmail_id: int, ignore_max_age: bool = False
116
+ self: Task, tracker_pk: int, killmail_id: int, ignore_max_age: bool = False
96
117
  ) -> None:
97
118
  """Run tracker for given killmail and trigger sending if needed."""
98
119
  retry_task_if_esi_is_down(self)
99
- tracker = Tracker.objects.get_cached(
120
+ tracker: Tracker = Tracker.objects.get_cached(
100
121
  pk=tracker_pk,
101
122
  select_related="webhook",
102
123
  timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
103
124
  )
104
- logger.info("%s: Started running tracker", tracker)
105
- killmail = Killmail.get(killmail_id)
125
+ try:
126
+ killmail = Killmail.get(killmail_id)
127
+ except KillmailDoesNotExist as ex:
128
+ logger.error("Aborting. %s", ex)
129
+ return
130
+
106
131
  killmail_new = tracker.process_killmail(
107
132
  killmail=killmail, ignore_max_age=ignore_max_age
108
133
  )
109
134
  if killmail_new:
135
+ logger.info("%s: Killmail %d matches", tracker, killmail_id)
110
136
  killmail_new.save()
111
137
  generate_killmail_message.delay(tracker_pk=tracker_pk, killmail_id=killmail_id)
112
- elif tracker.webhook.main_queue.size():
138
+ elif tracker.webhook.messages_queued():
113
139
  send_messages_to_webhook.delay(webhook_pk=tracker.webhook.pk)
114
140
 
115
141
 
116
142
  @shared_task(bind=True, max_retries=None)
117
- def generate_killmail_message(self, tracker_pk: int, killmail_id: int) -> None:
143
+ def generate_killmail_message(self: Task, tracker_pk: int, killmail_id: int) -> None:
118
144
  """Generate and enqueue message from given killmail and start sending."""
119
145
  retry_task_if_esi_is_down(self)
120
- tracker = Tracker.objects.get_cached(
146
+ tracker: Tracker = Tracker.objects.get_cached(
121
147
  pk=tracker_pk,
122
148
  select_related="webhook",
123
149
  timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
124
150
  )
125
- killmail = Killmail.get(killmail_id)
126
- logger.info("%s: Generating message from killmail %s", tracker, killmail.id)
151
+ try:
152
+ killmail = Killmail.get(killmail_id)
153
+ except KillmailDoesNotExist as ex:
154
+ logger.error("Aborting. %s", ex)
155
+ return
127
156
  try:
128
157
  tracker.generate_killmail_message(killmail)
129
158
  except Exception as ex:
@@ -142,12 +171,19 @@ def generate_killmail_message(self, tracker_pk: int, killmail_id: int) -> None:
142
171
  )
143
172
 
144
173
  send_messages_to_webhook.delay(webhook_pk=tracker.webhook.pk)
174
+ logger.info(
175
+ "%s: Added message from killmail %s to send queue", tracker, killmail.id
176
+ )
145
177
 
146
178
 
147
179
  @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
148
180
  def store_killmail(killmail_id: int) -> None:
149
- """stores killmail as EveKillmail object"""
150
- killmail = Killmail.get(killmail_id)
181
+ """Stores killmail as EveKillmail object."""
182
+ try:
183
+ killmail = Killmail.get(killmail_id)
184
+ except KillmailDoesNotExist as ex:
185
+ logger.error("Aborting. %s", ex)
186
+ return
151
187
  try:
152
188
  EveKillmail.objects.create_from_killmail(killmail, resolve_ids=False)
153
189
  except IntegrityError:
@@ -160,7 +196,7 @@ def store_killmail(killmail_id: int) -> None:
160
196
 
161
197
  @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
162
198
  def delete_stale_killmails() -> None:
163
- """deleted all EveKillmail objects that are considered stale"""
199
+ """Deletes all EveKillmail objects that are considered stale."""
164
200
  _, details = EveKillmail.objects.delete_stale()
165
201
  if details:
166
202
  logger.info("Deleted %d stale killmails", details["killtracker.EveKillmail"])
@@ -173,10 +209,10 @@ def delete_stale_killmails() -> None:
173
209
  retry_backoff=False,
174
210
  max_retries=None,
175
211
  )
176
- def send_messages_to_webhook(self, webhook_pk: int) -> None:
177
- """send all queued messages to given Webhook"""
212
+ def send_messages_to_webhook(self: Task, webhook_pk: int) -> None:
213
+ """Sends all queued messages to given Webhook."""
178
214
 
179
- webhook = Webhook.objects.get_cached(
215
+ webhook: Webhook = Webhook.objects.get_cached(
180
216
  pk=webhook_pk,
181
217
  timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
182
218
  )
@@ -184,39 +220,52 @@ def send_messages_to_webhook(self, webhook_pk: int) -> None:
184
220
  logger.info("%s: Webhook is disabled - aborting", webhook)
185
221
  return
186
222
 
187
- message = webhook.main_queue.dequeue()
188
- if message:
189
- logger.info("%s: Sending message to webhook", webhook)
190
- try:
191
- response = webhook.send_message_to_webhook(message)
192
- except WebhookTooManyRequests as ex:
193
- webhook.main_queue.enqueue(message)
194
- logger.warning(
195
- "%s: Too many requests for webhook. Blocked for %s seconds. Aborting.",
196
- webhook,
197
- ex.retry_after,
198
- )
199
- return
223
+ message = webhook.dequeue_message()
224
+ if not message:
225
+ logger.debug("%s: No more messages to send for webhook", webhook)
226
+ return
200
227
 
201
- if not response.status_ok:
202
- webhook.error_queue.enqueue(message)
203
- logger.warning(
204
- "%s: Failed to send message to webhook, will retry. "
205
- "HTTP status code: %d, response: %s",
206
- webhook,
207
- response.status_code,
208
- response.content,
209
- )
228
+ try:
229
+ response: dhooks_lite.WebhookResponse = webhook.send_message(message)
230
+ except WebhookTooManyRequests as ex:
231
+ webhook.enqueue_message(message)
232
+ logger.warning(
233
+ "%s: Too many requests for webhook. Blocked for %s seconds. Aborting.",
234
+ webhook,
235
+ ex.retry_after,
236
+ )
237
+ return
210
238
 
211
- raise self.retry(countdown=KILLTRACKER_DISCORD_SEND_DELAY)
239
+ if not response.status_ok:
240
+ webhook.enqueue_message(message, is_error=True)
241
+ logger.warning(
242
+ "%s: Failed to send message for Killmail %d to webhook, will retry. "
243
+ "HTTP status code: %d, response: %s",
244
+ webhook,
245
+ message.killmail_id,
246
+ response.status_code,
247
+ response.content,
248
+ )
249
+ else:
250
+ try:
251
+ message_id = response.content.get("id")
252
+ except AttributeError:
253
+ message_id = "?"
254
+ logger.info(
255
+ "%s: Discord message %s created for killmail %d",
256
+ webhook,
257
+ message_id,
258
+ message.killmail_id,
259
+ )
212
260
 
213
- logger.debug("%s: No more messages to send for webhook", webhook)
261
+ raise self.retry(countdown=KILLTRACKER_DISCORD_SEND_DELAY)
214
262
 
215
263
 
216
264
  @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
217
265
  def send_test_message_to_webhook(webhook_pk: int, count: int = 1) -> None:
218
- """send a test message to given webhook.
219
- Optional inform user about result if user ok is given
266
+ """Send a test message to given webhook.
267
+
268
+ Optional inform user about result if user ok is given.
220
269
  """
221
270
  try:
222
271
  webhook = Webhook.objects.get(pk=webhook_pk)
@@ -224,8 +273,10 @@ def send_test_message_to_webhook(webhook_pk: int, count: int = 1) -> None:
224
273
  logger.error("Webhook with pk = %s does not exist", webhook_pk)
225
274
  return
226
275
 
227
- logger.info("Sending %s test messages to webhook %s", count, webhook)
228
276
  for num in range(count):
229
277
  num_str = f"{num+1}/{count} " if count > 1 else ""
230
- webhook.enqueue_message(content=f"Test message {num_str}from {__title__}.")
278
+ message = DiscordMessage(content=f"Test message {num_str}from {__title__}.")
279
+ webhook.enqueue_message(message)
280
+
231
281
  send_messages_to_webhook.delay(webhook.pk)
282
+ logger.info("%s test messages submitted to webhook %s", count, webhook)
@@ -25,7 +25,7 @@ class TestCreateEmbed(NoSocketsTestCase):
25
25
  tracker = TrackerFactory()
26
26
  killmail = KillmailFactory()
27
27
  # when
28
- embed = discord_messages.create_embed(tracker, killmail)
28
+ embed = discord_messages._create_embed(tracker, killmail)
29
29
  # then
30
30
  self.assertIsInstance(embed, dhooks_lite.Embed)
31
31
 
@@ -34,7 +34,7 @@ class TestCreateEmbed(NoSocketsTestCase):
34
34
  tracker = TrackerFactory()
35
35
  killmail = KillmailFactory(zkb__total_value=None)
36
36
  # when
37
- embed = discord_messages.create_embed(tracker, killmail)
37
+ embed = discord_messages._create_embed(tracker, killmail)
38
38
  # then
39
39
  self.assertIsInstance(embed, dhooks_lite.Embed)
40
40
 
@@ -43,7 +43,7 @@ class TestCreateEmbed(NoSocketsTestCase):
43
43
  tracker = TrackerFactory()
44
44
  killmail = KillmailFactory(victim__alliance_id=None)
45
45
  # when
46
- embed = discord_messages.create_embed(tracker, killmail)
46
+ embed = discord_messages._create_embed(tracker, killmail)
47
47
  # then
48
48
  self.assertIsInstance(embed, dhooks_lite.Embed)
49
49
 
@@ -54,7 +54,7 @@ class TestCreateEmbed(NoSocketsTestCase):
54
54
  victim__alliance_id=None, victim__corporation_id=None
55
55
  )
56
56
  # when
57
- embed = discord_messages.create_embed(tracker, killmail)
57
+ embed = discord_messages._create_embed(tracker, killmail)
58
58
  # then
59
59
  self.assertIsInstance(embed, dhooks_lite.Embed)
60
60
 
@@ -64,7 +64,7 @@ class TestCreateEmbed(NoSocketsTestCase):
64
64
  killmail = KillmailFactory()
65
65
  killmail.attackers.remove(killmail.attacker_final_blow())
66
66
  # when
67
- embed = discord_messages.create_embed(tracker, killmail)
67
+ embed = discord_messages._create_embed(tracker, killmail)
68
68
  # then
69
69
  self.assertIsInstance(embed, dhooks_lite.Embed)
70
70
 
@@ -73,7 +73,7 @@ class TestCreateEmbed(NoSocketsTestCase):
73
73
  tracker = TrackerFactory()
74
74
  killmail = KillmailFactory().clone_with_tracker_info(tracker.pk)
75
75
  # when
76
- embed = discord_messages.create_embed(tracker, killmail)
76
+ embed = discord_messages._create_embed(tracker, killmail)
77
77
  # then
78
78
  self.assertIsInstance(embed, dhooks_lite.Embed)
79
79
 
@@ -85,6 +85,53 @@ class TestCreateEmbed(NoSocketsTestCase):
85
85
  tracker.pk, jumps=3, distance=3.5, matching_ship_type_ids=[ship_type.id]
86
86
  )
87
87
  # when
88
- embed = discord_messages.create_embed(tracker, killmail)
88
+ embed = discord_messages._create_embed(tracker, killmail)
89
89
  # then
90
90
  self.assertIsInstance(embed, dhooks_lite.Embed)
91
+
92
+
93
+ class TestDiscordMessage(NoSocketsTestCase):
94
+ def test_can_create(self):
95
+ o = discord_messages.DiscordMessage(content="content")
96
+ self.assertEqual(o.content, "content")
97
+
98
+ def test_should_raise_exception_when_invalid(self):
99
+ with self.assertRaises(ValueError):
100
+ discord_messages.DiscordMessage(username="user")
101
+
102
+ def test_can_convert_to_and_from_json_1(self):
103
+ o1 = discord_messages.DiscordMessage(
104
+ content="content",
105
+ )
106
+ s = o1.to_json()
107
+ o2 = discord_messages.DiscordMessage.from_json(s)
108
+ self.assertEqual(o1, o2)
109
+
110
+ def test_can_convert_to_and_from_json_2(self):
111
+ o1 = discord_messages.DiscordMessage(
112
+ avatar_url="avatar_url",
113
+ content="content",
114
+ embeds=[dhooks_lite.Embed(description="description")],
115
+ killmail_id=42,
116
+ username="username",
117
+ )
118
+ s = o1.to_json()
119
+ o2 = discord_messages.DiscordMessage.from_json(s)
120
+ self.assertEqual(o1, o2)
121
+
122
+
123
+ class TestDiscordMessageFromKillmail(NoSocketsTestCase):
124
+ @classmethod
125
+ def setUpClass(cls):
126
+ super().setUpClass()
127
+ load_eveuniverse()
128
+ load_eve_entities()
129
+
130
+ def test_should_create_from_killmail(self):
131
+ # given
132
+ tracker = TrackerFactory()
133
+ killmail = KillmailFactory()
134
+ # when
135
+ m = discord_messages.DiscordMessage.from_killmail(tracker, killmail)
136
+ # then
137
+ self.assertIsInstance(m.embeds[0], dhooks_lite.Embed)
@@ -51,8 +51,8 @@ if "discord" in app_labels():
51
51
  self.assertTrue(
52
52
  mock_import_discord_user.return_value.objects.group_to_role.called
53
53
  )
54
- self.assertEqual(self.webhook_1.main_queue.size(), 1)
55
- message = json.loads(self.webhook_1.main_queue.dequeue())
54
+ self.assertEqual(self.webhook_1._main_queue.size(), 1)
55
+ message = json.loads(self.webhook_1._main_queue.dequeue())
56
56
  self.assertIn(f"<@&{self.group_1.pk}>", message["content"])
57
57
 
58
58
  def test_can_ping_multiple_groups(self, mock_import_discord_user):
@@ -70,8 +70,8 @@ if "discord" in app_labels():
70
70
  self.assertTrue(
71
71
  mock_import_discord_user.return_value.objects.group_to_role.called
72
72
  )
73
- self.assertEqual(self.webhook_1.main_queue.size(), 1)
74
- message = json.loads(self.webhook_1.main_queue.dequeue())
73
+ self.assertEqual(self.webhook_1._main_queue.size(), 1)
74
+ message = json.loads(self.webhook_1._main_queue.dequeue())
75
75
  self.assertIn(f"<@&{self.group_1.pk}>", message["content"])
76
76
  self.assertIn(f"<@&{self.group_2.pk}>", message["content"])
77
77
 
@@ -91,8 +91,8 @@ if "discord" in app_labels():
91
91
  self.assertTrue(
92
92
  mock_import_discord_user.return_value.objects.group_to_role.called
93
93
  )
94
- self.assertEqual(self.webhook_1.main_queue.size(), 1)
95
- message = json.loads(self.webhook_1.main_queue.dequeue())
94
+ self.assertEqual(self.webhook_1._main_queue.size(), 1)
95
+ message = json.loads(self.webhook_1._main_queue.dequeue())
96
96
  self.assertIn(f"<@&{self.group_1.pk}>", message["content"])
97
97
  self.assertIn("@here", message["content"])
98
98
 
@@ -110,6 +110,6 @@ if "discord" in app_labels():
110
110
  self.assertTrue(
111
111
  mock_import_discord_user.return_value.objects.group_to_role.called
112
112
  )
113
- self.assertEqual(self.webhook_1.main_queue.size(), 1)
114
- message = json.loads(self.webhook_1.main_queue.dequeue())
113
+ self.assertEqual(self.webhook_1._main_queue.size(), 1)
114
+ message = json.loads(self.webhook_1._main_queue.dequeue())
115
115
  self.assertNotIn(f"<@&{self.group_1.pk}>", message["content"])