-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* feat!: structure for clean architecture * feat: make some use case for user * feat: implement sqlalchemy infra * docs: add diagram Signed-off-by: Ryu Juheon <[email protected]> * feat: meal and school entity * feat: discord_id column type is string * feat: neispy를 이용한 일부 기초적인 구조 * feat: 애플리케이션 * feat: 인수 변경 * test: 일부 테스트 코드 추가 * feat: 클라이언트 기초 구조 * feat: 에러 핸들러 구조 * feat: 개념증명 * feat: 타입힌트, 급식 명령어 일부 구현 * fix: fix string typos * style: apply black * fix: fix typo * feat: register string * style: apply isort * refactor: use future annotations * fix: fix docstrings * feat(error): SchoolInfoNotFound handler * feat: 학교 설정 추가 * style: change func name * feat: preferences * style: apply black and isort * fix(test): fix test_user TypeError * feat: timetable 구현 * style: apply isort and black * test(schoolinfo): edit entity * fix: change attribute sideeffect * refactor: Strings * feat: handle more exceptions * style: apply black * style: add type ignore * feat(exceptions): use message * test: fix user * feat: 학과 계열 분리 * fix: typo * docs: remove graph * style: remove unused import * fix(type): untyped function * refactor: use Strings * fix: address unhandled exceptions * refactor: remove unused exception * fix: address unhandled exceptions * fix: wrong message in exception * feat: preference command * feat: 의견사항 적용 * feat: 데이터베이스 마이그레이션 * refactor: 페이지네이터 Co-authored-by: Starcea / 스타샤 <[email protected]> * feat: profile command * refactor(profile): refactor codes * style: apply black and isort * refactor: 프로필 커맨드 리팩토링 * style: apply isort * style: apply isort * fix(strings): organize and fix typo * fix: typo * fix: handle MealNotFound exception * fix(timetable): show selected dates in embed * fix(config): load config from json * fix(neispy): start neispy with key * feat: embed 분리 * feat: raise school info * feat: Strings 나눔 * feat: 누락된것 수정 * refactor(timetable): use only get_timetable * style: apply black and isort * style: apply isort * docs: reformat develop README * fix: typo * deps: update neispy * refactor: 조식이 기본값으로 되게 변경 * chore: docker compose for deploy * style: apply code style --------- Signed-off-by: Ryu Juheon <[email protected]> Co-authored-by: Starcea <[email protected]> Co-authored-by: cuizzang <[email protected]>
- Loading branch information
1 parent
2a20654
commit cd9c13c
Showing
200 changed files
with
3,606 additions
and
2,326 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,26 +1,15 @@ | ||
from argparse import ArgumentParser | ||
from sys import argv | ||
|
||
from crenata.argparser import parse_args | ||
from crenata.config import CrenataConfig | ||
from crenata.discord.client import create_client | ||
from crenata.discord.commands import load_commands | ||
from crenata.discord.events.error import on_error | ||
from discord import Intents, Object | ||
from discord import Intents | ||
|
||
from crenata.application import create_app | ||
from crenata.infrastructure.utils.argparser import parse_args | ||
|
||
if __name__ == "__main__": | ||
config = CrenataConfig() | ||
parser = ArgumentParser("crenata") | ||
args = parse_args(parser, argv[1:]) | ||
config.update_with_args(args) | ||
|
||
client = create_client(config, intents=Intents.default()) | ||
client = create_app(args, intents=Intents.default()) | ||
|
||
commands = load_commands("crenata/discord/commands/**/*.py") | ||
for command in commands: | ||
if config.PRODUCTION: | ||
client.tree.add_command(command) | ||
else: | ||
client.tree.add_command(command, guild=Object(config.TEST_GUILD_ID)) | ||
setattr(client.tree, "on_error", on_error) | ||
client.run() |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
from argparse import Namespace | ||
|
||
from discord import Intents | ||
|
||
from crenata.application.client import Crenata | ||
from crenata.application.commands.exit import exit | ||
from crenata.application.commands.preferences import preferences | ||
from crenata.application.commands.profile import profile | ||
from crenata.application.commands.register import register | ||
from crenata.application.commands.school import school | ||
from crenata.application.error.callback import error_handler | ||
|
||
|
||
def create_app(args: Namespace, intents: Intents) -> Crenata: | ||
crenata = Crenata(intents=intents) | ||
|
||
crenata.config.update_with_args(args) | ||
|
||
crenata.tree.set_error_handler(error_handler) | ||
|
||
crenata.tree.add_command(register) | ||
crenata.tree.add_command(profile) | ||
crenata.tree.add_command(exit) | ||
crenata.tree.add_command(school) | ||
crenata.tree.add_command(preferences) | ||
|
||
return crenata |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
from __future__ import annotations | ||
|
||
from typing import Any | ||
|
||
from discord import Client, Intents, Object | ||
from neispy import Neispy | ||
|
||
from crenata.application.tree import CrenataCommandTree | ||
from crenata.infrastructure.sqlalchemy import Database | ||
from crenata.infrastructure.utils.config import CrenataConfig | ||
|
||
|
||
class Crenata(Client): | ||
def __init__(self, intents: Intents, *args: Any, **kwargs: Any) -> None: | ||
super().__init__(intents=intents, *args, **kwargs) | ||
|
||
self.tree = CrenataCommandTree(self) | ||
self.config = CrenataConfig() | ||
|
||
async def startup(self) -> None: | ||
self.neispy = Neispy(self.config.NEIS_API_KEY) | ||
self.database = await Database.setup(self.config.DB_URL) | ||
|
||
async def closeup(self) -> None: | ||
if self.neispy.session and not self.neispy.session.closed: | ||
await self.neispy.session.close() | ||
|
||
if getattr(self.database, "database", None): | ||
await self.database.engine.dispose() | ||
|
||
async def setup_hook(self) -> None: | ||
if self.config.PRODUCTION: | ||
await self.tree.sync() | ||
|
||
else: | ||
await self.tree.sync(guild=Object(self.config.TEST_GUILD_ID)) | ||
|
||
async def close(self) -> None: | ||
await self.closeup() | ||
|
||
return await super().close() | ||
|
||
async def start(self, token: str, *, reconnect: bool = True) -> None: | ||
await self.startup() | ||
|
||
return await super().start(token, reconnect=reconnect) | ||
|
||
def run(self, *args: Any, **kwargs: Any) -> None: | ||
""" | ||
Crenata를 실행합니다. | ||
토큰은 Config에서 로드하기 때문에 인자로 줄 필요가 없습니다. | ||
""" | ||
kwargs.update({"token": self.config.TOKEN}) | ||
|
||
return super().run(*args, **kwargs) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
from discord import Interaction, app_commands | ||
|
||
from crenata.application.client import Crenata | ||
from crenata.application.embeds.exit import exit_embed_builder | ||
from crenata.application.strings import ApplicationStrings | ||
from crenata.application.utils import InteractionLock | ||
from crenata.application.view.confirm import Confirm | ||
from crenata.core.user.usecases.delete import DeleteUserUseCase | ||
from crenata.core.user.usecases.get import GetUserUseCase | ||
from crenata.infrastructure.sqlalchemy.user.domain.repository import UserRepositoryImpl | ||
|
||
|
||
@app_commands.command(name="탈퇴", description="탈퇴합니다.") | ||
async def exit(interaction: Interaction[Crenata]) -> None: | ||
async with InteractionLock(interaction): | ||
user_repository = UserRepositoryImpl(interaction.client.database) | ||
get_user_usecase = GetUserUseCase(user_repository) | ||
|
||
user = await get_user_usecase.execute(interaction.user.id) | ||
|
||
embed = exit_embed_builder() | ||
|
||
view = Confirm(interaction.user.id) | ||
|
||
await interaction.response.send_message(embed=embed, view=view, ephemeral=True) | ||
|
||
if not await view.wait(): | ||
if view.is_confirm: | ||
delete_user_usecase = DeleteUserUseCase(user_repository) | ||
|
||
await delete_user_usecase.execute(user) | ||
|
||
await interaction.edit_original_response( | ||
content=ApplicationStrings.UNREGISTER_COMPLETED, | ||
embed=None, | ||
view=None, | ||
) | ||
|
||
return | ||
|
||
await interaction.edit_original_response( | ||
content=ApplicationStrings.USER_CANCELLED, embed=None, view=None | ||
) |
4 changes: 4 additions & 0 deletions
4
.../discord/commands/preferences/__init__.py → ...lication/commands/preferences/__init__.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
from discord import app_commands | ||
|
||
from crenata.application.commands.preferences.edit import edit | ||
|
||
preferences = app_commands.Group(name="환경설정", description="환경설정 관련 명령어입니다.") | ||
|
||
preferences.add_command(edit) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
from discord import app_commands | ||
from discord.interactions import Interaction | ||
|
||
from crenata.application.client import Crenata | ||
from crenata.application.strings import ApplicationStrings | ||
from crenata.application.utils import InteractionLock | ||
from crenata.core.preferences.domain.entity import Preferences | ||
from crenata.core.preferences.usecases.update import UpdatePreferencesUseCase | ||
from crenata.infrastructure.sqlalchemy.preferences.domain.repository import ( | ||
PreferencesRepositoryImpl, | ||
) | ||
|
||
|
||
@app_commands.command(name="변경", description="환경설정을 변경합니다.") | ||
@app_commands.describe(private="학교 이름을 비공개로 할지 여부입니다.") | ||
@app_commands.describe(ephemeral="자기 자신에게만 보이게 할지 여부입니다.") | ||
async def edit( | ||
interaction: Interaction[Crenata], private: bool, ephemeral: bool | ||
) -> None: | ||
async with InteractionLock(interaction): | ||
preferences_repository = PreferencesRepositoryImpl(interaction.client.database) | ||
update_preferences_usecase = UpdatePreferencesUseCase(preferences_repository) | ||
|
||
await update_preferences_usecase.execute( | ||
interaction.user.id, | ||
Preferences( | ||
private, | ||
ephemeral, | ||
), | ||
) | ||
|
||
await interaction.response.send_message( | ||
content=ApplicationStrings.PREFERENCE_EDITED, ephemeral=True | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from discord import Interaction, app_commands | ||
|
||
from crenata.application.client import Crenata | ||
from crenata.application.embeds.profile import profile_embed_builder | ||
from crenata.core.user.usecases.get import GetUserUseCase | ||
from crenata.infrastructure.sqlalchemy.user.domain.repository import UserRepositoryImpl | ||
|
||
|
||
@app_commands.command(name="프로필", description="내 프로필을 확인합니다.") | ||
async def profile(interaction: Interaction[Crenata]) -> None: | ||
user_repository = UserRepositoryImpl(interaction.client.database) | ||
get_user_usecase = GetUserUseCase(user_repository) | ||
|
||
user = await get_user_usecase.execute(interaction.user.id) | ||
|
||
is_private = user.preferences.private | ||
is_empheral = user.preferences.ephemeral | ||
|
||
embed = profile_embed_builder( | ||
interaction.user, | ||
user.school_info, | ||
is_private, | ||
is_empheral, | ||
) | ||
|
||
await interaction.response.send_message(embed=embed, ephemeral=is_empheral) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
from discord import Embed, Interaction, app_commands | ||
|
||
from crenata.application.client import Crenata | ||
from crenata.application.strings import ApplicationStrings | ||
from crenata.core.user.domain.entity import User | ||
from crenata.core.user.usecases.create import CreateUserUseCase | ||
from crenata.infrastructure.sqlalchemy.user.domain.repository import UserRepositoryImpl | ||
|
||
|
||
@app_commands.command(name="가입", description="가입합니다.") | ||
async def register(interaction: Interaction[Crenata]) -> None: | ||
user_repository = UserRepositoryImpl(interaction.client.database) | ||
create_user_usecase = CreateUserUseCase(user_repository) | ||
|
||
await create_user_usecase.execute(User.default(interaction.user.id)) | ||
|
||
embed = Embed(title=ApplicationStrings.REGISTER_COMPLETED) | ||
|
||
await interaction.response.send_message(embed=embed, ephemeral=True) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
from discord import app_commands | ||
|
||
from crenata.application.commands.school.meal import meal | ||
from crenata.application.commands.school.search import search | ||
from crenata.application.commands.school.setup import setup | ||
from crenata.application.commands.school.timetable import timetable | ||
from crenata.application.commands.school.users import users | ||
|
||
school = app_commands.Group(name="학교", description="학교 관련 명령어입니다.") | ||
|
||
school.add_command(meal) | ||
school.add_command(search) | ||
school.add_command(setup) | ||
school.add_command(users) | ||
school.add_command(timetable) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
from datetime import datetime | ||
from typing import Literal, Optional | ||
|
||
from discord import app_commands, ui | ||
from discord.interactions import Interaction | ||
from neispy.utils import KST | ||
|
||
from crenata.application.client import Crenata | ||
from crenata.application.embeds.meal import meal_embed_builder | ||
from crenata.application.interaction import school_page | ||
from crenata.application.utils import ToDatetime, respond | ||
from crenata.core.meal.exceptions import MealNameNotFound | ||
from crenata.core.meal.usecases.get import GetMealUseCase | ||
from crenata.core.school.usecases.get import GetSchoolUseCase | ||
from crenata.core.schoolinfo.exceptions import SchoolInfoNotFound | ||
from crenata.core.user.usecases.get import GetUserUseCase | ||
from crenata.infrastructure.neispy.meal.domain.repository import MealRepositoryImpl | ||
from crenata.infrastructure.neispy.school.domain.repository import SchoolRepositoryImpl | ||
from crenata.infrastructure.sqlalchemy.user.domain.repository import UserRepositoryImpl | ||
|
||
|
||
class AllergyUI(ui.Select[ui.View]): | ||
def __init__(self, executor_id: int) -> None: | ||
super().__init__(placeholder="알러지 정보") | ||
self.executor_id = executor_id | ||
self.add_option(label="1.난류, 2.우유, 3.메밀") | ||
self.add_option(label="4.땅콩, 5.대두, 6.밀") | ||
self.add_option(label="7.고등어, 8.게, 9.새우") | ||
self.add_option(label="10.돼지고기, 11.복숭아, 12.토마토") | ||
self.add_option(label="13.아황산염, 14.호두, 15.닭고기") | ||
self.add_option(label="16.쇠고기, 17.오징어, 18.조개류") | ||
|
||
async def callback(self, interaction: Interaction) -> None: | ||
if self.executor_id == interaction.user.id: | ||
self.placeholder = self.values[0] | ||
await interaction.response.edit_message(view=self.view) | ||
|
||
|
||
@app_commands.command(name="급식", description="급식 식단표를 가져옵니다.") | ||
async def meal( | ||
interaction: Interaction[Crenata], | ||
school_name: Optional[str] = None, | ||
meal_time: Literal["조식", "중식", "석식"] = "중식", | ||
date: Optional[app_commands.Transform[datetime, ToDatetime]] = None, | ||
) -> None: | ||
if date is None: | ||
date = datetime.now(tz=KST) | ||
|
||
if school_name is None: | ||
user_repository = UserRepositoryImpl(interaction.client.database) | ||
get_user_usecase = GetUserUseCase(user_repository) | ||
|
||
user = await get_user_usecase.execute(interaction.user.id) | ||
|
||
if user.school_info is None: | ||
raise SchoolInfoNotFound | ||
|
||
school_info = user.school_info | ||
is_private = user.preferences.private | ||
|
||
else: | ||
school_repository = SchoolRepositoryImpl(interaction.client.neispy) | ||
get_school_usecase = GetSchoolUseCase(school_repository) | ||
|
||
school_infos = await get_school_usecase.execute(school_name) | ||
|
||
school_info = await school_page(interaction, school_infos) | ||
|
||
is_private = True | ||
|
||
meal_repository = MealRepositoryImpl(interaction.client.neispy) | ||
get_meal_usecase = GetMealUseCase(meal_repository) | ||
|
||
meal = await get_meal_usecase.execute( | ||
school_info.edu_office_code, school_info.standard_school_code, date, meal_time | ||
) | ||
|
||
if not meal: | ||
raise MealNameNotFound | ||
|
||
embed = meal_embed_builder(meal, is_private) | ||
|
||
view = ui.View() | ||
select_allergy_ui = AllergyUI(interaction.user.id) | ||
view.add_item(select_allergy_ui) | ||
|
||
await respond(interaction, content=None, embed=embed, view=view) |
Oops, something went wrong.