Skip to content

Commit

Permalink
Merge branch 'main' into docker
Browse files Browse the repository at this point in the history
  • Loading branch information
howardt12345 committed Sep 17, 2024
2 parents 419489e + 8145e42 commit a9d64f6
Show file tree
Hide file tree
Showing 108 changed files with 40,703 additions and 2,620 deletions.
37 changes: 28 additions & 9 deletions .github/workflows/run_test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,28 +2,47 @@ name: Run Unit Test via Pytest

on:
push:
paths:
- '**/*.py'
- .github/workflows/run_test.yml

jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.11"]
python-version: ["3.11"]
poetry-version: ["1.8.3"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
- name: Install dependencies
- name: Install poetry
uses: abatilo/actions-poetry@v2
with:
poetry-version: ${{ matrix.poetry-version }}
- name: Poetry Dependencies Caching Step 1, set up a local virtual environment (if no poetry.toml file)
run: |
poetry config virtualenvs.create true --local
poetry config virtualenvs.in-project true --local
- uses: actions/cache@v3
name: Poetry Dependencies Caching Step 2, define a cache for the virtual environment based on the dependencies lock file
with:
path: ./.venv
key: venv-${{ hashFiles('poetry.lock') }}
- name: Install dependencies using Poetry
run: poetry install
- name: Run unit tests with pytest
run: |
python -m pip install --upgrade pip
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
poetry run python -m pytest tests/ -m 'not deployment' --durations=5
- name: Run deployment tests with pytest
run: |
python -m pytest tests/ -v -s
poetry run python -m pytest tests/ -m deployment --durations=0
# - name: Generate Coverage Report
# run: |
# coverage report -m
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -183,4 +183,6 @@ guilds.env.hjson
# for tocfl
*.ipynb
*.xlsx
tocfl_8000.csv
tocfl_8000.csv
commands/tocfl/*.csv
commands/tocfl/*.ipynb
108 changes: 108 additions & 0 deletions activities/activity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
from random import choice
import discord
from discord.app_commands import Choice
from discord import app_commands
from .watching_consts import LIST_OF_MOVIES
from .playing_consts import LIST_OF_GAMES

ACTIVITIES_DICT = {
"watching": LIST_OF_MOVIES,
"playing": LIST_OF_GAMES,
# "listening", # ! not supported yet
# "streaming", # ! not supported yet
}
ACTIVITY_TYPES = list(ACTIVITIES_DICT.keys())
ACTIVITY_CHOICES = [Choice(name=activity_type, value=activity_type) for activity_type in ACTIVITY_TYPES]


def get_random_activity_type():
return choice(ACTIVITY_TYPES)


def get_activity_item(activity_type: str, index: int = None):
"""
Returns a random activity item based on the activity type.
If index is provided, returns the item at that index.
Otherwise, returns a random item.
"""
assert activity_type in ACTIVITY_TYPES, f"Invalid activity type: {activity_type}. Allowed types: {ACTIVITY_TYPES}"
if activity_type == "watching":
return LIST_OF_MOVIES[index] if index is not None else choice(LIST_OF_MOVIES)
elif activity_type == "playing":
return LIST_OF_GAMES[index] if index is not None else choice(LIST_OF_GAMES)


def get_random_activity_as_discordpy_activity():
# get random activity type, then get random item from that activity type, then return it as a discord.Activity object
activity_type = get_random_activity_type()
activity_name = get_activity_item(activity_type)
if activity_type == "watching":
return discord.Activity(name=activity_name, type=discord.ActivityType.watching)
elif activity_type == "playing":
return discord.Activity(name=activity_name, type=discord.ActivityType.playing)


def register_commands(tree, this_guild: discord.Object, client: discord.Client):
@tree.command(
name="set_activity",
description=f"Sets the bot's status to an activity type (see: `/list_activities` and `/list_activity_items`).",
guild=this_guild,
)
@app_commands.checks.has_permissions(administrator=True)
@app_commands.choices(activity_choice=ACTIVITY_CHOICES)
@app_commands.describe(activity_choice="The activity type to set the bot's status to (random if not specified)")
@app_commands.describe(
activity_index="The index of the activity item to set the bot's status to (random if not specified)"
)
async def set_activity(
interaction: discord.Interaction, activity_choice: Choice[str] = None, activity_index: int = None
):
# if activity_index is provided but activity_choice is not, then it's an error
if activity_index is not None and activity_choice is None:
await interaction.response.send_message(
"You must provide the activity type if you want to set the activity index.", ephemeral=True
)
return
activity_type = get_random_activity_type() if activity_choice is None else activity_choice.value
activity_list_to_use = ACTIVITIES_DICT[activity_type]
if activity_index is not None and activity_index < 0 and activity_index >= len(activity_list_to_use):
await interaction.response.send_message("Invalid activity index. Please try again.", ephemeral=True)
return
activity_name = (
activity_list_to_use[activity_index] if activity_index is not None else get_activity_item(activity_type)
)
await interaction.response.send_message(
f"Set bot status to '{activity_type}' with value '{activity_name}' (index: {activity_index})"
)
if activity_type == "watching":
activity = discord.Activity(name=activity_name, type=discord.ActivityType.watching)
elif activity_type == "playing":
activity = discord.Activity(name=activity_name, type=discord.ActivityType.playing)
await client.change_presence(activity=activity)

@tree.command(
name="list_activities",
description="Lists all the activities that the bot can do.",
guild=this_guild,
)
@app_commands.checks.has_permissions(administrator=True)
async def list_activities(interaction: discord.Interaction):
activity_list = "\n".join([f"* {activity_type}" for activity_type in ACTIVITY_TYPES])
await interaction.response.send_message(
f"Here are the activities that the bot can do:\n{activity_list}",
)

@tree.command(
name="list_activity_items",
description="Lists all the items for a specific activity.",
guild=this_guild,
)
@app_commands.checks.has_permissions(administrator=True)
@app_commands.choices(activity_choice=ACTIVITY_CHOICES)
@app_commands.describe(activity_choice="The activity type to list the items for")
async def list_activity_items(interaction: discord.Interaction, activity_choice: Choice[str]):
activity_type = activity_choice.value
activity_list = "\n".join([f"{i}: {activity}" for i, activity in enumerate(ACTIVITIES_DICT[activity_type])])
await interaction.response.send_message(
f"Here are the items for the activity type `{activity_type}`:\n{activity_list}",
)
5 changes: 5 additions & 0 deletions activities/playing_consts.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
LIST_OF_GAMES = (
"Detention / 返校",
"Devotion / 還願",
"9 Sols / 九日",
)
47 changes: 47 additions & 0 deletions activities/watching.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# from random import choice
# import discord
# from discord.app_commands import Choice
# from discord import app_commands
# from .watching_consts import LIST_OF_MOVIES

# def get_random_movie():
# return choice(LIST_OF_MOVIES)

# def register_commands(
# tree, this_guild: discord.Object, client: discord.Client
# ):
# @tree.command(
# name="set_watching",
# description=f"Sets the bot's status to 'Watching' (movie) using a movie's index (/{LIST_MOVIES_CMD_NAME}).",
# guild=this_guild,
# )
# @app_commands.checks.has_permissions(administrator=True)
# async def set_watching_status(
# interaction: discord.Interaction, movie_index: int
# ):
# if movie_index < 0 or movie_index >= len(LIST_OF_MOVIES):
# await interaction.response.send_message(
# "Invalid movie index. Please try again.", ephemeral=True
# )
# return
# movie_name = LIST_OF_MOVIES[movie_index]
# movie_activity = discord.Activity(
# name=movie_name, type=discord.ActivityType.watching
# )
# await client.change_presence(activity=movie_activity)
# await interaction.response.send_message(
# f"Set bot status to 'Watching: {movie_name}'", ephemeral=True
# )

# @tree.command(
# name="list_movies",
# description="Lists all the movies that the bot can watch.",
# guild=this_guild,
# )
# @app_commands.checks.has_permissions(administrator=True)
# async def list_movies(interaction: discord.Interaction):
# movie_list = "\n".join([f"{i}: {movie}" for i, movie in enumerate(LIST_OF_MOVIES)])
# await interaction.response.send_message(
# f"Here are the movies that the bot can watch:\n{movie_list}",
# # ephemeral=True,
# )
81 changes: 49 additions & 32 deletions presence/watching_consts.py → activities/watching_consts.py
Original file line number Diff line number Diff line change
@@ -1,32 +1,49 @@
from discord.app_commands import Choice


LIST_OF_MOVIES = (
"City of Sadness / 悲情城市",
"7 Days in Heaven / 父後七日",
"Detention / 返校",
"Cape No. 7 / 海角七號",
"The World Between Us / 我們與惡的距離",
"Spicy Teacher / 麻辣鮮師",
"Warriors Of The Rainbow / 賽德克·巴萊",
"The Teenage Psychic / 通靈少女",
"KANO (2014)",
"Incantation / 咒",
"You Are the Apple of My Eye / 那些年,我們一起追的女孩",
"Din Tao: Leader of the Parade / 陣頭",
"Light the Night / 華燈初上",
"Gold Leaf / 茶金",
"Alifu / 阿里夫/芙",
"Your Name Engraved Herein / 刻在我心底的名字",
"Our Times / 我的少女時代",
"The Dull-Ice Flower / 魯冰花",
"Zone Pro Site / 總舖師",
"Beyond Beauty: Taiwan from Above / 看見台灣",
"Monga / 艋舺",
"The Tag-Along / 紅衣小女孩",
"Till We Meet Again / 月老",
)

MOVIE_CHOICES = [
Choice(name=movie_name, value=movie_name) for movie_name in LIST_OF_MOVIES
]
from discord.app_commands import Choice

LIST_MOVIES_CMD_NAME = "list_movies"

LIST_OF_MOVIES = (
"City of Sadness / 悲情城市",
"7 Days in Heaven / 父後七日",
"Detention / 返校",
"Cape No. 7 / 海角七號",
"The World Between Us / 我們與惡的距離",
"Spicy Teacher / 麻辣鮮師",
"Warriors Of The Rainbow / 賽德克·巴萊",
"The Teenage Psychic / 通靈少女",
"KANO (2014)",
"Incantation / 咒",
"You Are the Apple of My Eye / 那些年,我們一起追的女孩",
"Din Tao: Leader of the Parade / 陣頭",
"Light the Night / 華燈初上",
"Gold Leaf / 茶金",
"Alifu / 阿里夫/芙",
"Your Name Engraved Herein / 刻在我心底的名字",
"Our Times / 我的少女時代",
"The Dull-Ice Flower / 魯冰花",
"Zone Pro Site / 總舖師",
"Beyond Beauty: Taiwan from Above / 看見台灣",
"Monga / 艋舺",
"The Tag-Along / 紅衣小女孩",
"Till We Meet Again / 月老",
"Little Big Women / 孤味",
"Dear Tenant / 親愛的房客",
"Marry My Dead Body / 關於我和鬼變成家人的那件事",
"My Missing Valentine / 消失的情人節",
"Days / 日子",
"Classmates Minus / 同學麥娜絲",
"The Silent Forest / 無聲",
"I WeirDO / 怪胎",
"The Falls / 瀑布",
"Man in Love / 當男人戀愛時",
"Goddamned Asura / 該死的阿修羅",
"Gaga / 哈勇家",
"Incantation / 咒",
"The Post-Truth World / 罪後真相",
"The Pig, the Snake and the Pigeon / 周處除三害",
"Taiwan Crime Stories / 台灣犯罪故事",
)

# MOVIE_CHOICES = [
# Choice(name=movie_name, value=movie_name) for movie_name in LIST_OF_MOVIES
# ]
18 changes: 8 additions & 10 deletions bot.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from typing import Any
import discord
from presence import watching
from activities import activity
from discord.ext import tasks
from modules.config import fetch_config

Expand All @@ -12,26 +12,24 @@ def __init__(self, is_prod: bool, *args, **kwargs) -> None:
intents = discord.Intents.default()
# also turn on messages functionality
intents.message_content = True
movie_activity = watching.get_random_movie_as_activity()
movie_activity = activity.get_random_activity_as_discordpy_activity()
# config
self.is_prod = is_prod
self.guilds_dict = fetch_config(is_prod)
super().__init__(
intents=intents, activity=movie_activity, *args, **kwargs
)
super().__init__(intents=intents, activity=movie_activity, *args, **kwargs)

@tasks.loop(hours=CHANGE_STATUS_INTERVAL_HOURS)
async def set_watching_status(self):
movie_activity = watching.get_random_movie_as_activity()
await self.change_presence(activity=movie_activity)
async def set_activity(self):
random_activity = activity.get_random_activity_as_discordpy_activity()
await self.change_presence(activity=random_activity)

@set_watching_status.before_loop
@set_activity.before_loop
async def before_my_task(self):
await self.wait_until_ready() # wait until the bot logs in

async def setup_hook(self) -> None:
# start the task to run in the background
self.set_watching_status.start()
self.set_activity.start()

def fetch_config(self):
self.guilds_dict = fetch_config(self.is_prod)
Expand Down
9 changes: 5 additions & 4 deletions commands/basic/basic_commands.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@

from discord import app_commands

import discord


def register_commands(
tree: discord.app_commands.CommandTree,
guilds: list[discord.Object],
Expand All @@ -15,14 +15,15 @@ def register_commands(
async def website(interaction: discord.Interaction):
await interaction.response.send_message("https://placetw.com/")


@tree.command(
name="invite",
description="Invite this bot to your server!",
guilds=guilds,
)
async def invite_link(interaction: discord.Interaction):
await interaction.response.send_message("https://discord.com/oauth2/authorize?client_id=1134650883637006407&&permissions=2147484672&scope=bot")
await interaction.response.send_message(
"https://discord.com/oauth2/authorize?client_id=1134650883637006407&&permissions=2147484672&scope=bot"
)

@tree.command(
name="echo",
Expand All @@ -31,4 +32,4 @@ async def invite_link(interaction: discord.Interaction):
)
@app_commands.describe(given_str="The string you want echoed backed")
async def echo(interaction: discord.Interaction, given_str: str):
await interaction.response.send_message(f"You sent this: `{given_str}`")
await interaction.response.send_message(f"You sent this: `{given_str}`")
Loading

0 comments on commit a9d64f6

Please sign in to comment.