aa-rss-to-discord 2.4.0__tar.gz → 2.5.0__tar.gz

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 (55) hide show
  1. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/PKG-INFO +4 -4
  2. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/README.md +2 -2
  3. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/__init__.py +1 -1
  4. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.po +1 -1
  5. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/de/LC_MESSAGES/django.po +1 -1
  6. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/django.pot +2 -2
  7. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/es/LC_MESSAGES/django.mo +0 -0
  8. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/es/LC_MESSAGES/django.po +7 -5
  9. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.po +1 -1
  10. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.po +1 -1
  11. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/ja/LC_MESSAGES/django.po +1 -1
  12. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.po +1 -1
  13. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.po +1 -1
  14. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.po +1 -1
  15. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/ru/LC_MESSAGES/django.po +1 -1
  16. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/sk/LC_MESSAGES/django.po +1 -1
  17. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/uk/LC_MESSAGES/django.mo +0 -0
  18. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/uk/LC_MESSAGES/django.po +6 -5
  19. aa_rss_to_discord-2.5.0/aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  20. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.po +11 -12
  21. aa_rss_to_discord-2.5.0/aa_rss_to_discord/tasks.py +154 -0
  22. aa_rss_to_discord-2.5.0/aa_rss_to_discord/tests/test_tasks.py +236 -0
  23. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/pyproject.toml +1 -1
  24. aa_rss_to_discord-2.4.0/aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  25. aa_rss_to_discord-2.4.0/aa_rss_to_discord/tasks.py +0 -131
  26. aa_rss_to_discord-2.4.0/aa_rss_to_discord/tests/test_tasks.py +0 -176
  27. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/.gitignore +0 -0
  28. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/LICENSE +0 -0
  29. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/admin.py +0 -0
  30. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/apps.py +0 -0
  31. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/auth_hooks.py +0 -0
  32. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/constants.py +0 -0
  33. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/discordbot/cogs/__init__.py +0 -0
  34. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/discordbot/cogs/rss.py +0 -0
  35. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  36. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/de/LC_MESSAGES/django.mo +0 -0
  37. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  38. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  39. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/ja/LC_MESSAGES/django.mo +0 -0
  40. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  41. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  42. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  43. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/ru/LC_MESSAGES/django.mo +0 -0
  44. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/locale/sk/LC_MESSAGES/django.mo +0 -0
  45. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/managers.py +0 -0
  46. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/migrations/0001_initial.py +0 -0
  47. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/migrations/0002_enable_disable_rss_feeds.py +0 -0
  48. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/migrations/__init__.py +0 -0
  49. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/models.py +0 -0
  50. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/providers.py +0 -0
  51. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/tests/__init__.py +0 -0
  52. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/tests/test_auth_hooks.py +0 -0
  53. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/tests/test_managers.py +0 -0
  54. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/tests/test_models.py +0 -0
  55. {aa_rss_to_discord-2.4.0 → aa_rss_to_discord-2.5.0}/aa_rss_to_discord/tests/test_providers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aa-rss-to-discord
3
- Version: 2.4.0
3
+ Version: 2.5.0
4
4
  Summary: Alliance Auth module to post news from RSS feeds to your Discord
5
5
  Project-URL: Changelog, https://github.com/ppfeufer/aa-rss-to-discord/blob/master/CHANGELOG.md
6
6
  Project-URL: Codecov, https://codecov.io/gh/ppfeufer/aa-rss-to-discord
@@ -705,7 +705,7 @@ Requires-Python: <3.14,>=3.10
705
705
  Requires-Dist: allianceauth-discordbot>=3.0.5
706
706
  Requires-Dist: allianceauth<5,>=4.3.1
707
707
  Requires-Dist: feedparser
708
- Requires-Dist: py-cord>=2.7.0rc2; python_version == '3.13'
708
+ Requires-Dist: py-cord>=2.7.0rc2; python_version >= '3.13'
709
709
  Provides-Extra: tests-allianceauth-latest
710
710
  Requires-Dist: coverage; extra == 'tests-allianceauth-latest'
711
711
  Requires-Dist: django-webtest; extra == 'tests-allianceauth-latest'
@@ -773,7 +773,7 @@ Make sure you're in the virtual environment (venv) of your Alliance Auth
773
773
  installation Then install the latest release directly from PyPi.
774
774
 
775
775
  ```shell
776
- pip install aa-rss-to-discord==2.4.0
776
+ pip install aa-rss-to-discord==2.5.0
777
777
  ```
778
778
 
779
779
  ### Step 2: Configure Alliance Auth<a name="step-2-configure-alliance-auth"></a>
@@ -828,7 +828,7 @@ To update your existing installation of Alliance Auth RSS to Discord, first enab
828
828
  virtual environment (venv) of your Alliance Auth installation.
829
829
 
830
830
  ```bash
831
- pip install aa-rss-to-discord==2.4.0
831
+ pip install aa-rss-to-discord==2.5.0
832
832
 
833
833
  python manage.py migrate
834
834
  ```
@@ -60,7 +60,7 @@ Make sure you're in the virtual environment (venv) of your Alliance Auth
60
60
  installation Then install the latest release directly from PyPi.
61
61
 
62
62
  ```shell
63
- pip install aa-rss-to-discord==2.4.0
63
+ pip install aa-rss-to-discord==2.5.0
64
64
  ```
65
65
 
66
66
  ### Step 2: Configure Alliance Auth<a name="step-2-configure-alliance-auth"></a>
@@ -115,7 +115,7 @@ To update your existing installation of Alliance Auth RSS to Discord, first enab
115
115
  virtual environment (venv) of your Alliance Auth installation.
116
116
 
117
117
  ```bash
118
- pip install aa-rss-to-discord==2.4.0
118
+ pip install aa-rss-to-discord==2.5.0
119
119
 
120
120
  python manage.py migrate
121
121
  ```
@@ -5,6 +5,6 @@ App init
5
5
  # Django
6
6
  from django.utils.translation import gettext_lazy as _
7
7
 
8
- __version__ = "2.4.0"
8
+ __version__ = "2.5.0"
9
9
  __title__ = "RSS to Discord"
10
10
  __title_translated__ = _("RSS to Discord")
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Anonymous <noreply@weblate.org>\n"
13
13
  "Language-Team: Czech <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/cs/>\n"
@@ -6,7 +6,7 @@ msgid ""
6
6
  msgstr ""
7
7
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
8
8
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
9
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
9
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
10
10
  "PO-Revision-Date: 2025-04-25 14:24+0000\n"
11
11
  "Last-Translator: Peter Pfeufer <info@ppfeufer.de>\n"
12
12
  "Language-Team: German <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/de/>\n"
@@ -6,9 +6,9 @@
6
6
  #, fuzzy
7
7
  msgid ""
8
8
  msgstr ""
9
- "Project-Id-Version: AA RSS to Discord 2.4.0\n"
9
+ "Project-Id-Version: AA RSS to Discord 2.5.0\n"
10
10
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
11
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
11
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
12
12
  "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
13
13
  "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
14
14
  "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -2,24 +2,26 @@
2
2
  # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
3
  # This file is distributed under the same license as the PACKAGE package.
4
4
  # Zigor Fernandez Moreno <sietehierros@gmail.com>, 2023, 2024.
5
+ # Ivan <isangar2000@gmail.com>, 2025.
5
6
  msgid ""
6
7
  msgstr ""
7
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
8
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
9
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
- "PO-Revision-Date: 2024-05-10 14:10+0000\n"
11
- "Last-Translator: Zigor Fernandez Moreno <sietehierros@gmail.com>\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
+ "PO-Revision-Date: 2025-12-18 23:47+0000\n"
12
+ "Last-Translator: Ivan <isangar2000@gmail.com>\n"
12
13
  "Language-Team: Spanish <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/es/>\n"
13
14
  "Language: es\n"
14
15
  "MIME-Version: 1.0\n"
15
16
  "Content-Type: text/plain; charset=UTF-8\n"
16
17
  "Content-Transfer-Encoding: 8bit\n"
17
18
  "Plural-Forms: nplurals=2; plural=n != 1;\n"
18
- "X-Generator: Weblate 5.5.3\n"
19
+ "X-Generator: Weblate 5.14.3\n"
19
20
 
20
21
  #: aa_rss_to_discord/__init__.py:10
22
+ #, fuzzy
21
23
  msgid "RSS to Discord"
22
- msgstr ""
24
+ msgstr "RSS ir discord"
23
25
 
24
26
  #: aa_rss_to_discord/models.py:44
25
27
  msgid "RSS Feed"
@@ -9,7 +9,7 @@ msgid ""
9
9
  msgstr ""
10
10
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
11
11
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
12
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
12
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
13
13
  "PO-Revision-Date: 2025-08-03 04:24+0000\n"
14
14
  "Last-Translator: The “Devcutter” Guy <mick162534@gmail.com>\n"
15
15
  "Language-Team: French <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/fr/>\n"
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Anonymous <noreply@weblate.org>\n"
13
13
  "Language-Team: Italian <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/it/>\n"
@@ -6,7 +6,7 @@ msgid ""
6
6
  msgstr ""
7
7
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
8
8
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
9
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
9
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
10
10
  "PO-Revision-Date: 2024-08-05 10:10+0000\n"
11
11
  "Last-Translator: Anata_no_Usiro <yt23542354m@gmail.com>\n"
12
12
  "Language-Team: Japanese <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/ja/>\n"
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Hue Radient <seataoji@gmail.com>\n"
13
13
  "Language-Team: Korean <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/ko/>\n"
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Anonymous <noreply@weblate.org>\n"
13
13
  "Language-Team: Dutch <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/nl/>\n"
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Anonymous <noreply@weblate.org>\n"
13
13
  "Language-Team: Polish <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/pl/>\n"
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Dromiel <dimhry@yandex.ru>\n"
13
13
  "Language-Team: Russian <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/ru/>\n"
@@ -7,7 +7,7 @@ msgid ""
7
7
  msgstr ""
8
8
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
9
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
10
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
11
11
  "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
12
  "Last-Translator: Anonymous <noreply@weblate.org>\n"
13
13
  "Language-Team: Slovak <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/sk/>\n"
@@ -3,20 +3,21 @@
3
3
  # This file is distributed under the same license as the PACKAGE package.
4
4
  # Peter Pfeufer <info@ppfeufer.de>, 2023, 2024.
5
5
  # s0k0l -_- <salarysalo@gmail.com>, 2025.
6
+ # Варрус <horowiserus@gmail.com>, 2025.
6
7
  msgid ""
7
8
  msgstr ""
8
9
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
10
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
11
- "PO-Revision-Date: 2025-08-27 20:43+0000\n"
12
- "Last-Translator: s0k0l -_- <salarysalo@gmail.com>\n"
11
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
12
+ "PO-Revision-Date: 2025-12-15 14:17+0000\n"
13
+ "Last-Translator: Варрус <horowiserus@gmail.com>\n"
13
14
  "Language-Team: Ukrainian <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/uk/>\n"
14
15
  "Language: uk\n"
15
16
  "MIME-Version: 1.0\n"
16
17
  "Content-Type: text/plain; charset=UTF-8\n"
17
18
  "Content-Transfer-Encoding: 8bit\n"
18
19
  "Plural-Forms: nplurals=4; plural=n==1 ? 3 : (n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2);\n"
19
- "X-Generator: Weblate 5.13\n"
20
+ "X-Generator: Weblate 5.14.3\n"
20
21
 
21
22
  #: aa_rss_to_discord/__init__.py:10
22
23
  msgid "RSS to Discord"
@@ -24,7 +25,7 @@ msgstr "RSS до Діскорду"
24
25
 
25
26
  #: aa_rss_to_discord/models.py:44
26
27
  msgid "RSS Feed"
27
- msgstr "RSS-фід"
28
+ msgstr "RSS-стрічка"
28
29
 
29
30
  #: aa_rss_to_discord/models.py:45
30
31
  msgid "RSS Feeds"
@@ -1,39 +1,38 @@
1
1
  # SOME DESCRIPTIVE TITLE.
2
2
  # Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
3
3
  # This file is distributed under the same license as the PACKAGE package.
4
- # FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
5
- #
4
+ # AKA Patrick <hsc844766246@gmail.com>, 2025.
6
5
  msgid ""
7
6
  msgstr ""
8
7
  "Project-Id-Version: AA RSS to Discord 2.2.0\n"
9
8
  "Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
10
- "POT-Creation-Date: 2025-12-02 08:06+0100\n"
11
- "PO-Revision-Date: 2024-05-10 14:10+0000\n"
12
- "Last-Translator: Anonymous <noreply@weblate.org>\n"
13
- "Language-Team: Chinese (Simplified) <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/zh_Hans/>\n"
9
+ "POT-Creation-Date: 2026-01-05 22:28+0100\n"
10
+ "PO-Revision-Date: 2025-12-08 09:17+0000\n"
11
+ "Last-Translator: AKA Patrick <hsc844766246@gmail.com>\n"
12
+ "Language-Team: Chinese (Simplified Han script) <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/zh_Hans/>\n"
14
13
  "Language: zh_Hans\n"
15
14
  "MIME-Version: 1.0\n"
16
15
  "Content-Type: text/plain; charset=UTF-8\n"
17
16
  "Content-Transfer-Encoding: 8bit\n"
18
17
  "Plural-Forms: nplurals=1; plural=0;\n"
19
- "X-Generator: Weblate 5.5.3\n"
18
+ "X-Generator: Weblate 5.14.3\n"
20
19
 
21
20
  #: aa_rss_to_discord/__init__.py:10
22
21
  msgid "RSS to Discord"
23
- msgstr ""
22
+ msgstr "链接Discord的Rss"
24
23
 
25
24
  #: aa_rss_to_discord/models.py:44
26
25
  msgid "RSS Feed"
27
- msgstr ""
26
+ msgstr "RSS源"
28
27
 
29
28
  #: aa_rss_to_discord/models.py:45
30
29
  msgid "RSS Feeds"
31
- msgstr ""
30
+ msgstr "RSS源"
32
31
 
33
32
  #: aa_rss_to_discord/models.py:72
34
33
  msgid "Last Item"
35
- msgstr ""
34
+ msgstr "最后一项"
36
35
 
37
36
  #: aa_rss_to_discord/models.py:73
38
37
  msgid "Last Items"
39
- msgstr ""
38
+ msgstr "最后几项"
@@ -0,0 +1,154 @@
1
+ """
2
+ AA RSS To Discord Tasks
3
+ """
4
+
5
+ # Standard Library
6
+ import re
7
+
8
+ # Third Party
9
+ import feedparser
10
+ from aadiscordbot.tasks import send_message
11
+ from celery import group, shared_task
12
+
13
+ # Alliance Auth
14
+ from allianceauth.services.hooks import get_extension_logger
15
+ from allianceauth.services.tasks import QueueOnce
16
+
17
+ # AA RSS to Discord
18
+ from aa_rss_to_discord import __title__
19
+ from aa_rss_to_discord.constants import USER_AGENT
20
+ from aa_rss_to_discord.models import LastItem, RssFeeds
21
+ from aa_rss_to_discord.providers import AppLogger
22
+
23
+ logger = AppLogger(get_extension_logger(__name__), __title__)
24
+
25
+
26
+ def remove_emoji(string: str) -> str:
27
+ """
28
+ Removing these dumb as fuck emojis from the title string.
29
+ Like honestly, who in the hell needs that shit?
30
+
31
+ :param string: Input string
32
+ :type string: str
33
+ :return: String without emojis
34
+ :rtype: str
35
+ """
36
+
37
+ emoji_pattern = re.compile(
38
+ pattern="["
39
+ "\U0001f600-\U0001f64f" # Emoticons
40
+ "\U0001f300-\U0001f5ff" # Symbols & pictographs
41
+ "\U0001f680-\U0001f6ff" # Transport & map symbols
42
+ "\U0001f1e0-\U0001f1ff" # Flags (iOS)
43
+ "\U00002500-\U00002bef" # Chinese char
44
+ "\U00002702-\U000027b0"
45
+ "\U00002702-\U000027b0"
46
+ "\U000024c2-\U0001f251"
47
+ "\U0001f926-\U0001f937"
48
+ "\U00010000-\U0010ffff"
49
+ "\u2640-\u2642"
50
+ "\u2600-\u2b55"
51
+ "\u200d"
52
+ "\u23cf"
53
+ "\u23e9"
54
+ "\u231a"
55
+ "\ufe0f" # Dingbats
56
+ "\u3030"
57
+ "]+",
58
+ flags=re.UNICODE,
59
+ )
60
+
61
+ return emoji_pattern.sub(repl=r"", string=string)
62
+
63
+
64
+ @shared_task(**{"base": QueueOnce})
65
+ def fetch_rss() -> None:
66
+ """
67
+ Fetch all enabled RSS feeds and dispatch processing tasks.
68
+
69
+ :return: None
70
+ :rtype: None
71
+ """
72
+
73
+ rss_feeds = RssFeeds.objects.select_enabled()
74
+ if not rss_feeds:
75
+ logger.debug("No RSS feeds found to parse.")
76
+ return
77
+
78
+ feed_ids = [f.id for f in rss_feeds]
79
+ if not feed_ids:
80
+ logger.debug("No RSS feed ids to dispatch.")
81
+ return
82
+
83
+ group(_process_feed.s(fid) for fid in feed_ids).apply_async()
84
+
85
+
86
+ @shared_task
87
+ def _process_feed(rss_feed_id: int) -> None:
88
+ """
89
+ Process a single RSS feed by fetching its latest entry and posting to Discord if it's new.
90
+
91
+ :param rss_feed_id: ID of the RSS feed to process
92
+ :type rss_feed_id: int
93
+ :return: None
94
+ :rtype: None
95
+ """
96
+
97
+ try:
98
+ rss_feed = RssFeeds.objects.select_related("discord_channel").get(
99
+ id=rss_feed_id
100
+ )
101
+ except RssFeeds.DoesNotExist:
102
+ logger.debug("RSS feed %s not found", rss_feed_id)
103
+ return
104
+
105
+ logger.info(f'Fetching RSS Feed "{rss_feed.name}"')
106
+ feed = feedparser.parse(rss_feed.url, agent=USER_AGENT)
107
+
108
+ latest_entry = next(iter(getattr(feed, "entries", [])), None)
109
+ if not latest_entry:
110
+ logger.debug(f'No entries found for feed "{rss_feed.name}".')
111
+ return
112
+
113
+ feed_entry_title = remove_emoji(latest_entry.get("title", "No title"))
114
+ feed_entry_link = latest_entry.get("link")
115
+ feed_entry_time = latest_entry.get("published", latest_entry.get("updated"))
116
+ feed_entry_guid = latest_entry.get("id")
117
+
118
+ last_item = LastItem.objects.filter(rss_feed=rss_feed).first()
119
+ if last_item and (
120
+ last_item.rss_item_time,
121
+ last_item.rss_item_title,
122
+ last_item.rss_item_link,
123
+ last_item.rss_item_guid,
124
+ ) == (feed_entry_time, feed_entry_title, feed_entry_link, feed_entry_guid):
125
+ logger.debug(
126
+ 'News item "%s" for RSS Feed "%s" has already been posted to your Discord',
127
+ feed_entry_title,
128
+ rss_feed.name,
129
+ )
130
+ return
131
+
132
+ if not last_item:
133
+ logger.debug("This seems to be a completely new RSS feed: %s", rss_feed.name)
134
+
135
+ logger.info(
136
+ 'New entry found for RSS feed "%s", posting to Discord channel %s',
137
+ rss_feed.name,
138
+ rss_feed.discord_channel,
139
+ )
140
+
141
+ LastItem.objects.update_or_create(
142
+ rss_feed=rss_feed,
143
+ defaults={
144
+ "rss_item_time": feed_entry_time,
145
+ "rss_item_title": feed_entry_title,
146
+ "rss_item_link": feed_entry_link,
147
+ "rss_item_guid": feed_entry_guid,
148
+ },
149
+ )
150
+
151
+ send_message(
152
+ channel_id=rss_feed.discord_channel.channel,
153
+ message=f"**{rss_feed.name}**\n{feed_entry_link}",
154
+ )
@@ -0,0 +1,236 @@
1
+ """
2
+ Test cases for the fetch_rss task
3
+ """
4
+
5
+ # Standard Library
6
+ from types import SimpleNamespace
7
+ from unittest.mock import MagicMock, patch
8
+
9
+ # AA RSS to Discord
10
+ from aa_rss_to_discord.constants import USER_AGENT
11
+ from aa_rss_to_discord.models import RssFeeds
12
+ from aa_rss_to_discord.tasks import _process_feed, fetch_rss
13
+ from aa_rss_to_discord.tests import BaseTestCase
14
+
15
+
16
+ class TestFetchRss(BaseTestCase):
17
+ """
18
+ Test cases for the fetch_rss task
19
+ """
20
+
21
+ def test_fetch_rss_logs_message_when_no_rss_feeds_found(self):
22
+ """
23
+ Test that fetch_rss logs a message when no RSS feeds are found.
24
+
25
+ :return:
26
+ :rtype:
27
+ """
28
+
29
+ with (
30
+ patch(
31
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled"
32
+ ) as mock_select_enabled,
33
+ patch("aa_rss_to_discord.tasks.logger.debug") as mock_logger_debug,
34
+ ):
35
+ mock_select_enabled.return_value = []
36
+
37
+ fetch_rss()
38
+
39
+ mock_logger_debug.assert_called_once_with("No RSS feeds found to parse.")
40
+
41
+ def test_fetch_rss_dispatches_tasks_for_enabled_feeds(self):
42
+ """
43
+ Test that fetch_rss dispatches tasks for enabled RSS feeds.
44
+
45
+ :return:
46
+ :rtype:
47
+ """
48
+
49
+ with (
50
+ patch(
51
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled"
52
+ ) as mock_select_enabled,
53
+ patch("aa_rss_to_discord.tasks.group") as mock_group,
54
+ ):
55
+ mock_rss_feed_1 = MagicMock(id=1)
56
+ mock_rss_feed_2 = MagicMock(id=2)
57
+ mock_select_enabled.return_value = [mock_rss_feed_1, mock_rss_feed_2]
58
+
59
+ fetch_rss()
60
+
61
+ mock_group.assert_called_once()
62
+ mock_group.return_value.apply_async.assert_called_once()
63
+ dispatched_tasks = mock_group.call_args[0][0]
64
+ dispatched_list = list(dispatched_tasks)
65
+ self.assertEqual(len(dispatched_list), 2)
66
+ self.assertEqual(dispatched_list[0].args[0], 1)
67
+ self.assertEqual(dispatched_list[1].args[0], 2)
68
+
69
+ def test_handles_empty_rss_feed_ids_gracefully(self):
70
+ """
71
+ Test that fetch_rss handles an empty list of RSS feed IDs gracefully.
72
+
73
+ :return:
74
+ :rtype:
75
+ """
76
+
77
+ with (
78
+ patch(
79
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled"
80
+ ) as mock_select_enabled,
81
+ patch("aa_rss_to_discord.tasks.group") as mock_group,
82
+ patch("aa_rss_to_discord.tasks.logger.debug") as mock_logger_debug,
83
+ ):
84
+ mock_select_enabled.return_value = iter([])
85
+
86
+ fetch_rss()
87
+
88
+ mock_group.assert_not_called()
89
+ mock_logger_debug.assert_called_once_with("No RSS feed ids to dispatch.")
90
+
91
+
92
+ class TestHelperProcessRssFeed(BaseTestCase):
93
+ """
94
+ Test cases for the _process_feed task
95
+ """
96
+
97
+ def test_handles_missing_rss_feed_gracefully(self):
98
+ """
99
+ Test that _process_feed handles a missing RSS feed gracefully.
100
+
101
+ :return:
102
+ :rtype:
103
+ """
104
+
105
+ with patch(
106
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_related"
107
+ ) as mock_select_related:
108
+ mock_select_related.return_value.get.side_effect = RssFeeds.DoesNotExist
109
+
110
+ _process_feed(1)
111
+
112
+ mock_select_related.return_value.get.assert_called_once_with(id=1)
113
+
114
+ def test_logs_and_skips_when_no_entries_in_feed(self):
115
+ """
116
+ Test that _process_feed logs and skips processing when no entries are found in the feed.
117
+
118
+ :return:
119
+ :rtype:
120
+ """
121
+
122
+ with (
123
+ patch(
124
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_related"
125
+ ) as mock_select_related,
126
+ patch(
127
+ "aa_rss_to_discord.tasks.feedparser.parse", return_value={}
128
+ ) as mock_parse,
129
+ ):
130
+ mock_rss_feed = MagicMock(url="http://example.com", name="No Entries Feed")
131
+ mock_select_related.return_value.get.return_value = mock_rss_feed
132
+
133
+ _process_feed(1)
134
+
135
+ mock_parse.assert_called_once_with(mock_rss_feed.url, agent=USER_AGENT)
136
+
137
+ def test_posts_new_entry_to_discord(self):
138
+ """
139
+ Test that _process_feed posts a new entry to Discord when it's not a duplicate.
140
+
141
+ :return:
142
+ :rtype:
143
+ """
144
+
145
+ entry = {"title": "New Entry", "link": "http://example.com", "id": "123"}
146
+
147
+ with (
148
+ patch(
149
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_related"
150
+ ) as mock_select_related,
151
+ patch(
152
+ "aa_rss_to_discord.tasks.feedparser.parse",
153
+ return_value=SimpleNamespace(entries=[entry]),
154
+ ) as mock_parse,
155
+ patch("aa_rss_to_discord.tasks.LastItem.objects.filter") as mock_filter,
156
+ patch(
157
+ "aa_rss_to_discord.tasks.LastItem.objects.update_or_create"
158
+ ) as mock_update_or_create,
159
+ patch("aa_rss_to_discord.tasks.send_message") as mock_send_message,
160
+ ):
161
+ mock_rss_feed = MagicMock(
162
+ discord_channel=MagicMock(channel=12345),
163
+ name="Test Feed",
164
+ url="http://example.com",
165
+ )
166
+ mock_select_related.return_value.get.return_value = mock_rss_feed
167
+ mock_filter.return_value.first.return_value = None
168
+ mock_update_or_create.return_value = (MagicMock(), True)
169
+
170
+ _process_feed(1)
171
+
172
+ mock_parse.assert_called_once_with(mock_rss_feed.url, agent=USER_AGENT)
173
+ mock_update_or_create.assert_called_once()
174
+ self.assertTrue(
175
+ mock_send_message.called,
176
+ f"send_message not called. calls: {mock_send_message.call_args_list}",
177
+ )
178
+
179
+ call_args, call_kwargs = mock_send_message.call_args
180
+ called_channel = (
181
+ call_kwargs.get("channel_id")
182
+ if "channel_id" in call_kwargs
183
+ else (call_args[0] if len(call_args) > 0 else None)
184
+ )
185
+ called_message = (
186
+ call_kwargs.get("message")
187
+ if "message" in call_kwargs
188
+ else (
189
+ call_args[1]
190
+ if len(call_args) > 1
191
+ else (call_args[0] if len(call_args) == 1 else None)
192
+ )
193
+ )
194
+
195
+ self.assertEqual(called_channel, 12345)
196
+ self.assertIsNotNone(called_message)
197
+ self.assertIn(mock_rss_feed.url, called_message)
198
+
199
+ def test_skips_posting_duplicate_entry(self):
200
+ """
201
+ Test that _process_feed skips posting a duplicate entry to Discord.
202
+
203
+ :return:
204
+ :rtype:
205
+ """
206
+
207
+ entry = {"title": "Duplicate Entry", "link": "http://example.com", "id": "123"}
208
+
209
+ with (
210
+ patch(
211
+ "aa_rss_to_discord.tasks.RssFeeds.objects.select_related"
212
+ ) as mock_select_related,
213
+ patch(
214
+ "aa_rss_to_discord.tasks.feedparser.parse",
215
+ return_value=SimpleNamespace(entries=[entry]),
216
+ ),
217
+ patch("aa_rss_to_discord.tasks.LastItem.objects.filter") as mock_filter,
218
+ patch("aa_rss_to_discord.tasks.send_message") as mock_send_message,
219
+ ):
220
+ mock_rss_feed = MagicMock(
221
+ discord_channel=MagicMock(channel=12345),
222
+ name="Dup Feed",
223
+ url="http://example.com",
224
+ )
225
+ mock_select_related.return_value.get.return_value = mock_rss_feed
226
+ mock_existing = MagicMock(
227
+ rss_item_time=None,
228
+ rss_item_title="Duplicate Entry",
229
+ rss_item_link="http://example.com",
230
+ rss_item_guid="123",
231
+ )
232
+ mock_filter.return_value.first.return_value = mock_existing
233
+
234
+ _process_feed(1)
235
+
236
+ mock_send_message.assert_not_called()
@@ -42,7 +42,7 @@ dependencies = [
42
42
  "allianceauth>=4.3.1,<5",
43
43
  "allianceauth-discordbot>=3.0.5",
44
44
  "feedparser",
45
- "py-cord>=2.7.0rc2; python_version=='3.13'",
45
+ "py-cord>=2.7.0rc2; python_version>='3.13'",
46
46
  ]
47
47
  optional-dependencies.tests-allianceauth-latest = [
48
48
  "coverage",
@@ -1,131 +0,0 @@
1
- """
2
- AA RSS To Discord Tasks
3
- """
4
-
5
- # Standard Library
6
- import re
7
-
8
- # Third Party
9
- import feedparser
10
- from aadiscordbot.tasks import send_message
11
- from celery import shared_task
12
-
13
- # Alliance Auth
14
- from allianceauth.services.hooks import get_extension_logger
15
- from allianceauth.services.tasks import QueueOnce
16
-
17
- # AA RSS to Discord
18
- from aa_rss_to_discord import __title__
19
- from aa_rss_to_discord.constants import USER_AGENT
20
- from aa_rss_to_discord.models import LastItem, RssFeeds
21
- from aa_rss_to_discord.providers import AppLogger
22
-
23
- logger = AppLogger(get_extension_logger(__name__), __title__)
24
-
25
-
26
- def remove_emoji(string):
27
- """
28
- Removing these dumb as fuck emojis from the title string.
29
- Like honestly, who in the hell needs that shit?
30
-
31
- :param string:
32
- :type string:
33
- :return:
34
- :rtype:
35
- """
36
-
37
- emoji_pattern = re.compile(
38
- pattern="["
39
- "\U0001f600-\U0001f64f" # Emoticons
40
- "\U0001f300-\U0001f5ff" # Symbols & pictographs
41
- "\U0001f680-\U0001f6ff" # Transport & map symbols
42
- "\U0001f1e0-\U0001f1ff" # Flags (iOS)
43
- "\U00002500-\U00002bef" # Chinese char
44
- "\U00002702-\U000027b0"
45
- "\U00002702-\U000027b0"
46
- "\U000024c2-\U0001f251"
47
- "\U0001f926-\U0001f937"
48
- "\U00010000-\U0010ffff"
49
- "\u2640-\u2642"
50
- "\u2600-\u2b55"
51
- "\u200d"
52
- "\u23cf"
53
- "\u23e9"
54
- "\u231a"
55
- "\ufe0f" # Dingbats
56
- "\u3030"
57
- "]+",
58
- flags=re.UNICODE,
59
- )
60
-
61
- return emoji_pattern.sub(repl=r"", string=string)
62
-
63
-
64
- @shared_task(**{"base": QueueOnce})
65
- def fetch_rss() -> None:
66
- """
67
- Fetch RSS feeds and post new entries to Discord channels.
68
-
69
- :return:
70
- :rtype:
71
- """
72
-
73
- rss_feeds = RssFeeds.objects.select_enabled()
74
-
75
- if not rss_feeds:
76
- logger.debug("No RSS feeds found to parse.")
77
-
78
- return
79
-
80
- for rss_feed in rss_feeds:
81
- logger.info(f'Fetching RSS Feed "{rss_feed.name}"')
82
- feed = feedparser.parse(rss_feed.url, agent=USER_AGENT)
83
-
84
- try:
85
- latest_entry = feed.entries[0]
86
- feed_entry_title = remove_emoji(latest_entry.get("title", "No title"))
87
- feed_entry_link = latest_entry.get("link")
88
- feed_entry_time = latest_entry.get("published", latest_entry.updated)
89
- feed_entry_guid = latest_entry.get("id")
90
- except (AttributeError, IndexError) as exc:
91
- logger.debug(f'Error processing feed "{rss_feed.name}": {exc}')
92
-
93
- continue
94
-
95
- try:
96
- last_item = LastItem.objects.get(rss_feed=rss_feed)
97
- is_duplicate = (
98
- last_item.rss_item_time == feed_entry_time
99
- and last_item.rss_item_title == feed_entry_title
100
- and last_item.rss_item_link == feed_entry_link
101
- and last_item.rss_item_guid == feed_entry_guid
102
- )
103
-
104
- if is_duplicate:
105
- logger.debug(
106
- f'News item "{feed_entry_title}" for RSS Feed "{rss_feed.name}" '
107
- "has already been posted to your Discord"
108
- )
109
-
110
- continue
111
- except LastItem.DoesNotExist:
112
- logger.debug("This seems to be a completely new RSS feed.")
113
-
114
- logger.info(
115
- f"New entry found, posting to Discord channel {rss_feed.discord_channel}"
116
- )
117
-
118
- LastItem.objects.update_or_create(
119
- rss_feed=rss_feed,
120
- defaults={
121
- "rss_item_time": feed_entry_time,
122
- "rss_item_title": feed_entry_title,
123
- "rss_item_link": feed_entry_link,
124
- "rss_item_guid": feed_entry_guid,
125
- },
126
- )
127
-
128
- send_message(
129
- channel_id=rss_feed.discord_channel.channel,
130
- message=f"**{rss_feed.name}**\n{feed_entry_link}",
131
- )
@@ -1,176 +0,0 @@
1
- """
2
- Test cases for the fetch_rss task
3
- """
4
-
5
- # Standard Library
6
- from unittest.mock import MagicMock, patch
7
-
8
- # AA RSS to Discord
9
- from aa_rss_to_discord.models import LastItem
10
- from aa_rss_to_discord.tasks import fetch_rss
11
- from aa_rss_to_discord.tests import BaseTestCase
12
-
13
-
14
- class TestFetchRss(BaseTestCase):
15
- """
16
- Test fetch_rss task
17
- """
18
-
19
- def test_logs_warning_if_no_rss_feeds_found(self):
20
- """
21
- Test that a warning is logged if no RSS feeds are found.
22
-
23
- :return:
24
- :rtype:
25
- """
26
-
27
- with (
28
- patch(
29
- "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled",
30
- return_value=[],
31
- ),
32
- patch("aa_rss_to_discord.tasks.logger.debug") as mock_debug,
33
- ):
34
- fetch_rss()
35
- mock_debug.assert_called_once_with("No RSS feeds found to parse.")
36
-
37
- def test_processes_valid_rss_feed_entry(self):
38
- """
39
- Test that a valid RSS feed entry is processed and sent to Discord.
40
-
41
- :return:
42
- :rtype:
43
- """
44
-
45
- discord_channel = MagicMock()
46
- discord_channel.channel = 12345
47
-
48
- rss_feed = MagicMock()
49
- rss_feed.name = "Valid Feed"
50
- rss_feed.url = "http://example.com/rss"
51
- rss_feed.discord_channel = discord_channel
52
-
53
- # Use a MagicMock that acts like feedparser entry with both dict and attribute access
54
- feed_entry = MagicMock()
55
- feed_entry.get = lambda key, default=None: {
56
- "title": "Valid Entry",
57
- "link": "http://example.com/valid-entry",
58
- "published": "2023-10-01T12:00:00Z",
59
- "id": "entry-1",
60
- }.get(key, default)
61
- feed_entry.updated = (
62
- "2023-10-01T12:00:00Z" # Fallback if 'published' is missing
63
- )
64
-
65
- parsed_feed = MagicMock()
66
- parsed_feed.entries = [feed_entry]
67
-
68
- with (
69
- patch(
70
- "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled",
71
- return_value=[rss_feed],
72
- ),
73
- patch(
74
- "aa_rss_to_discord.tasks.feedparser.parse",
75
- return_value=parsed_feed,
76
- ),
77
- patch(
78
- "aa_rss_to_discord.tasks.LastItem.objects.get",
79
- side_effect=LastItem.DoesNotExist,
80
- ),
81
- patch(
82
- "aa_rss_to_discord.tasks.LastItem.objects.update_or_create",
83
- ),
84
- patch(
85
- "aa_rss_to_discord.tasks.send_message",
86
- ) as mock_send_message,
87
- ):
88
- fetch_rss()
89
-
90
- mock_send_message.assert_called_once()
91
- call_args = mock_send_message.call_args
92
- self.assertEqual(call_args.kwargs.get("channel_id"), 12345)
93
- message = call_args.kwargs.get("message", "")
94
- self.assertIn("http://example.com/valid-entry", message)
95
- self.assertIn("Valid Feed", message)
96
-
97
- def test_skips_duplicate_rss_feed_entry(self):
98
- """
99
- Test that a duplicate RSS feed entry is skipped.
100
-
101
- :return:
102
- :rtype:
103
- """
104
-
105
- rss_feed = MagicMock()
106
- rss_feed.name = "Duplicate Feed"
107
- rss_feed.url = "http://example.com/rss"
108
-
109
- # Create feed entry that supports both dict-style and attribute access
110
- feed_entry = MagicMock()
111
- feed_entry.get = lambda key, default=None: {
112
- "title": "Duplicate Entry",
113
- "link": "http://example.com/duplicate-entry",
114
- "published": "2023-10-01T12:00:00Z",
115
- "id": "entry-1",
116
- }.get(key, default)
117
- feed_entry.updated = "2023-10-01T12:00:00Z"
118
-
119
- last_item = MagicMock(
120
- rss_item_time="2023-10-01T12:00:00Z",
121
- rss_item_title="Duplicate Entry",
122
- rss_item_link="http://example.com/duplicate-entry",
123
- rss_item_guid="entry-1",
124
- )
125
-
126
- with (
127
- patch(
128
- "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled",
129
- return_value=[rss_feed],
130
- ),
131
- patch(
132
- "aa_rss_to_discord.tasks.feedparser.parse",
133
- return_value=MagicMock(entries=[feed_entry]),
134
- ),
135
- patch(
136
- "aa_rss_to_discord.tasks.LastItem.objects.get",
137
- return_value=last_item,
138
- ),
139
- patch("aa_rss_to_discord.tasks.logger.debug") as mock_debug,
140
- patch("aa_rss_to_discord.tasks.send_message") as mock_send_message,
141
- ):
142
- fetch_rss()
143
-
144
- mock_send_message.assert_not_called()
145
- mock_debug.assert_any_call(
146
- 'News item "Duplicate Entry" for RSS Feed "Duplicate Feed" has already been posted to your Discord'
147
- )
148
-
149
- def test_skips_rss_feed_with_no_entries(self):
150
- """
151
- Test that an RSS feed with no entries is skipped.
152
-
153
- :return:
154
- :rtype:
155
- """
156
-
157
- rss_feed = MagicMock()
158
- rss_feed.name = "Empty Feed"
159
- rss_feed.url = "http://example.com/rss"
160
-
161
- with (
162
- patch(
163
- "aa_rss_to_discord.tasks.RssFeeds.objects.select_enabled",
164
- return_value=[rss_feed],
165
- ),
166
- patch(
167
- "aa_rss_to_discord.tasks.feedparser.parse",
168
- return_value=MagicMock(entries=[]),
169
- ),
170
- patch("aa_rss_to_discord.tasks.logger.debug") as mock_debug,
171
- ):
172
- fetch_rss()
173
-
174
- mock_debug.assert_any_call(
175
- 'Error processing feed "Empty Feed": list index out of range'
176
- )