aa-killtracker 0.18.0a2__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.
Files changed (37) hide show
  1. {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/METADATA +7 -7
  2. {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/RECORD +33 -30
  3. killtracker/__init__.py +1 -1
  4. killtracker/admin.py +1 -1
  5. killtracker/app_settings.py +14 -7
  6. killtracker/core/discord.py +162 -0
  7. killtracker/core/helpers.py +13 -0
  8. killtracker/core/{discord_messages.py → trackers.py} +16 -75
  9. killtracker/core/{worker_shutdown.py → workers.py} +3 -4
  10. killtracker/core/{killmails.py → zkb.py} +32 -40
  11. killtracker/managers.py +1 -1
  12. killtracker/models/trackers.py +4 -5
  13. killtracker/models/webhooks.py +4 -53
  14. killtracker/signals.py +4 -4
  15. killtracker/tasks.py +47 -49
  16. killtracker/tests/core/test_discord.py +184 -0
  17. killtracker/tests/core/test_helpers.py +23 -0
  18. killtracker/tests/core/{test_discord_messages_1.py → test_tracker_1.py} +12 -39
  19. killtracker/tests/core/{test_discord_messages_2.py → test_tracker_2.py} +3 -3
  20. killtracker/tests/core/test_workers.py +49 -0
  21. killtracker/tests/core/{test_killmails.py → test_zkb.py} +58 -46
  22. killtracker/tests/models/test_killmails.py +0 -2
  23. killtracker/tests/models/test_trackers_1.py +1 -1
  24. killtracker/tests/models/test_trackers_2.py +2 -2
  25. killtracker/tests/models/test_webhooks.py +63 -0
  26. killtracker/tests/test_integration.py +26 -13
  27. killtracker/tests/test_tasks.py +68 -58
  28. killtracker/tests/test_utils.py +39 -0
  29. killtracker/tests/testdata/factories.py +1 -1
  30. killtracker/tests/testdata/helpers.py +1 -1
  31. killtracker/tests/utils.py +28 -0
  32. killtracker/exceptions.py +0 -32
  33. killtracker/tests/core/test_worker_shutdown.py +0 -34
  34. killtracker/tests/models/test_webhook.py +0 -164
  35. killtracker/tests/test_exceptions.py +0 -12
  36. {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/WHEEL +0 -0
  37. {aa_killtracker-0.18.0a2.dist-info → aa_killtracker-1.0.0a1.dist-info}/licenses/LICENSE +0 -0
@@ -1,13 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aa-killtracker
3
- Version: 0.18.0a2
3
+ Version: 1.0.0a1
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.19.0
24
- Requires-Dist: allianceauth>=3
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.0
27
- Requires-Dist: django-eveuniverse>=1.3
28
- Requires-Dist: redis-simple-mq>=0.5
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
@@ -1,19 +1,20 @@
1
- killtracker/__init__.py,sha256=6BDL-xJ19nYjor-SkiCJL5oH0bqet4neHQ2sXqb_Hm8,357
2
- killtracker/admin.py,sha256=HkpfXq0KhqgLjt4kpe8PigonZ-5_ZKQ6D-Z_l3b3jB0,14235
3
- killtracker/app_settings.py,sha256=lJ0T86Nmh2dP050mM5hu-OfqIhjic_H5GveYAAKWI3c,4016
1
+ killtracker/__init__.py,sha256=wIg1n4zLhPPBnSm9Q9U1g_FFh1IGzAzKfJQMohSx95s,356
2
+ killtracker/admin.py,sha256=IH04Q1etHUnXSJIhm3bnGfIbSU6lPSSaltLERb-OkYg,14229
3
+ killtracker/app_settings.py,sha256=KqWSS4OVKIa7DmBAa5AbwxFVXw5EQebPfBtPo1UJYgY,4295
4
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/exceptions.py,sha256=aFY6qxQOta1V-qvgKp19Cgy6M16O7Si8moc2205OsX0,846
8
7
  killtracker/forms.py,sha256=SUM0U-Sa6FPcGVpsYJbVxHfEtf7hb1Qc_l92Zfa1OLk,5193
9
- killtracker/managers.py,sha256=lufSPdNrU7KXLfhwvFU82hEsBH--eNMSlCVlFuZYYuI,4422
8
+ killtracker/managers.py,sha256=hrmpiuMzK-7p-AsT4ee0mMEhQv4b57AGmhlRTxEl7wk,4416
10
9
  killtracker/providers.py,sha256=3DiOlklBq5kLGDV9nMlMbP2-S8AodNgR6gtB4cWWCUo,358
11
- killtracker/signals.py,sha256=IgULFrfq7m6-glWGyWisNjnom9_q40-XK56Yld1lNi8,906
12
- killtracker/tasks.py,sha256=sIZsWrJM8QR2aDXEmjH8u_FT8gjzqlKifaQoTPSxnXo,9208
10
+ killtracker/signals.py,sha256=rFCg-WYMVPmAhuqX8KHNaicsP1nyj_HYA2O4Qr8wqhk,892
11
+ killtracker/tasks.py,sha256=9ZqItmm2tlzVA-lelNTyzpPuBYCUvfZR2nCV16Xu6fA,9267
13
12
  killtracker/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
- killtracker/core/discord_messages.py,sha256=zepZ90-xtfvPafG0dIZYUWprozOi5I4Kjoj0jEQG5_U,16602
15
- killtracker/core/killmails.py,sha256=6n2nuhiPbIQXJUf63E_3coGOQmJajx2QeDofO5FioRs,21842
16
- killtracker/core/worker_shutdown.py,sha256=HHLR5DjLV67Sb3TDssoUnoOWO6_gcUkoov2aKV5qRNE,1289
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
17
18
  killtracker/management/commands/killtracker_load_eve.py,sha256=5P2wr6LU-EMl9_gG-DKP2yw3eFXOU7ApeuGAWEbvCKk,1159
18
19
  killtracker/migrations/0001_initial_new.py,sha256=WszI5DmNFtk45IZ2Zul3_Ak-VXmye9ffJKFAsGIxoXA,27818
19
20
  killtracker/migrations/0001_squashed_all.py,sha256=KXwOcRayjGGGmleg7-aTcXeIi-92Mn4BaGkEY3iE9Vc,26221
@@ -31,41 +32,43 @@ killtracker/migrations/0009_remove_old_models.py,sha256=bDIz0hywpFKbHYhdXFXzra5D
31
32
  killtracker/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
32
33
  killtracker/models/__init__.py,sha256=WL6VD9MK7EsZot_RLPz0nX8WxJkp7RtQpw_mYpnVqu8,188
33
34
  killtracker/models/killmails.py,sha256=CCdrHkwUSoTOGqavG-TpMxDRleF9UEacOTui8v3-VT8,5181
34
- killtracker/models/trackers.py,sha256=ATbwaQRYO6WgVt5rY3WZicB7D-08EmdDKdJ3WN949dY,31121
35
- killtracker/models/webhooks.py,sha256=J4e5VupEpvcC-hWDg8BZ4ZrCSkqGFinGUDx95FKe52U,7593
35
+ killtracker/models/trackers.py,sha256=wo6KWl1GOASOquTTdAeo5DaZgRzKai1ek1GjQ49oOhY,31154
36
+ killtracker/models/webhooks.py,sha256=6S1wXwlOMth56oU0hsYH5PiimECMwZyLXiKok2yQo_g,5850
36
37
  killtracker/static/killtracker/killtracker_logo.png,sha256=3jc9zmYHqP60Np5piP5RfkX0_II-315DNjZ4FRGbqKc,74625
37
38
  killtracker/static/killtracker/zkb_icon.png,sha256=wuVfgyTbTs9qS4KGbDAH3Q9KVPgHqYV5eaVrjEmTjsE,328
38
39
  killtracker/templates/admin/killtracker/tracker/killmail_test.html,sha256=TI2ON8qf9pW4rX6G7pT990ZadkipwncQgGUC0FPcWQY,649
39
40
  killtracker/tests/__init__.py,sha256=CEt5qqp6ptDrx1lwuA6epZ5bzh1CwUJYXeMZqWK9jT0,278
40
41
  killtracker/tests/test_admin.py,sha256=XFAlF_Q4h5Z_agkmRtB30GmCayqQPblLolFrN41xu7Q,5659
41
42
  killtracker/tests/test_admin_2.py,sha256=rP0EscM9qUil_ZuXute3R8TQY2P5XSjmxHLbziCGcVw,1140
42
- killtracker/tests/test_exceptions.py,sha256=wheTwm0JOBqYVI-DJM4KK2usFAZT1Q-O52VMOnSAz8Y,326
43
- killtracker/tests/test_integration.py,sha256=d1W7102L1_fOUmQJpvPE_5ldJf9iZRrj3LfMsTvtGBo,3245
44
- killtracker/tests/test_tasks.py,sha256=eaxmImSJvYZneQqv5pYb5due0oS90JvLR0DHtGvyEh4,15990
45
- killtracker/tests/utils.py,sha256=lpWD3peOyxiwq7z8kO4GLQl98kZ4mEcWgyuUTCUSp_Y,442
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
46
47
  killtracker/tests/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
47
- killtracker/tests/core/test_discord_messages_1.py,sha256=P-s-zmStMi6fd0yrtKQJxiCVnD1GFF4JJ6ffrIXzYOw,4624
48
- killtracker/tests/core/test_discord_messages_2.py,sha256=r-NwE_bpJSnedw3nIyad9XWgLa5ly6UPgYB-A7Mba30,4782
49
- killtracker/tests/core/test_killmails.py,sha256=S08P50xMgq_hiKLeuryAcOI0hN3CnI-jzHKQaN9G5UU,16114
50
- killtracker/tests/core/test_worker_shutdown.py,sha256=wg0fJQKVPibpRbBfoPCQZycl4rRSGRr6hrYxcRhtEnc,1014
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
51
54
  killtracker/tests/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
52
- killtracker/tests/models/test_killmails.py,sha256=0ytwIGbTPb1OTzmK8B9VnR1ylxMuaYtp2tvCXzNOwNc,8293
53
- killtracker/tests/models/test_trackers_1.py,sha256=7DqrF7LE0jJn1ATOQRU8BPcqsvgUIx2ccabOUYsCN-o,42794
54
- killtracker/tests/models/test_trackers_2.py,sha256=kPCZuS9N3GUtoauMR96aZv9VI5ShAIQpJZLyo0SPOQM,5473
55
- killtracker/tests/models/test_webhook.py,sha256=VeBm5qdBUHcauFfH2-OoaD8uplqv4D3l2TtVrS5iQzc,6116
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
56
59
  killtracker/tests/testdata/__init__.py,sha256=9aQhf8V-DseZMWjJ_QMXOba6CypoHFSVpYRZEUh-oFc,212
57
60
  killtracker/tests/testdata/create_eveuniverse.py,sha256=Ewr5OBiQwjK1kwDCRh3A3f0Z-CCPi8Gp8dpsPgVAU4A,966
58
61
  killtracker/tests/testdata/evealliances.json,sha256=i6udrtUnWQjn71Iw17WWz9Eb1fdGUNhI1UYLt0hZHog,307
59
62
  killtracker/tests/testdata/evecorporations.json,sha256=Hk4WuplZl69rQcquOU-15lgUyvHRMZW0fnYfKz8oeWw,1229
60
63
  killtracker/tests/testdata/eveentities.json,sha256=0yStlAMEhednEiMKvKOYFsyRovtsGxw6IpWAyvnQXv4,1459
61
64
  killtracker/tests/testdata/eveuniverse.json,sha256=JagrJ-asKlkxrqj3dtAth6d5bhIpeEmlvBHgXjLTMSI,36117
62
- killtracker/tests/testdata/factories.py,sha256=WqmIthF6kgrdQrV5CSlub4hBYo42lC_8UORzZ8sGAp0,11019
63
- killtracker/tests/testdata/helpers.py,sha256=5T35ITnh16pTswR1O4t4IdVSpokC3x8_Me_vk3CgBB8,4741
65
+ killtracker/tests/testdata/factories.py,sha256=MEJLfslB13J_UnN7TXASL6poH1fkQ1NW5dQX_xtfPvA,11013
66
+ killtracker/tests/testdata/helpers.py,sha256=bsHv2HlbORuw5kU8qZ806nXPM0hqNUuj0qVQCm4KoZ8,4735
64
67
  killtracker/tests/testdata/killmails.json,sha256=HKfpXQJ_ikymBSewh4wWixYpLwC3kNBqi04cCms1AjA,45366
65
68
  killtracker/tests/testdata/load_eveuniverse.py,sha256=FitVc12E_ob56ezK5Y1q07Kbyc7N8poHiCNaLRGU4Jg,388
66
69
  killtracker/tools/drop_tables_killtracker.sql,sha256=fJrGY23NKRunpYC3hI-klFkWub4sBuhfP8TaT__zjPg,1726
67
70
  killtracker/tools/generate_conditions_text.py,sha256=LxAtTQIX-IbuqaNDMV1XHhq5AbrrlwxLWgXa1M7DWQo,767
68
- aa_killtracker-0.18.0a2.dist-info/licenses/LICENSE,sha256=XZiwB_S_40_HhnvLg5xvtBb3g1oGjPrk0rpFwk8iInE,1070
69
- aa_killtracker-0.18.0a2.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
70
- aa_killtracker-0.18.0a2.dist-info/METADATA,sha256=7xoqmIUN3AAKkHhGvuL04Fd3wP9QLCZEkPm4o2oH4l0,13929
71
- aa_killtracker-0.18.0a2.dist-info/RECORD,,
71
+ aa_killtracker-1.0.0a1.dist-info/licenses/LICENSE,sha256=XZiwB_S_40_HhnvLg5xvtBb3g1oGjPrk0rpFwk8iInE,1070
72
+ aa_killtracker-1.0.0a1.dist-info/WHEEL,sha256=G2gURzTEtmeR8nrdXUJfNiB3VYVxigPQ-bEQujpNiNs,82
73
+ aa_killtracker-1.0.0a1.dist-info/METADATA,sha256=D8mZwY0eM39wkvv8FcnkgaomKJ6KbQhb6cxew0iVqEo,13941
74
+ aa_killtracker-1.0.0a1.dist-info/RECORD,,
killtracker/__init__.py CHANGED
@@ -3,7 +3,7 @@
3
3
  # pylint: disable = invalid-name
4
4
  default_app_config = "killtracker.apps.KillmailsConfig"
5
5
 
6
- __version__ = "0.18.0a2"
6
+ __version__ = "1.0.0a1"
7
7
  __title__ = "Killtracker"
8
8
 
9
9
  APP_NAME = "aa-killtracker"
killtracker/admin.py CHANGED
@@ -12,7 +12,7 @@ from django.utils.safestring import mark_safe
12
12
  from allianceauth import NAME as site_header
13
13
 
14
14
  from killtracker import tasks
15
- from killtracker.core.killmails import Killmail
15
+ from killtracker.core.zkb import Killmail
16
16
  from killtracker.forms import (
17
17
  TrackerAdminForm,
18
18
  TrackerAdminKillmailIdForm,
@@ -13,13 +13,8 @@ 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", 500
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.
@@ -103,9 +98,21 @@ KILLTRACKER_STORAGE_KILLMAILS_LIFETIME = clean_setting(
103
98
  )
104
99
  """Max lifetime of killmails in temporary storage in seconds."""
105
100
 
106
- KILLTRACKER_ZKB_REQUEST_DELAY = clean_setting("KILLTRACKER_ZKB_REQUEST_DELAY", 500)
101
+ KILLTRACKER_ZKB_REQUEST_DELAY = clean_setting(
102
+ "KILLTRACKER_ZKB_REQUEST_DELAY", default_value=500, min_value=500
103
+ )
107
104
  """Delay between subsequent calls to ZKB API in milliseconds.
108
105
 
109
106
  This delay ensures the app does not breach the CloudFlare rate limit of currently
110
107
  two (2) requests per second per IP address.
111
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."""
@@ -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
@@ -1,14 +1,12 @@
1
- """This module allows to create Discord messages from killmails."""
1
+ """Generate Discord messages from tracked killmails."""
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
- import json
6
- from copy import copy
7
5
  from dataclasses import dataclass
8
- from typing import TYPE_CHECKING, List, Optional
6
+ from typing import TYPE_CHECKING, Optional
9
7
 
10
8
  import dhooks_lite
11
- from requests.exceptions import HTTPError
9
+ import requests
12
10
 
13
11
  from eveuniverse.helpers import EveEntityNameResolver
14
12
  from eveuniverse.models import EveEntity, EveSolarSystem
@@ -16,89 +14,32 @@ from eveuniverse.models import EveEntity, EveSolarSystem
16
14
  from allianceauth.eveonline.evelinks import dotlan, eveimageserver, zkillboard
17
15
  from allianceauth.services.hooks import get_extension_logger
18
16
  from app_utils.django import app_labels
19
- from app_utils.json import JSONDateTimeDecoder, JSONDateTimeEncoder
20
17
  from app_utils.logging import LoggerAddTag
21
18
  from app_utils.urls import static_file_absolute_url
22
19
  from app_utils.views import humanize_value
23
20
 
24
21
  from killtracker import __title__
25
-
26
- from .killmails import ZKB_KILLMAIL_BASEURL, Killmail, TrackerInfo
22
+ from killtracker.core.discord import DiscordMessage
23
+ from killtracker.core.zkb import ZKB_KILLMAIL_BASEURL, Killmail, TrackerInfo
27
24
 
28
25
  if TYPE_CHECKING:
29
26
  from killtracker.models import Tracker
30
27
 
31
-
32
28
  _ICON_SIZE = 128
33
29
 
34
-
35
30
  logger = LoggerAddTag(get_extension_logger(__name__), __title__)
36
31
 
37
32
 
38
- @dataclass
39
- class DiscordMessage:
40
- """A Discord message created from a Killmail."""
41
-
42
- avatar_url: Optional[str] = None
43
- content: Optional[str] = None
44
- embeds: Optional[List[dhooks_lite.Embed]] = None
45
- killmail_id: int = 0 # Killmail ID this message from created from
46
- tts: Optional[bool] = None
47
- username: Optional[str] = None
48
-
49
- def __post_init__(self):
50
- if not self.content and not self.embeds:
51
- raise ValueError("Message must have content or embeds to be valid")
52
-
53
- def to_json(self) -> str:
54
- """Converts a Discord message into a JSON object and returns it."""
55
-
56
- if self.embeds:
57
- embeds_list = [obj.asdict() for obj in self.embeds]
58
- else:
59
- embeds_list = None
60
-
61
- message = {}
62
- if self.killmail_id:
63
- message["killmail_id"] = self.killmail_id
64
- if self.content:
65
- message["content"] = self.content
66
- if embeds_list:
67
- message["embeds"] = embeds_list
68
- if self.tts:
69
- message["tts"] = self.tts
70
- if self.username:
71
- message["username"] = self.username
72
- if self.avatar_url:
73
- message["avatar_url"] = self.avatar_url
74
-
75
- return json.dumps(message, cls=JSONDateTimeEncoder)
76
-
77
- @classmethod
78
- def from_json(cls, s: str) -> "DiscordMessage":
79
- """Creates a DiscordMessage object from an JSON object and returns it."""
80
- message1: dict = json.loads(s, cls=JSONDateTimeDecoder)
81
- message2 = copy(message1)
82
- if message1.get("embeds"):
83
- message2["embeds"] = [
84
- dhooks_lite.Embed.from_dict(embed_dict)
85
- for embed_dict in message1.get("embeds")
86
- ]
87
- else:
88
- message2["embeds"] = None
89
- return cls(**message2)
90
-
91
- @classmethod
92
- def from_killmail(
93
- cls, tracker: Tracker, killmail: Killmail, intro_text: Optional[str] = None
94
- ) -> "DiscordMessage":
95
- """Creates a DiscordMessage object from a Killmail and returns it."""
96
- m = DiscordMessage(
97
- killmail_id=killmail.id,
98
- content=_create_content(tracker, intro_text),
99
- embeds=[_create_embed(tracker, killmail)],
100
- )
101
- return m
33
+ def create_discord_message_from_killmail(
34
+ tracker: Tracker, killmail: Killmail, intro_text: Optional[str] = None
35
+ ) -> "DiscordMessage":
36
+ """Creates a Discord message from a Killmail and returns it."""
37
+ m = DiscordMessage(
38
+ killmail_id=killmail.id,
39
+ content=_create_content(tracker, intro_text),
40
+ embeds=[_create_embed(tracker, killmail)],
41
+ )
42
+ return m
102
43
 
103
44
 
104
45
  @dataclass(frozen=True)
@@ -154,7 +95,7 @@ def _create_content(tracker: Tracker, intro_text: Optional[str] = None) -> str:
154
95
  for group in tracker.ping_groups.all():
155
96
  try:
156
97
  role = DiscordUser.objects.group_to_role(group) # type: ignore
157
- except HTTPError:
98
+ except requests.exceptions.HTTPError:
158
99
  logger.warning(
159
100
  "Failed to get Discord roles. Can not ping groups.",
160
101
  exc_info=True,
@@ -1,5 +1,4 @@
1
- """Module worker_shutdown allows tasks to find out
2
- whether their worker is currently shutting down.
1
+ """Allows tasks to find out whether their worker is currently shutting down.
3
2
 
4
3
  This enables long running tasks to abort early,
5
4
  which helps to speed up a warm worker shutdown.
@@ -19,12 +18,12 @@ _TIMEOUT_SECONDS = 120
19
18
  logger = LoggerAddTag(get_extension_logger(__name__), __title__)
20
19
 
21
20
 
22
- def reset(hostname: str) -> None:
21
+ def state_reset(hostname: str) -> None:
23
22
  """Resets the shutting down state for a worker."""
24
23
  cache.delete(_make_key(hostname))
25
24
 
26
25
 
27
- def set(hostname: str) -> None:
26
+ def state_set(hostname: str) -> None:
28
27
  """Sets a worker into the shutting down state."""
29
28
  cache.set(_make_key(hostname), "shutting down", timeout=_TIMEOUT_SECONDS)
30
29
 
@@ -1,4 +1,4 @@
1
- """Fetching killmails from ZKB."""
1
+ """Fetch killmails from zKillboard."""
2
2
 
3
3
  # pylint: disable = redefined-builtin
4
4
 
@@ -32,19 +32,22 @@ from killtracker.app_settings import (
32
32
  KILLTRACKER_STORAGE_KILLMAILS_LIFETIME,
33
33
  KILLTRACKER_ZKB_REQUEST_DELAY,
34
34
  )
35
- from killtracker.exceptions import KillmailDoesNotExist
35
+ from killtracker.core.helpers import datetime_or_none
36
36
  from killtracker.providers import esi
37
37
 
38
- logger = LoggerAddTag(get_extension_logger(__name__), __title__)
39
-
40
- ZKB_REDISQ_URL = "https://zkillredisq.stream/listen.php"
41
- ZKB_API_URL = "https://zkillboard.com/api/"
42
38
  ZKB_KILLMAIL_BASEURL = "https://zkillboard.com/kill/"
43
- REQUESTS_TIMEOUT = (5, 30)
44
- DEFAULT_429_TIMEOUT = 10
45
39
 
46
- MAIN_MINIMUM_COUNT = 2
47
- MAIN_MINIMUM_SHARE = 0.25
40
+ _KEY_RETRY_AT = "killtracker-zkb-retry-at"
41
+ _KEY_LAST_REQUEST = "killtracker-zkb-last-request"
42
+ _MAIN_MINIMUM_COUNT = 2
43
+ _MAIN_MINIMUM_SHARE = 0.25
44
+ _REQUESTS_TIMEOUT = (5, 30)
45
+ _ZKB_429_DEFAULT_TIMEOUT = 10
46
+ _ZKB_API_URL = "https://zkillboard.com/api/"
47
+ _ZKB_REDISQ_URL = "https://zkillredisq.stream/listen.php"
48
+
49
+ logger = LoggerAddTag(get_extension_logger(__name__), __title__)
50
+
48
51
 
49
52
  # TODO: Factor out logic for accessing the API to another module
50
53
 
@@ -57,6 +60,10 @@ class ZKBTooManyRequestsError(Exception):
57
60
  self.is_original = is_original
58
61
 
59
62
 
63
+ class KillmailDoesNotExist(Exception):
64
+ """Killmail does not exist in storage."""
65
+
66
+
60
67
  @dataclass
61
68
  class _KillmailBase:
62
69
  """Base class for all Killmail."""
@@ -264,8 +271,8 @@ class Killmail(_KillmailBase):
264
271
  jumps: Optional[int] = None,
265
272
  distance: Optional[float] = None,
266
273
  matching_ship_type_ids: Optional[List[int]] = None,
267
- minimum_count: int = MAIN_MINIMUM_COUNT,
268
- minimum_share: float = MAIN_MINIMUM_SHARE,
274
+ minimum_count: int = _MAIN_MINIMUM_COUNT,
275
+ minimum_share: float = _MAIN_MINIMUM_SHARE,
269
276
  ) -> "Killmail":
270
277
  """Clone this killmail and add tracker info."""
271
278
  main_ship_group = self._calc_main_attacker_ship_group(
@@ -419,45 +426,30 @@ class Killmail(_KillmailBase):
419
426
  if "," in KILLTRACKER_QUEUE_ID:
420
427
  raise ImproperlyConfigured("A queue ID must not contains commas.")
421
428
 
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()
429
+ retry_at = datetime_or_none(cache.get(_KEY_RETRY_AT))
430
+ if retry_at is not None and retry_at > now():
431
+ raise ZKBTooManyRequestsError(retry_at=retry_at, is_original=False)
442
432
 
433
+ last_request = datetime_or_none(cache.get(_KEY_LAST_REQUEST))
434
+ if last_request is not None:
443
435
  next_slot = last_request + dt.timedelta(
444
436
  milliseconds=KILLTRACKER_ZKB_REQUEST_DELAY
445
437
  )
446
438
  seconds = (next_slot - now()).total_seconds()
447
439
  if seconds > 0:
448
- logger.debug("Waiting %f seconds to for next free slot", seconds)
440
+ logger.debug("ZKB API: Waiting %f seconds for next free slot", seconds)
449
441
  sleep(seconds)
450
442
 
451
443
  response = requests.get(
452
- ZKB_REDISQ_URL,
444
+ _ZKB_REDISQ_URL,
453
445
  params={
454
446
  "queueID": quote_plus(KILLTRACKER_QUEUE_ID),
455
447
  "ttw": KILLTRACKER_REDISQ_TTW,
456
448
  },
457
- timeout=REQUESTS_TIMEOUT,
449
+ timeout=_REQUESTS_TIMEOUT,
458
450
  headers={"User-Agent": USER_AGENT_TEXT},
459
451
  )
460
- cache.set(key_last_request, dt.datetime.isoformat(now()), timeout=30)
452
+ cache.set(_KEY_LAST_REQUEST, now(), timeout=KILLTRACKER_ZKB_REQUEST_DELAY + 30)
461
453
  logger.debug(
462
454
  "Response from ZKB API: %d %s %s",
463
455
  response.status_code,
@@ -473,9 +465,9 @@ class Killmail(_KillmailBase):
473
465
  try:
474
466
  retry_after = int(response.headers["Retry-After"])
475
467
  except KeyError:
476
- retry_after = DEFAULT_429_TIMEOUT
468
+ retry_after = _ZKB_429_DEFAULT_TIMEOUT
477
469
  retry_at = now() + dt.timedelta(seconds=retry_after)
478
- cache.set(key_retry_at, retry_at.isoformat(), timeout=retry_after + 60)
470
+ cache.set(_KEY_RETRY_AT, retry_at, timeout=retry_after + 60)
479
471
  raise ZKBTooManyRequestsError(retry_at=retry_at, is_original=True)
480
472
 
481
473
  return None
@@ -514,9 +506,9 @@ class Killmail(_KillmailBase):
514
506
  "Trying to fetch killmail from ZKB API with killmail ID %d ...",
515
507
  killmail_id,
516
508
  )
517
- url = f"{ZKB_API_URL}killID/{killmail_id}/"
509
+ url = f"{_ZKB_API_URL}killID/{killmail_id}/"
518
510
  response = requests.get(
519
- url, timeout=REQUESTS_TIMEOUT, headers={"User-Agent": USER_AGENT_TEXT}
511
+ url, timeout=_REQUESTS_TIMEOUT, headers={"User-Agent": USER_AGENT_TEXT}
520
512
  )
521
513
  response.raise_for_status()
522
514
  zkb_data = response.json()