aa-killtracker 0.17.0__py3-none-any.whl → 1.0.0a1__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.
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0a1.dist-info}/METADATA +7 -7
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0a1.dist-info}/RECORD +36 -29
- killtracker/__init__.py +1 -1
- killtracker/admin.py +13 -8
- killtracker/app_settings.py +20 -10
- killtracker/apps.py +2 -4
- killtracker/core/discord.py +162 -0
- killtracker/core/helpers.py +13 -0
- killtracker/core/{discord_messages.py → trackers.py} +74 -59
- killtracker/core/workers.py +46 -0
- killtracker/core/{killmails.py → zkb.py} +97 -72
- killtracker/forms.py +1 -1
- killtracker/managers.py +3 -3
- killtracker/models/trackers.py +7 -10
- killtracker/models/webhooks.py +60 -128
- killtracker/providers.py +1 -1
- killtracker/signals.py +31 -0
- killtracker/tasks.py +141 -92
- killtracker/tests/core/test_discord.py +184 -0
- killtracker/tests/core/test_helpers.py +23 -0
- killtracker/tests/core/{test_discord_messages_1.py → test_tracker_1.py} +28 -8
- killtracker/tests/core/{test_discord_messages_2.py → test_tracker_2.py} +11 -11
- killtracker/tests/core/test_workers.py +49 -0
- killtracker/tests/core/{test_killmails.py → test_zkb.py} +109 -52
- killtracker/tests/models/test_killmails.py +0 -2
- killtracker/tests/models/test_trackers_1.py +24 -24
- killtracker/tests/models/test_trackers_2.py +6 -5
- killtracker/tests/models/test_webhooks.py +63 -0
- killtracker/tests/test_integration.py +25 -12
- killtracker/tests/test_tasks.py +161 -92
- killtracker/tests/test_utils.py +39 -0
- killtracker/tests/testdata/factories.py +1 -1
- killtracker/tests/testdata/helpers.py +1 -1
- killtracker/tests/utils.py +44 -0
- killtracker/exceptions.py +0 -32
- killtracker/tests/models/test_webhook.py +0 -150
- killtracker/tests/test_exceptions.py +0 -12
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0a1.dist-info}/WHEEL +0 -0
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0a1.dist-info}/licenses/LICENSE +0 -0
killtracker/models/webhooks.py
CHANGED
@@ -1,24 +1,20 @@
|
|
1
1
|
"""Webhooks models for killtracker."""
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import List, Optional
|
3
|
+
from typing import Optional
|
5
4
|
|
6
|
-
import dhooks_lite
|
7
5
|
from simple_mq import SimpleMQ
|
8
6
|
|
9
|
-
from django.core.cache import cache
|
10
7
|
from django.db import models
|
11
8
|
from django.utils.translation import gettext_lazy as _
|
12
9
|
|
13
10
|
from allianceauth.services.hooks import get_extension_logger
|
14
11
|
from app_utils.allianceauth import get_redis_client
|
15
|
-
from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
|
16
12
|
from app_utils.logging import LoggerAddTag
|
17
13
|
from app_utils.urls import static_file_absolute_url
|
18
14
|
|
19
|
-
from killtracker import
|
15
|
+
from killtracker import __title__
|
20
16
|
from killtracker.app_settings import KILLTRACKER_WEBHOOK_SET_AVATAR
|
21
|
-
from killtracker.
|
17
|
+
from killtracker.core.discord import DiscordMessage, send_message_to_webhook
|
22
18
|
from killtracker.managers import WebhookManager
|
23
19
|
|
24
20
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
@@ -27,8 +23,6 @@ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
|
27
23
|
class Webhook(models.Model):
|
28
24
|
"""A webhook to receive messages"""
|
29
25
|
|
30
|
-
HTTP_TOO_MANY_REQUESTS = 429
|
31
|
-
|
32
26
|
class WebhookType(models.IntegerChoices):
|
33
27
|
"""A webhook type."""
|
34
28
|
|
@@ -63,8 +57,8 @@ class Webhook(models.Model):
|
|
63
57
|
|
64
58
|
def __init__(self, *args, **kwargs) -> None:
|
65
59
|
super().__init__(*args, **kwargs)
|
66
|
-
self.
|
67
|
-
self.
|
60
|
+
self._main_queue = self._create_queue("main")
|
61
|
+
self._error_queue = self._create_queue("error")
|
68
62
|
|
69
63
|
def __str__(self) -> str:
|
70
64
|
return self.name
|
@@ -78,8 +72,8 @@ class Webhook(models.Model):
|
|
78
72
|
# method to avoid modifying the original state.
|
79
73
|
state = self.__dict__.copy()
|
80
74
|
# Remove the unpicklable entries.
|
81
|
-
del state["
|
82
|
-
del state["
|
75
|
+
del state["_main_queue"]
|
76
|
+
del state["_error_queue"]
|
83
77
|
return state
|
84
78
|
|
85
79
|
def __setstate__(self, state):
|
@@ -87,15 +81,15 @@ class Webhook(models.Model):
|
|
87
81
|
self.__dict__.update(state)
|
88
82
|
# Restore the previously opened file's state. To do so, we need to
|
89
83
|
# reopen it and read from it until the line count is restored.
|
90
|
-
self.
|
91
|
-
self.
|
84
|
+
self._main_queue = self._create_queue("main")
|
85
|
+
self._error_queue = self._create_queue("error")
|
92
86
|
|
93
87
|
def save(self, *args, **kwargs):
|
94
88
|
is_new = self.id is None # type: ignore
|
95
89
|
super().save(*args, **kwargs)
|
96
90
|
if is_new:
|
97
|
-
self.
|
98
|
-
self.
|
91
|
+
self._main_queue = self._create_queue("main")
|
92
|
+
self._error_queue = self._create_queue("error")
|
99
93
|
|
100
94
|
def _create_queue(self, suffix: str) -> Optional[SimpleMQ]:
|
101
95
|
redis_client = get_redis_client()
|
@@ -110,129 +104,67 @@ class Webhook(models.Model):
|
|
110
104
|
returns number of moved messages.
|
111
105
|
"""
|
112
106
|
counter = 0
|
113
|
-
if self.
|
107
|
+
if self._error_queue and self._main_queue:
|
114
108
|
while True:
|
115
|
-
message = self.
|
109
|
+
message = self._error_queue.dequeue()
|
116
110
|
if message is None:
|
117
111
|
break
|
118
112
|
|
119
|
-
self.
|
113
|
+
self._main_queue.enqueue(message)
|
120
114
|
counter += 1
|
121
115
|
|
122
116
|
return counter
|
123
117
|
|
124
|
-
def enqueue_message(
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
"""Enqueues a message to be send with this webhook"""
|
133
|
-
if not self.main_queue:
|
118
|
+
def enqueue_message(self, message: DiscordMessage, is_error: bool = False) -> int:
|
119
|
+
"""Enqueues a discord message to be send with this webhook.
|
120
|
+
|
121
|
+
Returns the updated number of messages in the main queue.
|
122
|
+
"""
|
123
|
+
q = self._error_queue if is_error else self._main_queue
|
124
|
+
|
125
|
+
if not q:
|
134
126
|
return 0
|
135
127
|
|
136
|
-
|
137
|
-
|
138
|
-
avatar_url = brand_url if KILLTRACKER_WEBHOOK_SET_AVATAR else avatar_url
|
139
|
-
return self.main_queue.enqueue(
|
140
|
-
self._discord_message_asjson(
|
141
|
-
content=content,
|
142
|
-
embeds=embeds,
|
143
|
-
tts=tts,
|
144
|
-
username=username,
|
145
|
-
avatar_url=avatar_url,
|
146
|
-
)
|
147
|
-
)
|
128
|
+
if KILLTRACKER_WEBHOOK_SET_AVATAR:
|
129
|
+
message.username = __title__
|
148
130
|
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
Raises ValueError if message is incomplete
|
160
|
-
"""
|
161
|
-
if not content and not embeds:
|
162
|
-
raise ValueError("Message must have content or embeds to be valid")
|
163
|
-
|
164
|
-
if embeds:
|
165
|
-
embeds_list = [obj.asdict() for obj in embeds]
|
166
|
-
else:
|
167
|
-
embeds_list = None
|
168
|
-
|
169
|
-
message = {}
|
170
|
-
if content:
|
171
|
-
message["content"] = content
|
172
|
-
if embeds_list:
|
173
|
-
message["embeds"] = embeds_list
|
174
|
-
if tts:
|
175
|
-
message["tts"] = tts
|
176
|
-
if username:
|
177
|
-
message["username"] = username
|
178
|
-
if avatar_url:
|
179
|
-
message["avatar_url"] = avatar_url
|
180
|
-
|
181
|
-
return json.dumps(message, cls=JSONDateTimeEncoder)
|
182
|
-
|
183
|
-
def send_message_to_webhook(self, message_json: str) -> dhooks_lite.WebhookResponse:
|
184
|
-
"""Send given message to webhook
|
185
|
-
|
186
|
-
Params
|
187
|
-
message_json: Discord message encoded in JSON
|
131
|
+
if KILLTRACKER_WEBHOOK_SET_AVATAR:
|
132
|
+
brand_url = static_file_absolute_url("killtracker/killtracker_logo.png")
|
133
|
+
message.avatar_url = brand_url
|
134
|
+
|
135
|
+
return q.enqueue(message.to_json())
|
136
|
+
|
137
|
+
def dequeue_message(self, is_error: bool = False) -> Optional[DiscordMessage]:
|
138
|
+
"""Dequeues a message from the main queue and return it.
|
139
|
+
|
140
|
+
Returns None if the queue is empty.
|
188
141
|
"""
|
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
|
-
logger.debug("headers: %s", response.headers)
|
216
|
-
logger.debug("status_code: %s", response.status_code)
|
217
|
-
logger.debug("content: %s", response.content)
|
218
|
-
if response.status_code == self.HTTP_TOO_MANY_REQUESTS:
|
219
|
-
logger.error(
|
220
|
-
"%s: Received too many requests error from API: %s",
|
221
|
-
self,
|
222
|
-
response.content,
|
223
|
-
)
|
224
|
-
try:
|
225
|
-
retry_after = int(response.headers["Retry-After"]) + 2
|
226
|
-
except (ValueError, KeyError):
|
227
|
-
retry_after = WebhookTooManyRequests.DEFAULT_RESET_AFTER
|
228
|
-
cache.set(
|
229
|
-
key=self._blocked_cache_key(), value="BLOCKED", timeout=retry_after
|
230
|
-
)
|
231
|
-
raise WebhookTooManyRequests(retry_after)
|
232
|
-
return response
|
233
|
-
|
234
|
-
def _blocked_cache_key(self) -> str:
|
235
|
-
return f"{__title__}_webhook_{self.pk}_blocked"
|
142
|
+
q = self._error_queue if is_error else self._main_queue
|
143
|
+
s = q.dequeue()
|
144
|
+
if not s:
|
145
|
+
return None
|
146
|
+
|
147
|
+
return DiscordMessage.from_json(s)
|
148
|
+
|
149
|
+
def messages_queued(self, is_error: bool = False) -> int:
|
150
|
+
"""Returns how many message are currently in the queue."""
|
151
|
+
q = self._error_queue if is_error else self._main_queue
|
152
|
+
if not q:
|
153
|
+
return 0
|
154
|
+
|
155
|
+
return q.size()
|
156
|
+
|
157
|
+
def delete_queued_messages(self, is_error: bool = False) -> int:
|
158
|
+
"""Deletes all messages in a queue and returns how many messages where deleted."""
|
159
|
+
q = self._error_queue if is_error else self._main_queue
|
160
|
+
if not q:
|
161
|
+
return 0
|
162
|
+
|
163
|
+
return q.clear()
|
164
|
+
|
165
|
+
def send_message(self, message: DiscordMessage) -> int:
|
166
|
+
"""Send a message to the webhook."""
|
167
|
+
return send_message_to_webhook(name=self.name, url=self.url, message=message)
|
236
168
|
|
237
169
|
@staticmethod
|
238
170
|
def create_message_link(name: str, url: str) -> str:
|
killtracker/providers.py
CHANGED
@@ -5,7 +5,7 @@ from esi.clients import EsiClientProvider
|
|
5
5
|
from allianceauth.services.hooks import get_extension_logger
|
6
6
|
from app_utils.logging import LoggerAddTag
|
7
7
|
|
8
|
-
from
|
8
|
+
from killtracker import USER_AGENT_TEXT, __title__
|
9
9
|
|
10
10
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
11
11
|
|
killtracker/signals.py
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
"""Module signals connects to celery signals."""
|
2
|
+
|
3
|
+
# pylint: disable=missing-function-docstring
|
4
|
+
|
5
|
+
from celery import signals
|
6
|
+
|
7
|
+
from allianceauth.services.hooks import get_extension_logger
|
8
|
+
from app_utils.logging import LoggerAddTag
|
9
|
+
|
10
|
+
from killtracker import __title__
|
11
|
+
from killtracker.core import workers
|
12
|
+
|
13
|
+
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
14
|
+
|
15
|
+
|
16
|
+
@signals.worker_ready.connect
|
17
|
+
def worker_ready_handler(sender, **kwargs):
|
18
|
+
workers.state_reset(sender.hostname)
|
19
|
+
logger.debug("worker_ready: %s", sender.hostname)
|
20
|
+
|
21
|
+
|
22
|
+
@signals.worker_shutting_down.connect
|
23
|
+
def worker_shutting_down_handler(sender, **kwargs):
|
24
|
+
workers.state_set(sender)
|
25
|
+
logger.debug("worker_shutting_down: %s", sender)
|
26
|
+
|
27
|
+
|
28
|
+
@signals.worker_shutdown.connect
|
29
|
+
def worker_shutdown_handler(sender, **kwargs):
|
30
|
+
workers.state_reset(sender.hostname)
|
31
|
+
logger.debug("worker_shutdown: %s", sender.hostname)
|
killtracker/tasks.py
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
"""Tasks for killtracker."""
|
2
2
|
|
3
|
-
|
3
|
+
import time
|
4
4
|
|
5
|
-
from celery import chain, shared_task
|
5
|
+
from celery import Task, chain, shared_task
|
6
6
|
|
7
7
|
from django.db import IntegrityError
|
8
8
|
from django.utils.timezone import now
|
@@ -11,58 +11,82 @@ from eveuniverse.tasks import update_unresolved_eve_entities
|
|
11
11
|
|
12
12
|
from allianceauth.services.hooks import get_extension_logger
|
13
13
|
from allianceauth.services.tasks import QueueOnce
|
14
|
-
from app_utils.caching import cached_queryset
|
15
14
|
from app_utils.esi import retry_task_if_esi_is_down
|
16
15
|
from app_utils.logging import LoggerAddTag
|
17
16
|
|
18
|
-
from
|
19
|
-
from .app_settings import (
|
17
|
+
from killtracker import __title__
|
18
|
+
from killtracker.app_settings import (
|
20
19
|
KILLTRACKER_DISCORD_SEND_DELAY,
|
21
20
|
KILLTRACKER_GENERATE_MESSAGE_MAX_RETRIES,
|
22
21
|
KILLTRACKER_GENERATE_MESSAGE_RETRY_COUNTDOWN,
|
23
22
|
KILLTRACKER_MAX_KILLMAILS_PER_RUN,
|
23
|
+
KILLTRACKER_MAX_MESSAGES_SENT_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
|
31
|
-
from .
|
32
|
-
|
30
|
+
from killtracker.core import workers
|
31
|
+
from killtracker.core.discord import (
|
32
|
+
DiscordMessage,
|
33
|
+
HTTPError,
|
34
|
+
WebhookRateLimitExhausted,
|
35
|
+
)
|
36
|
+
from killtracker.core.zkb import Killmail, KillmailDoesNotExist, ZKBTooManyRequestsError
|
37
|
+
from killtracker.models import EveKillmail, Tracker, Webhook
|
33
38
|
|
34
39
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
35
40
|
|
36
41
|
|
37
|
-
@shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
|
38
|
-
def run_killtracker(
|
39
|
-
"""
|
42
|
+
@shared_task(bind=True, base=QueueOnce, timeout=KILLTRACKER_TASKS_TIMEOUT)
|
43
|
+
def run_killtracker(self: Task) -> int:
|
44
|
+
"""Fetches and processes new killmails from ZKB API
|
45
|
+
and returns how many killmails were processed.
|
40
46
|
|
41
|
-
|
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
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
)
|
54
|
-
|
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
|
56
61
|
|
57
|
-
|
58
|
-
|
62
|
+
killmails_count = 0
|
63
|
+
for _ in range(KILLTRACKER_MAX_KILLMAILS_PER_RUN):
|
64
|
+
if is_timed_out():
|
65
|
+
break
|
66
|
+
|
67
|
+
if workers.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
|
86
|
+
|
87
|
+
killmails_count += 1
|
59
88
|
killmail.save()
|
60
|
-
|
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
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
105
|
-
|
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.
|
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
|
-
|
126
|
-
|
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
|
-
"""
|
150
|
-
|
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,63 +196,74 @@ 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
|
-
"""
|
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"])
|
167
203
|
|
168
204
|
|
169
205
|
@shared_task(
|
170
|
-
bind=True,
|
171
|
-
base=QueueOnce, # celery_once locks stay intact during retries
|
172
|
-
timeout=KILLTRACKER_TASKS_TIMEOUT,
|
173
|
-
retry_backoff=False,
|
174
|
-
max_retries=None,
|
206
|
+
bind=True, base=QueueOnce, timeout=KILLTRACKER_TASKS_TIMEOUT, max_retries=None
|
175
207
|
)
|
176
|
-
def send_messages_to_webhook(self, webhook_pk: int) -> None:
|
177
|
-
"""
|
208
|
+
def send_messages_to_webhook(self: Task, webhook_pk: int) -> None:
|
209
|
+
"""Sends queued messages to a webhook.
|
178
210
|
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
211
|
+
Note: This task will retry after processing a set number of messages
|
212
|
+
to avoid running potentially forever.
|
213
|
+
"""
|
214
|
+
|
215
|
+
webhook: Webhook = Webhook.objects.get(pk=webhook_pk)
|
183
216
|
if not webhook.is_enabled:
|
184
217
|
logger.info("%s: Webhook is disabled - aborting", webhook)
|
185
218
|
return
|
186
219
|
|
187
|
-
|
188
|
-
|
189
|
-
|
220
|
+
for _ in range(KILLTRACKER_MAX_MESSAGES_SENT_PER_RUN):
|
221
|
+
if workers.is_shutting_down(self):
|
222
|
+
logger.debug("Aborting due to worker shutdown")
|
223
|
+
return
|
224
|
+
|
225
|
+
message = webhook.dequeue_message()
|
226
|
+
if not message:
|
227
|
+
logger.debug("%s: No more messages to send for webhook", webhook)
|
228
|
+
break
|
229
|
+
|
190
230
|
try:
|
191
|
-
|
192
|
-
|
193
|
-
|
231
|
+
message_id = webhook.send_message(message)
|
232
|
+
|
233
|
+
except WebhookRateLimitExhausted as ex:
|
234
|
+
webhook.enqueue_message(message)
|
194
235
|
logger.warning(
|
195
|
-
"%s:
|
196
|
-
webhook,
|
197
|
-
ex.retry_after,
|
236
|
+
"%s: Webhook temporarily blocked. Retrying at %s.", webhook, ex.retry_at
|
198
237
|
)
|
199
|
-
|
238
|
+
raise self.retry(eta=ex.retry_at)
|
200
239
|
|
201
|
-
|
202
|
-
webhook.
|
240
|
+
except HTTPError as ex:
|
241
|
+
webhook.enqueue_message(message, is_error=True)
|
203
242
|
logger.warning(
|
204
|
-
"%s: Failed to send message to webhook, will retry. "
|
205
|
-
"HTTP status code: %d
|
243
|
+
"%s: Failed to send message for Killmail %d to webhook, will retry. "
|
244
|
+
"HTTP status code: %d",
|
206
245
|
webhook,
|
207
|
-
|
208
|
-
|
246
|
+
message.killmail_id,
|
247
|
+
ex.status_code,
|
209
248
|
)
|
249
|
+
continue
|
210
250
|
|
211
|
-
|
251
|
+
logger.info(
|
252
|
+
"%s: Discord message %s created for killmail %d",
|
253
|
+
webhook,
|
254
|
+
message_id,
|
255
|
+
message.killmail_id,
|
256
|
+
)
|
212
257
|
|
213
|
-
|
258
|
+
if webhook.messages_queued() > 0:
|
259
|
+
raise self.retry(countdown=KILLTRACKER_DISCORD_SEND_DELAY)
|
214
260
|
|
215
261
|
|
216
262
|
@shared_task(timeout=KILLTRACKER_TASKS_TIMEOUT)
|
217
263
|
def send_test_message_to_webhook(webhook_pk: int, count: int = 1) -> None:
|
218
|
-
"""
|
219
|
-
|
264
|
+
"""Send a test message to given webhook.
|
265
|
+
|
266
|
+
Optional inform user about result if user ok is given.
|
220
267
|
"""
|
221
268
|
try:
|
222
269
|
webhook = Webhook.objects.get(pk=webhook_pk)
|
@@ -224,8 +271,10 @@ def send_test_message_to_webhook(webhook_pk: int, count: int = 1) -> None:
|
|
224
271
|
logger.error("Webhook with pk = %s does not exist", webhook_pk)
|
225
272
|
return
|
226
273
|
|
227
|
-
logger.info("Sending %s test messages to webhook %s", count, webhook)
|
228
274
|
for num in range(count):
|
229
275
|
num_str = f"{num+1}/{count} " if count > 1 else ""
|
230
|
-
|
276
|
+
message = DiscordMessage(content=f"Test message {num_str}from {__title__}.")
|
277
|
+
webhook.enqueue_message(message)
|
278
|
+
|
231
279
|
send_messages_to_webhook.delay(webhook.pk)
|
280
|
+
logger.info("%s test messages submitted to webhook %s", count, webhook)
|