aa-killtracker 0.16.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,64 +1,92 @@
1
1
  """Tasks for killtracker."""
2
2
 
3
- from celery import chain, shared_task
3
+ import time
4
+
5
+ import dhooks_lite
6
+ from celery import Task, chain, shared_task
4
7
 
5
8
  from django.db import IntegrityError
9
+ from django.utils.timezone import now
6
10
  from eveuniverse.core.esitools import is_esi_online
7
11
  from eveuniverse.tasks import update_unresolved_eve_entities
8
12
 
9
13
  from allianceauth.services.hooks import get_extension_logger
10
14
  from allianceauth.services.tasks import QueueOnce
11
- from app_utils.caching import cached_queryset
12
15
  from app_utils.esi import retry_task_if_esi_is_down
13
16
  from app_utils.logging import LoggerAddTag
14
17
 
15
- from . import APP_NAME, __title__
16
- from .app_settings import (
18
+ from killtracker import __title__
19
+ from killtracker.app_settings import (
17
20
  KILLTRACKER_DISCORD_SEND_DELAY,
18
21
  KILLTRACKER_GENERATE_MESSAGE_MAX_RETRIES,
19
22
  KILLTRACKER_GENERATE_MESSAGE_RETRY_COUNTDOWN,
20
23
  KILLTRACKER_MAX_KILLMAILS_PER_RUN,
21
24
  KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS,
25
+ KILLTRACKER_RUN_TIMEOUT,
22
26
  KILLTRACKER_STORING_KILLMAILS_ENABLED,
23
27
  KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
24
28
  KILLTRACKER_TASKS_TIMEOUT,
25
29
  )
26
- from .core.killmails import Killmail
27
- from .exceptions import WebhookTooManyRequests
28
- 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
29
39
 
30
40
  logger = LoggerAddTag(get_extension_logger(__name__), __title__)
31
41
 
32
42
 
33
- @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
34
- def run_killtracker(runs: int = 0) -> None:
35
- """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.
36
46
 
37
- Will fetch new killmails from ZKB and start running trackers for them
47
+ This is the main periodic task for running Killtracker.
38
48
  """
39
49
  if not is_esi_online():
40
50
  logger.warning("ESI is currently offline. Aborting")
41
- return
51
+ return 0
42
52
 
43
- if runs == 0:
44
- logger.info("Killtracker run started...")
45
- qs = cached_queryset(
46
- Webhook.objects.filter(is_enabled=True),
47
- key=f"{APP_NAME}_enabled_webhooks",
48
- timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
49
- )
50
- for webhook in qs:
51
- 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
52
86
 
53
- killmail = Killmail.create_from_zkb_redisq()
54
- if killmail:
87
+ killmails_count += 1
55
88
  killmail.save()
56
- qs = cached_queryset(
57
- Tracker.objects.filter(is_enabled=True),
58
- key=f"{APP_NAME}_enabled_trackers",
59
- timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
60
- )
61
- for tracker in qs:
89
+ for tracker in Tracker.objects.filter(is_enabled=True):
62
90
  run_tracker.delay(tracker_pk=tracker.pk, killmail_id=killmail.id)
63
91
 
64
92
  if KILLTRACKER_STORING_KILLMAILS_ENABLED:
@@ -67,56 +95,64 @@ def run_killtracker(runs: int = 0) -> None:
67
95
  update_unresolved_eve_entities.si(),
68
96
  ).delay()
69
97
 
70
- total_killmails = runs + (1 if killmail else 0)
71
- if killmail and total_killmails < KILLTRACKER_MAX_KILLMAILS_PER_RUN:
72
- run_killtracker.delay(runs=runs + 1)
73
- else:
74
- if (
75
- KILLTRACKER_STORING_KILLMAILS_ENABLED
76
- and KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS > 0
77
- ):
78
- 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
+ )
79
104
 
80
- logger.info(
81
- "Killtracker runs completed. %d killmails received from ZKB",
82
- total_killmails,
83
- )
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
84
112
 
85
113
 
86
114
  @shared_task(bind=True, max_retries=None)
87
115
  def run_tracker(
88
- 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
89
117
  ) -> None:
90
118
  """Run tracker for given killmail and trigger sending if needed."""
91
119
  retry_task_if_esi_is_down(self)
92
- tracker = Tracker.objects.get_cached(
120
+ tracker: Tracker = Tracker.objects.get_cached(
93
121
  pk=tracker_pk,
94
122
  select_related="webhook",
95
123
  timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
96
124
  )
97
- logger.info("%s: Started running tracker", tracker)
98
- 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
+
99
131
  killmail_new = tracker.process_killmail(
100
132
  killmail=killmail, ignore_max_age=ignore_max_age
101
133
  )
102
134
  if killmail_new:
135
+ logger.info("%s: Killmail %d matches", tracker, killmail_id)
103
136
  killmail_new.save()
104
137
  generate_killmail_message.delay(tracker_pk=tracker_pk, killmail_id=killmail_id)
105
- elif tracker.webhook.main_queue.size():
138
+ elif tracker.webhook.messages_queued():
106
139
  send_messages_to_webhook.delay(webhook_pk=tracker.webhook.pk)
107
140
 
108
141
 
109
142
  @shared_task(bind=True, max_retries=None)
110
- 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:
111
144
  """Generate and enqueue message from given killmail and start sending."""
112
145
  retry_task_if_esi_is_down(self)
113
- tracker = Tracker.objects.get_cached(
146
+ tracker: Tracker = Tracker.objects.get_cached(
114
147
  pk=tracker_pk,
115
148
  select_related="webhook",
116
149
  timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
117
150
  )
118
- killmail = Killmail.get(killmail_id)
119
- 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
120
156
  try:
121
157
  tracker.generate_killmail_message(killmail)
122
158
  except Exception as ex:
@@ -135,12 +171,19 @@ def generate_killmail_message(self, tracker_pk: int, killmail_id: int) -> None:
135
171
  )
136
172
 
137
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
+ )
138
177
 
139
178
 
140
179
  @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
141
180
  def store_killmail(killmail_id: int) -> None:
142
- """stores killmail as EveKillmail object"""
143
- 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
144
187
  try:
145
188
  EveKillmail.objects.create_from_killmail(killmail, resolve_ids=False)
146
189
  except IntegrityError:
@@ -153,7 +196,7 @@ def store_killmail(killmail_id: int) -> None:
153
196
 
154
197
  @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
155
198
  def delete_stale_killmails() -> None:
156
- """deleted all EveKillmail objects that are considered stale"""
199
+ """Deletes all EveKillmail objects that are considered stale."""
157
200
  _, details = EveKillmail.objects.delete_stale()
158
201
  if details:
159
202
  logger.info("Deleted %d stale killmails", details["killtracker.EveKillmail"])
@@ -166,10 +209,10 @@ def delete_stale_killmails() -> None:
166
209
  retry_backoff=False,
167
210
  max_retries=None,
168
211
  )
169
- def send_messages_to_webhook(self, webhook_pk: int) -> None:
170
- """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."""
171
214
 
172
- webhook = Webhook.objects.get_cached(
215
+ webhook: Webhook = Webhook.objects.get_cached(
173
216
  pk=webhook_pk,
174
217
  timeout=KILLTRACKER_TASK_OBJECTS_CACHE_TIMEOUT,
175
218
  )
@@ -177,39 +220,52 @@ def send_messages_to_webhook(self, webhook_pk: int) -> None:
177
220
  logger.info("%s: Webhook is disabled - aborting", webhook)
178
221
  return
179
222
 
180
- message = webhook.main_queue.dequeue()
181
- if message:
182
- logger.info("%s: Sending message to webhook", webhook)
183
- try:
184
- response = webhook.send_message_to_webhook(message)
185
- except WebhookTooManyRequests as ex:
186
- webhook.main_queue.enqueue(message)
187
- logger.warning(
188
- "%s: Too many requests for webhook. Blocked for %s seconds. Aborting.",
189
- webhook,
190
- ex.retry_after,
191
- )
192
- return
223
+ message = webhook.dequeue_message()
224
+ if not message:
225
+ logger.debug("%s: No more messages to send for webhook", webhook)
226
+ return
193
227
 
194
- if not response.status_ok:
195
- webhook.error_queue.enqueue(message)
196
- logger.warning(
197
- "%s: Failed to send message to webhook, will retry. "
198
- "HTTP status code: %d, response: %s",
199
- webhook,
200
- response.status_code,
201
- response.content,
202
- )
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
203
238
 
204
- 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
+ )
205
260
 
206
- logger.debug("%s: No more messages to send for webhook", webhook)
261
+ raise self.retry(countdown=KILLTRACKER_DISCORD_SEND_DELAY)
207
262
 
208
263
 
209
264
  @shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
210
265
  def send_test_message_to_webhook(webhook_pk: int, count: int = 1) -> None:
211
- """send a test message to given webhook.
212
- 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.
213
269
  """
214
270
  try:
215
271
  webhook = Webhook.objects.get(pk=webhook_pk)
@@ -217,8 +273,10 @@ def send_test_message_to_webhook(webhook_pk: int, count: int = 1) -> None:
217
273
  logger.error("Webhook with pk = %s does not exist", webhook_pk)
218
274
  return
219
275
 
220
- logger.info("Sending %s test messages to webhook %s", count, webhook)
221
276
  for num in range(count):
222
277
  num_str = f"{num+1}/{count} " if count > 1 else ""
223
- 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
+
224
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"])