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.
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-0.18.0a2.dist-info}/METADATA +1 -1
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-0.18.0a2.dist-info}/RECORD +28 -24
- killtracker/__init__.py +1 -1
- killtracker/admin.py +13 -8
- killtracker/app_settings.py +7 -4
- killtracker/apps.py +2 -4
- killtracker/core/discord_messages.py +129 -55
- killtracker/core/killmails.py +92 -59
- killtracker/core/worker_shutdown.py +47 -0
- killtracker/forms.py +1 -1
- killtracker/managers.py +3 -3
- killtracker/models/trackers.py +5 -7
- killtracker/models/webhooks.py +71 -90
- killtracker/providers.py +1 -1
- killtracker/signals.py +31 -0
- killtracker/tasks.py +141 -90
- killtracker/tests/core/test_discord_messages_1.py +54 -7
- killtracker/tests/core/test_discord_messages_2.py +8 -8
- killtracker/tests/core/test_killmails.py +74 -29
- killtracker/tests/core/test_worker_shutdown.py +34 -0
- killtracker/tests/models/test_trackers_1.py +23 -23
- killtracker/tests/models/test_trackers_2.py +5 -4
- killtracker/tests/models/test_webhook.py +54 -40
- killtracker/tests/test_integration.py +2 -2
- killtracker/tests/test_tasks.py +107 -48
- killtracker/tests/utils.py +16 -0
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-0.18.0a2.dist-info}/WHEEL +0 -0
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-0.18.0a2.dist-info}/licenses/LICENSE +0 -0
killtracker/core/killmails.py
CHANGED
@@ -2,36 +2,35 @@
|
|
2
2
|
|
3
3
|
# pylint: disable = redefined-builtin
|
4
4
|
|
5
|
+
import datetime as dt
|
5
6
|
import json
|
6
7
|
from copy import deepcopy
|
7
8
|
from dataclasses import asdict, dataclass
|
8
|
-
from datetime import datetime
|
9
9
|
from http import HTTPStatus
|
10
|
+
from time import sleep
|
10
11
|
from typing import List, Optional, Set
|
11
12
|
from urllib.parse import quote_plus
|
12
13
|
|
13
14
|
import requests
|
14
15
|
from dacite import DaciteError, from_dict
|
15
|
-
from redis.exceptions import LockError
|
16
16
|
from simplejson.errors import JSONDecodeError
|
17
17
|
|
18
|
-
from django.conf import settings
|
19
18
|
from django.core.cache import cache
|
20
19
|
from django.core.exceptions import ImproperlyConfigured
|
21
20
|
from django.utils.dateparse import parse_datetime
|
21
|
+
from django.utils.timezone import now
|
22
22
|
from eveuniverse.models import EveType
|
23
23
|
|
24
24
|
from allianceauth.services.hooks import get_extension_logger
|
25
|
-
from app_utils.allianceauth import get_redis_client
|
26
25
|
from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
|
27
26
|
from app_utils.logging import LoggerAddTag
|
28
27
|
|
29
28
|
from killtracker import USER_AGENT_TEXT, __title__
|
30
29
|
from killtracker.app_settings import (
|
31
30
|
KILLTRACKER_QUEUE_ID,
|
32
|
-
KILLTRACKER_REDISQ_LOCK_TIMEOUT,
|
33
31
|
KILLTRACKER_REDISQ_TTW,
|
34
32
|
KILLTRACKER_STORAGE_KILLMAILS_LIFETIME,
|
33
|
+
KILLTRACKER_ZKB_REQUEST_DELAY,
|
35
34
|
)
|
36
35
|
from killtracker.exceptions import KillmailDoesNotExist
|
37
36
|
from killtracker.providers import esi
|
@@ -42,6 +41,7 @@ ZKB_REDISQ_URL = "https://zkillredisq.stream/listen.php"
|
|
42
41
|
ZKB_API_URL = "https://zkillboard.com/api/"
|
43
42
|
ZKB_KILLMAIL_BASEURL = "https://zkillboard.com/kill/"
|
44
43
|
REQUESTS_TIMEOUT = (5, 30)
|
44
|
+
DEFAULT_429_TIMEOUT = 10
|
45
45
|
|
46
46
|
MAIN_MINIMUM_COUNT = 2
|
47
47
|
MAIN_MINIMUM_SHARE = 0.25
|
@@ -49,6 +49,14 @@ MAIN_MINIMUM_SHARE = 0.25
|
|
49
49
|
# TODO: Factor out logic for accessing the API to another module
|
50
50
|
|
51
51
|
|
52
|
+
class ZKBTooManyRequestsError(Exception):
|
53
|
+
"""ZKB RedisQ API has returned 429 Too Many Requests HTTP status code."""
|
54
|
+
|
55
|
+
def __init__(self, retry_at: dt.datetime, is_original: bool = True):
|
56
|
+
self.retry_at = retry_at
|
57
|
+
self.is_original = is_original
|
58
|
+
|
59
|
+
|
52
60
|
@dataclass
|
53
61
|
class _KillmailBase:
|
54
62
|
"""Base class for all Killmail."""
|
@@ -160,7 +168,7 @@ class Killmail(_KillmailBase):
|
|
160
168
|
_STORAGE_BASE_KEY = "killtracker_storage_killmail_"
|
161
169
|
|
162
170
|
id: int
|
163
|
-
time: datetime
|
171
|
+
time: dt.datetime
|
164
172
|
victim: KillmailVictim
|
165
173
|
attackers: List[KillmailAttacker]
|
166
174
|
position: KillmailPosition
|
@@ -363,7 +371,10 @@ class Killmail(_KillmailBase):
|
|
363
371
|
|
364
372
|
@classmethod
|
365
373
|
def get(cls, id: int) -> "Killmail":
|
366
|
-
"""Fetch a killmail from temporary storage.
|
374
|
+
"""Fetch a killmail from temporary storage.
|
375
|
+
|
376
|
+
Raises KillmailDoesNotExist if killmail does not exit.
|
377
|
+
"""
|
367
378
|
data = cache.get(key=cls._storage_key(id))
|
368
379
|
if not data:
|
369
380
|
raise KillmailDoesNotExist(
|
@@ -391,9 +402,14 @@ class Killmail(_KillmailBase):
|
|
391
402
|
|
392
403
|
@classmethod
|
393
404
|
def create_from_zkb_redisq(cls) -> Optional["Killmail"]:
|
394
|
-
"""Fetches and returns a killmail from ZKB.
|
405
|
+
"""Fetches and returns a killmail from ZKB REDISQ API.
|
406
|
+
|
407
|
+
Will automatically wait for a free rate limit slot if needed.
|
408
|
+
Will re-raise TooManyRequests if a recent 429 timeout is not yet expired.
|
395
409
|
|
396
|
-
|
410
|
+
This method is not thread safe.
|
411
|
+
|
412
|
+
Returns None if no killmail was received.
|
397
413
|
"""
|
398
414
|
if not KILLTRACKER_QUEUE_ID:
|
399
415
|
raise ImproperlyConfigured(
|
@@ -403,51 +419,85 @@ class Killmail(_KillmailBase):
|
|
403
419
|
if "," in KILLTRACKER_QUEUE_ID:
|
404
420
|
raise ImproperlyConfigured("A queue ID must not contains commas.")
|
405
421
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
):
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
)
|
422
|
-
|
422
|
+
key_retry_at = "killtracker-retry-at"
|
423
|
+
if (v := cache.get(key_retry_at)) is not None:
|
424
|
+
try:
|
425
|
+
retry_at = dt.datetime.fromisoformat(v)
|
426
|
+
except (TypeError, ValueError):
|
427
|
+
cache.delete(key_retry_at)
|
428
|
+
logger.warning("unable to parse timestamp of retry at")
|
429
|
+
return None
|
430
|
+
|
431
|
+
if retry_at > now():
|
432
|
+
raise ZKBTooManyRequestsError(retry_at=retry_at, is_original=False)
|
433
|
+
|
434
|
+
key_last_request = "killtracker-last-request"
|
435
|
+
if (v := cache.get(key_last_request)) is not None:
|
436
|
+
try:
|
437
|
+
last_request = dt.datetime.fromisoformat(v)
|
438
|
+
except (TypeError, ValueError):
|
439
|
+
cache.delete(key_last_request)
|
440
|
+
logger.warning("unable to parse timestamp of last request")
|
441
|
+
last_request = now()
|
442
|
+
|
443
|
+
next_slot = last_request + dt.timedelta(
|
444
|
+
milliseconds=KILLTRACKER_ZKB_REQUEST_DELAY
|
445
|
+
)
|
446
|
+
seconds = (next_slot - now()).total_seconds()
|
447
|
+
if seconds > 0:
|
448
|
+
logger.debug("Waiting %f seconds to for next free slot", seconds)
|
449
|
+
sleep(seconds)
|
450
|
+
|
451
|
+
response = requests.get(
|
452
|
+
ZKB_REDISQ_URL,
|
453
|
+
params={
|
454
|
+
"queueID": quote_plus(KILLTRACKER_QUEUE_ID),
|
455
|
+
"ttw": KILLTRACKER_REDISQ_TTW,
|
456
|
+
},
|
457
|
+
timeout=REQUESTS_TIMEOUT,
|
458
|
+
headers={"User-Agent": USER_AGENT_TEXT},
|
459
|
+
)
|
460
|
+
cache.set(key_last_request, dt.datetime.isoformat(now()), timeout=30)
|
461
|
+
logger.debug(
|
462
|
+
"Response from ZKB API: %d %s %s",
|
463
|
+
response.status_code,
|
464
|
+
response.headers,
|
465
|
+
response.text,
|
466
|
+
)
|
467
|
+
|
468
|
+
if not response.ok:
|
423
469
|
logger.warning(
|
424
|
-
"
|
425
|
-
exc_info=settings.DEBUG, # provide details in DEBUG mode
|
470
|
+
"ZKB API returned error: %d %s", response.status_code, response.text
|
426
471
|
)
|
427
|
-
|
472
|
+
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
473
|
+
try:
|
474
|
+
retry_after = int(response.headers["Retry-After"])
|
475
|
+
except KeyError:
|
476
|
+
retry_after = DEFAULT_429_TIMEOUT
|
477
|
+
retry_at = now() + dt.timedelta(seconds=retry_after)
|
478
|
+
cache.set(key_retry_at, retry_at.isoformat(), timeout=retry_after + 60)
|
479
|
+
raise ZKBTooManyRequestsError(retry_at=retry_at, is_original=True)
|
428
480
|
|
429
|
-
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
430
|
-
logger.error("429 Client Error: Too many requests: %s", response.text)
|
431
481
|
return None
|
432
482
|
|
433
|
-
response.raise_for_status()
|
434
|
-
|
435
483
|
try:
|
436
484
|
data = response.json()
|
437
485
|
except JSONDecodeError:
|
438
|
-
logger.error("Error
|
486
|
+
logger.error("Error parsing ZKB API response:\n%s", response.text)
|
439
487
|
return None
|
440
488
|
|
441
|
-
if data:
|
442
|
-
logger.
|
489
|
+
if not data or "package" not in data or not data["package"]:
|
490
|
+
logger.info("ZKB did not return a killmail")
|
491
|
+
return None
|
443
492
|
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
493
|
+
package_data = data["package"]
|
494
|
+
km = cls._create_from_dict(package_data)
|
495
|
+
if km is not None:
|
496
|
+
logger.info("ZKB returned killmail %d", km.id)
|
497
|
+
else:
|
498
|
+
logger.info("Failed to parse killmail from ZKB")
|
448
499
|
|
449
|
-
|
450
|
-
return None
|
500
|
+
return km
|
451
501
|
|
452
502
|
@classmethod
|
453
503
|
def create_from_zkb_api(cls, killmail_id: int) -> Optional["Killmail"]:
|
@@ -601,20 +651,3 @@ class Killmail(_KillmailBase):
|
|
601
651
|
params[prop] = zkb_data[prop]
|
602
652
|
|
603
653
|
return KillmailZkb(**params)
|
604
|
-
|
605
|
-
@staticmethod
|
606
|
-
def lock_key() -> str:
|
607
|
-
"""Key used for lock operation on Redis."""
|
608
|
-
return f"{__title__.upper()}_REDISQ_LOCK"
|
609
|
-
|
610
|
-
@classmethod
|
611
|
-
def reset_lock_key(cls):
|
612
|
-
"""Delete lock key if it exists.
|
613
|
-
|
614
|
-
It can happen that a lock key is not cleaned up
|
615
|
-
and then prevents this class from ever acquiring a lock again.
|
616
|
-
To prevent this we are deleting the lock key at system start.
|
617
|
-
"""
|
618
|
-
redis = get_redis_client()
|
619
|
-
if redis.delete(cls.lock_key()) > 0:
|
620
|
-
logger.warning("A stuck lock key was cleared.")
|
@@ -0,0 +1,47 @@
|
|
1
|
+
"""Module worker_shutdown allows tasks to find out
|
2
|
+
whether their worker is currently shutting down.
|
3
|
+
|
4
|
+
This enables long running tasks to abort early,
|
5
|
+
which helps to speed up a warm worker shutdown.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from celery import Task
|
9
|
+
|
10
|
+
from django.core.cache import cache
|
11
|
+
|
12
|
+
from allianceauth.services.hooks import get_extension_logger
|
13
|
+
from app_utils.logging import LoggerAddTag
|
14
|
+
|
15
|
+
from killtracker import __title__
|
16
|
+
|
17
|
+
_TIMEOUT_SECONDS = 120
|
18
|
+
|
19
|
+
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
20
|
+
|
21
|
+
|
22
|
+
def reset(hostname: str) -> None:
|
23
|
+
"""Resets the shutting down state for a worker."""
|
24
|
+
cache.delete(_make_key(hostname))
|
25
|
+
|
26
|
+
|
27
|
+
def set(hostname: str) -> None:
|
28
|
+
"""Sets a worker into the shutting down state."""
|
29
|
+
cache.set(_make_key(hostname), "shutting down", timeout=_TIMEOUT_SECONDS)
|
30
|
+
|
31
|
+
|
32
|
+
def is_shutting_down(task: Task) -> bool:
|
33
|
+
"""Reports whether the worker of a celery task is currently shutting down."""
|
34
|
+
try:
|
35
|
+
hostname = str(task.request.hostname)
|
36
|
+
except (AttributeError, TypeError, ValueError):
|
37
|
+
logger.warning("Failed to retrieve hostname: %s", task)
|
38
|
+
return False
|
39
|
+
|
40
|
+
if cache.get(_make_key(hostname)) is None:
|
41
|
+
return False
|
42
|
+
|
43
|
+
return True
|
44
|
+
|
45
|
+
|
46
|
+
def _make_key(hostname: str) -> str:
|
47
|
+
return f"killtracker-worker-shutting-down-{hostname}"
|
killtracker/forms.py
CHANGED
@@ -7,7 +7,7 @@ from django.core.exceptions import ValidationError
|
|
7
7
|
from django.forms.widgets import TextInput
|
8
8
|
from django.utils.translation import gettext_lazy as _
|
9
9
|
|
10
|
-
from .models import Tracker
|
10
|
+
from killtracker.models import Tracker
|
11
11
|
|
12
12
|
|
13
13
|
def field_nice_display(name: str) -> str:
|
killtracker/managers.py
CHANGED
@@ -13,9 +13,9 @@ from allianceauth.services.hooks import get_extension_logger
|
|
13
13
|
from app_utils.caching import ObjectCacheMixin
|
14
14
|
from app_utils.logging import LoggerAddTag
|
15
15
|
|
16
|
-
from
|
17
|
-
from .app_settings import KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS
|
18
|
-
from .core.killmails import Killmail, _KillmailCharacter
|
16
|
+
from killtracker import __title__
|
17
|
+
from killtracker.app_settings import KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS
|
18
|
+
from killtracker.core.killmails import Killmail, _KillmailCharacter
|
19
19
|
|
20
20
|
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
21
21
|
|
killtracker/models/trackers.py
CHANGED
@@ -28,6 +28,7 @@ from app_utils.logging import LoggerAddTag
|
|
28
28
|
from killtracker import __title__
|
29
29
|
from killtracker.app_settings import KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER
|
30
30
|
from killtracker.constants import EveCategoryId, EveGroupId
|
31
|
+
from killtracker.core.discord_messages import DiscordMessage
|
31
32
|
from killtracker.core.killmails import Killmail
|
32
33
|
from killtracker.managers import TrackerManager
|
33
34
|
|
@@ -881,12 +882,9 @@ class Tracker(models.Model):
|
|
881
882
|
def generate_killmail_message(
|
882
883
|
self, killmail: Killmail, intro_text: Optional[str] = None
|
883
884
|
) -> int:
|
884
|
-
"""
|
885
|
+
"""Generate a message from given killmail and enqueue for later sending.
|
885
886
|
|
886
|
-
|
887
|
+
Returns the new queue size.
|
887
888
|
"""
|
888
|
-
|
889
|
-
|
890
|
-
content = discord_messages.create_content(self, intro_text)
|
891
|
-
embed = discord_messages.create_embed(self, killmail)
|
892
|
-
return self.webhook.enqueue_message(content=content, embeds=[embed])
|
889
|
+
message = DiscordMessage.from_killmail(self, killmail, intro_text)
|
890
|
+
return self.webhook.enqueue_message(message)
|
killtracker/models/webhooks.py
CHANGED
@@ -1,7 +1,6 @@
|
|
1
1
|
"""Webhooks models for killtracker."""
|
2
2
|
|
3
|
-
import
|
4
|
-
from typing import List, Optional
|
3
|
+
from typing import Optional
|
5
4
|
|
6
5
|
import dhooks_lite
|
7
6
|
from simple_mq import SimpleMQ
|
@@ -12,12 +11,12 @@ from django.utils.translation import gettext_lazy as _
|
|
12
11
|
|
13
12
|
from allianceauth.services.hooks import get_extension_logger
|
14
13
|
from app_utils.allianceauth import get_redis_client
|
15
|
-
from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
|
16
14
|
from app_utils.logging import LoggerAddTag
|
17
15
|
from app_utils.urls import static_file_absolute_url
|
18
16
|
|
19
17
|
from killtracker import APP_NAME, HOMEPAGE_URL, __title__, __version__
|
20
18
|
from killtracker.app_settings import KILLTRACKER_WEBHOOK_SET_AVATAR
|
19
|
+
from killtracker.core.discord_messages import DiscordMessage
|
21
20
|
from killtracker.exceptions import WebhookTooManyRequests
|
22
21
|
from killtracker.managers import WebhookManager
|
23
22
|
|
@@ -63,8 +62,8 @@ class Webhook(models.Model):
|
|
63
62
|
|
64
63
|
def __init__(self, *args, **kwargs) -> None:
|
65
64
|
super().__init__(*args, **kwargs)
|
66
|
-
self.
|
67
|
-
self.
|
65
|
+
self._main_queue = self._create_queue("main")
|
66
|
+
self._error_queue = self._create_queue("error")
|
68
67
|
|
69
68
|
def __str__(self) -> str:
|
70
69
|
return self.name
|
@@ -78,8 +77,8 @@ class Webhook(models.Model):
|
|
78
77
|
# method to avoid modifying the original state.
|
79
78
|
state = self.__dict__.copy()
|
80
79
|
# Remove the unpicklable entries.
|
81
|
-
del state["
|
82
|
-
del state["
|
80
|
+
del state["_main_queue"]
|
81
|
+
del state["_error_queue"]
|
83
82
|
return state
|
84
83
|
|
85
84
|
def __setstate__(self, state):
|
@@ -87,15 +86,15 @@ class Webhook(models.Model):
|
|
87
86
|
self.__dict__.update(state)
|
88
87
|
# Restore the previously opened file's state. To do so, we need to
|
89
88
|
# reopen it and read from it until the line count is restored.
|
90
|
-
self.
|
91
|
-
self.
|
89
|
+
self._main_queue = self._create_queue("main")
|
90
|
+
self._error_queue = self._create_queue("error")
|
92
91
|
|
93
92
|
def save(self, *args, **kwargs):
|
94
93
|
is_new = self.id is None # type: ignore
|
95
94
|
super().save(*args, **kwargs)
|
96
95
|
if is_new:
|
97
|
-
self.
|
98
|
-
self.
|
96
|
+
self._main_queue = self._create_queue("main")
|
97
|
+
self._error_queue = self._create_queue("error")
|
99
98
|
|
100
99
|
def _create_queue(self, suffix: str) -> Optional[SimpleMQ]:
|
101
100
|
redis_client = get_redis_client()
|
@@ -110,94 +109,70 @@ class Webhook(models.Model):
|
|
110
109
|
returns number of moved messages.
|
111
110
|
"""
|
112
111
|
counter = 0
|
113
|
-
if self.
|
112
|
+
if self._error_queue and self._main_queue:
|
114
113
|
while True:
|
115
|
-
message = self.
|
114
|
+
message = self._error_queue.dequeue()
|
116
115
|
if message is None:
|
117
116
|
break
|
118
117
|
|
119
|
-
self.
|
118
|
+
self._main_queue.enqueue(message)
|
120
119
|
counter += 1
|
121
120
|
|
122
121
|
return counter
|
123
122
|
|
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:
|
123
|
+
def enqueue_message(self, message: DiscordMessage, is_error: bool = False) -> int:
|
124
|
+
"""Enqueues a discord message to be send with this webhook.
|
125
|
+
|
126
|
+
Returns the updated number of messages in the main queue.
|
127
|
+
"""
|
128
|
+
q = self._error_queue if is_error else self._main_queue
|
129
|
+
|
130
|
+
if not q:
|
134
131
|
return 0
|
135
132
|
|
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
|
-
)
|
133
|
+
if KILLTRACKER_WEBHOOK_SET_AVATAR:
|
134
|
+
message.username = __title__
|
148
135
|
|
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
|
136
|
+
if KILLTRACKER_WEBHOOK_SET_AVATAR:
|
137
|
+
brand_url = static_file_absolute_url("killtracker/killtracker_logo.png")
|
138
|
+
message.avatar_url = brand_url
|
139
|
+
|
140
|
+
return q.enqueue(message.to_json())
|
141
|
+
|
142
|
+
def dequeue_message(self, is_error: bool = False) -> Optional[DiscordMessage]:
|
143
|
+
"""Dequeues a message from the main queue and return it.
|
144
|
+
|
145
|
+
Returns None if the queue is empty.
|
188
146
|
"""
|
147
|
+
q = self._error_queue if is_error else self._main_queue
|
148
|
+
s = q.dequeue()
|
149
|
+
if not s:
|
150
|
+
return None
|
151
|
+
|
152
|
+
return DiscordMessage.from_json(s)
|
153
|
+
|
154
|
+
def messages_queued(self, is_error: bool = False) -> int:
|
155
|
+
"""Returns how many message are currently in the queue."""
|
156
|
+
q = self._error_queue if is_error else self._main_queue
|
157
|
+
if not q:
|
158
|
+
return 0
|
159
|
+
|
160
|
+
return q.size()
|
161
|
+
|
162
|
+
def delete_queued_messages(self, is_error: bool = False) -> int:
|
163
|
+
"""Deletes all messages in a queue and returns how many messages where deleted."""
|
164
|
+
q = self._error_queue if is_error else self._main_queue
|
165
|
+
if not q:
|
166
|
+
return 0
|
167
|
+
|
168
|
+
return q.clear()
|
169
|
+
|
170
|
+
def send_message(self, message: DiscordMessage) -> dhooks_lite.WebhookResponse:
|
171
|
+
"""Send a message to the webhook."""
|
189
172
|
timeout = cache.ttl(self._blocked_cache_key()) # type: ignore
|
190
173
|
if timeout:
|
191
174
|
raise WebhookTooManyRequests(timeout)
|
192
175
|
|
193
|
-
message = json.loads(message_json, cls=JSONDateTimeDecoder)
|
194
|
-
if message.get("embeds"):
|
195
|
-
embeds = [
|
196
|
-
dhooks_lite.Embed.from_dict(embed_dict)
|
197
|
-
for embed_dict in message.get("embeds")
|
198
|
-
]
|
199
|
-
else:
|
200
|
-
embeds = None
|
201
176
|
hook = dhooks_lite.Webhook(
|
202
177
|
url=self.url,
|
203
178
|
user_agent=dhooks_lite.UserAgent(
|
@@ -205,16 +180,21 @@ class Webhook(models.Model):
|
|
205
180
|
),
|
206
181
|
)
|
207
182
|
response = hook.execute(
|
208
|
-
content=message.
|
209
|
-
embeds=embeds,
|
210
|
-
username=message.
|
211
|
-
avatar_url=message.
|
183
|
+
content=message.content,
|
184
|
+
embeds=message.embeds,
|
185
|
+
username=message.username,
|
186
|
+
avatar_url=message.avatar_url,
|
212
187
|
wait_for_response=True,
|
213
188
|
max_retries=0, # we will handle retries ourselves
|
214
189
|
)
|
215
|
-
logger.debug(
|
216
|
-
|
217
|
-
|
190
|
+
logger.debug(
|
191
|
+
"%s: Response from Discord for creating message from killmail %d: %s %s %s",
|
192
|
+
self,
|
193
|
+
message.killmail_id,
|
194
|
+
response.status_code,
|
195
|
+
response.headers,
|
196
|
+
response.content,
|
197
|
+
)
|
218
198
|
if response.status_code == self.HTTP_TOO_MANY_REQUESTS:
|
219
199
|
logger.error(
|
220
200
|
"%s: Received too many requests error from API: %s",
|
@@ -229,6 +209,7 @@ class Webhook(models.Model):
|
|
229
209
|
key=self._blocked_cache_key(), value="BLOCKED", timeout=retry_after
|
230
210
|
)
|
231
211
|
raise WebhookTooManyRequests(retry_after)
|
212
|
+
|
232
213
|
return response
|
233
214
|
|
234
215
|
def _blocked_cache_key(self) -> 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 worker_shutdown
|
12
|
+
|
13
|
+
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
14
|
+
|
15
|
+
|
16
|
+
@signals.worker_ready.connect
|
17
|
+
def worker_ready_handler(sender, **kwargs):
|
18
|
+
worker_shutdown.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
|
+
worker_shutdown.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
|
+
worker_shutdown.reset(sender.hostname)
|
31
|
+
logger.debug("worker_shutdown: %s", sender.hostname)
|