Skip to content

Commit 3ffffb5

Browse files
authored
Merge pull request #27 from Marvel-Discord/feat/migrate-newschannelping-to-redis
Stage 1: migrate newschannelsping from Postgres to Redis
2 parents 1f5f5c4 + e4bc245 commit 3ffffb5

18 files changed

Lines changed: 456 additions & 37 deletions

‎.env.example‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ POSTGRES_PORT=5432
1111
GLOBAL_SLASHIES=True
1212
DATABASE_LISTENER_LOGS=True
1313

14+
# Redis
15+
REDIS_URL=redis://redis:6379/0
16+
1417
# Server and Channel IDs
1518
HOMESERVER=
1619
NEWSCHANNELS=1028517433637605406

‎.gitignore‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,6 @@ betatesting.py
44
__pycache__/
55
.idea/*
66
.env
7+
.venv/
8+
.pytest_cache/
9+
.superpowers/

‎Pipfile‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,12 @@ aiohttp = "~=3.14.3"
1212
natsort = "~=8.3.1"
1313
beautifulsoup4 = "~=4.12.3"
1414
tmdbsimple = "~=2.9.1"
15+
redis = ">=5.0.0"
1516

1617
[dev-packages]
18+
pytest = ">=8.0.0"
19+
pytest-asyncio = ">=0.23.0"
20+
fakeredis = ">=2.20.0"
1721

1822
[requires]
1923
python_version = "3.12"

‎cogs/news.py‎

Lines changed: 42 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -187,50 +187,55 @@ async def send_news_ping(self, message):
187187
f"*{t}*" for t in titles
188188
)
189189

190-
channelinfo = await self.bot.db.fetchrow(
191-
"SELECT * FROM newschannelsping WHERE channel_id = $1", channel.id
192-
)
193-
if channelinfo:
194-
if channelinfo["latest_message_id"]:
195-
try:
196-
oldmsg = await channel.fetch_message(
197-
channelinfo["latest_message_id"]
198-
)
199-
except NotFound:
200-
pass
190+
key = f"jocasta:bot:newschannelsping:{channel.id}"
191+
latest_message_id = None
192+
if self.bot.redis:
193+
try:
194+
latest_message_id = await self.bot.redis.get(key)
195+
except Exception as e:
196+
print(f"[News] Failed to read from Redis: {e}")
197+
198+
if latest_message_id:
199+
try:
200+
oldmsg = await channel.fetch_message(int(latest_message_id))
201+
except NotFound:
202+
pass
203+
except ValueError:
204+
print(
205+
f"[News] Invalid message_id in Redis for channel "
206+
f"{channel.id}: {latest_message_id!r}"
207+
)
208+
except Exception as e:
209+
print(f"[News] Failed to fetch old message: {e}")
210+
else:
211+
if (
212+
discord.utils.utcnow() - oldmsg.created_at
213+
).total_seconds() >= newspingbuffertime:
214+
await oldmsg.delete()
201215
else:
202-
if (
203-
discord.utils.utcnow() - oldmsg.created_at
204-
).total_seconds() >= newspingbuffertime:
205-
await oldmsg.delete()
206-
else:
207-
current = [
208-
i.strip("*")
209-
for i in oldmsg.content.replace(
210-
f"{self.newsrole.mention} ", ""
211-
).split("\n")
212-
]
213-
for t in titles:
214-
if t not in current:
215-
current.append(t)
216-
new = formatmsg(self.newsrole.mention, current)
217-
await oldmsg.edit(content=new)
218-
return
219-
else:
220-
await self.bot.db.execute(
221-
"INSERT INTO newschannelsping (channel_id) VALUES ($1)", channel.id
222-
)
216+
current = [
217+
i.strip("*")
218+
for i in oldmsg.content.replace(
219+
f"{self.newsrole.mention} ", ""
220+
).split("\n")
221+
]
222+
for t in titles:
223+
if t not in current:
224+
current.append(t)
225+
new = formatmsg(self.newsrole.mention, current)
226+
await oldmsg.edit(content=new)
227+
return
223228

224229
msg = await channel.send(
225230
formatmsg(self.newsrole.mention, titles),
226231
view=self.PingRoleView(self.newsrole, "Add/Remove Ping Role"),
227232
)
228233

229-
await self.bot.db.execute(
230-
"UPDATE newschannelsping SET latest_message_id = $2 WHERE channel_id = $1",
231-
channel.id,
232-
msg.id,
233-
)
234+
if self.bot.redis:
235+
try:
236+
await self.bot.redis.set(key, str(msg.id))
237+
except Exception as e:
238+
print(f"[News] Failed to write to Redis: {e}")
234239

235240
@commands.Cog.listener()
236241
async def on_member_join(self, member):

‎config.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"host": os.getenv("POSTGRES_HOST"),
1414
"port": int(os.getenv("POSTGRES_PORT", 5432)),
1515
}
16+
redis_url = os.getenv("REDIS_URL", "redis://localhost:6379/0")
1617
global_slashies = os.getenv("GLOBAL_SLASHIES", "False").lower() == "true"
1718
database_listener_logs = os.getenv("DATABASE_LISTENER_LOGS", "True").lower() == "true"
1819

‎docker-compose.yml‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,12 @@ services:
88
volumes:
99
- .:/app
1010
command: python main.py
11+
depends_on:
12+
- redis
13+
redis:
14+
image: redis:7-alpine
15+
restart: unless-stopped
16+
volumes:
17+
- redis-data:/data
18+
volumes:
19+
redis-data:

‎funcs/redis.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import redis.asyncio as redis
2+
from discord.ext import commands
3+
4+
from config import *
5+
6+
7+
class RedisCog(commands.Cog, name="Redis"):
8+
"""Manages the bot's Redis connection and exposes it as `bot.redis`.
9+
10+
On failure to connect, `bot.redis` is set to None — cogs that use it
11+
must handle this case (graceful degradation).
12+
"""
13+
14+
def __init__(self, bot):
15+
self.bot = bot
16+
17+
async def cog_load(self):
18+
self.bot.redis = redis.from_url(redis_url, decode_responses=True)
19+
try:
20+
await self.bot.redis.ping()
21+
print("Connected to Redis")
22+
except Exception as e:
23+
print(f"Failed to connect to Redis: {e}")
24+
self.bot.redis = None
25+
26+
async def cog_unload(self):
27+
if self.bot.redis:
28+
await self.bot.redis.aclose()
29+
30+
31+
async def setup(bot):
32+
await bot.add_cog(RedisCog(bot))

‎main.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ class Jocasta(commands.Bot):
88
async def setup_hook(self):
99
initial_extensions = [
1010
"funcs.postgresql",
11+
"funcs.redis",
1112
"cogs.owner",
1213
"cogs.polls",
1314
"cogs.time",

‎pytest.ini‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
[pytest]
2+
asyncio_mode = auto
3+
testpaths = tests
4+
python_files = test_*.py
5+
python_classes = Test*
6+
python_functions = test_*

‎requirements.txt‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,7 @@ aiohttp>=3.13.3
66
natsort~=8.3.1
77
beautifulsoup4~=4.12.3
88
tmdbsimple~=2.9.1
9+
redis>=5.0.0
10+
pytest>=8.0.0
11+
pytest-asyncio>=0.23.0
12+
fakeredis>=2.20.0

0 commit comments

Comments
 (0)