Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,6 @@ platforms such as GitHub discussions/issues might be added in the future.

| variable | required | default | description |
|----------------------------------|----------|------------------------------------------------------|--------------------------------------------------------------------------------------------|
| DAILY_TASKS | False | `true` | Daily tasks on or off. |
| DAILY_RELEASES | False | `true` | Send a message for each game released on this day in history. |
| DAILY_CHANNEL_ID | False | `None` | Required if daily_tasks is enabled. |
| DAILY_TASKS_UTC_HOUR | False | `12` | The hour to run daily tasks. |
| DATA_REPO | False | `https://github.com/LizardByte/support-bot-data` | Repository to store persistent data. This repository should be private! |
| DATA_REPO_BRANCH | False | `master` | Branch to store persistent data. |
| DISCORD_BOT_TOKEN | True | `None` | Token from Bot page on discord developer portal. |
Expand All @@ -61,8 +57,6 @@ platforms such as GitHub discussions/issues might be added in the future.
| GITHUB_TOKEN | True | `None` | GitHub personal access token. Must have `read:org` |
| GITHUB_WEBHOOK_SECRET_KEY | True | `None` | A secret value to ensure webhooks are from trusted sources. |
| GRAVATAR_EMAIL | False | `None` | Gravatar email address for bot avatar. |
| IGDB_CLIENT_ID | False | `None` | Required if daily_releases is enabled. |
| IGDB_CLIENT_SECRET | False | `None` | Required if daily_releases is enabled. |
| PRAW_CLIENT_ID | True | `None` | `client_id` from reddit app setup page. |
| PRAW_CLIENT_SECRET | True | `None` | `client_secret` from reddit app setup page. |
| PRAW_SUBREDDIT | True | `None` | Subreddit to monitor (reddit user should be moderator of the subreddit) |
Expand Down
1 change: 0 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
cryptography==44.0.3
Flask==3.1.1
GitPython==3.1.44
igdb-api-v4==0.3.3
libgravatar==1.0.4
mistletoe==1.4.0
praw==7.8.1
Expand Down
12 changes: 0 additions & 12 deletions src/discord_bot/bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@ def __init__(self, *args, **kwargs):
self.ephemeral_db = {}
self.oauth_states = {}
self.clean_ephemeral_cache = tasks.clean_ephemeral_cache
self.daily_task = tasks.daily_task
self.role_update_task = tasks.role_update_task

self.load_extension(
Expand Down Expand Up @@ -74,16 +73,6 @@ async def on_ready(self):
self.clean_ephemeral_cache.start(bot=self)
self.role_update_task.start(bot=self)

try:
os.environ['DAILY_TASKS']
except KeyError:
self.daily_task.start(bot=self)
else:
if os.environ['DAILY_TASKS'].lower() == 'true':
self.daily_task.start(bot=self)
else:
print("'DAILY_TASKS' environment variable is disabled")

await self.sync_commands()

async def async_send_message(
Expand Down Expand Up @@ -284,7 +273,6 @@ def start_threaded(self):
def stop(self, future: asyncio.Future = None):
print("Attempting to stop tasks")
self.DEGRADED = True
self.daily_task.stop()
self.role_update_task.stop()
self.clean_ephemeral_cache.stop()
print("Attempting to close bot connection")
Expand Down
71 changes: 0 additions & 71 deletions src/discord_bot/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,55 +4,6 @@
# lib imports
import requests

# convert month number to igdb human-readable month
month_dictionary = {
1: 'Jan',
2: 'Feb',
3: 'Mar',
4: 'Apr',
5: 'May',
6: 'Jun',
7: 'Jul',
8: 'Aug',
9: 'Sep',
10: 'Oct',
11: 'Nov',
12: 'Dec'
}


def igdb_authorization(client_id: str, client_secret: str) -> Any:
"""
Authorization for IGDB.

Return an authorization dictionary for the IGDB api.

Parameters
----------
client_id : str
IGDB/Twitch API client id.
client_secret : str
IGDB/Twitch client secret.

Returns
-------
Any
Authorization dictionary.
"""
grant_type = 'client_credentials'

auth_headers = {
'Accept': 'application/json',
'client_id': client_id,
'client_secret': client_secret,
'grant_type': grant_type
}

token_url = 'https://id.twitch.tv/oauth2/token'

authorization = post_json(url=token_url, headers=auth_headers)
return authorization


def get_json(url: str) -> Any:
"""
Expand All @@ -74,25 +25,3 @@ def get_json(url: str) -> Any:
data = res.json()

return data


def post_json(url: str, headers: dict) -> Any:
"""
Make a POST request and get the response in json.

Makes a POST request with given headers to the given url.

Parameters
----------
url : str
The url for the POST request.
headers : dict
Headers for the POST request.

Returns
-------
Any
The json response.
"""
result = requests.post(url=url, data=headers).json()
return result
154 changes: 0 additions & 154 deletions src/discord_bot/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,14 @@
import asyncio
import copy
from datetime import datetime, UTC
import json
import os

# lib imports
import discord
from discord.ext import tasks
from igdb.wrapper import IGDBWrapper

# local imports
from src.common.common import avatar, bot_name, bot_url, colors
from src.common import sponsors
from src.discord_bot.bot import Bot
from src.discord_bot.helpers import igdb_authorization, month_dictionary


@tasks.loop(seconds=30)
Expand All @@ -32,155 +27,6 @@ async def clean_ephemeral_cache(bot: Bot) -> bool:
return True


@tasks.loop(minutes=60.0)
async def daily_task(bot: Bot) -> bool:
"""
Run daily task loop.

This function runs on a schedule, every 60 minutes. Create an embed and thread for each game released
on this day in history (according to IGDB), if enabled.

Returns
-------
bool
True if the task ran successfully, False otherwise.
"""
date = datetime.now(UTC)
if date.hour != int(os.getenv(key='DAILY_TASKS_UTC_HOUR', default=12)):
return False

daily_releases = True if os.getenv(key='DAILY_RELEASES', default='true').lower() == 'true' else False
if not daily_releases:
print("'DAILY_RELEASES' environment variable is disabled")
return False

try:
channel_id = int(os.environ['DAILY_CHANNEL_ID'])
except KeyError:
print("'DAILY_CHANNEL_ID' not defined in environment variables.")
return False

igdb_auth = igdb_authorization(client_id=os.environ['IGDB_CLIENT_ID'],
client_secret=os.environ['IGDB_CLIENT_SECRET'])
wrapper = IGDBWrapper(client_id=os.environ['IGDB_CLIENT_ID'], auth_token=igdb_auth['access_token'])

end_point = 'release_dates'
fields = [
'human',
'game.name',
'game.summary',
'game.url',
'game.genres.name',
'game.rating',
'game.cover.url',
'game.artworks.url',
'game.platforms.name',
'game.platforms.url'
]

where = f'human="{month_dictionary[date.month]} {date.day:02d}"*'
limit = 500
query = f'fields {", ".join(fields)}; where {where}; limit {limit};'

byte_array = bytes(wrapper.api_request(endpoint=end_point, query=query))
json_result = json.loads(byte_array)

game_ids = []

for game in json_result:
try:
game_id = game['game']['id']
except KeyError:
continue

if game_id in game_ids:
continue # do not repeat the same game... even though it could be a different platform
game_ids.append(game_id)

try:
embed = discord.Embed(
title=game['game']['name'],
url=game['game']['url'],
description=game['game']['summary'][0:2000 - 1],
color=colors['purple']
)
except KeyError:
continue

try:
rating = round(game['game']['rating'] / 20, 1)
embed.add_field(
name='Average Rating',
value=f'⭐{rating}',
inline=True
)
except KeyError:
continue
if rating < 4.0: # reduce the number of messages per day
continue

try:
embed.add_field(
name='Release Date',
value=game['human'],
inline=True
)
except KeyError:
pass

try:
embed.set_thumbnail(url=f"https:{game['game']['cover']['url'].replace('_thumb', '_original')}")
except KeyError:
pass

try:
embed.set_image(url=f"https:{game['game']['artworks'][0]['url'].replace('_thumb', '_original')}")
except KeyError:
pass

try:
platforms = ', '.join(platform['name'] for platform in game['game']['platforms'])
name = 'Platforms' if len(game['game']['platforms']) > 1 else 'Platform'

embed.add_field(
name=name,
value=platforms,
inline=False
)
except KeyError:
pass

try:
genres = ', '.join(genre['name'] for genre in game['game']['genres'])
name = 'Genres' if len(game['game']['genres']) > 1 else 'Genre'

embed.add_field(
name=name,
value=genres,
inline=False
)
except KeyError:
pass

embed.set_author(
name=bot_name,
url=bot_url,
icon_url=avatar
)

embed.set_footer(
text='Data provided by IGDB',
icon_url='https://www.igdb.com/favicon-196x196.png'
)

message = bot.send_message(channel_id=channel_id, embed=embed)
thread = bot.create_thread(message=message, name=embed.title)

print(f'thread created: {thread.name}')

return True


@tasks.loop(minutes=1.0)
async def role_update_task(bot: Bot, test_mode: bool = False) -> bool:
"""
Expand Down
1 change: 0 additions & 1 deletion tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ def discord_bot():
time.sleep(1)

bot.role_update_task.stop()
bot.daily_task.stop()
bot.clean_ephemeral_cache.stop()

yield bot
Expand Down
35 changes: 0 additions & 35 deletions tests/unit/discord/test_tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,6 @@ def set_env_variable(env_var_name, request):
del os.environ[env_var_name]


@pytest.fixture(scope='function')
def set_daily_channel_id(request):
yield from set_env_variable('DAILY_CHANNEL_ID', request)


@pytest.fixture(scope='function')
def set_daily_releases(request):
yield from set_env_variable('DAILY_RELEASES', request)


@pytest.mark.asyncio
@pytest.mark.parametrize("db_start, expected_keys", [
(
Expand Down Expand Up @@ -85,31 +75,6 @@ async def test_clean_ephemeral_cache(discord_bot, mocker, db_start, expected_key
assert v['expires_at'] >= datetime.now(UTC) - timedelta(minutes=5), f"Key {k} should not have expired"


@pytest.mark.asyncio
@pytest.mark.parametrize("skip, set_daily_releases, set_daily_channel_id, expected", [
(True, 'false', None, False),
(False, 'false', None, False),
(False, 'true', None, False),
(False, 'true', os.environ['DISCORD_GITHUB_STATUS_CHANNEL_ID'], True),
], indirect=["set_daily_releases", "set_daily_channel_id"])
async def test_daily_task(discord_bot, mocker, skip, set_daily_releases, set_daily_channel_id, expected):
"""
WHEN the daily task is called
THEN check that the task runs without error
"""
# Patch datetime.datetime at the location where it's imported in `tasks`
mock_datetime = mocker.patch('src.discord_bot.tasks.datetime', autospec=True)
mock_datetime.now.return_value = datetime(2023, 1, 1, 1 if skip else 12, 0, 0, tzinfo=timezone.utc)

# Run the daily task
result = await tasks.daily_task(bot=discord_bot)

assert result is expected

# Verify that datetime.now() was called
mock_datetime.now.assert_called_once()


@pytest.mark.asyncio
@pytest.mark.parametrize("skip", [True, False])
async def test_role_update_task(discord_bot, discord_db_users, mocker, skip):
Expand Down