aa-rss-to-discord 2.3.5__py3-none-any.whl → 2.4.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_rss_to_discord/__init__.py +1 -1
- aa_rss_to_discord/constants.py +1 -1
- aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/de/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/django.pot +2 -2
- aa_rss_to_discord/locale/es/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/ja/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/ru/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/sk/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/uk/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.po +1 -1
- aa_rss_to_discord/managers.py +1 -1
- aa_rss_to_discord/providers.py +43 -0
- aa_rss_to_discord/tasks.py +59 -135
- aa_rss_to_discord/tests/__init__.py +41 -0
- aa_rss_to_discord/tests/test_auth_hooks.py +49 -0
- aa_rss_to_discord/tests/test_managers.py +32 -0
- aa_rss_to_discord/tests/test_models.py +165 -0
- aa_rss_to_discord/tests/test_providers.py +99 -0
- aa_rss_to_discord/tests/test_tasks.py +176 -0
- {aa_rss_to_discord-2.3.5.dist-info → aa_rss_to_discord-2.4.0.dist-info}/METADATA +4 -4
- aa_rss_to_discord-2.4.0.dist-info/RECORD +51 -0
- {aa_rss_to_discord-2.3.5.dist-info → aa_rss_to_discord-2.4.0.dist-info}/WHEEL +1 -1
- aa_rss_to_discord-2.3.5.dist-info/RECORD +0 -44
- {aa_rss_to_discord-2.3.5.dist-info → aa_rss_to_discord-2.4.0.dist-info}/licenses/LICENSE +0 -0
aa_rss_to_discord/__init__.py
CHANGED
aa_rss_to_discord/constants.py
CHANGED
|
@@ -9,7 +9,7 @@ from feedparser import USER_AGENT as feedparser_user_agent
|
|
|
9
9
|
from aa_rss_to_discord import __version__
|
|
10
10
|
|
|
11
11
|
APP_NAME = "aa-rss-to-discord"
|
|
12
|
-
APP_NAME_USERAGENT = "
|
|
12
|
+
APP_NAME_USERAGENT = "AaRssToDiscord"
|
|
13
13
|
GITHUB_URL = f"https://github.com/ppfeufer/{APP_NAME}"
|
|
14
14
|
USER_AGENT = (
|
|
15
15
|
f"{APP_NAME_USERAGENT}/{__version__} (+{GITHUB_URL}) via {feedparser_user_agent}"
|
|
@@ -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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
9
|
+
"POT-Creation-Date: 2025-12-02 08:06+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.
|
|
9
|
+
"Project-Id-Version: AA RSS to Discord 2.4.0\n"
|
|
10
10
|
"Report-Msgid-Bugs-To: https://github.com/ppfeufer/aa-rss-to-discord/issues\n"
|
|
11
|
-
"POT-Creation-Date: 2025-
|
|
11
|
+
"POT-Creation-Date: 2025-12-02 08:06+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"
|
|
@@ -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-
|
|
9
|
+
"POT-Creation-Date: 2025-12-02 08:06+0100\n"
|
|
10
10
|
"PO-Revision-Date: 2024-05-10 14:10+0000\n"
|
|
11
11
|
"Last-Translator: Zigor Fernandez Moreno <sietehierros@gmail.com>\n"
|
|
12
12
|
"Language-Team: Spanish <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/es/>\n"
|
|
@@ -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
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
9
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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"
|
|
@@ -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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+0100\n"
|
|
11
11
|
"PO-Revision-Date: 2025-08-27 20:43+0000\n"
|
|
12
12
|
"Last-Translator: s0k0l -_- <salarysalo@gmail.com>\n"
|
|
13
13
|
"Language-Team: Ukrainian <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/uk/>\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-
|
|
10
|
+
"POT-Creation-Date: 2025-12-02 08:06+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: Chinese (Simplified) <https://weblate.ppfeufer.de/projects/alliance-auth-apps/aa-rss-to-discord/zh_Hans/>\n"
|
aa_rss_to_discord/managers.py
CHANGED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Providers for the AA RSS to Discord app.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AppLogger(logging.LoggerAdapter):
|
|
10
|
+
"""
|
|
11
|
+
Custom logger adapter that adds a prefix to log messages.
|
|
12
|
+
|
|
13
|
+
Taken from the `allianceauth-app-utils` package.
|
|
14
|
+
Credits to: Erik Kalkoken
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, my_logger, prefix):
|
|
18
|
+
"""
|
|
19
|
+
Initializes the AppLogger with a logger and a prefix.
|
|
20
|
+
|
|
21
|
+
:param my_logger: Logger instance
|
|
22
|
+
:type my_logger: logging.Logger
|
|
23
|
+
:param prefix: Prefix string to add to log messages
|
|
24
|
+
:type prefix: str
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
super().__init__(my_logger, {})
|
|
28
|
+
|
|
29
|
+
self.prefix = prefix
|
|
30
|
+
|
|
31
|
+
def process(self, msg, kwargs):
|
|
32
|
+
"""
|
|
33
|
+
Prepares the log message by adding the prefix.
|
|
34
|
+
|
|
35
|
+
:param msg: Log message
|
|
36
|
+
:type msg: str
|
|
37
|
+
:param kwargs: Additional keyword arguments
|
|
38
|
+
:type kwargs: dict
|
|
39
|
+
:return: Prefixed log message and kwargs
|
|
40
|
+
:rtype: tuple
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
return f"[{self.prefix}] {msg}", kwargs
|
aa_rss_to_discord/tasks.py
CHANGED
|
@@ -3,28 +3,24 @@ AA RSS To Discord Tasks
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
5
|
# Standard Library
|
|
6
|
-
import logging
|
|
7
6
|
import re
|
|
8
7
|
|
|
9
8
|
# Third Party
|
|
10
9
|
import feedparser
|
|
10
|
+
from aadiscordbot.tasks import send_message
|
|
11
11
|
from celery import shared_task
|
|
12
12
|
|
|
13
|
-
# Django
|
|
14
|
-
from django.apps import apps
|
|
15
|
-
|
|
16
13
|
# Alliance Auth
|
|
17
14
|
from allianceauth.services.hooks import get_extension_logger
|
|
18
15
|
from allianceauth.services.tasks import QueueOnce
|
|
19
16
|
|
|
20
|
-
# Alliance Auth (External Libs)
|
|
21
|
-
from app_utils.logging import LoggerAddTag
|
|
22
|
-
|
|
23
17
|
# AA RSS to Discord
|
|
24
18
|
from aa_rss_to_discord import __title__
|
|
19
|
+
from aa_rss_to_discord.constants import USER_AGENT
|
|
25
20
|
from aa_rss_to_discord.models import LastItem, RssFeeds
|
|
21
|
+
from aa_rss_to_discord.providers import AppLogger
|
|
26
22
|
|
|
27
|
-
logger =
|
|
23
|
+
logger = AppLogger(get_extension_logger(__name__), __title__)
|
|
28
24
|
|
|
29
25
|
|
|
30
26
|
def remove_emoji(string):
|
|
@@ -66,142 +62,70 @@ def remove_emoji(string):
|
|
|
66
62
|
|
|
67
63
|
|
|
68
64
|
@shared_task(**{"base": QueueOnce})
|
|
69
|
-
def fetch_rss() -> None:
|
|
65
|
+
def fetch_rss() -> None:
|
|
70
66
|
"""
|
|
71
|
-
Fetch RSS feeds and post to Discord
|
|
67
|
+
Fetch RSS feeds and post new entries to Discord channels.
|
|
72
68
|
|
|
73
69
|
:return:
|
|
74
70
|
:rtype:
|
|
75
71
|
"""
|
|
76
72
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
send_message,
|
|
82
|
-
)
|
|
73
|
+
rss_feeds = RssFeeds.objects.select_enabled()
|
|
74
|
+
|
|
75
|
+
if not rss_feeds:
|
|
76
|
+
logger.debug("No RSS feeds found to parse.")
|
|
83
77
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
if rss_feeds:
|
|
87
|
-
for rss_feed in rss_feeds:
|
|
88
|
-
logger.info(msg=f'Fetching RSS Feed "{rss_feed.name}"')
|
|
89
|
-
|
|
90
|
-
feed = feedparser.parse(url_file_stream_or_string=rss_feed.url)
|
|
91
|
-
|
|
92
|
-
feed_entry_title = "No title"
|
|
93
|
-
feed_entry_link = None
|
|
94
|
-
feed_entry_time = None
|
|
95
|
-
feed_entry_guid = None
|
|
96
|
-
has_last_item = False
|
|
97
|
-
last_item = None
|
|
98
|
-
post_entry = False
|
|
99
|
-
|
|
100
|
-
try:
|
|
101
|
-
latest_entry = feed.entries[0]
|
|
102
|
-
|
|
103
|
-
feed_entry_title = remove_emoji(
|
|
104
|
-
string=latest_entry.get("title", "No title")
|
|
105
|
-
)
|
|
106
|
-
feed_entry_link = latest_entry.get("link", None)
|
|
107
|
-
feed_entry_time = latest_entry.get(
|
|
108
|
-
"published", latest_entry.updated
|
|
109
|
-
)
|
|
110
|
-
feed_entry_guid = latest_entry.get("id", None)
|
|
111
|
-
except AttributeError as exc:
|
|
112
|
-
logger.debug(
|
|
113
|
-
msg=f'Malformed RSS feed item in feed "{rss_feed.name}". Error: {exc}'
|
|
114
|
-
)
|
|
115
|
-
except IndexError as exc:
|
|
116
|
-
logger.debug(
|
|
117
|
-
msg=f'Could not index the RSS feed "{rss_feed.name}". Error: {exc}'
|
|
118
|
-
)
|
|
119
|
-
else:
|
|
120
|
-
post_entry = True
|
|
121
|
-
has_last_item = True
|
|
122
|
-
|
|
123
|
-
try:
|
|
124
|
-
last_item = LastItem.objects.get(rss_feed=rss_feed)
|
|
125
|
-
|
|
126
|
-
if (
|
|
127
|
-
last_item
|
|
128
|
-
and last_item.rss_item_time == feed_entry_time
|
|
129
|
-
and last_item.rss_item_title == feed_entry_title
|
|
130
|
-
and last_item.rss_item_link == feed_entry_link
|
|
131
|
-
and last_item.rss_item_guid == feed_entry_guid
|
|
132
|
-
):
|
|
133
|
-
logger.debug(
|
|
134
|
-
msg=(
|
|
135
|
-
f'News item "{feed_entry_title}" for RSS Feed '
|
|
136
|
-
f'"{rss_feed.name}" has already been posted to your Discord'
|
|
137
|
-
)
|
|
138
|
-
)
|
|
139
|
-
post_entry = False
|
|
140
|
-
except LastItem.DoesNotExist:
|
|
141
|
-
logger.debug(msg="This seems to be a completely new RSS feed.")
|
|
142
|
-
|
|
143
|
-
has_last_item = False
|
|
78
|
+
return
|
|
144
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:
|
|
145
105
|
logger.debug(
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
f"post_entry => {post_entry}, "
|
|
149
|
-
f'feed_entry_link => "{feed_entry_link}", '
|
|
150
|
-
f'feed_entry_title => "{feed_entry_title}", '
|
|
151
|
-
f"feed_entry_time => {feed_entry_time}, "
|
|
152
|
-
f"feed_entry_guid => {feed_entry_guid}"
|
|
153
|
-
)
|
|
106
|
+
f'News item "{feed_entry_title}" for RSS Feed "{rss_feed.name}" '
|
|
107
|
+
"has already been posted to your Discord"
|
|
154
108
|
)
|
|
155
109
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
LastItem(
|
|
178
|
-
rss_feed=rss_feed,
|
|
179
|
-
rss_item_time=feed_entry_time,
|
|
180
|
-
rss_item_title=feed_entry_title,
|
|
181
|
-
rss_item_link=feed_entry_link,
|
|
182
|
-
rss_item_guid=feed_entry_guid,
|
|
183
|
-
).save()
|
|
184
|
-
|
|
185
|
-
discord_message = f"**{rss_feed.name}**\n{feed_entry_link}"
|
|
186
|
-
|
|
187
|
-
send_message(
|
|
188
|
-
channel_id=rss_feed.discord_channel.channel,
|
|
189
|
-
message=discord_message,
|
|
190
|
-
)
|
|
191
|
-
else:
|
|
192
|
-
logger.debug(
|
|
193
|
-
msg=(
|
|
194
|
-
f'No item for feed "{rss_feed.name}" to post. '
|
|
195
|
-
'Missing either "post_entry" to be "True" or '
|
|
196
|
-
'either "feed_entry_link" or "feed_entry_guid" is "None".'
|
|
197
|
-
)
|
|
198
|
-
)
|
|
199
|
-
else:
|
|
200
|
-
logger.debug(msg="No RSS feeds found to parse.")
|
|
201
|
-
else:
|
|
202
|
-
logging.info(
|
|
203
|
-
msg=(
|
|
204
|
-
"AA Discordbot (https://github.com/pvyParts/allianceauth-discordbot) "
|
|
205
|
-
"needs to be installed and configured."
|
|
206
|
-
)
|
|
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}",
|
|
207
131
|
)
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Initialize the tests
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
import socket
|
|
7
|
+
|
|
8
|
+
# Django
|
|
9
|
+
from django.test import TestCase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class SocketAccessError(Exception):
|
|
13
|
+
"""Error raised when a test script accesses the network"""
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class BaseTestCase(TestCase):
|
|
17
|
+
"""Variation of Django's TestCase class that prevents any network use.
|
|
18
|
+
|
|
19
|
+
Example:
|
|
20
|
+
|
|
21
|
+
.. code-block:: python
|
|
22
|
+
|
|
23
|
+
class TestMyStuff(BaseTestCase):
|
|
24
|
+
def test_should_do_what_i_need(self): ...
|
|
25
|
+
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
@classmethod
|
|
29
|
+
def setUpClass(cls):
|
|
30
|
+
cls.socket_original = socket.socket
|
|
31
|
+
socket.socket = cls.guard
|
|
32
|
+
return super().setUpClass()
|
|
33
|
+
|
|
34
|
+
@classmethod
|
|
35
|
+
def tearDownClass(cls):
|
|
36
|
+
socket.socket = cls.socket_original
|
|
37
|
+
return super().tearDownClass()
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def guard(*args, **kwargs):
|
|
41
|
+
raise SocketAccessError("Attempted to access network")
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# Standard Library
|
|
2
|
+
from unittest import mock
|
|
3
|
+
|
|
4
|
+
# AA RSS to Discord
|
|
5
|
+
from aa_rss_to_discord.auth_hooks import register_cogs
|
|
6
|
+
from aa_rss_to_discord.tests import BaseTestCase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestAuthHooks(BaseTestCase):
|
|
10
|
+
"""
|
|
11
|
+
Test the auth hooks
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def test_registers_discord_cogs_hook_correctly(self):
|
|
15
|
+
"""
|
|
16
|
+
Tests that the discord cogs hook is registered correctly.
|
|
17
|
+
|
|
18
|
+
:return:
|
|
19
|
+
:rtype:
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
result = register_cogs()
|
|
23
|
+
|
|
24
|
+
self.assertIn("aa_rss_to_discord.discordbot.cogs.rss", result)
|
|
25
|
+
|
|
26
|
+
def test_returns_list_of_cogs(self):
|
|
27
|
+
"""
|
|
28
|
+
Tests that the register_cogs function returns a list.
|
|
29
|
+
|
|
30
|
+
:return:
|
|
31
|
+
:rtype:
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
result = register_cogs()
|
|
35
|
+
|
|
36
|
+
self.assertIsInstance(result, list)
|
|
37
|
+
|
|
38
|
+
def test_handles_empty_hook_registration_gracefully(self):
|
|
39
|
+
"""
|
|
40
|
+
Tests that the register_cogs function handles an empty hook registration gracefully.
|
|
41
|
+
|
|
42
|
+
:return:
|
|
43
|
+
:rtype:
|
|
44
|
+
"""
|
|
45
|
+
|
|
46
|
+
with mock.patch("allianceauth.hooks.register", return_value=None):
|
|
47
|
+
result = register_cogs()
|
|
48
|
+
|
|
49
|
+
self.assertIsNotNone(result)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# AA RSS to Discord
|
|
2
|
+
from aa_rss_to_discord.models import RssFeeds
|
|
3
|
+
from aa_rss_to_discord.tests import BaseTestCase
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class TestRssFeedManager(BaseTestCase):
|
|
7
|
+
"""
|
|
8
|
+
Test the RSS Feed Manager
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
def test_returns_only_enabled_feeds(self):
|
|
12
|
+
RssFeeds.objects.create(name="Feed 1", enabled=True)
|
|
13
|
+
RssFeeds.objects.create(name="Feed 2", enabled=False)
|
|
14
|
+
RssFeeds.objects.create(name="Feed 3", enabled=True)
|
|
15
|
+
|
|
16
|
+
result = RssFeeds.objects.select_enabled()
|
|
17
|
+
|
|
18
|
+
self.assertEqual(result.count(), 2)
|
|
19
|
+
self.assertTrue(all(feed.enabled for feed in result))
|
|
20
|
+
|
|
21
|
+
def test_handles_no_enabled_feeds_gracefully(self):
|
|
22
|
+
RssFeeds.objects.create(name="Feed 1", enabled=False)
|
|
23
|
+
RssFeeds.objects.create(name="Feed 2", enabled=False)
|
|
24
|
+
|
|
25
|
+
result = RssFeeds.objects.select_enabled()
|
|
26
|
+
|
|
27
|
+
self.assertEqual(result.count(), 0)
|
|
28
|
+
|
|
29
|
+
def test_handles_empty_database_gracefully(self):
|
|
30
|
+
result = RssFeeds.objects.select_enabled()
|
|
31
|
+
|
|
32
|
+
self.assertEqual(result.count(), 0)
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# Third Party
|
|
2
|
+
from aadiscordbot.models import Channels, Servers
|
|
3
|
+
|
|
4
|
+
# AA RSS to Discord
|
|
5
|
+
from aa_rss_to_discord.models import LastItem, RssFeeds
|
|
6
|
+
from aa_rss_to_discord.tests import BaseTestCase
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestRssFeeds(BaseTestCase):
|
|
10
|
+
"""
|
|
11
|
+
Test the RssFeeds model.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
def test_saves_and_retrieves_rss_feed_correctly(self):
|
|
15
|
+
"""
|
|
16
|
+
Tests that an RSS feed can be saved and retrieved correctly.
|
|
17
|
+
|
|
18
|
+
:return:
|
|
19
|
+
:rtype:
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
feed = RssFeeds.objects.create(
|
|
23
|
+
name="Feed 1", url="http://example.com/rss", enabled=True
|
|
24
|
+
)
|
|
25
|
+
retrieved_feed = RssFeeds.objects.get(id=feed.id)
|
|
26
|
+
|
|
27
|
+
self.assertEqual(retrieved_feed.name, "Feed 1")
|
|
28
|
+
self.assertEqual(retrieved_feed.url, "http://example.com/rss")
|
|
29
|
+
self.assertTrue(retrieved_feed.enabled)
|
|
30
|
+
|
|
31
|
+
def test_handles_null_discord_channel_gracefully(self):
|
|
32
|
+
"""
|
|
33
|
+
Tests that the model handles a null discord_channel gracefully.
|
|
34
|
+
|
|
35
|
+
:return:
|
|
36
|
+
:rtype:
|
|
37
|
+
"""
|
|
38
|
+
|
|
39
|
+
feed = RssFeeds.objects.create(
|
|
40
|
+
name="Feed 2", url="http://example.com/rss", discord_channel=None
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
self.assertIsNone(feed.discord_channel)
|
|
44
|
+
|
|
45
|
+
def test_string_representation_includes_name_and_channel(self):
|
|
46
|
+
"""
|
|
47
|
+
Tests that the string representation of the RssFeeds model
|
|
48
|
+
|
|
49
|
+
:return:
|
|
50
|
+
:rtype:
|
|
51
|
+
"""
|
|
52
|
+
server = Servers.objects.create(server=1, name="Test Server")
|
|
53
|
+
channel = Channels.objects.create(name="General", server=server, channel=1)
|
|
54
|
+
feed = RssFeeds.objects.create(name="Feed 3", discord_channel=channel)
|
|
55
|
+
|
|
56
|
+
self.assertEqual(
|
|
57
|
+
str(feed), 'RSS Feed "Feed 3" for channel "General" On "Test Server"'
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
def test_allows_disabling_rss_feed(self):
|
|
61
|
+
"""
|
|
62
|
+
Tests that an RSS feed can be disabled.
|
|
63
|
+
|
|
64
|
+
:return:
|
|
65
|
+
:rtype:
|
|
66
|
+
"""
|
|
67
|
+
feed = RssFeeds.objects.create(name="Feed 4", enabled=True)
|
|
68
|
+
feed.enabled = False
|
|
69
|
+
feed.save()
|
|
70
|
+
|
|
71
|
+
updated_feed = RssFeeds.objects.get(id=feed.id)
|
|
72
|
+
|
|
73
|
+
self.assertFalse(updated_feed.enabled)
|
|
74
|
+
|
|
75
|
+
def test_handles_empty_name_and_url_gracefully(self):
|
|
76
|
+
"""
|
|
77
|
+
Tests that the model handles empty name and url fields gracefully.
|
|
78
|
+
|
|
79
|
+
:return:
|
|
80
|
+
:rtype:
|
|
81
|
+
"""
|
|
82
|
+
|
|
83
|
+
feed = RssFeeds.objects.create(name="", url="")
|
|
84
|
+
|
|
85
|
+
self.assertEqual(feed.name, "")
|
|
86
|
+
self.assertEqual(feed.url, "")
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
class TestLastItem(BaseTestCase):
|
|
90
|
+
"""
|
|
91
|
+
Test the LastItem model.
|
|
92
|
+
"""
|
|
93
|
+
|
|
94
|
+
def test_string_representation_includes_item_title(self):
|
|
95
|
+
"""
|
|
96
|
+
Tests that the string representation of the LastItem model includes the item title.
|
|
97
|
+
|
|
98
|
+
:return:
|
|
99
|
+
:rtype:
|
|
100
|
+
"""
|
|
101
|
+
|
|
102
|
+
feed = RssFeeds.objects.create(name="Feed 1", url="http://example.com/rss")
|
|
103
|
+
item = LastItem.objects.create(
|
|
104
|
+
rss_feed=feed,
|
|
105
|
+
rss_item_title="Sample Item",
|
|
106
|
+
rss_item_link="http://example.com/item",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
self.assertEqual(str(item), 'RSS Entry "Sample Item"')
|
|
110
|
+
|
|
111
|
+
def test_handles_empty_item_title_gracefully(self):
|
|
112
|
+
"""
|
|
113
|
+
Tests that the model handles an empty item title gracefully.
|
|
114
|
+
|
|
115
|
+
:return:
|
|
116
|
+
:rtype:
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
feed = RssFeeds.objects.create(name="Feed 2", url="http://example.com/rss")
|
|
120
|
+
item = LastItem.objects.create(
|
|
121
|
+
rss_feed=feed,
|
|
122
|
+
rss_item_title="",
|
|
123
|
+
rss_item_link="http://example.com/item",
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
self.assertEqual(str(item), 'RSS Entry ""')
|
|
127
|
+
|
|
128
|
+
def test_saves_and_retrieves_last_item_correctly(self):
|
|
129
|
+
"""
|
|
130
|
+
Tests that a LastItem can be saved and retrieved correctly.
|
|
131
|
+
|
|
132
|
+
:return:
|
|
133
|
+
:rtype:
|
|
134
|
+
"""
|
|
135
|
+
|
|
136
|
+
feed = RssFeeds.objects.create(name="Feed 3", url="http://example.com/rss")
|
|
137
|
+
item = LastItem.objects.create(
|
|
138
|
+
rss_feed=feed,
|
|
139
|
+
rss_item_time="2023-10-01T12:00:00Z",
|
|
140
|
+
rss_item_title="Item Title",
|
|
141
|
+
rss_item_link="http://example.com/item",
|
|
142
|
+
rss_item_guid="12345",
|
|
143
|
+
)
|
|
144
|
+
retrieved_item = LastItem.objects.get(id=item.id)
|
|
145
|
+
|
|
146
|
+
self.assertEqual(retrieved_item.rss_item_time, "2023-10-01T12:00:00Z")
|
|
147
|
+
self.assertEqual(retrieved_item.rss_item_title, "Item Title")
|
|
148
|
+
self.assertEqual(retrieved_item.rss_item_link, "http://example.com/item")
|
|
149
|
+
self.assertEqual(retrieved_item.rss_item_guid, "12345")
|
|
150
|
+
|
|
151
|
+
def test_handles_null_guid_gracefully(self):
|
|
152
|
+
"""
|
|
153
|
+
Tests that the model handles a null guid gracefully.
|
|
154
|
+
|
|
155
|
+
:return:
|
|
156
|
+
:rtype:
|
|
157
|
+
"""
|
|
158
|
+
|
|
159
|
+
feed = RssFeeds.objects.create(name="Feed 4", url="http://example.com/rss")
|
|
160
|
+
item = LastItem.objects.create(
|
|
161
|
+
rss_feed=feed,
|
|
162
|
+
rss_item_guid="",
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
self.assertEqual(item.rss_item_guid, "")
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Test for the providers module.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
# Standard Library
|
|
6
|
+
import logging
|
|
7
|
+
|
|
8
|
+
# AA RSS to Discord
|
|
9
|
+
# AA Intel Tool
|
|
10
|
+
from aa_rss_to_discord.providers import AppLogger
|
|
11
|
+
from aa_rss_to_discord.tests import BaseTestCase
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TestAppLogger(BaseTestCase):
|
|
15
|
+
"""
|
|
16
|
+
Test the AppLogger provider.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
def test_adds_prefix_to_log_message(self):
|
|
20
|
+
"""
|
|
21
|
+
Tests that the AppLogger correctly adds a prefix to log messages.
|
|
22
|
+
|
|
23
|
+
:return:
|
|
24
|
+
:rtype:
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
logger = logging.getLogger("test_logger")
|
|
28
|
+
app_logger = AppLogger(logger, "PREFIX")
|
|
29
|
+
|
|
30
|
+
with self.assertLogs("test_logger", level="INFO") as log:
|
|
31
|
+
app_logger.info("This is a test message")
|
|
32
|
+
|
|
33
|
+
self.assertIn("[PREFIX] This is a test message", log.output[0])
|
|
34
|
+
|
|
35
|
+
def test_handles_empty_prefix(self):
|
|
36
|
+
"""
|
|
37
|
+
Tests that the AppLogger handles an empty prefix correctly.
|
|
38
|
+
|
|
39
|
+
:return:
|
|
40
|
+
:rtype:
|
|
41
|
+
"""
|
|
42
|
+
|
|
43
|
+
logger = logging.getLogger("test_logger")
|
|
44
|
+
app_logger = AppLogger(logger, "")
|
|
45
|
+
|
|
46
|
+
with self.assertLogs("test_logger", level="INFO") as log:
|
|
47
|
+
app_logger.info("Message without prefix")
|
|
48
|
+
|
|
49
|
+
self.assertIn("Message without prefix", log.output[0])
|
|
50
|
+
|
|
51
|
+
def test_handles_non_string_prefix(self):
|
|
52
|
+
"""
|
|
53
|
+
Tests that the AppLogger handles a non-string prefix correctly.
|
|
54
|
+
|
|
55
|
+
:return:
|
|
56
|
+
:rtype:
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
logger = logging.getLogger("test_logger")
|
|
60
|
+
app_logger = AppLogger(logger, 123)
|
|
61
|
+
|
|
62
|
+
with self.assertLogs("test_logger", level="INFO") as log:
|
|
63
|
+
app_logger.info("Message with numeric prefix")
|
|
64
|
+
|
|
65
|
+
self.assertIn("[123] Message with numeric prefix", log.output[0])
|
|
66
|
+
|
|
67
|
+
def test_handles_special_characters_in_prefix(self):
|
|
68
|
+
"""
|
|
69
|
+
Tests that the AppLogger handles special characters in the prefix correctly.
|
|
70
|
+
|
|
71
|
+
:return:
|
|
72
|
+
:rtype:
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
logger = logging.getLogger("test_logger")
|
|
76
|
+
app_logger = AppLogger(logger, "!@#$%^&*()")
|
|
77
|
+
|
|
78
|
+
with self.assertLogs("test_logger", level="INFO") as log:
|
|
79
|
+
app_logger.info("Message with special characters in prefix")
|
|
80
|
+
|
|
81
|
+
self.assertIn(
|
|
82
|
+
"[!@#$%^&*()] Message with special characters in prefix", log.output[0]
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
def test_handles_empty_message(self):
|
|
86
|
+
"""
|
|
87
|
+
Tests that the AppLogger handles an empty log message correctly.
|
|
88
|
+
|
|
89
|
+
:return:
|
|
90
|
+
:rtype:
|
|
91
|
+
"""
|
|
92
|
+
|
|
93
|
+
logger = logging.getLogger("test_logger")
|
|
94
|
+
app_logger = AppLogger(logger, "PREFIX")
|
|
95
|
+
|
|
96
|
+
with self.assertLogs("test_logger", level="INFO") as log:
|
|
97
|
+
app_logger.info("")
|
|
98
|
+
|
|
99
|
+
self.assertIn("[PREFIX] ", log.output[0])
|
|
@@ -0,0 +1,176 @@
|
|
|
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
|
+
)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: aa-rss-to-discord
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.4.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
|
|
@@ -702,10 +702,10 @@ Classifier: Programming Language :: Python :: 3.13
|
|
|
702
702
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
703
703
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
704
704
|
Requires-Python: <3.14,>=3.10
|
|
705
|
-
Requires-Dist: allianceauth-app-utils>=1.14.2
|
|
706
705
|
Requires-Dist: allianceauth-discordbot>=3.0.5
|
|
707
706
|
Requires-Dist: allianceauth<5,>=4.3.1
|
|
708
707
|
Requires-Dist: feedparser
|
|
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.
|
|
776
|
+
pip install aa-rss-to-discord==2.4.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.
|
|
831
|
+
pip install aa-rss-to-discord==2.4.0
|
|
832
832
|
|
|
833
833
|
python manage.py migrate
|
|
834
834
|
```
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
aa_rss_to_discord/__init__.py,sha256=tshvB4Ib8of9KNQB_JuWVhJ7TJ0nePhojMS47YIbaT0,177
|
|
2
|
+
aa_rss_to_discord/admin.py,sha256=bbvD0w0vFgD4kR4riWIwpOWpb9t8R6BI7ofYqTZbjDw,440
|
|
3
|
+
aa_rss_to_discord/apps.py,sha256=jtIy1uXn92yURf7QG7TxzLniSbOAL41OzqjCLxTwMbA,458
|
|
4
|
+
aa_rss_to_discord/auth_hooks.py,sha256=k5mLzQR1NwtA5MCedLzPyULEhv4ylwqvFpTPZ62eWJ0,273
|
|
5
|
+
aa_rss_to_discord/constants.py,sha256=SD5aSlRanXsE07tV5q4PxVJXH0Xgivr8DjfNgyTDaqk,383
|
|
6
|
+
aa_rss_to_discord/managers.py,sha256=oERpP9OXZNg3_SJCKiMZrt7aa_NGxKswZntfgYkpRdA,307
|
|
7
|
+
aa_rss_to_discord/models.py,sha256=G-_LjShFDQFcbcg3nuR5mwjBl4zmANsgF8PIrokJOdU,1781
|
|
8
|
+
aa_rss_to_discord/providers.py,sha256=AwZ4QgWg_LNQsWjwHCurAFBHEFwTXGEDGUP6xTKxGJc,1029
|
|
9
|
+
aa_rss_to_discord/tasks.py,sha256=4L-vdX1570nyVp1NBl6yJ13a9hW2aT_ABK1YY_8y9Ss,3807
|
|
10
|
+
aa_rss_to_discord/discordbot/cogs/__init__.py,sha256=3u3OJfO3Xh1GightCaX0lhbUbP7Lq2c1nY4Tg1FsrZo,27
|
|
11
|
+
aa_rss_to_discord/discordbot/cogs/rss.py,sha256=aPov4y-ckTwzXdXfKtA5WZZKwy7myOlW2xyK5yWGhy4,8174
|
|
12
|
+
aa_rss_to_discord/locale/django.pot,sha256=4uKpgRNE82d-SXtbOIxfkhJ0yE_Yo146_XoneZPQNDQ,962
|
|
13
|
+
aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.mo,sha256=e5Bcjwg3sIKgDgHKifbl2DcH0atHMSygiZwgvKEaifs,565
|
|
14
|
+
aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.po,sha256=zeffaDdZEfeeIWiUetKO_Ft7muDgkHtMdVxecVC5yQQ,1187
|
|
15
|
+
aa_rss_to_discord/locale/de/LC_MESSAGES/django.mo,sha256=NGLMzDZwRKCvQH_RtK9wm6UXKWHLuipasTLnjaosbRE,717
|
|
16
|
+
aa_rss_to_discord/locale/de/LC_MESSAGES/django.po,sha256=Y1mynnUEi0AgHUQZUKVNSQL0EquILhK3RYPBnQkqz9g,1183
|
|
17
|
+
aa_rss_to_discord/locale/es/LC_MESSAGES/django.mo,sha256=BNXYj-j0OPYFuLpm0uJS_b28tdtbs1BOMYp_HCWcoC8,664
|
|
18
|
+
aa_rss_to_discord/locale/es/LC_MESSAGES/django.po,sha256=XVZ6DPLize4AS-LRH5ayMsXuY4txIHe7IBwHpib0EPU,1196
|
|
19
|
+
aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.mo,sha256=zwgaGEqI-UlHpc9q1irjc4xDxZJZBheixtIWz-43q1U,721
|
|
20
|
+
aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.po,sha256=XmpegMaCzAX5_y1YQzFbwPM6FpcoWFiK0ZjuGeA1s5A,1337
|
|
21
|
+
aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.mo,sha256=jyxuImpxL3PnEGxbqzYoLvrE014f-wPENwh0LMuWviM,487
|
|
22
|
+
aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.po,sha256=z4pSagMW-mF7R9XxQQNPKfMbKW7g-0y9Qh8oByNgWZk,1107
|
|
23
|
+
aa_rss_to_discord/locale/ja/LC_MESSAGES/django.mo,sha256=dzafm9mmWwOeYbuJWyMmxTKxqdBu7ptUXdhXllkSoY4,770
|
|
24
|
+
aa_rss_to_discord/locale/ja/LC_MESSAGES/django.po,sha256=zdH0HWBf8QcZl_t7Nuc5-QB0OqVDFLeypouFYh3c_Uk,1229
|
|
25
|
+
aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.mo,sha256=1IW9bwgVPrzDl_0crEO0UMaPF4jP3SyTUrZeRjalzCk,738
|
|
26
|
+
aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.po,sha256=AWBaC8P8bCMgZ7NYF2fRBLhDvAk9Qc7W9YMWbMfZpmI,1239
|
|
27
|
+
aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.mo,sha256=JLv2IW5Kp2wqE7MqNn9P9blZ_I-Jp0CIWiHsadYgFUU,483
|
|
28
|
+
aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.po,sha256=ClrnrkigalQgcw1UT8pDjtrxyWR8psEChhU4YXtTyIk,1105
|
|
29
|
+
aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.mo,sha256=2ugIqkNNVUGxIgfxUjU8SCIBJvSCn6vGzAV9Xnqiyag,543
|
|
30
|
+
aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.po,sha256=yIUIUBx_eDd_qT4zWCOd_VjDPKf9xxrGH-Pv0GQqkrc,1164
|
|
31
|
+
aa_rss_to_discord/locale/ru/LC_MESSAGES/django.mo,sha256=BzuRJnMwz-rpbr8rc_K755H-sstix0LsOpb7lSbkRuo,865
|
|
32
|
+
aa_rss_to_discord/locale/ru/LC_MESSAGES/django.po,sha256=CNH5PJAu9dFOgtgOttRhpHDgxvKIQx39P2mgIftCemw,1351
|
|
33
|
+
aa_rss_to_discord/locale/sk/LC_MESSAGES/django.mo,sha256=WPcDxnOaK_bCNIPoZQRxryi5F97g2RNn0Zuh9us90U0,562
|
|
34
|
+
aa_rss_to_discord/locale/sk/LC_MESSAGES/django.po,sha256=I2P3KN-Q0-EyywmQ_aO54i4zcQ5RGVeri0Dbtr4lLCc,1180
|
|
35
|
+
aa_rss_to_discord/locale/uk/LC_MESSAGES/django.mo,sha256=5DL6DnJ0kP7s9PbVr92PAeVQf-cGMSQibbnoJmZJHG8,858
|
|
36
|
+
aa_rss_to_discord/locale/uk/LC_MESSAGES/django.po,sha256=VUl9mZPHBA6kTyLhWO0pw9lmOozXPgaZ-EAbZwQhHlo,1352
|
|
37
|
+
aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.mo,sha256=DaODnWQcYUSgtxUaPmZoEG6IQ190DS3WzCh6uNvhHh8,518
|
|
38
|
+
aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.po,sha256=7OkKDvfLIkZWOBi98fyPNVTlHXw_Kch8VIYzZejAQQs,1122
|
|
39
|
+
aa_rss_to_discord/migrations/0001_initial.py,sha256=TD8ZmWLleWEw1G3PlQh3HBU4pRXxwIfp_LMQj4IyTDE,2764
|
|
40
|
+
aa_rss_to_discord/migrations/0002_enable_disable_rss_feeds.py,sha256=apLeuxILrxvTntSc1bixfcDqpgt49VscyT6inLKU9k8,396
|
|
41
|
+
aa_rss_to_discord/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
42
|
+
aa_rss_to_discord/tests/__init__.py,sha256=hQ-yK-riPHikEE78PrbFZUKRToIZhif7-mD-yiHzjkM,873
|
|
43
|
+
aa_rss_to_discord/tests/test_auth_hooks.py,sha256=DZLEdVO9E82GEqPMv6_JOgSLPjU_DrB9uLWDgWgQ34w,1147
|
|
44
|
+
aa_rss_to_discord/tests/test_managers.py,sha256=Ar4Mc4z1k0701jjAWKTqe9BzxBjX-32KIdT4VgXfJ_s,1023
|
|
45
|
+
aa_rss_to_discord/tests/test_models.py,sha256=XtqGVZQbbtyDktYlHYsKFrLb3qB-UpU7wIpA6gXsKL8,4823
|
|
46
|
+
aa_rss_to_discord/tests/test_providers.py,sha256=ZF1zV3-_yFWwruiR_g2yB-RRy_t5W0WPbaF7guFRXF8,2677
|
|
47
|
+
aa_rss_to_discord/tests/test_tasks.py,sha256=tJykTvZ4FHTkZP0J6MUA5Me-0NP-mK_uPqNNrGWuv2Y,5584
|
|
48
|
+
aa_rss_to_discord-2.4.0.dist-info/METADATA,sha256=k40M2wasM80fl20MBe1DQeqKO4GPv_naRfZ2QixZ9Ow,50408
|
|
49
|
+
aa_rss_to_discord-2.4.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
|
|
50
|
+
aa_rss_to_discord-2.4.0.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
51
|
+
aa_rss_to_discord-2.4.0.dist-info/RECORD,,
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
aa_rss_to_discord/__init__.py,sha256=9tzlo0jwY5yWYqBaCtzzQNG1dnPYCnEQm3S3ZoHd30k,177
|
|
2
|
-
aa_rss_to_discord/admin.py,sha256=bbvD0w0vFgD4kR4riWIwpOWpb9t8R6BI7ofYqTZbjDw,440
|
|
3
|
-
aa_rss_to_discord/apps.py,sha256=jtIy1uXn92yURf7QG7TxzLniSbOAL41OzqjCLxTwMbA,458
|
|
4
|
-
aa_rss_to_discord/auth_hooks.py,sha256=k5mLzQR1NwtA5MCedLzPyULEhv4ylwqvFpTPZ62eWJ0,273
|
|
5
|
-
aa_rss_to_discord/constants.py,sha256=A-HavWaksOcqEjaaAR-iXwWS9dtZSH3G-yP9SFBZU7s,386
|
|
6
|
-
aa_rss_to_discord/managers.py,sha256=bM4Mi4J-DFKVUhTFrKRh7FmZfZrWcUgOlF7Yeu69lw0,303
|
|
7
|
-
aa_rss_to_discord/models.py,sha256=G-_LjShFDQFcbcg3nuR5mwjBl4zmANsgF8PIrokJOdU,1781
|
|
8
|
-
aa_rss_to_discord/tasks.py,sha256=_PSOR-lapSkvp97X9TFg7hxEYCU8cDANnUJlv2iL-pI,7189
|
|
9
|
-
aa_rss_to_discord/discordbot/cogs/__init__.py,sha256=3u3OJfO3Xh1GightCaX0lhbUbP7Lq2c1nY4Tg1FsrZo,27
|
|
10
|
-
aa_rss_to_discord/discordbot/cogs/rss.py,sha256=aPov4y-ckTwzXdXfKtA5WZZKwy7myOlW2xyK5yWGhy4,8174
|
|
11
|
-
aa_rss_to_discord/locale/django.pot,sha256=svcGwCHcVY3AcPwkDUarFR1bKHHX5_uGtsB-9X9ENA0,962
|
|
12
|
-
aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.mo,sha256=e5Bcjwg3sIKgDgHKifbl2DcH0atHMSygiZwgvKEaifs,565
|
|
13
|
-
aa_rss_to_discord/locale/cs_CZ/LC_MESSAGES/django.po,sha256=ntYEbl5vjSvFMmjRxSfBxZuTX75Ffo_ihqooWeYSbTU,1187
|
|
14
|
-
aa_rss_to_discord/locale/de/LC_MESSAGES/django.mo,sha256=NGLMzDZwRKCvQH_RtK9wm6UXKWHLuipasTLnjaosbRE,717
|
|
15
|
-
aa_rss_to_discord/locale/de/LC_MESSAGES/django.po,sha256=yssediYbyvhuzyUfVDexBKh_7it3espx-b110kyxvTA,1183
|
|
16
|
-
aa_rss_to_discord/locale/es/LC_MESSAGES/django.mo,sha256=BNXYj-j0OPYFuLpm0uJS_b28tdtbs1BOMYp_HCWcoC8,664
|
|
17
|
-
aa_rss_to_discord/locale/es/LC_MESSAGES/django.po,sha256=NFJwhA-j_mIueiLUfxy0fuDu7V5CRI8pUstJbR6ptx4,1196
|
|
18
|
-
aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.mo,sha256=zwgaGEqI-UlHpc9q1irjc4xDxZJZBheixtIWz-43q1U,721
|
|
19
|
-
aa_rss_to_discord/locale/fr_FR/LC_MESSAGES/django.po,sha256=Nw4jWJRrRZNkuufT9TpgkgYNWXRlNk7Gc0Da_CO715k,1337
|
|
20
|
-
aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.mo,sha256=jyxuImpxL3PnEGxbqzYoLvrE014f-wPENwh0LMuWviM,487
|
|
21
|
-
aa_rss_to_discord/locale/it_IT/LC_MESSAGES/django.po,sha256=RlpHgVdyYehAWi61EMB7fAQIsEGdsEHajvnjy9wUBVA,1107
|
|
22
|
-
aa_rss_to_discord/locale/ja/LC_MESSAGES/django.mo,sha256=dzafm9mmWwOeYbuJWyMmxTKxqdBu7ptUXdhXllkSoY4,770
|
|
23
|
-
aa_rss_to_discord/locale/ja/LC_MESSAGES/django.po,sha256=W-4CMav695caeBR7cbV5R4owXlBYM8aG7_TO1A8FFFM,1229
|
|
24
|
-
aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.mo,sha256=1IW9bwgVPrzDl_0crEO0UMaPF4jP3SyTUrZeRjalzCk,738
|
|
25
|
-
aa_rss_to_discord/locale/ko_KR/LC_MESSAGES/django.po,sha256=3d802zgj25pNPxShISl8hAjsXnQH1V8kZ5dOrUl9RbE,1239
|
|
26
|
-
aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.mo,sha256=JLv2IW5Kp2wqE7MqNn9P9blZ_I-Jp0CIWiHsadYgFUU,483
|
|
27
|
-
aa_rss_to_discord/locale/nl_NL/LC_MESSAGES/django.po,sha256=zEbu4pNNNdUB_UmDNP0Uju-pYGpOHCS457ncxtnd05s,1105
|
|
28
|
-
aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.mo,sha256=2ugIqkNNVUGxIgfxUjU8SCIBJvSCn6vGzAV9Xnqiyag,543
|
|
29
|
-
aa_rss_to_discord/locale/pl_PL/LC_MESSAGES/django.po,sha256=g0Q1NWEmMQcHrUyHwL0vQB-ITWyGmrhX9_Lrt9_qcMA,1164
|
|
30
|
-
aa_rss_to_discord/locale/ru/LC_MESSAGES/django.mo,sha256=BzuRJnMwz-rpbr8rc_K755H-sstix0LsOpb7lSbkRuo,865
|
|
31
|
-
aa_rss_to_discord/locale/ru/LC_MESSAGES/django.po,sha256=1lu_ovcdG1z3G1QluM8igtI0CaTDiIMhTQNrjUzIogg,1351
|
|
32
|
-
aa_rss_to_discord/locale/sk/LC_MESSAGES/django.mo,sha256=WPcDxnOaK_bCNIPoZQRxryi5F97g2RNn0Zuh9us90U0,562
|
|
33
|
-
aa_rss_to_discord/locale/sk/LC_MESSAGES/django.po,sha256=RZRxawM8rpf0VEB89-PxYCD032dLq14_lXwuzi_M3Vs,1180
|
|
34
|
-
aa_rss_to_discord/locale/uk/LC_MESSAGES/django.mo,sha256=5DL6DnJ0kP7s9PbVr92PAeVQf-cGMSQibbnoJmZJHG8,858
|
|
35
|
-
aa_rss_to_discord/locale/uk/LC_MESSAGES/django.po,sha256=yq6RStf91Tbu8bQY2rWukO7xy5Y7Ue5jkNZR1JBw8Oo,1352
|
|
36
|
-
aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.mo,sha256=DaODnWQcYUSgtxUaPmZoEG6IQ190DS3WzCh6uNvhHh8,518
|
|
37
|
-
aa_rss_to_discord/locale/zh_Hans/LC_MESSAGES/django.po,sha256=CJNIxXWG4KxvbISHvnrvHTsktCX82h7dMDMCA2-PrTQ,1122
|
|
38
|
-
aa_rss_to_discord/migrations/0001_initial.py,sha256=TD8ZmWLleWEw1G3PlQh3HBU4pRXxwIfp_LMQj4IyTDE,2764
|
|
39
|
-
aa_rss_to_discord/migrations/0002_enable_disable_rss_feeds.py,sha256=apLeuxILrxvTntSc1bixfcDqpgt49VscyT6inLKU9k8,396
|
|
40
|
-
aa_rss_to_discord/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
aa_rss_to_discord-2.3.5.dist-info/METADATA,sha256=5lrqUpzOAr9YpU99jid4oazFOH4uW29FCKxQqnMT-tQ,50395
|
|
42
|
-
aa_rss_to_discord-2.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
|
43
|
-
aa_rss_to_discord-2.3.5.dist-info/licenses/LICENSE,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
44
|
-
aa_rss_to_discord-2.3.5.dist-info/RECORD,,
|
|
File without changes
|