aa-killtracker 0.17.0__py3-none-any.whl → 1.0.0__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.0.dist-info}/METADATA +7 -8
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.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.0.dist-info}/WHEEL +0 -0
- {aa_killtracker-0.17.0.dist-info → aa_killtracker-1.0.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,12 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: aa-killtracker
|
3
|
-
Version: 0.
|
3
|
+
Version: 1.0.0
|
4
4
|
Summary: An app for running killmail trackers with Alliance Auth and Discord.
|
5
5
|
Author-email: Erik Kalkoken <kalkoken87@gmail.com>
|
6
6
|
Requires-Python: >=3.8
|
7
7
|
Description-Content-Type: text/markdown
|
8
8
|
Classifier: Environment :: Web Environment
|
9
9
|
Classifier: Framework :: Django
|
10
|
-
Classifier: Framework :: Django :: 4.0
|
11
10
|
Classifier: Framework :: Django :: 4.2
|
12
11
|
Classifier: Intended Audience :: End Users/Desktop
|
13
12
|
Classifier: License :: OSI Approved :: MIT License
|
@@ -17,15 +16,16 @@ Classifier: Programming Language :: Python :: 3.8
|
|
17
16
|
Classifier: Programming Language :: Python :: 3.9
|
18
17
|
Classifier: Programming Language :: Python :: 3.10
|
19
18
|
Classifier: Programming Language :: Python :: 3.11
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
20
20
|
Classifier: Topic :: Internet :: WWW/HTTP
|
21
21
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
22
22
|
License-File: LICENSE
|
23
|
-
Requires-Dist: allianceauth-app-utils>=1.
|
24
|
-
Requires-Dist: allianceauth>=
|
23
|
+
Requires-Dist: allianceauth-app-utils>=1.26
|
24
|
+
Requires-Dist: allianceauth>=4,<5
|
25
25
|
Requires-Dist: dacite
|
26
|
-
Requires-Dist: dhooks-lite>=1.
|
27
|
-
Requires-Dist: django-eveuniverse>=1.
|
28
|
-
Requires-Dist: redis-simple-mq>=0
|
26
|
+
Requires-Dist: dhooks-lite>=1.1
|
27
|
+
Requires-Dist: django-eveuniverse>=1.5
|
28
|
+
Requires-Dist: redis-simple-mq>=1.0
|
29
29
|
Project-URL: Home, https://gitlab.com/ErikKalkoken/aa-killtracker
|
30
30
|
|
31
31
|
# Killtracker
|
@@ -242,7 +242,6 @@ Note that all settings are optional and the app will use the documented default
|
|
242
242
|
Name | Description | Default
|
243
243
|
-- | -- | --
|
244
244
|
`KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER`| Ignore killmails that are older than the given number in minutes. Sometimes killmails appear belated on ZKB, this feature ensures they don't create new alerts | `60`
|
245
|
-
`KILLTRACKER_MAX_KILLMAILS_PER_RUN`| Maximum number of killmails retrieved from ZKB by task run. This value should be set such that the task that fetches new killmails from ZKB every minute will reliable finish within one minute. To test this run a "Catch all" tracker and see how many killmails your system is capable of processing. Note that you can get that information from the worker's log file. It will look something like this: `Total killmails received from ZKB in 49 secs: 100` | `100`
|
246
245
|
`KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS`| Killmails older than set number of days will be purged from the database. If you want to keep all killmails set this to 0. Note that this setting is only relevant if you have storing killmails enabled. | `30`
|
247
246
|
`KILLTRACKER_QUEUE_ID`| Unique ID used to identify this server when fetching killmails from zKillboard. This setting is mandatory. | ``
|
248
247
|
`KILLTRACKER_STORING_KILLMAILS_ENABLED`| If set to true Killtracker will automatically store all received killmails in the local database. This can be useful if you want to run analytics on killmails etc. However, please note that Killtracker itself currently does not use stored killmails in any way. | `False`
|
@@ -1,17 +1,20 @@
|
|
1
|
-
killtracker/__init__.py,sha256=
|
2
|
-
killtracker/admin.py,sha256=
|
3
|
-
killtracker/app_settings.py,sha256=
|
4
|
-
killtracker/apps.py,sha256=
|
1
|
+
killtracker/__init__.py,sha256=Pi-3zSugKHOaitGQHTp9XWoyaiw6k0pRhcNTmCdrBlg,354
|
2
|
+
killtracker/admin.py,sha256=IH04Q1etHUnXSJIhm3bnGfIbSU6lPSSaltLERb-OkYg,14229
|
3
|
+
killtracker/app_settings.py,sha256=KqWSS4OVKIa7DmBAa5AbwxFVXw5EQebPfBtPo1UJYgY,4295
|
4
|
+
killtracker/apps.py,sha256=lWolk2CVEHPNIMEylbK2fSuCSTRW1n6LBmiDjLoqqq4,384
|
5
5
|
killtracker/checks.py,sha256=ytqWlhIbCsvGKi3xFbUvAZzbUlgOhiOrB67PZ2WfxM4,1311
|
6
6
|
killtracker/constants.py,sha256=W7-VARE3B4_sdzV_9k7qunUzlxTcdSAeSp_ZJVnbUhw,2514
|
7
|
-
killtracker/
|
8
|
-
killtracker/
|
9
|
-
killtracker/
|
10
|
-
killtracker/
|
11
|
-
killtracker/tasks.py,sha256=
|
7
|
+
killtracker/forms.py,sha256=SUM0U-Sa6FPcGVpsYJbVxHfEtf7hb1Qc_l92Zfa1OLk,5193
|
8
|
+
killtracker/managers.py,sha256=hrmpiuMzK-7p-AsT4ee0mMEhQv4b57AGmhlRTxEl7wk,4416
|
9
|
+
killtracker/providers.py,sha256=3DiOlklBq5kLGDV9nMlMbP2-S8AodNgR6gtB4cWWCUo,358
|
10
|
+
killtracker/signals.py,sha256=rFCg-WYMVPmAhuqX8KHNaicsP1nyj_HYA2O4Qr8wqhk,892
|
11
|
+
killtracker/tasks.py,sha256=9ZqItmm2tlzVA-lelNTyzpPuBYCUvfZR2nCV16Xu6fA,9267
|
12
12
|
killtracker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
13
|
-
killtracker/core/
|
14
|
-
killtracker/core/
|
13
|
+
killtracker/core/discord.py,sha256=cJY1qZ1ULlndliqn304B2deZkgL8VRqnAN_E5bXbTPk,5330
|
14
|
+
killtracker/core/helpers.py,sha256=Ulwhz96IopCCNPD6m7ylz9IGnxknIvLSCfcLrmJ8uDE,335
|
15
|
+
killtracker/core/trackers.py,sha256=ZENNdZ96L9AP3nIaP3_s9uFIVRgq_kLPGLK-rKT5gig,14693
|
16
|
+
killtracker/core/workers.py,sha256=m9Vp7C-eXSfQpf0ugzTfuqcPBPKvarfds_4mb-JwJ-o,1278
|
17
|
+
killtracker/core/zkb.py,sha256=n9eD9RT5xZ3Mc88ItoaQE_1lJAniqpr_oyps47sdGYQ,21481
|
15
18
|
killtracker/management/commands/killtracker_load_eve.py,sha256=5P2wr6LU-EMl9_gG-DKP2yw3eFXOU7ApeuGAWEbvCKk,1159
|
16
19
|
killtracker/migrations/0001_initial_new.py,sha256=WszI5DmNFtk45IZ2Zul3_Ak-VXmye9ffJKFAsGIxoXA,27818
|
17
20
|
killtracker/migrations/0001_squashed_all.py,sha256=KXwOcRayjGGGmleg7-aTcXeIi-92Mn4BaGkEY3iE9Vc,26221
|
@@ -29,39 +32,43 @@ killtracker/migrations/0009_remove_old_models.py,sha256=bDIz0hywpFKbHYhdXFXzra5D
|
|
29
32
|
killtracker/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
30
33
|
killtracker/models/__init__.py,sha256=WL6VD9MK7EsZot_RLPz0nX8WxJkp7RtQpw_mYpnVqu8,188
|
31
34
|
killtracker/models/killmails.py,sha256=CCdrHkwUSoTOGqavG-TpMxDRleF9UEacOTui8v3-VT8,5181
|
32
|
-
killtracker/models/trackers.py,sha256=
|
33
|
-
killtracker/models/webhooks.py,sha256=
|
35
|
+
killtracker/models/trackers.py,sha256=wo6KWl1GOASOquTTdAeo5DaZgRzKai1ek1GjQ49oOhY,31154
|
36
|
+
killtracker/models/webhooks.py,sha256=6S1wXwlOMth56oU0hsYH5PiimECMwZyLXiKok2yQo_g,5850
|
34
37
|
killtracker/static/killtracker/killtracker_logo.png,sha256=3jc9zmYHqP60Np5piP5RfkX0_II-315DNjZ4FRGbqKc,74625
|
35
38
|
killtracker/static/killtracker/zkb_icon.png,sha256=wuVfgyTbTs9qS4KGbDAH3Q9KVPgHqYV5eaVrjEmTjsE,328
|
36
39
|
killtracker/templates/admin/killtracker/tracker/killmail_test.html,sha256=TI2ON8qf9pW4rX6G7pT990ZadkipwncQgGUC0FPcWQY,649
|
37
40
|
killtracker/tests/__init__.py,sha256=CEt5qqp6ptDrx1lwuA6epZ5bzh1CwUJYXeMZqWK9jT0,278
|
38
41
|
killtracker/tests/test_admin.py,sha256=XFAlF_Q4h5Z_agkmRtB30GmCayqQPblLolFrN41xu7Q,5659
|
39
42
|
killtracker/tests/test_admin_2.py,sha256=rP0EscM9qUil_ZuXute3R8TQY2P5XSjmxHLbziCGcVw,1140
|
40
|
-
killtracker/tests/
|
41
|
-
killtracker/tests/
|
42
|
-
killtracker/tests/
|
43
|
+
killtracker/tests/test_integration.py,sha256=fOAOu-lKYFAk0hYYT-V4F3KgmPcrD7JgcR-w-z3lStg,3516
|
44
|
+
killtracker/tests/test_tasks.py,sha256=hTxXKoza-hgAUsK2zj5qMkl4igHKB7SB2OkPFJtgXeA,16274
|
45
|
+
killtracker/tests/test_utils.py,sha256=1TA0pzCpq8CAKRBTi_xZ9_45n3rPx7UclkgFPzT1VFY,1130
|
46
|
+
killtracker/tests/utils.py,sha256=auwXHSpLwI_Qi3NmQUOCrIAcEsuw04ZaNJW82-jD4DU,1139
|
43
47
|
killtracker/tests/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
44
|
-
killtracker/tests/core/
|
45
|
-
killtracker/tests/core/
|
46
|
-
killtracker/tests/core/
|
48
|
+
killtracker/tests/core/test_discord.py,sha256=w_QU1kVfo4uKl1xbj1C5NFQuPqASiveMjf0Q0fEIRWY,6163
|
49
|
+
killtracker/tests/core/test_helpers.py,sha256=pQSi9QBOZWOem9NMzcO-qmc_EIWyhyA_aOevuLXrBk4,613
|
50
|
+
killtracker/tests/core/test_tracker_1.py,sha256=EjGX2XyHDMAfhevUwi90YcPBSUhTlDv1c7bMgv4qUX0,3528
|
51
|
+
killtracker/tests/core/test_tracker_2.py,sha256=ZQ3bZ4tvG3Xf_z8pMQllVlAcWOaltSP2BzqmJAezBEc,4748
|
52
|
+
killtracker/tests/core/test_workers.py,sha256=stId22PF5VgrSOpCJV5ZiDak7eKM5rNOS7pbx1QNp6Q,1722
|
53
|
+
killtracker/tests/core/test_zkb.py,sha256=lfsJlQgtX-V4rmnkwF60-c2IeRO2tRdead0RAS6WMQg,16557
|
47
54
|
killtracker/tests/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
48
|
-
killtracker/tests/models/test_killmails.py,sha256=
|
49
|
-
killtracker/tests/models/test_trackers_1.py,sha256=
|
50
|
-
killtracker/tests/models/test_trackers_2.py,sha256
|
51
|
-
killtracker/tests/models/
|
55
|
+
killtracker/tests/models/test_killmails.py,sha256=pNJ7vSLqSl6BbokePdJnQNFWIDwbxkxugpP-QVoJcKQ,8247
|
56
|
+
killtracker/tests/models/test_trackers_1.py,sha256=gqywaysnijyF_DT2TdiSJuLc1FUegftnO1pYECMX_Tc,42788
|
57
|
+
killtracker/tests/models/test_trackers_2.py,sha256=-iVWnyFvoRb9Y7yKskjc5mmUl3XX53jdQYo8fri4TgA,5458
|
58
|
+
killtracker/tests/models/test_webhooks.py,sha256=PtbTxNfYH-NKpLnLZrzbOOqHlJ9vY4Gy19tf2TsHbzI,2597
|
52
59
|
killtracker/tests/testdata/__init__.py,sha256=9aQhf8V-DseZMWjJ_QMXOba6CypoHFSVpYRZEUh-oFc,212
|
53
60
|
killtracker/tests/testdata/create_eveuniverse.py,sha256=Ewr5OBiQwjK1kwDCRh3A3f0Z-CCPi8Gp8dpsPgVAU4A,966
|
54
61
|
killtracker/tests/testdata/evealliances.json,sha256=i6udrtUnWQjn71Iw17WWz9Eb1fdGUNhI1UYLt0hZHog,307
|
55
62
|
killtracker/tests/testdata/evecorporations.json,sha256=Hk4WuplZl69rQcquOU-15lgUyvHRMZW0fnYfKz8oeWw,1229
|
56
63
|
killtracker/tests/testdata/eveentities.json,sha256=0yStlAMEhednEiMKvKOYFsyRovtsGxw6IpWAyvnQXv4,1459
|
57
64
|
killtracker/tests/testdata/eveuniverse.json,sha256=JagrJ-asKlkxrqj3dtAth6d5bhIpeEmlvBHgXjLTMSI,36117
|
58
|
-
killtracker/tests/testdata/factories.py,sha256=
|
59
|
-
killtracker/tests/testdata/helpers.py,sha256=
|
65
|
+
killtracker/tests/testdata/factories.py,sha256=MEJLfslB13J_UnN7TXASL6poH1fkQ1NW5dQX_xtfPvA,11013
|
66
|
+
killtracker/tests/testdata/helpers.py,sha256=bsHv2HlbORuw5kU8qZ806nXPM0hqNUuj0qVQCm4KoZ8,4735
|
60
67
|
killtracker/tests/testdata/killmails.json,sha256=HKfpXQJ_ikymBSewh4wWixYpLwC3kNBqi04cCms1AjA,45366
|
61
68
|
killtracker/tests/testdata/load_eveuniverse.py,sha256=FitVc12E_ob56ezK5Y1q07Kbyc7N8poHiCNaLRGU4Jg,388
|
62
69
|
killtracker/tools/drop_tables_killtracker.sql,sha256=fJrGY23NKRunpYC3hI-klFkWub4sBuhfP8TaT__zjPg,1726
|
63
70
|
killtracker/tools/generate_conditions_text.py,sha256=LxAtTQIX-IbuqaNDMV1XHhq5AbrrlwxLWgXa1M7DWQo,767
|
64
|
-
aa_killtracker-0.
|
65
|
-
aa_killtracker-0.
|
66
|
-
aa_killtracker-0.
|
67
|
-
aa_killtracker-0.
|
71
|
+
aa_killtracker-1.0.0.dist-info/licenses/LICENSE,sha256=XZiwB_S_40_HhnvLg5xvtBb3g1oGjPrk0rpFwk8iInE,1070
|
72
|
+
aa_killtracker-1.0.0.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
|
73
|
+
aa_killtracker-1.0.0.dist-info/METADATA,sha256=of14ijiJxhikyRX3EjazGngQlvdynnaeZFKyJXK3Qf8,13442
|
74
|
+
aa_killtracker-1.0.0.dist-info/RECORD,,
|
killtracker/__init__.py
CHANGED
killtracker/admin.py
CHANGED
@@ -4,16 +4,21 @@
|
|
4
4
|
|
5
5
|
|
6
6
|
from django.contrib import admin
|
7
|
+
from django.db.models import QuerySet
|
7
8
|
from django.http import HttpResponseRedirect
|
8
9
|
from django.shortcuts import render
|
9
10
|
from django.utils.safestring import mark_safe
|
10
11
|
|
11
12
|
from allianceauth import NAME as site_header
|
12
13
|
|
13
|
-
from
|
14
|
-
from .core.
|
15
|
-
from .forms import
|
16
|
-
|
14
|
+
from killtracker import tasks
|
15
|
+
from killtracker.core.zkb import Killmail
|
16
|
+
from killtracker.forms import (
|
17
|
+
TrackerAdminForm,
|
18
|
+
TrackerAdminKillmailIdForm,
|
19
|
+
field_nice_display,
|
20
|
+
)
|
21
|
+
from killtracker.models import EveKillmail, EveKillmailAttacker, Tracker, Webhook
|
17
22
|
|
18
23
|
|
19
24
|
class EveKillmailAttackerInline(admin.TabularInline):
|
@@ -46,17 +51,17 @@ class WebhookAdmin(admin.ModelAdmin):
|
|
46
51
|
list_filter = ("is_enabled",)
|
47
52
|
ordering = ("name",)
|
48
53
|
|
49
|
-
def _messages_in_queue(self, obj):
|
50
|
-
return obj.
|
54
|
+
def _messages_in_queue(self, obj: Webhook):
|
55
|
+
return obj.messages_queued()
|
51
56
|
|
52
57
|
actions = ["send_test_message", "purge_messages"]
|
53
58
|
|
54
59
|
@admin.display(description="Purge queued messages of selected webhooks")
|
55
|
-
def purge_messages(self, request, queryset):
|
60
|
+
def purge_messages(self, request, queryset: QuerySet[Webhook]):
|
56
61
|
actions_count = 0
|
57
62
|
killmails_deleted = 0
|
58
63
|
for webhook in queryset:
|
59
|
-
killmails_deleted += webhook.
|
64
|
+
killmails_deleted += webhook.delete_queued_messages()
|
60
65
|
actions_count += 1
|
61
66
|
self.message_user(
|
62
67
|
request,
|
killtracker/app_settings.py
CHANGED
@@ -6,20 +6,15 @@ KILLTRACKER_REDISQ_LOCK_TIMEOUT = clean_setting("KILLTRACKER_REDISQ_LOCK_TIMEOUT
|
|
6
6
|
"""Timeout for lock to ensure atomic access to ZKB RedisQ."""
|
7
7
|
|
8
8
|
KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER = clean_setting(
|
9
|
-
"KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER",
|
9
|
+
"KILLTRACKER_KILLMAIL_MAX_AGE_FOR_TRACKER", 600
|
10
10
|
)
|
11
11
|
"""Ignore killmails that are older than the given number in minutes
|
12
12
|
sometimes killmails appear belated on ZKB,
|
13
13
|
this feature ensures they don't create new alerts.
|
14
14
|
"""
|
15
15
|
|
16
|
-
KILLTRACKER_MAX_KILLMAILS_PER_RUN = clean_setting(
|
17
|
-
"KILLTRACKER_MAX_KILLMAILS_PER_RUN", 100
|
18
|
-
)
|
19
|
-
"""Maximum number of killmails retrieved from ZKB by task run."""
|
20
|
-
|
21
16
|
KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS = clean_setting(
|
22
|
-
"KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS", 30
|
17
|
+
"KILLTRACKER_PURGE_KILLMAILS_AFTER_DAYS", default_value=30, min_value=0
|
23
18
|
)
|
24
19
|
"""Killmails older than set number of days will be purged from the database.
|
25
20
|
If you want to keep all killmails set this to 0.
|
@@ -62,11 +57,14 @@ when creating trackers.
|
|
62
57
|
#####################
|
63
58
|
# INTERNAL SETTINGS
|
64
59
|
|
65
|
-
KILLTRACKER_REDISQ_TTW = clean_setting("KILLTRACKER_REDISQ_TTW",
|
60
|
+
KILLTRACKER_REDISQ_TTW = clean_setting("KILLTRACKER_REDISQ_TTW", 1)
|
66
61
|
"""Max duration to wait for new killmails from redisq in seconds."""
|
67
62
|
|
68
63
|
KILLTRACKER_TASKS_TIMEOUT = clean_setting("KILLTRACKER_TASKS_TIMEOUT", 1_800)
|
69
|
-
"""Tasks hard timeout."""
|
64
|
+
"""Tasks hard timeout in seconds."""
|
65
|
+
|
66
|
+
KILLTRACKER_RUN_TIMEOUT = clean_setting("KILLTRACKER_RUN_TIMEOUT", 55)
|
67
|
+
"""Timeout for killtracker run in seconds."""
|
70
68
|
|
71
69
|
KILLTRACKER_DISCORD_SEND_DELAY = clean_setting(
|
72
70
|
"KILLTRACKER_DISCORD_SEND_DELAY", default_value=2, min_value=1, max_value=900
|
@@ -100,9 +98,21 @@ KILLTRACKER_STORAGE_KILLMAILS_LIFETIME = clean_setting(
|
|
100
98
|
)
|
101
99
|
"""Max lifetime of killmails in temporary storage in seconds."""
|
102
100
|
|
103
|
-
KILLTRACKER_ZKB_REQUEST_DELAY = clean_setting(
|
101
|
+
KILLTRACKER_ZKB_REQUEST_DELAY = clean_setting(
|
102
|
+
"KILLTRACKER_ZKB_REQUEST_DELAY", default_value=500, min_value=500
|
103
|
+
)
|
104
104
|
"""Delay between subsequent calls to ZKB API in milliseconds.
|
105
105
|
|
106
106
|
This delay ensures the app does not breach the CloudFlare rate limit of currently
|
107
107
|
two (2) requests per second per IP address.
|
108
108
|
"""
|
109
|
+
|
110
|
+
KILLTRACKER_MAX_KILLMAILS_PER_RUN = clean_setting(
|
111
|
+
"KILLTRACKER_MAX_KILLMAILS_PER_RUN", default_value=500, min_value=1
|
112
|
+
)
|
113
|
+
"""Maximum number of killmails retrieved from ZKB by task run."""
|
114
|
+
|
115
|
+
KILLTRACKER_MAX_MESSAGES_SENT_PER_RUN = clean_setting(
|
116
|
+
"KILLTRACKER_MAX_MESSAGES_SENT_PER_RUN", default_value=10, min_value=1
|
117
|
+
)
|
118
|
+
"""Maximum number of messages processed per task run."""
|
killtracker/apps.py
CHANGED
@@ -9,7 +9,5 @@ class KillmailsConfig(AppConfig):
|
|
9
9
|
verbose_name = f"Killtracker v{__version__}"
|
10
10
|
|
11
11
|
def ready(self) -> None:
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
Killmail.reset_lock_key()
|
12
|
+
import killtracker.checks # noqa: F401 pylint: disable=unused-import
|
13
|
+
import killtracker.signals # noqa: F401 pylint: disable=unused-import
|
@@ -0,0 +1,162 @@
|
|
1
|
+
"""Send messages to Discord webhooks."""
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
import json
|
5
|
+
from copy import copy
|
6
|
+
from dataclasses import dataclass
|
7
|
+
from http import HTTPStatus
|
8
|
+
from time import sleep
|
9
|
+
from typing import List, Optional
|
10
|
+
|
11
|
+
import dhooks_lite
|
12
|
+
|
13
|
+
from django.core.cache import cache
|
14
|
+
from django.utils.timezone import now
|
15
|
+
|
16
|
+
from allianceauth.services.hooks import get_extension_logger
|
17
|
+
from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
|
18
|
+
from app_utils.logging import LoggerAddTag
|
19
|
+
|
20
|
+
from killtracker import APP_NAME, HOMEPAGE_URL, __title__, __version__
|
21
|
+
from killtracker.app_settings import KILLTRACKER_DISCORD_SEND_DELAY
|
22
|
+
from killtracker.core.helpers import datetime_or_none
|
23
|
+
|
24
|
+
_DEFAULT_429_TIMEOUT = 600
|
25
|
+
|
26
|
+
logger = LoggerAddTag(get_extension_logger(__name__), __title__)
|
27
|
+
|
28
|
+
|
29
|
+
class HTTPError(Exception):
|
30
|
+
"""A HTTP error."""
|
31
|
+
|
32
|
+
def __init__(self, status_code: int):
|
33
|
+
self.status_code = status_code
|
34
|
+
|
35
|
+
|
36
|
+
class WebhookRateLimitExhausted(Exception):
|
37
|
+
"""The rate limit of a webhook has been exhausted."""
|
38
|
+
|
39
|
+
def __init__(self, retry_at: dt.datetime, is_original: bool = True):
|
40
|
+
self.retry_at = retry_at
|
41
|
+
self.is_original = is_original
|
42
|
+
|
43
|
+
|
44
|
+
@dataclass
|
45
|
+
class DiscordMessage:
|
46
|
+
"""A Discord message created from a Killmail."""
|
47
|
+
|
48
|
+
avatar_url: Optional[str] = None
|
49
|
+
content: Optional[str] = None
|
50
|
+
embeds: Optional[List[dhooks_lite.Embed]] = None
|
51
|
+
killmail_id: int = 0 # Killmail ID this message from created from
|
52
|
+
username: Optional[str] = None
|
53
|
+
|
54
|
+
def __post_init__(self):
|
55
|
+
if not self.content and not self.embeds:
|
56
|
+
raise ValueError("Message must have content or embeds to be valid")
|
57
|
+
|
58
|
+
def to_json(self) -> str:
|
59
|
+
"""Converts a Discord message into a JSON object and returns it."""
|
60
|
+
|
61
|
+
if self.embeds:
|
62
|
+
embeds_list = [obj.asdict() for obj in self.embeds]
|
63
|
+
else:
|
64
|
+
embeds_list = None
|
65
|
+
|
66
|
+
message = {}
|
67
|
+
if self.killmail_id:
|
68
|
+
message["killmail_id"] = self.killmail_id
|
69
|
+
if self.content:
|
70
|
+
message["content"] = self.content
|
71
|
+
if embeds_list:
|
72
|
+
message["embeds"] = embeds_list
|
73
|
+
if self.username:
|
74
|
+
message["username"] = self.username
|
75
|
+
if self.avatar_url:
|
76
|
+
message["avatar_url"] = self.avatar_url
|
77
|
+
|
78
|
+
return json.dumps(message, cls=JSONDateTimeEncoder)
|
79
|
+
|
80
|
+
@classmethod
|
81
|
+
def from_json(cls, s: str) -> "DiscordMessage":
|
82
|
+
"""Creates a DiscordMessage object from an JSON object and returns it."""
|
83
|
+
message1: dict = json.loads(s, cls=JSONDateTimeDecoder)
|
84
|
+
message2 = copy(message1)
|
85
|
+
if message1.get("embeds"):
|
86
|
+
message2["embeds"] = [
|
87
|
+
dhooks_lite.Embed.from_dict(embed_dict)
|
88
|
+
for embed_dict in message1.get("embeds")
|
89
|
+
]
|
90
|
+
else:
|
91
|
+
message2["embeds"] = None
|
92
|
+
return cls(**message2)
|
93
|
+
|
94
|
+
|
95
|
+
def send_message_to_webhook(name: str, url: str, message: DiscordMessage) -> int:
|
96
|
+
"""Send a message to a Discord webhook and returns the ID of new message."""
|
97
|
+
|
98
|
+
key_retry_at = _make_key_retry_at(url)
|
99
|
+
retry_at = datetime_or_none(cache.get(key_retry_at))
|
100
|
+
if retry_at is not None and retry_at > now():
|
101
|
+
raise WebhookRateLimitExhausted(retry_at=retry_at, is_original=False)
|
102
|
+
|
103
|
+
key_last_request = _make_key_last_request(url)
|
104
|
+
last_request = datetime_or_none(cache.get(key_last_request))
|
105
|
+
if last_request is not None:
|
106
|
+
next_slot = last_request + dt.timedelta(seconds=KILLTRACKER_DISCORD_SEND_DELAY)
|
107
|
+
seconds = (next_slot - now()).total_seconds()
|
108
|
+
if seconds > 0:
|
109
|
+
logger.debug(
|
110
|
+
"%s: Waiting %f seconds for next free slot for webhook", name, seconds
|
111
|
+
)
|
112
|
+
sleep(seconds)
|
113
|
+
|
114
|
+
hook = dhooks_lite.Webhook(
|
115
|
+
url=url,
|
116
|
+
user_agent=dhooks_lite.UserAgent(
|
117
|
+
name=APP_NAME, url=HOMEPAGE_URL, version=__version__
|
118
|
+
),
|
119
|
+
)
|
120
|
+
response = hook.execute(
|
121
|
+
content=message.content,
|
122
|
+
embeds=message.embeds,
|
123
|
+
username=message.username,
|
124
|
+
avatar_url=message.avatar_url,
|
125
|
+
wait_for_response=True,
|
126
|
+
max_retries=0, # we will handle retries ourselves
|
127
|
+
)
|
128
|
+
cache.set(key_last_request, now(), timeout=KILLTRACKER_DISCORD_SEND_DELAY + 30)
|
129
|
+
logger.debug(
|
130
|
+
"%s: Response from Discord for creating message from killmail %d: %s %s %s",
|
131
|
+
name,
|
132
|
+
message.killmail_id,
|
133
|
+
response.status_code,
|
134
|
+
response.headers,
|
135
|
+
response.content,
|
136
|
+
)
|
137
|
+
if not response.status_ok:
|
138
|
+
if response.status_code == HTTPStatus.TOO_MANY_REQUESTS:
|
139
|
+
try:
|
140
|
+
retry_after = int(response.headers["Retry-After"])
|
141
|
+
except KeyError:
|
142
|
+
retry_after = _DEFAULT_429_TIMEOUT
|
143
|
+
retry_at = now() + dt.timedelta(seconds=retry_after)
|
144
|
+
cache.set(key_retry_at, retry_at, timeout=retry_after + 60)
|
145
|
+
raise WebhookRateLimitExhausted(retry_at=retry_at, is_original=True)
|
146
|
+
|
147
|
+
raise HTTPError(response.status_code)
|
148
|
+
|
149
|
+
try:
|
150
|
+
message_id = int(response.content.get("id"))
|
151
|
+
except (AttributeError, ValueError):
|
152
|
+
message_id = 0
|
153
|
+
|
154
|
+
return message_id
|
155
|
+
|
156
|
+
|
157
|
+
def _make_key_last_request(url):
|
158
|
+
return f"killtracker-webhook-last-request-{url}"
|
159
|
+
|
160
|
+
|
161
|
+
def _make_key_retry_at(url):
|
162
|
+
return f"killtracker-webhook-retry-at-{url}"
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"""Helper functions for core modules."""
|
2
|
+
|
3
|
+
import datetime as dt
|
4
|
+
from typing import Any, Optional
|
5
|
+
|
6
|
+
|
7
|
+
def datetime_or_none(v: Any) -> Optional[dt.datetime]:
|
8
|
+
"""Returns as datetime when it is a datetime else returns None."""
|
9
|
+
if v is None:
|
10
|
+
return None
|
11
|
+
if not isinstance(v, dt.datetime):
|
12
|
+
return None
|
13
|
+
return v
|