diff --git a/JAS/UI.py b/JAS/UI.py new file mode 100644 index 0000000..c2a04f7 --- /dev/null +++ b/JAS/UI.py @@ -0,0 +1,385 @@ +import sys, logging, os, multiprocessing +import logging, logging.handlers +from time import sleep +import uuid, clipboard + +#Pyside6 +from PySide6.QtCore import QRunnable, Slot, QThreadPool, QObject, Signal, QThread +from PySide6.QtWidgets import QApplication, QMainWindow, QWidget, QHBoxLayout, QVBoxLayout, QGridLayout +from PySide6.QtWidgets import QPlainTextEdit, QLabel, QLineEdit, QComboBox, QStatusBar +from PySide6.QtGui import QIcon, QScreen, QAction, QFont + +#get libs +from JAS.resources.Addons import read_json, write_json, MAIN_PATH, IMG_FOLDER_PATH, resource_path +from JAS.resources.Exceptions import * + +class QueueMsg(): + msg_dict = { + "가동 실패":'봇 가동에 실패하였습니다.', + "봇 운영중":"봇 가동이 성공적으로 진행되었습니다.", + "봇 정지":"봇이 중지되었습니다.", + "경고":"봇 가동 중 경고 사항이 발견되었습니다.", + "오류 발생":'봇 가동 중 오류가 발생하였습니다.', + } + + @property + def run_failed(self) -> str: return "가동 실패" + @property + def started(self) -> str: return "봇 운영중" + @property + def closed(self) -> str: return "봇 정지" + @property + def warn(self) -> str: return "경고" + @property + def error(self) -> str: return "오류 발생" + + +class PySideHdr(logging.handlers.RotatingFileHandler): + def __init__(self, logbox, filepath) -> None: + super().__init__(filename=filepath, encoding='utf-8', maxBytes=32 * 1024 * 1024, backupCount=5) + self.logbox:QPlainTextEdit = logbox + + def emit(self, record): + self.logbox.appendPlainText(self.format(record)) + + +class WorkerSignals(QObject): + started = Signal(str) + closed = Signal(str) + run_fail = Signal(str) + error = Signal(str) + warning = Signal(str) + terminate = Signal() + + queue_check_in = Signal(object) + queue_check_out = Signal() + + +class Worker(QRunnable): + signals = WorkerSignals() + + def __init__(self) -> None: + super().__init__() + self.is_stop = False + self.queue_empty = True + self.queue_msg = '' + self.signals.terminate.connect(self.stop) + self.signals.queue_check_in.connect(self.check_in) + + @Slot() + def run(self): + wait_count = 0 + connected = False + while True: + if self.is_stop: + return + + if not connected: + if wait_count == 100: + print('===launch failed===') + self.signals.run_fail.emit(QueueMsg().run_failed) + break + else: + wait_count += 1 + sleep(1) + + self.check_out() + if self.queue_empty: + wait_time = 5 if connected else 1 + sleep(wait_time) + else: + if 'started' == self.queue_msg: # 봇 가동 확인 + print('===bot connected===') + connected = True + self.signals.started.emit(QueueMsg().started) + elif 'warn' == self.queue_msg: + print('===warning===') + self.signals.warning.emit(QueueMsg().warn) + sleep(1) + elif 'closed' == self.queue_msg: + print('===closed===') + self.signals.closed.emit(QueueMsg().closed) + self.is_stop = True + break + elif 'error' == self.queue_msg: + print('===error===') + self.signals.error.emit(QueueMsg().error) + break + self.queue_empty = True + self.queue_msg = '' + pass + + @Slot() + def stop(self): + print('stop msg send') + self.is_stop = True + + @Slot() + def check_out(self) -> None: + # print('checkout') + self.signals.queue_check_out.emit() + sleep(1) + return + + @Slot() + def check_in(self, result): + if type(result) == bool: + self.queue_empty = result + else: + self.queue_msg = result + +# Main Window +class ComuBotAPP(QMainWindow): + rec_queue:multiprocessing.Queue + send_queue:multiprocessing.Queue + data = { + "TOKEN":"", + "PROFILE":"", + "TEST_SERVER_ID":"", + "AUTH_JSON_PATH":"", + "AUTH_KEY":"", + } + is_stoped = True + + def __init__(self) -> None: + super().__init__() + self.get_vars() + self.define_btns() + self.set_logger() + self.threadpool:QThreadPool = QThreadPool() + self.initUI() + + def initUI(self): + self.cwidget = self.center_widget_init() + self.setCentralWidget(self.cwidget) + + self.setWindowTitle(f'JAS [DEMO]') + self.setWindowIcon(QIcon(resource_path('img/UI/icon.png'))) + self.setGeometry(300,300,600,500) + + self.setStatusBar(QStatusBar()) + + self.toolbar = self.toolbar_init() + self.logger.info('===안녕하세요 여러분의 도우미 J.A.S.입니다===') + self.center() + + # cWidget + def center_widget_init(self): + cwidget = QWidget() + + grid = QGridLayout() + grid.addWidget(self.tokenLabel, 0, 0) + grid.addWidget(self.serverLabel, 1, 0) + grid.addWidget(self.statusLabel, 2, 0) + + grid.addWidget(self.tokenLine, 0, 1) + grid.addWidget(self.serverLine, 1, 1) + grid.addWidget(self.statusLine, 2, 1) + grid.addWidget(self.logText, 3, 0, 1, 2) + + cwidget.setLayout(grid) + return cwidget + + # toolbar + def toolbar_init(self): + toolbar = self.addToolBar('main toolbar') + toolbar.setMovable(False) + toolbar.addAction(self.runbtn) + toolbar.addAction(self.stopbtn) + toolbar.addSeparator() + toolbar.addAction(self.savebtn) + toolbar.addAction(self.openbtn) + toolbar.addSeparator() + toolbar.addAction(self.exitbtn) + return toolbar + + def center(self): + center = QScreen.availableGeometry(QApplication.primaryScreen()).center() + geo = self.frameGeometry() + geo.moveCenter(center) + self.move(geo.topLeft()) + + def define_btns(self): + runAction = QAction(QIcon(resource_path('img/UI/play.png')), '실행', self) + runAction.setShortcut('Ctrl+R') + runAction.setStatusTip('봇 구동') + runAction.triggered.connect(self.run) + self.runbtn = runAction + + stopAction = QAction(QIcon(resource_path('img/UI/stop.png')), '정지', self) + stopAction.setShortcut('Ctrl+E') + stopAction.setStatusTip('봇 정지') + stopAction.triggered.connect(self.stop) + stopAction.setEnabled(False) + self.stopbtn = stopAction + + saveAction = QAction(QIcon(resource_path('img/UI/disk.png')), '설정 저장', self) + saveAction.setShortcut('Ctrl+S') + saveAction.setStatusTip('설정 저장') + saveAction.triggered.connect(self.save_setting) + self.savebtn = saveAction + + openAction = QAction(QIcon(resource_path('img/UI/folder.png')), '데이터 위치 열기', self) + openAction.setShortcut('Ctrl+O') + openAction.setStatusTip('데이터 위치 열기') + openAction.triggered.connect(self.open_btn) + self.openbtn = openAction + + exitAction = QAction(QIcon(resource_path('img/UI/exit.png')), '종료', self) + exitAction.setShortcut('Ctrl+Q') + exitAction.setStatusTip('종료') + exitAction.triggered.connect(self.close_btn) + self.exitbtn = exitAction + + self.tokenLabel = QLabel('봇 Token') + self.serverLabel = QLabel('테스트 서버 ID') + self.statusLabel = QLabel('상태') + + self.tokenLine = QLineEdit(self.data["TOKEN"]) + self.tokenLine.setEchoMode(QLineEdit.Password) + self.serverLine = QLineEdit(str(self.data["TEST_SERVER_ID"])) + self.statusLine = QLabel() + self.logText = QPlainTextEdit() + self.logText.setReadOnly(True) + self.logText.setFont(QFont("Courier New", 10)) + + def set_logger(self): + logger = logging.getLogger('PySide6') + logger.setLevel(logging.INFO) + + filepath = os.path.join(MAIN_PATH, 'UI.log') + pyside_handler = PySideHdr(self.logText, filepath) + dt_fmt = '%m-%d %H:%M:%S' + formatter = logging.Formatter('[{asctime}][{levelname:<8}] {message}', dt_fmt, style='{') + pyside_handler.setFormatter(formatter) + logger.addHandler(pyside_handler) + + self.logger = logger + + def get_vars(self): + self.data = read_json() + + def set_vars(self): + self.data['TOKEN'] = self.tokenLine.text() + self.data['PROFILE'] = '테스트' + self.data['TEST_SERVER_ID'] = int(self.serverLine.text()) + + write_json(self.data) + + # signals + def check_queue(self): + is_empty = self.rec_queue.empty() + if is_empty: + self.worker.signals.queue_check_in.emit(True) + else: + self.worker.signals.queue_check_in.emit(False) + self.worker.signals.queue_check_in.emit(self.rec_queue.get()) + + def started(self, string): + print('started', string) + self.statusLine.setText(string) + self.logger.info(QueueMsg.msg_dict[string]) + + def closed(self, string): + print('closed', string) + self.logger.info(QueueMsg.msg_dict[string]) + self.statusLine.setText('봇 정지') + self.is_stoped = True + self.available_on_stop() + + def run_failed(self, string): + print('run fail', string) + self.is_stoped = True + self.statusLine.setText(string) + self.logger.error(QueueMsg.msg_dict[string]) + + def warning(self, string): + print('warning', string) + self.logger.warn(QueueMsg.msg_dict[string]) + self.logger.warn(self.rec_queue.get(timeout=10)) + + def error_occured(self, string): + print('error_occured', string) + self.is_stoped = True + self.statusLine.setText(string) + self.logger.error(QueueMsg.msg_dict[string]) + self.logger.warn(self.rec_queue.get(timeout=10)) + + def disable_on_start(self): + self.runbtn.setEnabled(False) + self.savebtn.setEnabled(False) + self.openbtn.setEnabled(False) + self.exitbtn.setEnabled(False) + + self.stopbtn.setEnabled(True) + + self.tokenLine.setEnabled(False) + self.serverLine.setEnabled(False) + + def available_on_stop(self): + self.runbtn.setEnabled(True) + self.savebtn.setEnabled(True) + self.openbtn.setEnabled(True) + self.exitbtn.setEnabled(True) + + self.stopbtn.setEnabled(False) + + self.tokenLine.setEnabled(True) + self.serverLine.setEnabled(True) + + + @Slot() + def run(self): + print('===run bot===') + try: + self.send_queue.put('start bot') + self.statusLine.setText('봇 가동') + self.logger.info('봇을 가동합니다.') + + worker = Worker() + worker.signals.queue_check_out.connect(self.check_queue) + worker.signals.started.connect(self.started) + worker.signals.run_fail.connect(self.run_failed) + worker.signals.closed.connect(self.closed) + worker.signals.warning.connect(self.warning) + worker.signals.error.connect(self.error_occured) + self.threadpool.start(worker) + self.worker = worker + self.is_stoped = False + self.disable_on_start() + except Exception as e: + print(traceback.format_exc()) + self.statusLine.setText('봇 실행 중 오류 발생') + self.logger.error(str(e)) + self.available_on_stop() + + @Slot() + def stop(self): + print('===stop bot===') + self.send_queue.put('stop bot') + self.logger.info('봇을 정지합니다.') + # self.worker.signals.terminate.emit() + + @Slot() + def save_setting(self): + print('===save setting===') + self.set_vars() + self.statusLine.setText('설정 저장') + self.logger.info('설정이 저장되었습니다.') + + @Slot() + def open_btn(self): + print('=====open folder=====') + os.startfile(MAIN_PATH) + + @Slot() + def close_btn(self): + print('===exit app===') + if self.is_stoped: + self.send_queue.put_nowait('stop process') + self.close() + else: + self.logger.warn('봇이 가동중입니다.') + self.logger.info('봇을 정지 시킨 후 앱을 종료 해 주시기 바랍니다.') + + diff --git a/JAS/__init__.py b/JAS/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/JAS/cogs/__init__.py b/JAS/cogs/__init__.py new file mode 100644 index 0000000..792ea3c --- /dev/null +++ b/JAS/cogs/__init__.py @@ -0,0 +1 @@ +__all__ = ['fishing', 'gather', 'inventory', 'manage','store'] diff --git a/JAS/cogs/community.py b/JAS/cogs/community.py new file mode 100644 index 0000000..a141782 --- /dev/null +++ b/JAS/cogs/community.py @@ -0,0 +1,691 @@ +import discord, shutil, os +from discord import Color, File +from JAS.resources.Base import commands, app_commands, CommandBase, AppCommandBase, Views, Embeds +from JAS.resources.Addons import resize_img, key_gen, filename, IMG_FOLDER_PATH, resource_path +from JAS.resources.Exceptions import * +from asyncio import sleep + +class Community(CommandBase): + async def anon_message(self, message:discord.Message): + print(f'{message.author.display_name} | {message.author.id} | {message.content}') + content = message.content + + channel_id = list(filter(lambda x: x.name == self.channel.anon, message.guild.text_channels))[0].id + channel = message.guild.get_channel(channel_id) + await channel.send(embed=Embeds.general(content)) + + async def chara_talk(self, message:discord.Message): + id = message.author.id + code = self.user[id].charas # self.connector.get_user_chara(id)[0] + chara = self.charas[code] + content = message.content + color = chara.color or Color.brand_green() + path = os.path.join(IMG_FOLDER_PATH, str(message.guild.id), f'{filename(chara.name)}.png') + embedmsg, icon = Embeds.chara_talk(self.charas[code], path, content, color) + print(icon.filename) + files = [icon] + if message.attachments: + for attachment in message.attachments: + file = await attachment.to_file(use_cached=True) + files.append(file) + channel_name = message.channel.name + webhook = list(filter(lambda x:x.name == f"{channel_name} Chat", await message.guild.webhooks())) + print(webhook) + if not webhook: + webhook = await message.channel.create_webhook(name=f"{channel_name} Chat") + else: + webhook = webhook[0] + # if message.reference: + # print(message.reference) + # # msg = await message.channel.fetch_message(message.reference.message_id) + # # await msg.reply(embed=embedmsg, files=files) + # print(await webhook.fetch()) + # msg = await webhook.fetch_message(message.reference.message_id) + # await msg.reply(embed=embedmsg, files=files) + # else: + # await webhook.send(embed=embedmsg, files=files, username=chara.name, avatar_url=message.author.display_avatar.url, wait=True) + await webhook.send(content=message.reference.jump_url if message.reference else None, + embed=embedmsg, files=files, + username=chara.name, + avatar_url=message.author.display_avatar.url, + wait=True) + # await webhook.delete() + + @app_commands.command(name="스레드명", description="현재 속해있는 스레드의 이름을 변경합니다") + @app_commands.rename(title="이름") + @app_commands.describe(title="변경하고자 하는 이름") + async def change_thread_name(self, inter:discord.Interaction, title:str): + await self.on_ready(inter) + try: + if inter.channel.type==discord.ChannelType.text: + return await inter.response.send_message(embed=Embeds.general('해당 채널은 스레드가 아닙니다','스레드에 접속된 상태에서 다시 시도해 보세요.'), ephemeral=True) + else: + before_title = inter.channel.name + await inter.channel.edit(name=title) + await inter.response.send_message(embed=Embeds.general('스레드 명이 변경되었습니다',f'{before_title} > {title}'), ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="투표", description="투표를 생성합니다. 항목은 최대 5개까지 설정 가능합니다. 기한을 설정하지 않을 시 10분으로 설정됩니다.") + @app_commands.rename(timeout="기한", + op1="항목1", + op2="항목2", + op3="항목3", + op4="항목4", + op5="항목5") + @app_commands.describe(timeout="투표 마감 기한을 설정해 주세요. 기준은 분입니다. 예) 10", + op1="투표 항목. /로 설명을 기입할 수 있습니다. 예) 투표한다/당신은 투표를 해야한다.", + op2="투표 항목. /로 설명을 기입할 수 있습니다. 예) 투표한다/당신은 투표를 해야한다.", + op3="투표 항목. /로 설명을 기입할 수 있습니다. 예) 투표한다/당신은 투표를 해야한다.", + op4="투표 항목. /로 설명을 기입할 수 있습니다. 예) 투표한다/당신은 투표를 해야한다.", + op5="투표 항목. /로 설명을 기입할 수 있습니다. 예) 투표한다/당신은 투표를 해야한다.") + async def vote(self, inter:discord.Interaction, + op1:str, op2:str, op3:str="", op4:str="", op5:str="", + timeout:int=10): + await self.on_ready(inter, check_user=False, check_chara=False) + embedmsg = Embeds.vote(timeout, op1, op2, op3, op4, op5) + await inter.response.send_message(embed=embedmsg, allowed_mentions=discord.AllowedMentions.none()) + msg = await inter.edit_original_response() + await msg.add_reaction('1️⃣') + await msg.add_reaction('2️⃣') + if op3: await msg.add_reaction(discord.Reaction('3️⃣')) + if op4: await msg.add_reaction(discord.Reaction('4️⃣')) + if op5: await msg.add_reaction(discord.Reaction('5️⃣')) + # print(msg) + + await sleep(timeout*60) + + msg = await inter.channel.fetch_message(msg.id) + # print(msg) + result = {} + total = sum([reaction.count-1 for reaction in msg.reactions]) + # print(msg.reactions) + for reaction in msg.reactions: + # print(reaction) + if reaction.emoji == '1️⃣': + key = op1.split('/')[0] + if reaction.emoji == '2️⃣': + key = op2.split('/')[0] + if reaction.emoji == '3️⃣': + key = op3.split('/')[0] + if reaction.emoji == '4️⃣': + key = op4.split('/')[0] + if reaction.emoji == '5️⃣': + key = op5.split('/')[0] + count = reaction.count-1 + percent = int(count/total*100) + result[key] = [percent, count] + resultmsg = Embeds.vote_result(total, result) + + await inter.channel.send(content=msg.jump_url, embed=resultmsg) + + @commands.command(name="캐릭터등록", hidden=True) + @commands.has_any_role('관리자') + async def add_chara(self, ctx:commands.Context, + user:discord.Member=commands.parameter(displayed_name="추가 대상 사용자",description="캐릭터 추가를 진행할 사용자",displayed_default="@사용자")): + """ + 대상자의 캐릭터를 등록합니다. + 이름, 키워드(옵션), 설명을 등록합니다. + 이미지를 첨부할 시 해당 이미지를 아이콘으로 사용합니다. 첨부되지 않은 경우 기본 아이콘이 사용됩니다. + 이미지 사이즈 100x100 + """ + try: + chara_codes = list(self.charas.keys()) + id = user.id + code = self.user[id].charas + if code in chara_codes: + return await ctx.send(embed=Embeds.setting('이미 캐릭터가 등록되어 있습니다.','정보 변경을 원하신다면 `!캐릭터변경`을 사용해 주시기 바랍니다.')) + infoview=Views.AddChara() + await ctx.send(embed=Embeds.setting("캐릭터를 등록하시겠습니까?"), view=infoview) + await infoview.info.wait() + name = infoview.info.name.value + keyword = infoview.info.keyword.value + desc = infoview.info.desc.value + link = infoview.info.link.value + if 'https://' not in link: + link = 'https://'+ link + self.connector.set_charactor_data(id, code, name, keyword, desc, link) + path = os.path.join(IMG_FOLDER_PATH, str(ctx.guild.id), f'{filename(name)}.png') + if ctx.message.attachments: + icon=ctx.message.attachments[0] + if icon.height > 300 or icon.width > 300: + await resize_img(icon, path) + else: + await icon.save(path) + chara_embed, icon = Embeds.show_chara(self.charas[code], path) + await ctx.send(embeds=[Embeds.setting(f"{name}을/를 등록하였습니다."),chara_embed], file=icon) + else: + shutil.copy(resource_path(IMG_FOLDER_PATH,'sample.png'), path) + chara_embed, icon = Embeds.show_chara(self.charas[code], path) + await ctx.send(embeds=[Embeds.setting(f"{name}을/를 등록하였습니다.","기본 이미지가 등록 되었습니다.\n`!캐릭터변경`으로 인장을 등록해 주시기 바랍니다."),chara_embed], file=icon) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(name="캐릭터변경", hidden=True) + @commands.has_any_role('관리자') + async def change_chara(self, ctx:commands.Context, + user:discord.Member=commands.parameter(displayed_name="추가 대상 사용자",description="캐릭터 변경을 진행할 사용자",displayed_default="@사용자")): + """ + 대상자의 캐릭터를 변경합니다. + 이름, 키워드(옵션), 설명을 등록합니다. + 이미지를 첨부할 시 해당 이미지를 아이콘으로 사용합니다. 첨부되지 않은 경우 기본 아이콘이 사용됩니다. + 이미지 사이즈 100x100 + """ + try: + id = user.id + code = self.user[id].charas + before_name = self.charas[code].name + infoview=Views.ChangeChara(before_name, self.charas[code]) + await ctx.send(embed=Embeds.setting(f'{before_name}을/를 수정하시겠습니까?'), view=infoview) + await infoview.info.wait() + if infoview.cancel: return + name = infoview.info.name.value + keyword = infoview.info.keyword.value + desc = infoview.info.desc.value + link = infoview.info.link.value + if 'https://' not in link: + link = 'https://'+ link + path = os.path.join(IMG_FOLDER_PATH, str(ctx.guild.id), f'{filename(name)}.png') + if ctx.message.attachments: + icon=ctx.message.attachments[0] + if icon.height > 300 or icon.width > 300: + await resize_img(icon, path) + else: + await icon.save(path) + self.connector.update_charactor_data(code, name, keyword, desc, link) + chara_embed, icon = Embeds.show_chara(self.charas[code], path) + await ctx.send(embeds=[Embeds.setting(f"{name}의 정보가 변경되었습니다."),chara_embed], file=icon) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(name="캐릭터삭제", hidden=True) + @commands.has_any_role('관리자') + async def delete_chara(self, ctx:commands.Context, + user:discord.Member=commands.parameter(displayed_name="추가 대상 사용자",description="캐릭터 삭제를 진행할 사용자",displayed_default="@사용자")): + """ + 대상의 캐릭터를 삭제합니다. + """ + try: + id = user.id + code = self.user[id].charas + name = self.charas[code].name + remove_view = Views.RemoveChara(name) + await ctx.send(embed=Embeds.warning(f"캐릭터 {name}을/를 삭제하시겠습니까?"),view=remove_view) + await remove_view.wait() + if remove_view.cancel: return + self.connector.delete_charactor_data(code, id) + path = os.path.join(IMG_FOLDER_PATH, str(ctx.guild.id), f'{filename(name)}.png') + os.remove(path) + except Exception as e: + self.bot.logger.error(e) + +class CharaGroup(AppCommandBase): + def __init__(self, bot, name, description): + super().__init__(bot, name, description) + self.bot.tree.add_command(app_commands.ContextMenu( + name="캐릭터 정보", + callback=self.view_chara, + )) + + @app_commands.command(name="등록", description="커뮤에서 사용할 캐릭터를 등록합니다.") + @app_commands.rename(icon="아이콘") + @app_commands.describe(icon="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.") + async def add_chara_slash(self, interaction:discord.Interaction, icon:discord.Attachment=None): + """ + 자신의 캐릭터를 등록합니다. + 이름, 키워드(옵션), 설명을 등록합니다. + 이미지를 첨부할 시 해당 이미지를 아이콘으로 사용합니다. 첨부되지 않은 경우 기본 아이콘이 사용됩니다. + 이미지 사이즈 300x300 + """ + await self.on_ready(interaction, check_chara=False) + try: + chara_codes = list(self.charas.keys()) + id = interaction.user.id + code = key_gen() + if code in chara_codes: + return await interaction.response.send_message(embed=Embeds.setting('이미 캐릭터가 등록되어 있습니다.','정보 변경을 원하신다면 `!캐릭터변경`을 사용해 주시기 바랍니다.'), ephemeral=True, delete_after=10) + modal=Views.CharaInfo("캐릭터정보") + await interaction.response.send_modal(modal) + await modal.wait() + if not modal.finish: return + name = modal.name.value + keyword = modal.keyword.value + desc = modal.desc.value + link = modal.link.value + if 'https://' not in link: + link = 'https://'+ link + self.connector.set_charactor_data(id, code, name, keyword, desc, link) + path = os.path.join(IMG_FOLDER_PATH, str(interaction.guild_id), f'{filename(name)}.png') + if icon: + if icon.height > 300 or icon.width > 300: + await resize_img(icon, path) + else: + await icon.save(path) + chara_embed, icon = Embeds.show_chara(self.charas[code], path) + await interaction.followup.send(embeds=[Embeds.setting(f"{name}을/를 등록하였습니다."),chara_embed], file=icon, ephemeral=True) + else: + shutil.copy(resource_path(IMG_FOLDER_PATH,'sample.png'), path) + chara_embed, icon = Embeds.show_chara(self.charas[code], path) + await interaction.followup.send(embeds=[Embeds.setting(f"{name}을/를 등록하였습니다.","기본 이미지가 등록 되었습니다.\n`!캐릭터변경`으로 인장을 등록해 주시기 바랍니다."),chara_embed], file=icon, ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="변경", description="커뮤에서 사용할 캐릭터 정보를 변경합니다.") + @app_commands.rename(icon="아이콘") + @app_commands.describe(icon="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.") + async def update_chara_slash(self, interaction:discord.Interaction, icon:discord.Attachment=None): + """ + 자신의 캐릭터를 변경합니다. + 이름, 키워드(옵션), 설명을 등록합니다. + 이미지를 첨부할 시 해당 이미지를 아이콘으로 사용합니다. 첨부되지 않은 경우 기본 아이콘이 사용됩니다. + 이미지 사이즈 300x300 + """ + await self.on_ready(interaction) + try: + id = interaction.user.id + code = self.user[id].charas + before_name = self.charas[code].name + modal=Views.CharaInfo(f"{before_name} 정보", self.charas[code]) + await interaction.response.send_modal(modal) + await modal.wait() + if not modal.finish: return + name = modal.name.value + keyword = modal.keyword.value + desc = modal.desc.value + link = modal.link.value + if 'https://' not in link: + link = 'https://'+ link + path = os.path.join(IMG_FOLDER_PATH, str(interaction.guild_id), f'{filename(name)}.png') + if icon: + if icon.height > 300 or icon.width > 300: + await resize_img(icon, path) + else: + await icon.save(path) + self.connector.update_charactor_data(code, name, keyword, desc, link) + chara_embed, icon = Embeds.show_chara(self.charas[code], path) + await interaction.followup.send(embeds=[Embeds.setting(f"{name}의 정보가 변경되었습니다."),chara_embed], file=icon, ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="삭제", description="자신의 캐릭터를 삭제합니다.") + async def delete_chara_slash(self, interaction:discord.Interaction): + """ + 자신의 캐릭터를 삭제합니다. + """ + await self.on_ready(interaction) + try: + id = interaction.user.id + code = self.user[id].charas + name = self.charas[code].name + remove_view = Views.RemoveChara(name) + await interaction.response.send_message(embed=Embeds.warning(f"캐릭터 {name}을/를 삭제하시겠습니까?"),view=remove_view, ephemeral=True) + await remove_view.wait() + if remove_view.cancel: return + self.connector.delete_charactor_data(code, id) + path = os.path.join(IMG_FOLDER_PATH, str(interaction.guild_id), f'{filename(name)}.png') + os.remove(path) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="확인", description="자신 혹은 타인의 캐릭터를 확인합니다.") + @app_commands.rename(user="사용자명") + @app_commands.describe(user="확인하고 싶은 캐릭터의 대상자명. 입력하지 않으면 자신의 캐릭터를 확인합니다.") + async def show_user_chara(self, interaction:discord.Interaction, + user:discord.Member=None): + """ + 타인의 캐릭터를 확인합니다. + 유저명은 필수도 입력해 주시기 바랍니다. + """ + await self.on_ready(interaction, check_chara=False) + try: + if user: + code = self.user[user.id].charas + else: + code = self.user[interaction.user.id].charas + chara = self.charas[code] + path = os.path.join(IMG_FOLDER_PATH, str(interaction.guild_id), f'{filename(chara.name)}.png') + msg, icon = Embeds.show_chara(chara, path) + await interaction.response.send_message(embed=msg, file=icon, ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + async def view_chara(self, interaction:discord.Interaction, user:discord.User): + await self.on_ready(interaction) + try: + code = self.user[user.id].charas + chara = self.charas[code] + path = os.path.join(IMG_FOLDER_PATH, str(interaction.guild_id), f'{filename(chara.name)}.png') + msg, icon = Embeds.show_chara(chara, path) + await interaction.response.send_message(embed=msg, file=icon, ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="색상", description="자신의 캐릭터 대화창의 색상을 변경합니다.") + @app_commands.rename(input_color='색상코드') + @app_commands.describe(input_color="원하는 색상코드를 입력해 주세요. 예) #1E45D2") + async def change_chara_color(self, interaction:discord.Interaction, input_color:str=''): + """ + 캐릭터 대화창의 색상을 변경합니다. + """ + await self.on_ready(interaction) + try: + code = self.user[interaction.user.id].charas + if input_color: + try: + color = int(input_color, 16) + Color(color) + print('direct',color) + except: + color_code = input_color.replace('#','0x') if input_color.startswith('#') else f'0x{input_color}' + color = int(color_code, 16) + print('convert',color) + else: + color_view = Views.CharaColor() + await interaction.response.send_message(embed=Embeds.general('원하시는 색상을 선택해 주세요'),view=color_view, ephemeral=True) + await color_view.wait() + if not color_view.finish: + return await interaction.delete_original_response() + color = int(color_view.select.values[0]) + self.connector.update_charactor_color(code, color) + msg = Embeds.general(title="캐릭터 색상이 변경되었습니다",color=Color(color)) + if input_color: + await interaction.response.send_message(embed=msg, ephemeral=True) + else: + await interaction.edit_original_response(embed=msg, view=None) + except ValueError as e: + print('input_color error') + await interaction.response.send_message(embed=Embeds.warning('올바른 색상코드가 아닙니다', f'입력된 값: {input_color}'), ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + +@app_commands.default_permissions(manage_guild=True) +@app_commands.checks.has_role('관리자') +class NPCGroup(AppCommandBase): + @app_commands.command(name="등록", description="커뮤에서 사용할 NPC를 등록합니다.") + @app_commands.rename(color="캐릭터색상", icon1="아이콘1",icon2="아이콘2",icon3="아이콘3",icon4="아이콘4") + @app_commands.describe( + color="캐릭터의 채팅창 색상 예) #4F231E", + icon1="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.", + icon2="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.", + icon3="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.", + icon4="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.") + async def add_NPC_slash(self, interaction:discord.Interaction, + color:str='', + icon1:discord.Attachment=None, + icon2:discord.Attachment=None, + icon3:discord.Attachment=None, + icon4:discord.Attachment=None,): + """ + 새로운 NPC 정보를 등록합니다. + 필요사항 + 이름 / 상황 + """ + await self.on_ready(interaction, check_user=False, check_chara=False) + try: + guild_id = interaction.guild_id + code = 'NPC_'+ key_gen() + modal=Views.NPCInfo() + await interaction.response.send_modal(modal) + await modal.wait() + if not modal.finish: return + name = modal.name.value + vers1 = modal.vers1.value + vers2 = modal.vers2.value + vers3 = modal.vers3.value + vers4 = modal.vers4.value + vers_list = [ + [vers1.split('/')[0], '/'.join(vers1.split('/')[1:])], + [vers2.split('/')[0], '/'.join(vers2.split('/')[1:])], + [vers3.split('/')[0], '/'.join(vers3.split('/')[1:])], + [vers4.split('/')[0], '/'.join(vers4.split('/')[1:])], + ] + title = f"NPC {name}을/를 등록하였습니다." + + # NPC 색상 + if color: + try: + color = int(color, 16) + Color(color) + print('direct',color) + except: + color_code = color.replace('#','0x') if color.startswith('#') else f'0x{color}' + color = int(color_code, 16) + print('convert',color) + + self.connector.set_NPC_info(code, name, color) + for vers_data in vers_list: + case, vers = vers_data + if vers: + path = os.path.join(IMG_FOLDER_PATH, str(interaction.guild_id), filename(f'{name}_{case}.png')) + + if vers_list.index(vers_data) == 0: + icon = icon1 + elif vers_list.index(vers_data) == 1: + icon = icon2 + elif vers_list.index(vers_data) == 2: + icon = icon3 + elif vers_list.index(vers_data) == 3: + icon = icon4 + + if icon: + if icon.height > 300 or icon.width > 300: + await resize_img(icon, path) + else: + await icon.save(path) + else: + shutil.copy(resource_path(IMG_FOLDER_PATH,'sample.png'), path) + + self.connector.set_NPC_vers(code, case, vers) + chara_embed = Embeds.show_npc(self.npc[code]) + files = [] + for case in self.npc[code].case: + file = File(os.path.join(IMG_FOLDER_PATH, str(guild_id), filename(f'{name}_{case}.png'))) + files.append(file) + await interaction.followup.send(embeds=[Embeds.setting(title),chara_embed], files=files) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="변경", description="커뮤에서 사용할 NPC 정보를 변경합니다.") + @app_commands.rename(color="캐릭터색상", icon1="아이콘1",icon2="아이콘2",icon3="아이콘3",icon4="아이콘4") + @app_commands.describe( + color="캐릭터의 채팅창 색상 예) #4F231E", + icon1="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.", + icon2="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.", + icon3="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.", + icon4="캐릭터를 표시하는 아이콘. 정사각형 형태의 그림을 넣어주세요.") + async def update_NPC_slash(self, interaction:discord.Interaction, + color:str='', + icon1:discord.Attachment=None, + icon2:discord.Attachment=None, + icon3:discord.Attachment=None, + icon4:discord.Attachment=None,): + """ + NPC를 변경합니다. + 이름, 키워드(옵션), 설명을 등록합니다. + 이미지를 첨부할 시 해당 이미지를 아이콘으로 사용합니다. 첨부되지 않은 경우 기본 아이콘이 사용됩니다. + 이미지 사이즈 300x300 + """ + await self.on_ready(interaction, check_user=False, check_chara=False) + try: + guild_id = interaction.guild_id + + data = [[self.npc[code].name, code] for code in self.npc.keys()] + select_view = Views.UpdateNPC(data, self.npc) + await interaction.response.send_message(embed=Embeds.setting('변경할 NPC를 선택해 주세요'), view=select_view) + await select_view.wait() + if select_view.cancel: return + code = select_view.select.values[0] + + # NPC 색상 + if color: + try: + color = int(color, 16) + Color(color) + print('direct',color) + except: + color_code = color.replace('#','0x') if color.startswith('#') else f'0x{color}' + color = int(color_code, 16) + print('convert',color) + + name = select_view.modal.name.value + vers1 = select_view.modal.vers1.value + vers2 = select_view.modal.vers2.value + vers3 = select_view.modal.vers3.value + vers4 = select_view.modal.vers4.value + vers_list = [ + [vers1.split('/')[0], '/'.join(vers1.split('/')[1:])], + [vers2.split('/')[0], '/'.join(vers2.split('/')[1:])], + [vers3.split('/')[0], '/'.join(vers3.split('/')[1:])], + [vers4.split('/')[0], '/'.join(vers4.split('/')[1:])], + ] + title = f"NPC {name}을/를 등록하였습니다." + self.connector.update_NPC_info(code, name, Color) + + self.npc[code].number = 0 + self.npc[code].case = [] + self.npc[code].vers = [] + + for vers_data in vers_list: + case, vers = vers_data + if vers: + path = os.path.join(IMG_FOLDER_PATH, str(guild_id), filename(f'{name}_{case}.png')) + + if vers_list.index(vers_data) == 0: + icon = icon1 + elif vers_list.index(vers_data) == 1: + icon = icon2 + elif vers_list.index(vers_data) == 2: + icon = icon3 + elif vers_list.index(vers_data) == 3: + icon = icon4 + + if icon: + if icon.height > 300 or icon.width > 300: + await resize_img(icon, path) + else: + await icon.save(path) + else: + shutil.copy(resource_path(IMG_FOLDER_PATH,'sample.png'), path) + + self.connector.set_NPC_vers(code, case, vers) + chara_embed = Embeds.show_npc(self.npc[code]) + files = [] + for case in self.npc[code].case: + file = File(os.path.join(IMG_FOLDER_PATH, str(guild_id), filename(f'{name}_{case}.png'))) + files.append(file) + await interaction.followup.send(embeds=[Embeds.setting(title),chara_embed], files=files, ephemeral=True) + # id = interaction.user.id + # code = self.user[id].charas + # before_name = self.charas[code].name + # modal=Views.NPCInfo(f"{before_name} 정보", self.npc[code]) + # await interaction.response.send_modal(modal) + # await modal.wait() + # if not modal.finish: return + # name = modal.name.value + # if icon: + # if icon.height > 300 or icon.width > 300: + # await resize_img(icon, path) + # else: + # await icon.save(path) + # self.connector.update_charactor_data(code, name, keyword, desc, link) + # chara_embed, icon = Embeds.show_chara(self.charas[code], path) + # await interaction.followup.send(embeds=[Embeds.setting(f"NPC {name}의 정보가 변경되었습니다."),chara_embed], file=icon, ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="삭제", description="NPC를 삭제합니다.") + async def delete_NPC_slash(self, interaction:discord.Interaction): + """ + 자신의 캐릭터를 삭제합니다. + """ + await self.on_ready(interaction, check_user=False, check_chara=False) + try: + guild_id = interaction.guild_id + data = [[self.npc[code].name, code] for code in self.npc.keys()] + select_view = Views.RemoveNPC(data) + await interaction.response.send_message(embed=Embeds.setting('삭제할 NPC를 선택해 주세요'), view=select_view) + await select_view.wait() + if select_view.cancel: return + code = select_view.select.values[0] + name = self.npc[code].name + cases = self.npc[code].case + + self.connector.delete_NPC_data(code) + + for case in cases: + path = os.path.join(IMG_FOLDER_PATH, str(guild_id), filename(f'{name}_{case}.png')) + os.remove(path) + except Exception as e: + self.bot.logger.error(e) + + class NPC_Vers_Num(discord.enums.Enum): + 대사1 = 0 + 대사2 = 1 + 대사3 = 2 + 대사4 = 3 + + async def NPC_talk(self, inter:discord.Interaction, code, vers): + npc = self.npc[code] + content = vers + color = npc.color or Color.brand_green() + target_case = npc.case[npc.vers.index(vers)] + path = os.path.join(IMG_FOLDER_PATH, str(inter.guild_id), filename(f'{npc.name}_{target_case}.png')) + embedmsg, icon = Embeds.chara_talk(self.npc[code], path, content, color) + # print(icon.filename) + files = [icon] + channel_name = inter.channel.name + webhook = list(filter(lambda x:x.name == f"{channel_name} Chat", await inter.guild.webhooks())) + # print(webhook) + if not webhook: + webhook = await inter.channel.create_webhook(name=f"{channel_name} Chat") + else: + webhook = webhook[0] + # if message.reference: + # print(message.reference) + # # msg = await message.channel.fetch_message(message.reference.message_id) + # # await msg.reply(embed=embedmsg, files=files) + # print(await webhook.fetch()) + # msg = await webhook.fetch_message(message.reference.message_id) + # await msg.reply(embed=embedmsg, files=files) + # else: + # await webhook.send(embed=embedmsg, files=files, username=chara.name, avatar_url=message.author.display_avatar.url, wait=True) + await webhook.send(embed=embedmsg, files=files, + username=npc.name, + avatar_url=inter.user.display_avatar, + wait=True) + # await webhook.delete() + + @app_commands.command(name="대사", description="NPC의 대사를 출력합니다.") + @app_commands.rename(number="출력대사") + @app_commands.describe(number="출력할 대사를 선택합니다. 대사의 번호를 선택해 주세요") + async def NPC_chat_slash(self, interaction:discord.Interaction, + number:NPC_Vers_Num): + """ + NPC의 대사를 사용합니다. + + """ + await self.on_ready(interaction, check_user=False, check_chara=False) + try: + data = [[self.npc[code].name, code] for code in self.npc.keys()] + select_view = Views.SelectNPC(data) + + await interaction.response.send_message(embed=Embeds.setting('변경할 NPC를 선택해 주세요'), view=select_view) + await select_view.wait() + if select_view.cancel: return + code = select_view.select.values[0] + + npc = self.npc[code] + if number.value >= npc.number: + total_vers = [npc.case[npc.vers.index(vers)]+' / '+vers for vers in npc.vers] + return await interaction.channel.send(embed=Embeds.general(f'현재 {npc.name}의 대사는 {npc.number}개 등록되어 있습니다', f'사용 가능한 대사:\n{total_vers}')) + target_vers = npc.vers[number.value] + await self.NPC_talk(interaction, code, target_vers) + except Exception as e: + self.bot.logger.error(e) + +async def setup(bot:commands.Bot): + await bot.add_cog(Community(bot)) + bot.tree.add_command(CharaGroup(bot=bot, name="캐릭터", description="커뮤 캐릭터를 등록하거나 관리합니다.")) + bot.tree.add_command(NPCGroup(bot=bot, name="npc", description="커뮤 NPC를 등록하거나 관리합니다.")) + + diff --git a/JAS/cogs/fishing.py b/JAS/cogs/fishing.py new file mode 100644 index 0000000..eb3d582 --- /dev/null +++ b/JAS/cogs/fishing.py @@ -0,0 +1,378 @@ +import discord +from discord import Color, Member +from JAS.resources.Exceptions import * +from JAS.resources.Base import commands, app_commands, CommandBase, AppCommandBase, Embeds, Views +from JAS.resources.Addons import key_gen +from math import floor +from random import choice, randrange, random +from time import strftime,localtime +from asyncio import sleep +from copy import deepcopy + +class Fishing(CommandBase): + def check_fishing_limit(self, user_id, channel): + max_count = self.setting.max_fishing + date = strftime('%Y-%m-%d',localtime()) + fishing_count = self.connector.get_fishing_history(user_id, channel, date) + if fishing_count >= max_count: + raise FishingLimit('낚시 횟수가 초과되었습니다') + + def fishing(self, fish_list, loc, user_id): + grades = { + "초라함": 0.5, + "일반": 1, + "희귀": 1.8, + "전설": 3, + "경이": 5 + } + + fishcode = choice(fish_list) + fish = self.fishes[fishcode] + print(fish) + + name = fish.name + dice = randrange(1,101) + print(dice) + grade = "경이" if dice < 2 else "전설" if dice < 11 else "희귀" if dice < 31 else "일반" if dice < 91 else "초라함" + num = 1 + length = round(randrange(fish.min, fish.max)+random(),2) + price = floor(length/(fish.min+fish.max)*2*fish.baseprice*grades[grade]) + great = bool(length>(fish.max*0.8)) + itemcode = f'{fishcode}-{num}-{str(random())[2:]}' + + now = strftime('%Y-%m-%d %H:%M:%S',localtime()) + self.connector.set_fishing_history(now, user_id, str(loc), name, length) + print('history logging success') + + desc = self.items[fishcode].desc + code = self.user[user_id].charas + chara_name = self.charas[code].name + if len(self.invs[code].items) <= self.invs[code].size: + self.connector.reg_item(itemcode, name, desc, num, price) + self.connector.store_item(code, itemcode, num) + title = "🎉월척이오!!!!!" if great else "🎣낚시 성공!" + desc = f'{chara_name}님이 {name}을 낚았습니다.' + color = Color.gold() if great or grade == "경이" else Color.green() if grade != "초라함" else Color.dark_grey() + data = (grade, f'{length} cm', f'G {price}') + else: + self.connector.add_gold(code, price) + print('set gold success') + desc=f'가방이 꽉 차 담을 수 없었습니다...\n{chara_name}은/는 G{price}를 얻었습니다.' + color = Color.dark_grey() + data = (grade, f'{length} cm', f'G {price}') + return (title, desc, color, data) + + @app_commands.command(name="낚시", description="낚시터에서 물고기를 낚습니다.") + async def fishing_slash(self, interaction:discord.Interaction): + """ + 낚시터에서 낚시를 합니다. + """ + loc = interaction.channel + user_id = interaction.user.id + await self.on_ready(interaction) + + try: + await interaction.response.send_message(embed=Embeds.general("낚시를 시작합니다.",None,Color.green()), ephemeral=True) + print("check fishing limit") + self.check_fishing_limit(user_id, loc) + + try: + fish_list = self.fish_data[str(loc)] + except: + raise IncorrectPlace('올바른 낚시터가 아닙니다.') + wait_time = randrange(4,10) + print(f'대기시간: {wait_time}') + await interaction.edit_original_response(embed=Embeds.fishing(0)) + await sleep(1) + for i in range(wait_time+1): + fishingview = Views.FishingView(False) + if i == wait_time: + fishingview = Views.FishingView(True) + type = 9 + elif i < 2: + type = i + else: + type = randrange(2,5) + await interaction.edit_original_response(embed=Embeds.fishing(type), view=fishingview) + await fishingview.wait() + if fishingview.result: + break + + # 낚시 버튼 클릭한 경우 + if fishingview.result: + if fishingview.hooked: + # 낚시에 성공한 경우 + title, desc, color, data = self.fishing(fish_list, loc, user_id) + else: + # 낚시에 실패한 경우 + title = "낚시 실패" + desc = "너무 일찍 당겨버렸다..." + color=Color.dark_grey() + data = None + else: + title = "낚시 실패..." + desc = "물고기가 미끼를 먹고 유유히 사라졌다..." + color = Color.dark_grey() + data = None + embeded = Embeds.fishing_result(title, desc, color, data) + await interaction.delete_original_response() + await interaction.channel.send(embed=embeded, view=None) + except FishingLimit as e: + print(e) + print('>> Error: exceed fishing limit') + print(traceback.format_exc()) + title = '더이상 고기를 낚을 수 없을것 같습니다…' + desc = None + color = Color.brand_red() + data = None + embeded = Embeds.fishing_result(title, desc, color, data) + await interaction.edit_original_response(embed=embeded, view=None) + except SellingError as e: + print(e) + print('>> Error: inventory error accured') + print(traceback.format_exc()) + title = "물고기 판매 도중 오류가 발생하였습니다." + desc = "다시 시도해 주시기 바랍니다" + color = Color.red() + data = None + embeded = Embeds.fishing_result(title, desc, color, data) + await interaction.edit_original_response(embed=embeded, view=None) + except FishingError as e: + print(e) + print('>> Error: fishing error accured') + print(traceback.format_exc()) + title = "낚시 도중 오류가 발생하였습니다." + desc = "다시 시도해 주시기 바랍니다" + color = Color.red() + data = None + embeded = Embeds.fishing_result(title, desc, color, data) + await interaction.edit_original_response(embed=embeded, view=None) + except IncorrectPlace as e: + print(e) + print('>> Error: incorrect place') + print(traceback.format_exc()) + title = "올바른 낚시터가 아닙니다" + desc = "다른 곳을 찾아가 보세요" + color = Color.yellow() + data = None + embeded = Embeds.fishing_result(title, desc, color, data) + await interaction.edit_original_response(embed=embeded, view=None) + except Exception as e: + print(e) + print('>> Error: Unkown Error') + print(traceback.format_exc()) + title = '예상치 못한 오류가 발생하였습니다.' + desc = '지속적으로 발생할 경우 관리자에게 문의 바랍니다.' + color = Color.red() + data = None + embeded = Embeds.fishing_result(title, desc, color, data) + await interaction.edit_original_response(embed=embeded, view=None) + + @app_commands.command(name="물고기목록", description="낚시터의 물고기 목록을 확인한다.") + @app_commands.rename(loc="낚시터") + @app_commands.describe(loc="지정된 낚시터. 입력할 경우 해당 낚시터의 물고기만 확인가능하다.") + async def 물고기목록(self, interaction:discord.Interaction, + loc:discord.TextChannel=None): + """ + 현재 낚시 가능한 물고기 목록을 확인합니다. + 낚시터 명을 입력할 경우 해당 낚시터의 목록만 확인 가능합니다. + """ + await self.on_ready(interaction) + try: + if loc: + try: + data = {} + data[loc.name] = self.fish_data[loc.name] + title = f"{loc.name}에서 발견 가능한 물고기 입니다." + await interaction.response.send_message(embed=Embeds.fish_data(title, data, self.fishes, True)) + except Exception as e: + print(e) + await interaction.response.send_message(embed=Embeds.general('해당 낚시터에는 물고기가 없습니다.',""), ephemeral=True) + else: + await interaction.response.send_message(embed=Embeds.fish_data("낚시터에서 발견 가능한 물고기 입니다.", self.fish_data, self.fishes, False)) + except Exception as e: + self.bot.logger.error(e)(e) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def 낚시설정(self, message:commands.Context, + value:int=commands.parameter(displayed_name="최대 횟수",description="- 낚시가 가능한 최대 횟수")): + """ + 최대 낚시 횟수를 변경합니다. + 횟수는 숫자로 입력해주시기 바랍니다. + """ + if value: + try: + self.connector.set_max_fishing(value) + await message.send(embed=Embeds.setting(f'최대 낚시 횟수가 {value}회로 변경되었습니다.')) + except Exception as e: + print(e) + print(traceback.format_exc()) + await message.send(embed=Embeds.error()) + else: + await message.send(embed=Embeds.warning('변경하고자 하는 수치를 입력해 주시기 바랍니다'), delete_after=5) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def 낚시이력삭제(self, message:commands.Context, + user:Member=commands.parameter(displayed_name="대상 유저",description="- 이력을 삭제하고자 하는 유저명. @를 통해 입력해 주세요")): + """ + 사용자의 낚시 이력을 삭제합니다. + @태그를 통해 입력해 주시기 바랍니다. + """ + if user: + if type(user) == Member: + self.connector.delete_fishing_history(user.id) + name = user.nick or user.name + await message.send(embed=Embeds.setting(f'{name}의 이력을 삭제하였습니다.')) + else: + await message.send(embed=Embeds.warning('@태그를 이용해 사용자를 입력 바랍니다.'), delete_after=5) + else: + await message.send(embed=Embeds.warning('대상 사용자를 입력해 주시기 바랍니다.'), delete_after=5) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def 물고기추가(self, message:commands.Context, + name:str=commands.parameter(displayed_name="물고기 이름-",default=None, displayed_default="text",description="추가하고자 하는 물고기 이름입니다.")): + """ + 새로운 물고기를 추가합니다. + 필요한 정보 + 이름 / 설명 / 최소길이 / 최고길이 / 기본 가격 / 등장위치(채널명) + """ + if not name: + await message.send(embed=Embeds.warning('물고기 이름을 입력해 주시기 바랍니다.'), delete_after=5) + return + infoview = Views.AddFishView() + await message.send(embed=Embeds.setting(f"{name}을/를 추가하시겠습니까?",None),view=infoview) + await infoview.wait() + if infoview.cancel:return + desc = infoview.modal.desc.value + min = int(infoview.modal.min.value) + max = int(infoview.modal.max.value) + baseprice = int(infoview.modal.baseprice.value) + loc = infoview.modal.loc.value + + try: + self.connector.add_fish_data(name, desc, min, max, baseprice, loc) + await message.send(embed=Embeds.add_fish(f'{name}을/를 추가했습니다.',name,desc,min,max,baseprice,loc)) + except CannotAddFish as e: + print(e) + print('>> Error: cannot add fish') + print(traceback.format_exc()) + await message.send(embed=Embeds.warning('물고기를 추가하지 못하였습니다. 다시 시도해 주시기 바랍니다.'), delete_after=5) + except Exception as e: + print(e) + print('>> Error: unkown error') + print(traceback.format_exc()) + raise e + + @commands.command(name="물고기변경", hidden=True) + @commands.has_any_role('관리자') + async def change_fish_data(self, message:commands.Context, + name:str=commands.parameter(displayed_name="물고기 이름-", default=None, description="수정하고자 하는 물고기 이름")): + """ + 낚시터의 물고기 정보를 변경합니다. + """ + msg = None + try: + if name: + code_list = list(filter(lambda x: self.fishes[x].name==name,list(self.fishes.keys()))) + if not code_list: + raise FishNotFOund('해당이름 물고기 없음') + code = code_list[0] + else: + title = "변경하실 물고기를 선택해 주세요." + selectview = Views.SelectFishView(self.fishes) + msg = await message.send(embed=Embeds.setting(title,None), view=selectview) + await selectview.wait() + if selectview.cancel: return + code = selectview.fish.values[0] + name = self.items[code].name + + print(f'{name} | {code}') + data = deepcopy(self.fishes[code]) + data.desc = self.items[code].desc + print(data) + infoview = Views.ChangeFishView(data) + if msg: + await msg.edit(embed=Embeds.setting(f"{name}을/를 변경하시겠습니까?",None),view=infoview) + else: + await message.send(embed=Embeds.setting(f"{name}을/를 변경하시겠습니까?",None),view=infoview) + await infoview.wait() + if infoview.cancel: return + desc = infoview.modal.desc.value + min = int(infoview.modal.min.value) + max = int(infoview.modal.max.value) + baseprice = int(infoview.modal.baseprice.value) + loc = infoview.modal.loc.value + + self.connector.change_fish_data(code, desc, min, max, baseprice, loc) + await message.send(embed=Embeds.add_fish(f'{name}을/를 변경했습니다.',name,desc,min,max,baseprice,loc)) + except CannotAddFish as e: + print(e) + print('>> Error: cannot add fish') + print(traceback.format_exc()) + await message.send(embed=Embeds.warning('물고기를 변경하지 못하였습니다. 다시 시도해 주시기 바랍니다.'), delete_after=5) + except FishNotFOund as e: + print(e) + print('>> Error: fish not found') + print(traceback.format_exc()) + await message.send(embed=Embeds.warning('해당 이름의 물고기가 존재하지 않습니다. 물고기 이름을 다시 확인해 주시기 바랍니다.'), delete_after=5) + except Exception as e: + print(e) + print('>> Error: unkown error') + print(traceback.format_exc()) + raise e + + @commands.command(name="물고기삭제", hidden=True) + @commands.has_any_role('관리자') + async def delete_fish_data(self, message:commands.Context, + name:str=commands.parameter(displayed_name="물고기 이름-", default=None, description="삭제하고자 하는 물고기 이름")): + """ + 낚시터의 물고기 정보를 삭제합니다. + """ + try: + msg = None + if name: + code_list = list(filter(lambda x: self.fishes[x].name==name,list(self.fishes.keys()))) + if not code_list: + raise FishNotFOund('해당이름 물고기 없음') + code = code_list[0] + else: + title = "삭제하실 물고기를 선택해 주세요." + selectview = Views.SelectFishView(self.fishes) + msg = await message.send(embed=Embeds.setting(title,None), view=selectview) + await selectview.wait() + if selectview.cancel: return + code = selectview.fish.values[0] + name = self.items[code].name + + print(f'{name} | {code}') + delview = Views.ConfirmFishDelete() + if msg: + await msg.edit(embed=Embeds.warning(f"{name}을/를 삭제하시겠습니까?"),view=delview) + else: + await message.send(embed=Embeds.warning(f"{name}을/를 삭제하시겠습니까?"),view=delview) + await delview.wait() + if delview.cancel: return + self.connector.delete_fish(code) + await message.send(embed=Embeds.setting(f"{name}을/를 삭제하였습니다.",None)) + except CannotAddFish as e: + print(e) + print('>> Error: cannot add fish') + print(traceback.format_exc()) + await message.send(embed=Embeds.warning('물고기를 삭제하지 못하였습니다. 다시 시도해 주시기 바랍니다.'), delete_after=5) + except FishNotFOund as e: + print(e) + print('>> Error: fish not found') + print(traceback.format_exc()) + await message.send(embed=Embeds.warning('해당 이름의 물고기가 존재하지 않습니다. 물고기 이름을 다시 확인해 주시기 바랍니다.'), delete_after=5) + except Exception as e: + print(e) + print('>> Error: unkown error') + print(traceback.format_exc()) + raise e + +async def setup(bot:commands.Bot): + await bot.add_cog(Fishing(bot)) + diff --git a/JAS/cogs/inventory.py b/JAS/cogs/inventory.py new file mode 100644 index 0000000..bab1a77 --- /dev/null +++ b/JAS/cogs/inventory.py @@ -0,0 +1,174 @@ +import discord +from JAS.resources.Exceptions import * +from JAS.resources.Base import JAS, commands, app_commands, CommandBase, AppCommandBase, Views, Embeds +from random import randrange, random +from asyncio import sleep +from copy import deepcopy + +class Inventory(CommandBase): + def __init__(self, bot: JAS) -> None: + super().__init__(bot) + bot.tree.add_command(app_commands.ContextMenu( + name="송금", + callback=self.transfer_gold_menu, + )) + + @app_commands.command(name="소지품") + @app_commands.rename(check_gold="골드확인") + @app_commands.describe(check_gold="소지중인 골드만 확인합니다.") + @app_commands.choices(check_gold=[ + app_commands.Choice(name="골드만 확인", value='True'), + app_commands.Choice(name="소지품 확인", value='False'), + ]) + async def inventory(self, interaction:discord.Interaction, + check_gold:app_commands.Choice[str]=None): + """ + 현재 자신이 가지고 있는 소지품을 확인합니다. + 남은 가방공간, 아이템, 골드를 확인가능합니다. + """ + await self.on_ready(interaction) + try: + id = interaction.user.id + code = self.user[id].charas + if check_gold: + if check_gold.value == 'True': + gold = self.invs[code].gold # self.connector.get_gold(id) + return await interaction.response.send_message(embed=Embeds.store(f'현재 G{gold}를 소지하고 있습니다.'), ephemeral=True) + name = self.charas[code].name + title = f'{name}님의 소지품입니다.' + inventory = self.invs[code] + desc = f'남은 공간: {len(inventory.items)} / {inventory.size}' + await interaction.response.send_message(embed=Embeds.inventory(title=title, desc=desc, inventory=inventory, items=self.items), ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + def send_gold(self, id, user, gold): + fromcode = self.user[id].charas + tocode = self.user[user].charas + current_gold = self.invs[fromcode].gold + if current_gold < gold: + raise NotEnoughGold('골드가 충분하지 않습니다.') + self.connector.add_gold(fromcode, -gold) + self.connector.add_gold(tocode, gold) + + @app_commands.command(name="송금",description="상대방에게 골드를 전달합니다.") + @app_commands.rename(user="대상", gold="금액") + @app_commands.describe(user="송금하고자 하는 대상", gold="송금하고자 하는 금액. 입력하지 않으면 10골드를 송금합니다.") + async def transfer_gold(self, interaction:discord.Interaction, + user:discord.Member, + gold:int=10): + """ + 골드를 다른 사람에게 전달합니다. + 골드를 입력하지 않은 경우 10골드가 전달됩니다. + """ + await self.on_ready(interaction) + try: + id = interaction.user.id + name = interaction.user.display_name + + user_name = user.display_name + user_id = user.id + code = self.user[user_id].charas + if not code: + return await interaction.response.send_message(embed=Embeds.general('캐릭터가 존재하지 않는 사용자에게 송금할 수 없습니다.'), ephemeral=True) + + transfer_view = Views.TransferView() + await interaction.response.send_message(embed=Embeds.store(f'{user_name}에게 송금하시겠습니까?',f'G{gold}를 전달합니다.'), view=transfer_view, ephemeral=True) + await transfer_view.wait() + if transfer_view.cancel: + return await interaction.edit_original_response(embed=Embeds.general('작업을 취소합니다.')) + await interaction.delete_original_response() + self.send_gold(id, user_id, gold) + await interaction.channel.send(embed=Embeds.store(f'{name}이/가 {user_name}에게 G{gold}를 전달하였습니다.')) + except NotEnoughGold as e: + await interaction.edit_original_response(embed=Embeds.warning('소지한 골드가 부족합니다.'), view=None) + except Exception as e: + self.bot.logger.error(e) + + async def transfer_gold_menu(self, interaction:discord.Interaction, user:discord.User): + await self.on_ready(interaction) + try: + id = interaction.user.id + name = interaction.user.display_name + gold = 10 + + user_name = user.display_name + user_id = user.id + code = self.user[user_id].charas + if not code: + return await interaction.response.send_message(embed=Embeds.general('캐릭터가 존재하지 않는 사용자에게 송금할 수 없습니다.'), ephemeral=True) + + transfer_view = Views.TransferView() + await interaction.response.send_message(embed=Embeds.store(f'{user_name}에게 송금하시겠습니까?',f'G{gold}를 전달합니다.'), view=transfer_view, ephemeral=True) + await transfer_view.wait() + if transfer_view.cancel: + return await interaction.edit_original_response(embed=Embeds.general('작업을 취소합니다.')) + self.send_gold(id, user_id, gold) + await interaction.delete_original_response() + await interaction.channel.send(embed=Embeds.store(f'{name}이/가 {user_name}에게 G{gold}를 전달하였습니다.')) + except NotEnoughGold as e: + print(e) + print('>> Error: not enough gold') + print(traceback.format_exc()) + await interaction.edit_original_response(embed=Embeds.warning('소지한 골드가 부족합니다.'), view=None) + except Exception as e: + self.bot.logger.error(e) + + @app_commands.command(name="아이템판매", description="갖고있는 아이템을 판매합니다.") + async def item_sold(self, interaction:discord.Interaction): + """ + 소지품을 상점에 판매합니다. + """ + await self.on_ready(interaction) + try: + user_id = interaction.user.id + code = self.user[user_id].charas + if len(self.invs[code].items) == 0: + return await interaction.response.send_message(embed=Embeds.general('판매 가능한 아이템이 없습니다.'), ephemeral=True) + item_view = Views.SoldItemView(self.invs[code].items, self.items) + await interaction.response.send_message(embed=Embeds.store("판매할 아이템을 선택해 주세요"), view=item_view, ephemeral=True) + await item_view.wait() + if item_view.cancel: + return await interaction.delete_original_response() + sold_items = item_view.select.values + result = 0 + for item in sold_items: + price = self.items[item].price + self.connector.sold_item(code, item, price) + result = result+price + await interaction.edit_original_response(embed=Embeds.sold_item(result), view=None) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(name="아이템부여", hidden=True) + async def spawn_item(self, ctx:commands.Context, + user:discord.Member=commands.parameter(displayed_name="대상 유저",default=None,description="아이템을 부여할 유저입니다.",displayed_default="@사용자")): + """ + 해당 유저에게 아이템을 부여합니다. + """ + try: + if not user: + return await ctx.send(embed=Embeds.warning('유저가 입력되지 않았습니다.','@로 유저를 입력해 주시기 바랍니다.')) + else: + try: + user.id + except: + return await ctx.send(embed=Embeds.warning('유저는 @태그로 입력해야 합니다.','@로 유저를 입력해 주시기 바랍니다.')) + select_view = Views.SpawnItem(self.items) + await ctx.send(embed=Embeds.setting("부여할 아이템을 선택해 주세요."), view=select_view) + await select_view.wait() + if select_view.cancel: return + items = [] + code = self.user[ctx.author.id].charas + for itemcode in select_view.select.values: + itemdata = self.items[itemcode] + self.connector.spawn_item(code, itemcode, itemdata) + items.append(itemdata.name) + await ctx.send(embed=Embeds.setting(f"{user.display_name}에게 아이템을 부여하였습니다.",f"부여한 아이템: {', '.join(items)}")) + except Exception as e: + print(e) + print(traceback.format_exc()) + raise e + +async def setup(bot:commands.Bot): + await bot.add_cog(Inventory(bot)) diff --git a/JAS/cogs/manage.py b/JAS/cogs/manage.py new file mode 100644 index 0000000..a4d78c5 --- /dev/null +++ b/JAS/cogs/manage.py @@ -0,0 +1,610 @@ +import discord, os +from discord import Permissions, Color, PermissionOverwrite +from discord.abc import GuildChannel +from JAS.resources.Exceptions import * +from JAS.resources.Base import commands, app_commands, CommandBase, AppCommandBase, Embeds, Views +from JAS.resources.Addons import resource_path + +class Manage(CommandBase): + @commands.Cog.listener() + async def on_member_join(self, member:discord.Member): + guild_id = member.guild.id + await self.__get_connection__(guild_id) + role = list(filter(lambda x:x.name==self.role.visitor, member.guild.roles))[0] + await member.add_roles(role) + + @commands.Cog.listener() + async def on_reaction_add(self, reaction:discord.Reaction, user:discord.Member): + if not user.bot and reaction.message.type == discord.MessageType.chat_input_command: + poll_msg = discord.utils.get(self.bot.cached_messages, id=reaction.message.id) + for reac in poll_msg.reactions: + async for reac_user in reac.users(): + if user == reac_user: + if str(reac.emoji) != str(reaction.emoji): + await poll_msg.remove_reaction(reac.emoji, user) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def sync(self, ctx:commands.Context): + try: + print('start sync') + waitmsg = await ctx.send(embed=Embeds.setting('/명령어 동기화를 시작합니다','잠시만 기다려 주세요')) + self.bot.tree.copy_global_to(guild=discord.Object(id=ctx.guild.id)) + comm_list = await self.bot.tree.sync(guild=ctx.guild) + print(comm_list) + print(f'sync complete on {ctx.guild.name}') + await waitmsg.edit(embed=Embeds.setting('/명령어가 동기화 되었습니다','지금부터 /명령어를 사용할 수 있습니다.')) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def total_reset(self, ctx:commands.Context): + msg:discord.Message = await ctx.send(embed=Embeds.setting('/명령어 초기화를 시작합니다','잠시만 기다려 주세요')) + self.bot.tree.clear_commands(guild=None) + await self.bot.tree.sync(guild=None) + for guild in self.bot.guilds: + self.bot.tree.clear_commands(guild=guild) + await self.bot.tree.sync(guild=guild) + await self.bot.reload_extensions(self) + print(list(map(lambda x: x.name, self.bot.commands))) + print(list(map(lambda x: x.name, await self.bot.tree.fetch_commands(guild=None)))) + await msg.edit(embed=Embeds.setting('/명령어가 초기화 되었습니다')) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def showDB(self, message:commands.Context, + category:str=None): + categories = [ + "setting", + "user", + "fishing", + "gather", + "items", + "merge", + "inventory", + "store", + "chara", + "npc", + ] + if category and category not in categories: + return await message.send(embed=Embeds.setting(f'사용 가능한 목록은 다음과 같습니다.{", ".join(categories)}')) + DB_data = self.connector.data + print(DB_data) + if category: + index = categories.index(category) + contents = [DB_data.showDB()[index]] + else: + contents = DB_data.showDB() + + for content in contents: + if len(content) > 1990: + rowmsg = '' + for row in content.split('\n'): + if len(rowmsg) + len(row) > 1980: + await message.send(f'```{rowmsg}```') + rowmsg = '' + rowmsg = rowmsg + row + '\n' + if rowmsg: + await message.send(f'```{rowmsg}```') + else: + await message.send(f'```{content}```') + + @commands.command(name="업데이트", hidden=True) + @commands.has_any_role('관리자') + async def update_data(self, ctx:commands.Context): + try: + guild_id = ctx.guild.id + + self.bot.var_manage[guild_id].data = self.bot.var_manage[guild_id].__get_data__() + await ctx.send(embed=Embeds.general('','업데이트가 완료되었습니다')) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(hidden=True) + @commands.has_any_role('관리자') + async def 서버정보(self, ctx:commands.Context): + """ + 현재 서버 정보를 표시합니다. + 표시되는 정보: 서버 ID + """ + await ctx.send(f'현재 서버 ID는 `{ctx.guild.id}`입니다.') + + async def get_user_list(self, data:list[discord.Member]): + result = [] + async for user in data: + if not user.bot: + name = user.display_name + id = user.id + result.append([id, name]) + return result + + #회원가입 + @app_commands.command(name="가입", description="커뮤에서 활동하기 위해 회원가입을 진행합니다. 가입 혀용 상태에서만 가능합니다.") + async def register_slash(self, interaction:discord.Interaction): + """ + 회원가입을 진행합니다. + 회원가입은 가입허용 상태에서만 진행 가능합니다. + """ + await self.on_ready(interaction, check_user=False, check_chara=False) + try: + if self.setting.accept_user: + username = interaction.user.display_name + user_id = interaction.user.id + + if self.connector.check_user(user_id): + await interaction.response.send_message(embed=Embeds.setting('이미 등록된 사용자입니다.'), ephemeral=True) + else: + reg_role = list(filter(lambda x:x.name==self.role.registered, interaction.guild.roles))[0] + visit_role = list(filter(lambda x:x.name==self.role.visitor, interaction.guild.roles))[0] + await interaction.user.remove_roles(visit_role) + await interaction.user.add_roles(reg_role) + id = list(filter(lambda x: x.name == self.channel.anon, interaction.guild.text_channels))[0].id + private = await interaction.guild.get_channel(id).create_thread( + name=f'{interaction.user.display_name} 전용 익명방', + type=discord.ChannelType.private_thread, + message=None, + invitable=False) + await private.send(embed=Embeds.guide_user(username)) + private.id + self.connector.set_user(user_id, username, private.id) + await private.add_user(interaction.user) + await interaction.response.send_message(embed=Embeds.setting(f'회원가입이 완료되었습니다. 환영합니다 {username}님!')) + else: + await interaction.delete_original_response() + await interaction.channel.send(embed=Embeds.warning('현재는 회원가입을 할 수 없습니다','허용이 되기 전까지 기다려 주시기 바랍니다.')) + except ConnectionError as e: + self.bot.logger.warn(e,'Error: register failed') + await interaction.response.send_message(embed=Embeds.warning('등록 중 오류가 발생하였습니다. 잠시 후 다시 시도해 주시기 바랍니다.')) + except Exception as e: + self.bot.logger.error(e) + + # 회원탈퇴 + @app_commands.command(name="탈퇴", description="탈퇴하여 회원정보를 삭제합니다. 해당 명령어는 복구할 수 없습니다.") + async def delete_user_slash(self, interaction:discord.Interaction): + """ + 탈퇴를 진행합니다. + 회원정보가 모두 삭제됩니다. + """ + await self.on_ready(interaction, check_chara=False) + try: + name = interaction.user.display_name + # thread = list(filter(lambda x: x.name == f"{name} 전용 익명방", interaction.guild.threads)) + thread = self.user[interaction.user.id].thread + for role in interaction.user.roles: + if role.name != '@everyone' and role.name != self.role.admin: + await interaction.user.remove_roles(role) + visitor = discord.utils.get(interaction.guild.roles, name=self.role.visitor) + await interaction.user.add_roles(visitor) + if thread: + await interaction.guild.get_channel_or_thread(thread).delete() + else: + print('thread not found') + self.connector.delete_user(interaction.user.id) + await interaction.response.send_message(embed=Embeds.setting(f"{name}의 탈퇴절차가 완료되었습니다."), ephemeral=True) + except Exception as e: + self.bot.logger.error(e) + + # 회원목록 + @commands.command() + async def 회원목록(self, ctx:commands.Context): + """ + 현재 서버 내의 유저 목록을 보여줍니다. + """ + user_list = self.connector.get_user_list() + result = '' + for user in user_list: + result = result+'\n'+user[1] + print(result) + await ctx.send(embed=Embeds.general('현재 등록된 사용자 입니다.',result)) + + @app_commands.command(name="공지", description="임베드된 공지를 작성합니다.") + @app_commands.rename(title="제목",body="내용", pin="고정여부") + @app_commands.choices(pin=[ + app_commands.Choice(name='등록',value='Y'), + app_commands.Choice(name='미등록',value='N'), + ]) + @app_commands.checks.has_role('관리자') + async def notice(self, inter:discord.Interaction, title:str, body:str, pin:str='Y'): + """ + 임베드된 공지를 작성합니다. + """ + msg = await inter.channel.send(embed=Embeds.general(title, body)) + if pin == 'Y': await msg.pin() + await inter.response.send_message('공지를 게시하였습니다.',delete_after=3,ephemeral=True) + + @app_commands.command(name="문의", description="익명 문의를 남깁니다. 해당 문의에 대한 답변은 QnA에 등재됩니다.") + @app_commands.rename(args="문의내용", attachment="첨부파일") + @app_commands.describe(args="문의하고자 하는 내용", attachment="문의 내용과 관련된 첨부파일") + async def submit_ticket(self, interaction: discord.Interaction, args:app_commands.Range[str,1,2000], attachment:discord.Attachment=None): + """ + 익명문의를 진행합니다. + 관리자에게 문의하고픈 내용을 작성하여 전송한다. + """ + await self.on_ready(interaction, check_chara=False, check_user=False) + try: + comment = args + if attachment: + file = await attachment.to_file() + else: + file = None + channel = discord.utils.get(interaction.guild.text_channels, name=self.channel.manage) + await interaction.response.send_message(embed=Embeds.setting("접수가 완료되었습니다."),ephemeral=True, delete_after=5) + response_view = Views.Ticket(comment) + await channel.send(embed=Embeds.setting('익명문의가 접수되었습니다',comment), file=file, view=response_view) + await response_view.wait() + if response_view.cancel: return + answer = response_view.modal.content + qna_channel = discord.utils.get(interaction.guild.text_channels, name=self.channel.qna) + await qna_channel.send(embed=Embeds.anon_qna(comment, answer)) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(name="봇이름", hidden=True) + @commands.has_any_role('관리자') + @commands.has_permissions(change_nickname=True) + async def change_bot_nick(self, ctx:commands.Context, + nick=commands.parameter(displayed_name="닉네임", description="표시할 봇의 이름")): + """ + 봇의 이름을 변경합니다. + 실제 이름이 아닌 표시되는 이름이 변경됩니다. + """ + if not nick: + return await ctx.send(embed=Embeds.general('닉네임이 입력되지 않았습니다','봇이 사용할 닉네임을 입력해 주시기 바랍니다.'), delete_after=10) + else: + await ctx.guild.me.edit(nick=nick) + await ctx.send(embed=Embeds.setting('봇의 이름이 변경되었습니다', f'이제부터 저를 {nick}이라고 불러주세요.')) + + async def set_channel_permission(self, channel:discord.TextChannel, overite:dict[discord.Role, discord.PermissionOverwrite]): + for role in list(overite.keys()): + await channel.set_permissions(role, overwrite=overite[role]) + + + @commands.command(name="관리", hidden=True) + @commands.has_any_role('관리자') + async def manage(self, ctx:commands.Context, *, + args:str=commands.parameter(displayed_name="메뉴명-",default=None, description="각 관리 카테고리. 바로 해당 카테고리로 이동 가능하다.")): + """ + !관리자 전용 + 서버 내 혹은 커뮤 봇의 각종 설정을 관리합니다. + + 사용법 + !관리 (메뉴명) + """ + try: + selectembeds={ + None : Embeds.setting('관리자 설정을 시작합니다','원하시는 대상을 선택해 주세요.'), + "유저" : Embeds.setting('유저 관리','각 유저에 대한 관리를 진행합니다.','[관리 ▶ 유저]'), + "유저 정보" : Embeds.setting('유저 정보 확인','확인하고자 하는 사용자를 선택해 주세요','[관리 ▶ 유저 ▶ 정보]'), + "인벤토리" : Embeds.setting('인벤토리 관리', '각 유저의 인벤토리를 확인하고 내용을 수정랍니다.','[관리 ▶ 인벤토리]'), + "인벤토리 골드" : Embeds.setting('유저 골드 추가', '골드를 지급할 유저와 금액을 선택해 주세요.','[관리 ▶ 인벤토리 ▶ 골드]'), + "인벤토리 크기" : Embeds.setting('유저 인벤토리 추가', '인벤토리 크기를 조정할 유저를 선택해 주세요','[관리 ▶ 인벤토리 ▶ 크기]'), + "스탯" : Embeds.setting('스탯 관리', '스탯과 관련된 관리를 진행합니다.','[관리 ▶ 스탯]'), + "스탯 변경" : Embeds.setting('스탯 변경', '스탯 명을 변경하시겠습니까?\n`입력방법:(스탯명),(스탯명),...`\n스탯을 사용하지 않는다면 공란으로 비워주시기 바랍니다.','[관리 ▶ 스탯 ▶ 변경]'), + "시스템" : Embeds.setting('시스템 관리', '봇의 각종 수치를 변경하고 확인합니다.','[관리 ▶ 시스템]'), + "시스템 확인" : Embeds.show_system("현재 설정을 표시합니다.", self.setting,'[관리 ▶ 시스템 ▶ 확인]'), + "시스템 회원가입" : Embeds.user_accept(self.connector,'[관리 ▶ 시스템 ▶ 회원가입]'), + "시스템 변경" : Embeds.setting('시스템 기본수치 변경', '변경할 설정 값을 선택해 주세요.','[관리 ▶ 시스템 ▶ 변경]'), + } + selectviews={ + None : Views.ManageView(), + "유저" : Views.UserManageView(), + "유저 정보": Views.UserInfo(self.connector, await self.get_user_list(ctx.guild.fetch_members(limit=None))), + "인벤토리" : Views.InventoryManageView(self.connector), + "인벤토리 골드" : Views.InventoryGoldChange(self.connector, await self.get_user_list(ctx.guild.fetch_members(limit=None))), + "인벤토리 크기" : Views.InventorySizeChange(self.connector, await self.get_user_list(ctx.guild.fetch_members(limit=None))), + "스탯" : Views.StatManageView(self.connector), + "스탯 변경" : Views.StatNameChange(self.connector), + "시스템" : Views.SystemManageView(), + "시스템 확인" : None, + "시스템 회원가입" : Views.RegView(self.connector), + "시스템 변경" : Views.ChangeSettingView(self.connector), + } + embeded=selectembeds[args] #or discord.Embed() + messageview=selectviews[args] #or view.ManageView() + + msg = await ctx.send(embed=embeded, view=messageview) + while True: + await msg.edit(embed=embeded, view=messageview) + if messageview: + await messageview.wait() + print(f'result: {messageview.result} | cancel: {messageview.cancel}') + if messageview.cancel: + await msg.delete() + break + if not messageview.result: + break + else: + break + key = messageview.result + embeded = selectembeds[key] + messageview = selectviews[key] + except KeyError as e: + return await ctx.send(embed=Embeds.warning('존재하지 않는 메뉴명입니다', f'사용 가능한 메뉴명은 다음과 같습니다.\n{", ".join(list(filter(lambda x: x ,selectembeds.keys())))}')) + except Exception as e: + self.bot.logger.error(e) + + # 커뮤 구축 + @commands.command(name="서버구축", hidden=True) + @commands.has_permissions(manage_channels=True, manage_guild=True, manage_messages=True, manage_roles=True) + @commands.bot_has_guild_permissions(manage_channels=True, manage_guild=True, manage_messages=True, manage_roles=True) + async def setup_guild(self, ctx:commands.Context, + arg=commands.parameter(displayed_name="재구축:", default=None, description="서버 재구축시 사용.")): + """ + 최초 서버 설정시 사용. + """ + print('start') + main_msg = await ctx.send(embed=Embeds.setting("서버구축을 시작합니다","대략 2~3분 정도의 시간이 소요되오니 잠시만 기다려 주세요.")) + guild = ctx.guild + cog_list = list(self.bot.cogs.keys()) + try: + channels = list(map(lambda x: x.name , guild.text_channels)) + categories = list(map(lambda x: x.name , guild.categories)) + forums = list(map(lambda x: x.name , guild.forums)) + roles = list(map(lambda x: x.name , guild.roles)) + print(f''' +{channels} +{categories} +{forums} +{roles} + ''') + + # 역할 설정 + everyone_permission = Permissions(view_channel=True, read_message_history=False) + + await guild.default_role.edit(permissions=everyone_permission) + + if "관리자" not in roles: + admin = await guild.create_role( + name="관리자", + color=Color.gold(), + permissions=Permissions.all(), + hoist=True, + mentionable=True) + ctx.author.add_roles(admin) + else: + admin = list(filter(lambda x: x.name=="관리자", guild.roles))[0] + await admin.edit(permissions=Permissions.all()) + + await ctx.author.add_roles(admin) + + if "가입자" not in roles: + user = await guild.create_role( + name="가입자",color=Color.green(), + permissions=Permissions(448891571264), + hoist=True) + else: + user = list(filter(lambda x: x.name=="가입자", guild.roles))[0] + await user.edit(permissions=Permissions(448891571264)) + + if "방문자" not in roles: + visitor = await guild.create_role( + name="방문자", + color=Color.greyple(), + permissions=Permissions.none()) + else: + visitor = list(filter(lambda x: x.name=="방문자", guild.roles))[0] + await visitor.edit(permissions=Permissions.none()) + + bot_role = discord.utils.get(guild.roles, name='JAS') + + # 권한 설정 + admin_overwrites = { + bot_role:PermissionOverwrite(administrator=True), + visitor:PermissionOverwrite(view_channel=False), + user:PermissionOverwrite(view_channel=False), + guild.default_role:PermissionOverwrite(view_channel=False), + } + announce_overwrite={ + visitor:PermissionOverwrite(view_channel=True, + read_message_history=True), + user:PermissionOverwrite(view_channel=True, + read_message_history=True, + send_messages=False), + } + general_overwrites={ + visitor:PermissionOverwrite(view_channel=False), + user:PermissionOverwrite(view_channel=True, + read_messages=True, + send_messages=True, + use_application_commands=True, + read_message_history=True, + embed_links=True, + attach_files=True, + add_reactions=True, + send_messages_in_threads=True), + } + join_overites={ + user:PermissionOverwrite(view_channel=False), + visitor:PermissionOverwrite(view_channel=True, + send_messages=True, + use_application_commands=True), + } + anon_overwrites={ + visitor:PermissionOverwrite(view_channel=False), + user:PermissionOverwrite(view_channel=True, + send_messages=False, + read_message_history=True, + use_application_commands=True, + attach_files=True, + send_messages_in_threads=True), + } + community_overwrites={ + visitor:PermissionOverwrite(view_channel=False), + user:PermissionOverwrite(view_channel=True, + send_messages=False, + read_message_history=True, + use_application_commands=True, + create_public_threads=True, + embed_links=True, + attach_files=True, + add_reactions=True, + send_messages_in_threads=True), + } + + # 카테고리 확인 + if "일반 채팅" not in categories: + chat_cat = await guild.create_category(name="일반 채팅", position=1, overwrites=general_overwrites) + else: + chat_cat = discord.utils.get(guild.categories, name="일반 채팅") + await self.set_channel_permission(chat_cat, general_overwrites) + if "공지사항" not in categories: + notice_cat = await guild.create_category(name="공지사항", position=0, overwrites=announce_overwrite) + else: + notice_cat = discord.utils.get(guild.categories, name="공지사항") + await self.set_channel_permission(notice_cat, announce_overwrite) + if self.channel.community not in categories and 'Community' in cog_list: + comm_cat = await guild.create_category(name=self.channel.community, position=2, overwrites=community_overwrites) + else: + comm_cat = discord.utils.get(guild.categories, name=self.channel.community) + await self.set_channel_permission(comm_cat, community_overwrites) + if 'Fishing' in cog_list: + if "낚시터" not in categories: + fishing_cat = await guild.create_category(name="낚시터", position=3) + else: + fishing_cat = discord.utils.get(guild.categories, name="낚시터") + await fishing_cat.edit(overwrites=general_overwrites) + if 'Gather' in cog_list: + if "채집터" not in categories: + gather_cat = await guild.create_category(name="채집터", position=4) + else: + gather_cat = discord.utils.get(guild.categories, name="채집터") + await gather_cat.edit(overwrites=general_overwrites) + if "관리자" not in categories: + admin_cat = await guild.create_category(name="관리자", position=5, overwrites=admin_overwrites) + else: + admin_cat = discord.utils.get(guild.categories, name="관리자") + await admin_cat.edit(overwrites=admin_overwrites) + + # 관리 생성 + if self.channel.manage not in channels: + administor = await guild.create_text_channel(name=self.channel.manage, category=admin_cat) + else: + administor = discord.utils.get(guild.text_channels, name=self.channel.manage) + # id = list(filter(lambda x: x.name == self.channel.manage, guild.text_channels))[0].id + # administor = guild.get_channel(id) + + # 공지 생성 + if "공지" not in channels: + public = await guild.create_text_channel(position=0, name="공지", category=notice_cat) + else: + public = discord.utils.get(guild.text_channels, name="공지") + await public.move(category=notice_cat, beginning=True) + # 규칙 생성 + if "규칙" not in channels: + rules = await guild.create_text_channel(position=1, name="규칙", category=notice_cat) + else: + rules = discord.utils.get(guild.text_channels, name="규칙") + await rules.move(category=notice_cat, beginning=True, offset=1) + # 세계관 생성 + if "세계관" not in channels: + world = await guild.create_text_channel(position=2, name="세계관", category=notice_cat) + else: + world = discord.utils.get(guild.text_channels, name="세계관") + await world.move(category=notice_cat, beginning=True, offset=2) + # 가입 생성 + if "가입" not in channels: + join = await guild.create_text_channel(position=3, name=self.channel.join, category=notice_cat, reason="/가입을 진행해 주세요", overwrites=join_overites) + else: + join = discord.utils.get(guild.text_channels, name=self.channel.join) + await join.edit(overwrites=join_overites) + await join.move(category=notice_cat, beginning=True, offset=3) + # QnA 생성 + if "질의응답" not in channels: + qna = await guild.create_text_channel(position=4,name="질의응답", category=notice_cat) + else: + qna = discord.utils.get(guild.text_channels, name="질의응답") + await qna.move(category=notice_cat, beginning=True, offset=4) + # 익명 + if "오너게시판" not in channels: + author = await guild.create_text_channel(name="오너게시판", category=chat_cat, + reason="개인 스레드에서 보낸 익명 메세지가 모이는 곳입니다.\n개인 스레드가 보이지 않는다면 우측 상단의 스레드를 통해 확인 바랍니다.") + msg = await author.send("""> ## 오너게시판 +> +> 자유롭게 대화할 수 있는 게시판 입니다. 텍관모집, 이벤트 조율 등등의 내용을 진핼할 때 이용 가능합니다. +""") + await msg.pin() + else: + author = discord.utils.get(guild.text_channels, name="오너게시판") + await author.move(category=chat_cat, beginning=True) + # 익명 + if "익명게시판" not in channels: + anon = await guild.create_text_channel(name="익명게시판", category=chat_cat, + reason="개인 스레드에서 보낸 익명 메세지가 모이는 곳입니다.\n개인 스레드가 보이지 않는다면 우측 상단의 스레드를 통해 확인 바랍니다.", + overwrites=anon_overwrites) + else: + anon = discord.utils.get(guild.text_channels, name=self.channel.anon) + await anon.edit(overwrites=anon_overwrites) + await anon.move(category=chat_cat, beginning=True, offset=1) + # 한역 + if "캐입역극" not in channels: + comm = await guild.create_text_channel(name="캐입역극", category=chat_cat, + reason="캐입역극을 진행 가능합니다.(=탐라대화)\n개인 간의 한역을 위한 스레드 생성 시 원하는 대화에 답글을 작성한 뒤 해당 답글을 바탕으로 스레드를 생성하시길 바랍니다.",) + else: + comm = discord.utils.get(guild.text_channels, name="캐입역극") + await comm.move(category=comm_cat, beginning=True) + # 상점 + if 'Store' in cog_list: + if "상점" not in channels: + store = await guild.create_text_channel(name="상점", category=chat_cat) + else: + store = discord.utils.get(guild.text_channels, name="상점") + await store.move(category=chat_cat, beginning=True, offset=2) + # 낚시터 + if 'Fishing' in cog_list: + if "기본-낚시터" not in channels: + fishing = await guild.create_text_channel(name="기본-낚시터", category=fishing_cat, reason="낚시 채널입니다. /낚시로 낚시를 시작해 보세요!") + msg = await fishing.send(content='''> ## 낚시 + > + > `/낚시` 명령어를 통해 낚시를 진행할 수 있습니다. + > `/물고기목록` 명령어를 통해 낚을 수 있는 물고기를 확인해 보세요! + ''') + await msg.pin() + else: + fishing = discord.utils.get(guild.text_channels, name="기본-낚시터") + await fishing.move(category=fishing_cat, beginning=True) + # 채집터 + if 'Gather' in cog_list: + if "기본-채집터" not in channels: + gather = await guild.create_text_channel(name="기본-채집터", category=gather_cat, reason="채집용 채널입니다! /채집을 진행해 보세요!") + msg = await gather.send(content='''> ## 채집 + > + > `/채집` 명령어를 통해 채집을 진행할 수 있습니다. + > `/채집목록` 명령어를 통해 수집 가능한 아이템을 확인해 보세요! + ''') + await msg.pin() + else: + gather = discord.utils.get(guild.text_channels, name="기본-채집터") + await gather.move(category=gather_cat, beginning=True) + + try: + # 조사게시판? + # 가입 가이드라인 출력 + await guild.edit( + community = True, + rules_channel=rules, + public_updates_channel=administor, + preferred_locale=discord.Locale.korean) + # 한역 생성 + if "토론" not in forums: + tags = ( + discord.ForumTag(name="마감"), + discord.ForumTag(name="이어가요"), + discord.ForumTag(name="중단해요") + ) + topic = """ + 토론을 진행할 수 있는 공간입니다. + """ + talk = await guild.create_forum(name="토론", topic=topic, category=chat_cat, available_tags=tags) + else: + id = list(filter(lambda x: x.name == "토론", guild.forums))[0].id + talk = guild.get_channel(id) + + await main_msg.edit(embed=Embeds.setting("서버구축이 완료되었습니다")) + except: + await main_msg.edit(embed=Embeds.setting("서버구축이 일부 완료되었습니다",'커뮤니티 설정이 완료되지 않았습니다.\n커뮤니티 기능을 사용하길 원하신다면 커뮤니티를 활성화 한 후 `!서버구축`을 실행해 주시기 바랍니다.')) + except Exception as e: + self.bot.logger.error(e) + +async def setup(bot:commands.Bot): + await bot.add_cog(Manage(bot)) \ No newline at end of file diff --git a/JAS/cogs/stat.py b/JAS/cogs/stat.py new file mode 100644 index 0000000..c8bdc00 --- /dev/null +++ b/JAS/cogs/stat.py @@ -0,0 +1,207 @@ +import discord +from JAS.resources.Exceptions import * +from JAS.resources.Base import commands, app_commands, CommandBase, AppCommandBase, Embeds, Views +from asyncio import sleep + +class Stat(CommandBase): + @app_commands.command(name="스탯", description="캐릭터의 스탯분배를 진행합니다.") + async def stat(self, interaction:discord.Interaction): + """ + 캐릭터의 스탯분배를 진행합니다. + """ + await self.on_ready(interaction) + try: + await interaction.response.defer(ephemeral=True) + code = self.user[interaction.user.id].charas + chara = self.charas[code] + stat_names = chara.stat.stat_names or self.setting.stat_names + point = chara.stat.point + base_view = Views.SpendStat(interaction.user) + stat1_view = Views.AllStat(1,chara.stat,interaction.user) + stat2_view = Views.AllStat(2,chara.stat,interaction.user) + base = await interaction.channel.send(embed=Embeds.setting("스탯포인트를 소모하여 스탯을 조정합니다", f"남은 포인트: {point}"), view = base_view) + stat1 = await interaction.channel.send(view = stat1_view) + stat2 = await interaction.channel.send(view = stat2_view) + process=True + while process: + if base_view.cancel or base_view.finish: + print("delete stat view") + await stat1.delete() + await stat2.delete() + process = False + else: + changed = False + if stat1_view.stat1_sub.clicked: + print("stat1 sub") + stat1_view.stat1_sub.clicked=False + if stat1_view.stat1_value > stat1_view.stat1_origin: + stat1_view.stat1_value -= 1 + stat1_view.point += 1 + stat1_view.stat1_current.label=f"{stat_names[0]}: {stat1_view.stat1_value}" + point = stat1_view.point + changed = True + if stat1_view.stat1_add.clicked: + print("stat1 add") + stat1_view.stat1_add.clicked=False + if stat1_view.point > 0: + stat1_view.stat1_value += 1 + stat1_view.point -= 1 + stat1_view.stat1_current.label=f"{stat_names[0]}: {stat1_view.stat1_value}" + point = stat1_view.point + changed = True + elif stat1_view.stat2_sub.clicked: + print("stat2 sub") + stat1_view.stat2_sub.clicked=False + if stat1_view.stat2_value > stat1_view.stat2_origin: + stat1_view.stat2_value -= 1 + stat1_view.point += 1 + stat1_view.stat2_current.label=f"{stat_names[1]}: {stat1_view.stat2_value}" + point = stat1_view.point + changed = True + elif stat1_view.stat2_add.clicked: + print("stat2 add") + stat1_view.stat2_add.clicked=False + if stat1_view.point > 0: + stat1_view.stat2_value += 1 + stat1_view.point -= 1 + stat1_view.stat2_current.label=f"{stat_names[1]}: {stat1_view.stat2_value}" + point = stat1_view.point + changed = True + elif stat1_view.stat3_sub.clicked: + print("stat3 sub") + stat1_view.stat3_sub.clicked=False + if stat1_view.stat3_value > stat1_view.stat3_origin: + stat1_view.stat3_value -= 1 + stat1_view.point += 1 + stat1_view.stat3_current.label=f"{stat_names[2]}: {stat1_view.stat3_value}" + point = stat1_view.point + changed = True + elif stat1_view.stat3_add.clicked: + print("stat3 add") + stat1_view.stat3_add.clicked=False + if stat1_view.point > 0: + stat1_view.stat3_value += 1 + stat1_view.point -= 1 + stat1_view.stat3_current.label=f"{stat_names[2]}: {stat1_view.stat3_value}" + point = stat1_view.point + changed = True + elif stat2_view.stat4_sub.clicked: + print("stat4 sub") + stat2_view.stat4_sub.clicked=False + if stat2_view.stat4_value > stat2_view.stat4_origin: + stat2_view.stat4_value -= 1 + stat2_view.point += 1 + stat2_view.stat4_current.label=f"{stat_names[3]}: {stat2_view.stat4_value}" + point = stat2_view.point + changed = True + elif stat2_view.stat4_add.clicked: + print("stat4 add") + stat2_view.stat4_add.clicked=False + if stat2_view.point > 0: + stat2_view.stat4_value += 1 + stat2_view.point -= 1 + stat2_view.stat4_current.label=f"{stat_names[3]}: {stat2_view.stat4_value}" + point = stat2_view.point + changed = True + elif stat2_view.stat5_sub.clicked: + print("stat5 sub") + stat2_view.stat5_sub.clicked=False + if stat2_view.stat5_value > stat2_view.stat5_origin: + stat2_view.stat5_value -= 1 + stat2_view.point += 1 + stat2_view.stat5_current.label=f"{stat_names[4]}: {stat2_view.stat5_value}" + point = stat2_view.point + changed = True + elif stat2_view.stat5_add.clicked: + print("stat5 add") + stat2_view.stat5_add.clicked=False + if stat2_view.point > 0: + stat2_view.stat5_value += 1 + stat2_view.point -= 1 + stat2_view.stat5_current.label=f"{stat_names[4]}: {stat2_view.stat5_value}" + point = stat2_view.point + changed = True + elif stat2_view.stat6_sub.clicked: + print("stat6 sub") + stat2_view.stat6_sub.clicked=False + if stat2_view.stat6_value > stat2_view.stat6_origin: + stat2_view.stat6_value -= 1 + stat2_view.point += 1 + stat2_view.stat6_current.label=f"{stat_names[5]}: {stat2_view.stat6_value}" + point = stat2_view.point + changed = True + elif stat2_view.stat6_add.clicked: + print("stat6 add") + stat2_view.stat6_add.clicked=False + if stat2_view.point > 0: + stat2_view.stat6_value += 1 + stat2_view.point -= 1 + stat2_view.stat6_current.label=f"{stat_names[5]}: {stat2_view.stat6_value}" + point = stat2_view.point + changed = True + if changed: + print("value changed") + stat1_view.point = point + stat2_view.point = point + # disable subtract button + stat1_view.stat1_sub.disabled = True if stat1_view.stat1_origin == stat1_view.stat1_value else False + stat1_view.stat2_sub.disabled = True if stat1_view.stat2_origin == stat1_view.stat2_value else False + stat1_view.stat3_sub.disabled = True if stat1_view.stat3_origin == stat1_view.stat3_value else False + stat2_view.stat4_sub.disabled = True if stat2_view.stat4_origin == stat2_view.stat4_value else False + stat2_view.stat5_sub.disabled = True if stat2_view.stat5_origin == stat2_view.stat5_value else False + stat2_view.stat6_sub.disabled = True if stat2_view.stat6_origin == stat2_view.stat6_value else False + # disable add button + stat1_view.stat1_add.disabled=True if point ==0 else False + stat1_view.stat2_add.disabled=True if point ==0 else False + stat1_view.stat3_add.disabled=True if point ==0 else False + stat2_view.stat4_add.disabled=True if point ==0 else False + stat2_view.stat5_add.disabled=True if point ==0 else False + stat2_view.stat6_add.disabled=True if point ==0 else False + await base.edit(embed=Embeds.setting("스탯", f"남은 포인트: {point}")) + await stat1.edit(view=stat1_view) + await stat2.edit(view=stat2_view) + await sleep(0.1) + await interaction.delete_original_response() + if base_view.finish: + point = stat1_view.point + stat1 = stat1_view.stat1_value + stat2 = stat1_view.stat2_value + stat3 = stat1_view.stat3_value + stat4 = stat2_view.stat4_value + stat5 = stat2_view.stat5_value + stat6 = stat2_view.stat6_value + # stat = f'{stat1},{stat2},{stat3},{stat4},{stat5},{stat6}' + self.connector.update_stat(code, stat_names, point, [stat1, stat2, stat3, stat4, stat5, stat6]) + await base.edit(embed=Embeds.show_stat_result(chara), view=None) + except Exception as e: + self.bot.logger.error(e) + + @commands.command(name="스탯초기화", hidden=True) + @commands.has_any_role('관리자') + async def clear_stat(self, ctx:commands.Context, user:discord.Member=None): + """ + 스탯을 초기화 합니다. + 전체 스탯을 1로 만들며 이미 투자한 스탯은 포인트로 전환됩니다. + """ + try: + if not user: + return await ctx.send(embed=Embeds.general('초기화를 진행할 사용자를 입력해 주시기 바랍니다')) + code = self.user[user.id].charas + chara = self.charas[code] + stat = chara.stat + all_stat = stat.stat1+stat.stat2+stat.stat3+stat.stat4+stat.stat5+stat.stat6 + point = stat.point + all_stat-6 + stat.stat1 = 1 + stat.stat2 = 1 + stat.stat3 = 1 + stat.stat4 = 1 + stat.stat5 = 1 + stat.stat6 = 1 + + self.connector.update_stat(code, stat.stat_names, point, 1,1,1,1,1,1) + await ctx.send(embed=Embeds.setting("스탯을 초기화 하였습니다.", f"총 스탯포인트: {point}")) + except Exception as e: + self.bot.logger.error(e) + +async def setup(bot:commands.Bot): + await bot.add_cog(Stat(bot)) \ No newline at end of file diff --git a/JAS/cogs/store.py b/JAS/cogs/store.py new file mode 100644 index 0000000..27f759a --- /dev/null +++ b/JAS/cogs/store.py @@ -0,0 +1,75 @@ +import discord +from JAS.resources.Exceptions import * +from JAS.resources.Base import commands, app_commands, CommandBase, AppCommandBase, Embeds, Views +from random import random, randrange, choice +from asyncio import sleep + +class Store(CommandBase): + @app_commands.command(name="랜덤박스", description="일정 금액을 소비하여 랜덤뽑기를 진행합니다.") + async def random_box(self, interaction:discord.Interaction): + """ + 일정 금액을 소비하여 뽑기를 진행합니다. + """ + await self.on_ready(interaction) + result_text = [ + "잭팟!\n넣은 금액의 네배를 얻습니다.", + "2등상!\n넣은 금액의 두배가 나왔습니다.", + "3등상\n본전은 찾았습니다.", + "참가상\n사용한 금액의 반을 돌려 받습니다.", + "꽝!\n랜덤박스는 잠잠합니다...", + ] + try: + price = self.setting.random_box + code = self.user[interaction.user.id].charas + gold = self.invs[code].gold + if price > gold: + return await interaction.response.send_message(embed=Embeds.general('금액이 부족합니다',f'현재 소지금은 {gold}입니다.'), ephemeral=True) + else: + self.connector.add_gold(code, -price) + dice = randrange(0,100) + if dice < 1: + result_title = "만세!" + result = result_text[0] + result_color = discord.Color.gold() + self.connector.add_gold(code, price*4) + elif dice < 5: + result_title = "야호!" + result = result_text[1] + result_color = discord.Color.green() + self.connector.add_gold(code, price*2) + elif dice < 20: + result_title = "괜찮아!" + result = result_text[2] + result_color = discord.Color.green() + self.connector.add_gold(code, price) + elif dice < 60: + result_title = "이런..." + result = result_text[3] + result_color = discord.Color.light_grey() + self.connector.add_gold(code, int(price/2)) + else: + result_title = "..." + result = result_text[4] + result_color = discord.Color(0) + + title_list = [ + "힘차게 돌아갑니다!", + "덜컹덜컹 돌아갑니다", + "힘없이 돌아갑니다...", + "수상한 소음을 내며 돌아갑니다...?", + ] + title = f"랜덤박스가 {choice(title_list)}" + embed_msg = Embeds.random_box(title, 0) + await interaction.response.send_message(embed=embed_msg) + for i in range(1,8): + embed_msg = Embeds.random_box(title, i) + await interaction.edit_original_response(embed=embed_msg) + await sleep(0.5) + await interaction.edit_original_response(embed=Embeds.general(result_title, result, result_color)) + except Exception as e: + self.bot.logger.error(e) + +async def setup(bot:commands.Bot): + await bot.add_cog(Store(bot)) + + diff --git a/JAS/mailing.py b/JAS/mailing.py new file mode 100644 index 0000000..3457d47 --- /dev/null +++ b/JAS/mailing.py @@ -0,0 +1,63 @@ +import smtplib, ssl, os +from email.header import Header +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from email.mime.application import MIMEApplication + +if __name__ != '__main__': + from COMUBOT.resources.Addons import MAIN_PATH +else: + from resources.Addons import MAIN_PATH + +class Mailer(): + def __init__(self, host, port,mail_ID, mail_PW) -> None: + smtp = smtplib.SMTP(host=host, port=port) + smtp.starttls() + self.sender = smtp + self.ID = mail_ID + self.PW = mail_PW + + def login(self): + self.sender.login(self.ID, self.PW) + + def exit(self): + self.sender.quit() + + def send_email(self, body_type, mail_body, mail_subject="[NOTICE] 디코봇으로 부터 발송된 메일입니다."): + self.login() + if body_type == 0: + body = MIMEText(mail_body, 'plain', 'utf-8') + elif body_type == 1: + body = MIMEText(mail_body, 'html', 'utf-8') + else: + raise + + message = MIMEMultipart() + message["subject"] = Header(s=mail_subject, charset='utf-8') + message["FROM"] = self.ID + message["TO"] = self.ID + message.attach(body) + self.get_log(message) + + self.sender.sendmail(self.ID, self.ID, message.as_string()) + self.exit() + + def get_log(self, message:MIMEMultipart): + files = [os.path.join(MAIN_PATH, 'discord.log'), os.path.join(MAIN_PATH, 'discord_debug.log')] + + for file in files: + if os.path.exists(file): + attachment = MIMEApplication(open(file, 'rb').read()) + attachment.add_header('Content-Disposition', 'attachment', filename=file.split('\\')[-1]) + message.attach(attachment) + + +if __name__ == '__main__': + from resources.Addons import read_json + json_data = read_json() + SMTP_SERVER = json_data['SMTP_SERVER'] + SMTP_PORT = json_data['SMTP_PORT'] + MAIL_ID = json_data['MAIL_ID'] + MAIL_PW = json_data['MAIL_PW'] + mail = Mailer(SMTP_SERVER, SMTP_PORT, MAIL_ID, MAIL_PW) + mail.send_email(0,'test') diff --git a/JAS/resources/Addons.py b/JAS/resources/Addons.py new file mode 100644 index 0000000..1e741ab --- /dev/null +++ b/JAS/resources/Addons.py @@ -0,0 +1,137 @@ +import math, discord, re, json +import os, sys, shutil +from PIL import Image +from io import BytesIO +from random import randrange +from traceback import format_exc + +PRJ_NAME = 'JAS' +if sys.platform.startswith('win'): + MAIN_PATH = os.path.join(os.getenv('APPDATA'), PRJ_NAME) +else: + MAIN_PATH = os.path.join(os.getcwd(), '..', 'DATA') +CONFIG_PATH = os.path.join(MAIN_PATH, 'config.json') +DB_FOLDER_PATH = os.path.join(MAIN_PATH, 'DB') +IMG_FOLDER_PATH = os.path.join(MAIN_PATH, 'IMG') + +def resource_path(*path): + try: + base_path = sys._MEIPASS + except Exception: + base_path = os.path.abspath(".") + + return os.path.join(base_path, *path) + +def prep_env(): + for path in [MAIN_PATH, DB_FOLDER_PATH, IMG_FOLDER_PATH]: + if not os.path.exists(path): + os.makedirs(path, exist_ok=True) + if path == MAIN_PATH: + data = { + "TOKEN":"", + "PROFILE":"가동", + "TEST_SERVER_ID":"", + "AUTH_JSON_PATH":"", + "SMTP_SERVER":"smtp.gmail.com", + "SMTP_PORT":587, + "MAIL_ID":"", + "MAIL_PW":"", + } + write_json(data) + if path == IMG_FOLDER_PATH: + for filename in ['img/sample.png']: + shutil.copy(resource_path(filename), os.path.join(IMG_FOLDER_PATH,filename.split('/')[-1])) + +def read_json(): + with open(CONFIG_PATH, 'r', encoding='UTF8') as f: + json_data:dict[str, str] = json.load(f) + f.close() + return json_data + +def write_json(data): + with open(CONFIG_PATH, 'w', encoding='UTF8') as f: + json_data = json.dumps(data, indent=2) + f.writelines(json_data) + f.close() + +def currency(type, gold): + content = f"G {gold}" + return content + +def filename(str:str) -> str: + filterlist = ['?','/','\\',':','*','"','<','>'] + replaceist = ['|']*len(filterlist) + result = str.translate(str.maketrans(''.join(filterlist), ''.join(replaceist))).replace('|', '') + return result + +async def resize_img(attachment:discord.Attachment, path): + size = (300,300) + image = await attachment.read(use_cached=True) + + with Image.open(BytesIO(image)) as img: + img.thumbnail(size) + img.save(path) + +def key_gen(key_len=8): + """ + 8자의 랜덤한 키를 생성합니다. + + Args: + length: 키의 길이 (기본값: 8) + + Returns: + 8자의 랜덤한 키 + """ + chars = "abcdefghijklmnopqrstuvwxyz0123456789" + + key = '' + for _ in range(key_len): + key = key + chars[randrange(len(chars))] + return key + +def dice(count:int, num:int) -> list[int]: + """ + 주사위를 굴려 결괏값을 리스트로 반환받습니다. + + Args: + count: 주사위의 개수 + num: 주사위의 눈수 + + Returns: + 각각의 주사위 결과가 담긴 리스트 + """ + result = randrange(num)+1 + return [result] + dice(count-1, num) if count != 1 else [result] + +def desc_converter(str:str): + result = str + match_bold = re.compile('\[[^]]*]') + match_dice = re.compile('\[[0-9]+d[0-9]+\]') + # print(re.findall(match_dice, result)) + for text in re.findall(match_dice, result): + # print(text) + if text: + dice_count = int(text[1:-1].split('d')[0]) + dice_num = int(text[1:-1].split('d')[1]) + dice_result = sum(dice(dice_count, dice_num)) + dice_result = f'[{dice_result}]' + result = result.replace(text, dice_result, 1) + # print(result) + for text in re.findall(match_bold, result): + if text: + bold_result = f'[**`{text[1:-1]}`**]' + result = result.replace(text, bold_result) + # print(result) + return result + +def open_image(path) -> Image.Image: + img = Image.open(path) + return img + +def merge_image(base:Image.Image, input:Image.Image, x, y) -> Image.Image: + base.paste(input, (x, y), input) + return base + +def crop_icon(base:Image.Image, x, y, width) -> Image.Image: + icon = base.crop((x, y, x+width, y+width)) + return icon \ No newline at end of file diff --git a/JAS/resources/Base.py b/JAS/resources/Base.py new file mode 100644 index 0000000..2f6bca6 --- /dev/null +++ b/JAS/resources/Base.py @@ -0,0 +1,421 @@ +import discord, os +import logging, logging.handlers, multiprocessing +from traceback import format_exc +from discord import app_commands +from discord.ext import commands, tasks +from JAS.resources import Embeds, Views +from JAS.resources.Connector import Connector +import JAS.resources.Connector as Conn +from JAS.resources.Addons import MAIN_PATH, resource_path +from asyncio import sleep + +class Logger: + PROFILE = '' + + def __init__(self, logger:logging.Logger) -> None: + self.logger = logger + # self.bot.logger.info('=====bot.Logger======') + + def info(self, message): + self.logger.info(message) + if self.PROFILE != '테스트': + print(message) + + def debug(self, message): + self.logger.debug(message) + if self.PROFILE != '테스트': + print(message) + + def warn(self, exception, status='Unkown Error Occured'): + self.logger.warn(status) + self.logger.warn(str(exception)) + self.logger.warn(format_exc()) + if self.PROFILE != '테스트': + print('>> ',status) + print(exception) + print(format_exc()) + + def error(self, exception, status='Unkown Error Occured'): + self.logger.error(status) + self.logger.error(str(exception)) + self.logger.error(format_exc()) + if self.PROFILE != '테스트': + print('>> ',status) + print(exception) + print(format_exc()) + raise exception + +logger_tmp = logging.getLogger('discord') +logger_tmp.setLevel(logging.DEBUG) +logging.getLogger('discord.http').setLevel(logging.DEBUG) + +handler = logging.handlers.RotatingFileHandler( + filename=os.path.join(MAIN_PATH, 'discord.log'), + encoding='utf-8', + maxBytes=32 * 1024 * 1024, # 32 MiB + backupCount=5, # Rotate through 5 files +) +dt_fmt = '%Y-%m-%d %H:%M:%S' +formatter = logging.Formatter('[{asctime}] [{levelname:<8}] {name}: {message}', dt_fmt, style='{') +handler.setFormatter(formatter) +logger_tmp.addHandler(handler) + +logger = Logger(logger_tmp) + + +class JAS(commands.Bot): + rec_queue:multiprocessing.Queue=None + send_queue:multiprocessing.Queue=None + TOKEN:str = '' + AUTH_JSON_PATH:str = '' + PROFILE:str = '' + TEST_SERVER_ID:int = 0 + _version = 'demo' + is_quit = False + + def __init__(self, *, intents: discord.Intents, command_prefix:str, activity:discord.CustomActivity, logger): + self.var_manage:dict['server_id':int, Connector] = {} + self.var_registered:bool = False + self.var_proceed:bool = False + self.ver_cog_commands:dict['cog_name':str,list['command_name':str]] = {} + self.ver_admin_commands:list['command_name':str] = [] + super().__init__(intents=intents, command_prefix=command_prefix, activity=activity) + self.logger:Logger = logger + self.tree.error(coro=self.on_app_command_error) + + # 확장코드 등재 + async def load_extensions(self, bot:commands.Bot): + for filename in os.listdir(resource_path('JAS','cogs')): + if filename.startswith('setup') or filename.startswith("_"): + continue + if filename.endswith('.py'): + await bot.load_extension(f'JAS.cogs.{filename[:-3]}') + self.logger.info(f'load complete: {filename[:-3]}') + + # 확장코드 등재 + async def reload_extensions(self, bot:commands.Bot): + for filename in os.listdir(resource_path('JAS','cogs')): + if filename.startswith('setup') or filename.startswith("_"): + continue + if filename.endswith('.py'): + await bot.load_extension(f'JAS.cogs.{filename[:-3]}') + self.logger.info(f'reload complete: {filename[:-3]}') + + # 시작시 서버 ID 확보 및 DB 설정 + async def setup_hook(self): + self.logger.info('-'*30) + await self.load_extensions(self) + self.logger.info('base install complete') + self.logger.info('-'*30) + await sleep(1) + + for command in self.commands: + command_name = command.name + if command.checks: + self.ver_admin_commands.append(command_name) + if command.cog: + cog_name = command.cog_name + if cog_name not in list(self.ver_cog_commands.keys()): + self.ver_cog_commands[cog_name] = [] + self.ver_cog_commands[cog_name].append(command_name) + + self.logger.info('admin commands') + self.logger.info('-'*20) + self.logger.info(self.ver_admin_commands) + self.logger.info('-'*30) + await sleep(1) + self.logger.info('cog commands') + self.logger.info('-'*20) + for cog in self.ver_cog_commands: + self.logger.info(cog) + self.logger.info(self.ver_cog_commands[cog]) + self.logger.info('-'*30) + await sleep(1) + self.logger.info('app commands') + self.logger.info('-'*20) + self.logger.info(list(map(lambda x: x.name, await self.tree.fetch_commands(guild=None)))) + self.logger.info(f'load {len(self.commands)} commands') + self.logger.info('-'*30) + + async def on_ready(self): + self.check_close.start() + self.logger.info(f'We have logged in as {self.user}') + self.logger.info('-'*30) + self.logger.info(f'ID: {self.user.id}') + self.logger.info(f'Discord version: {discord.__version__}') + self.logger.info('-'*30) + self.logger.info(f'PROFILE : {self.PROFILE}') + self.logger.info(f'TEST_SERVER_ID : {self.TEST_SERVER_ID}') + self.logger.info('-'*30) + + self.var_manage[self.TEST_SERVER_ID] = Connector(self.TEST_SERVER_ID) + + self.logger.info('get DB data complete') + self.logger.info('-'*20) + self.logger.info(self.var_manage) + self.logger.info('='*30) + if self.send_queue: + self.send_queue.put('started') + self.logger.info('>> bot on to go') + + async def on_message(self, message:discord.Message): + if message.author.bot: + return + + if message.guild.id != self.TEST_SERVER_ID: + print('This is only for test server') + return + + self.var_proceed = False + self.var_registered = False + + self.logger.debug(message.content) + + try: + self.var_manage[message.guild.id] + except: + try: + self.logger.info(f'Connect {message.guild.name}|{message.guild.id} DB') + self.var_manage[message.guild.id]=Connector(message.guild.id) + except Exception as e: + self.logger.error(e, 'DB error occured') + await message.channel.send(embed=Embeds.error(e)) + return + + connector:Connector = self.var_manage[message.guild.id] + command = message.content[1:].split(' ')[0] + + if not message.content.startswith('!'): + self.logger.info('-'*10) + if message.channel.parent.name == connector.data.setting.channel.anon: + self.var_proceed = True + self.var_registered = True + self.logger.debug('anon message') + return + elif message.channel.category.name in connector.data.setting.channel.community: + self.var_proceed = True + self.var_registered = True + self.logger.debug("chara message") + return + self.logger.debug('not a command') + return + + if not message.author.guild_permissions.administrator: + self.logger.debug('not a admin') + if command in self.ver_admin_commands: + await message.delete() + await message.channel.send(embed=Embeds.general('관리자 전용 명령어입니다.'), delete_after=5) + self.var_proceed = False + return + else: + if message.content.startswith('!서버구축'): + self.var_proceed = True + self.var_registered = True + return + + if command in self.ver_admin_commands: + if message.channel.name != connector.data.setting.channel.manage: + self.var_proceed = False + await message.delete() + await message.channel.send(embed=Embeds.general('관리자 전용 명령어는 관리채널에서 사용할 수 있습니다.'), delete_after=3) + return + else: + self.logger.debug('admin command') + self.var_proceed = True + self.var_registered = True + return + + if not connector.check_user(message.author.id) and not message.content.startswith("!회원가입"): + await message.channel.send(embed=Embeds.setting('아직 회원가입이 되지 않은 유저입니다.','`/가입`을 먼저 진행해 주시기 바랍니다.'), delete_after=5) + if not message.author.top_role.permissions.administrator: + await message.delete() + self.var_registered = False + return + elif connector.check_chara(message.author.id) == 0 and not message.content.startswith("!캐릭터등록"): + await message.channel.send(embed=Embeds.setting('아직 캐릭터 등록이 되지 않은 유저입니다.','`/캐릭터등록`을 먼저 진행해 주시기 바랍니다.'), delete_after=5) + if not message.author.top_role.permissions.administrator: + await message.delete() + self.var_registered = False + return + + self.var_proceed = True + self.var_registered = True + + if command == "help": + await message.delete() + if message.content == '!help': + return await self.process_commands(message) + + if message.content.split(' ')[1] in self.ver_admin_commands: + if message.author.top_role.permissions.administrator: + if not message.channel.name == connector.data.setting.channel.manage: + await message.channel.send(embed=Embeds.setting('해당 명령어는 관리 채널에서만 사용 가능합니다.'), delete_after=3) + else: + await self.process_commands(message) + else: + await message.channel.send(embed=Embeds.general('해당 내용은 관리자만 확인 가능합니다.'), delete_after=5) + else: + await self.process_commands(message) + return + + if command not in list(map(lambda x: x.name, self.commands)): + self.logger.debug("no match command") + await message.delete() + await self.process_commands(message) + return + + async def on_command_error(self, message:commands.Context, error): + self.logger.warn(error, 'command error occured') + if self.send_queue: + self.send_queue.put('warn') + self.send_queue.put(str(error)) + if isinstance(error, commands.CommandNotFound): + return await message.send(embed=Embeds.general("잘못된 명령어 입니다", "명령어 목록을 확인하고 싶으시다면 `!help`를 이용해 주세요"), delete_after=30) + elif isinstance(error, commands.MissingRole): + return await message.send(embed=Embeds.general("역할이 충분하지 않습니다", "역할을 확인해 주세요"), delete_after=30) + elif isinstance(error, commands.MissingRequiredArgument): + return await message.send(embed=Embeds.general("입력되지 않은 변수가 있습니다", f"`{error.args[0].replace('is a required argument that is missing', '`가 입력되지 않았습니다')}"), delete_after=30) + else: + return await message.send(embed=Embeds.error(error)) + + async def on_app_command_error(self, interaction:discord.Interaction, error:app_commands.AppCommandError): + self.logger.warn(error, 'app command error occured') + if self.send_queue: + self.send_queue.put('warn') + self.send_queue.put(str(error)) + if isinstance(error, app_commands.MissingAnyRole): + await interaction.response.send_message(embed=Embeds.general("사용할 수 없는 명령어 입니다", "관리자 전용 명령어입니다"), ephemeral=True, delete_after=30) + elif isinstance(error, app_commands.AppCommandError): + if 'NoUser' in error.args: + await interaction.response.send_message(embed=Embeds.setting('아직 회원가입이 되지 않은 유저입니다.','`/가입`을 먼저 진행해 주시기 바랍니다.'), ephemeral=True, delete_after=30) + elif 'NoChara' in error.args: + await interaction.response.send_message(embed=Embeds.setting('아직 캐릭터 등록이 되지 않은 유저입니다.','`/캐릭터등록`을 먼저 진행해 주시기 바랍니다.'), ephemeral=True, delete_after=30) + else: + await interaction.response.send_message(embed=Embeds.error(error)) + else: + await interaction.response.send_message(embed=Embeds.error(error)) + + @tasks.loop(seconds=5) + async def check_close(self): + queue_data = '' + + if self.rec_queue: + if not self.rec_queue.empty(): + queue_data = self.rec_queue.get() + + if 'stop bot' == queue_data: + if self.send_queue: + self.send_queue.put('closed') + print('>> stop bot') + self.is_quit = True + await self.close() + exit(0) + +class Data: + connector:Connector + setting:Conn.Vars + channel:Conn.Channel + role:Conn.Roles + user:dict[int, Conn.User] + charas:dict[str, Conn.Chara] + npc:dict[str, Conn.NPC] + invs:dict[str, Conn.Backpack] + items:dict[str, Conn.Item] + fish_data:dict[str, list[str]] + fishes:dict[str, Conn.Fish] + +def __connection__(self, guild_id): + # connection + self.connector = self.bot.var_manage[guild_id] + # server infos + self.setting = self.connector.data.setting.data + self.channel = self.connector.data.setting.channel + self.role = self.connector.data.setting.role + self.user = self.connector.data.user.data + # chara data + self.charas = self.connector.data.chara.data + self.npc = self.connector.data.npc.data + self.invs = self.connector.data.inventory.data + self.items = self.connector.data.items.data + # fishing data + self.fish_data = self.connector.data.fishing.data + self.fishes = self.connector.data.fishing.fish + # print('connection complete') + +async def __on_ready__(self, interaction:discord.Interaction, check_user=True, check_chara=True): + self.bot.logger.debug('-'*10) + try: + command_name = f'{interaction.command.parent.name} {interaction.command.name}' + except: + command_name = interaction.command.name + self.bot.logger.debug(command_name) + await self.__get_connection__(interaction.guild_id) + user_id = interaction.user.id + + if not check_user or not check_chara: + return + + if user_id not in list(self.user.keys()): + raise app_commands.AppCommandError('NoUser','회원가입이 진행되지 않은 유저입니다.') + + if not self.user[user_id].charas: + raise app_commands.AppCommandError('NoChara','캐릭터등록이 진행되지 않은 유저입니다.') + + +class CommandBase(commands.Cog, Data): + def __init__(self, bot:JAS) -> None: + self.bot:JAS = bot + super().__init__() + + async def __get_connection__(self, guild_id): + __connection__(self, guild_id) + + async def on_ready(self, interaction:discord.Interaction, check_user=True, check_chara=True): + await __on_ready__(self, interaction, check_user, check_chara) + + @commands.Cog.listener() + async def on_message(self, message:discord.Message): + if message.author.bot: + return + + if not (self.bot.var_registered and self.bot.var_proceed): + return + + command_list = self.bot.ver_cog_commands[self.__cog_name__] + + await self.__get_connection__(message.guild.id) + command = message.content[1:].split(' ')[0] + if command in command_list: + self.bot.logger.debug('-'*10) + await message.delete() + await self.bot.process_commands(message) + return + + if self.__cog_name__ == "Community" and not message.content.startswith('!'): + if message.channel.category.name in self.channel.community and message.channel.type.value == 0 : + self.bot.logger.debug('send chara message') + await message.delete() + self.bot.logger.debug('-'*10) + await self.chara_talk(message) + return + + if message.channel.parent.name == self.channel.anon: + prefix = self.bot.command_prefix + if not message.content.startswith(prefix) and '전용 익명방' not in message.content: + self.bot.logger.debug('send anon message') + self.bot.logger.debug('-'*10) + await self.anon_message(message) + return + +@app_commands.default_permissions(send_messages=True) +class AppCommandBase(app_commands.Group, Data): + def __init__(self, bot, name, description): + super().__init__(name=name, description=description) + self.bot:JAS = bot + + async def __get_connection__(self, guild_id): + __connection__(self, guild_id) + + async def on_ready(self, interaction:discord.Interaction, check_user=True, check_chara=True): + await __on_ready__(self, interaction, check_user, check_chara) + \ No newline at end of file diff --git a/JAS/resources/Buttons.py b/JAS/resources/Buttons.py new file mode 100644 index 0000000..81ab516 --- /dev/null +++ b/JAS/resources/Buttons.py @@ -0,0 +1,49 @@ +from typing import Optional, Union +from discord.emoji import Emoji +from discord.partial_emoji import PartialEmoji +from discord.ui import Button +from discord.ext import commands +from discord import ButtonStyle, Interaction + +class TransferButton(Button): + def __init__(self): + super().__init__(label="송금", style=ButtonStyle.primary, row=1) + +class cancel(Button): + def __init__(self, row=None): + super().__init__(label="취소", style=ButtonStyle.secondary, row=row) + + async def callback(self, Interaction): + await Interaction.response.send_message('작업을 취소합니다.') + +class sub_stat(Button): + def __init__(self, row, user): + self.clicked=False + self.user_id = user.id + super().__init__(label="-", style=ButtonStyle.red, row=row) + + async def callback(self, interaction:Interaction): + if self.user_id != interaction.user.id: + return await interaction.response.send_message('스탯은 본인만 조정할 수 있습니다', ephemeral=True) + print("sub button click") + await interaction.response.defer() + self.clicked=True + +class add_stat(Button): + def __init__(self, row, user): + self.clicked=False + self.user_id = user.id + super().__init__(label="+", style=ButtonStyle.green, row=row) + + async def callback(self, interaction:Interaction): + if self.user_id != interaction.user.id: + return await interaction.response.send_message('스탯은 본인만 조정할 수 있습니다', ephemeral=True) + print("add button click") + await interaction.response.defer() + self.clicked=True + +class current_stat(Button): + def __init__(self, name, stat, row=0): + super().__init__(label=f'{name} : {stat}', style=ButtonStyle.secondary, row=row, disabled=True) + + async def callback(self, interaction:Interaction): ... diff --git a/JAS/resources/Connector.py b/JAS/resources/Connector.py new file mode 100644 index 0000000..5df32c8 --- /dev/null +++ b/JAS/resources/Connector.py @@ -0,0 +1,1083 @@ +import sqlite3, gspread +from JAS.setup import Setup +from JAS.resources.Exceptions import * +from copy import deepcopy +from random import random + +def return_string(string): + result = [] + result.append('='*30) + result.extend(string) + result.append('='*30) + return '\n'.join(result) + +class Connector: + def __init__(self, guild_id): + self.id = guild_id + self.conn = None + self.cur = None + self.__connect__() + self.data:Data = self.__get_data__() + + def __str__(self): + return str(self.data) + + def __connect__(self, update:bool=False, isDict:bool=False): + if not self.conn or update: + self.conn = Setup().setup(self.id) + else: + self.conn = Setup().connect(self.id) + self.conn.row_factory = sqlite3.Row if isDict else None + self.cur = self.conn.cursor() + + def __commit__(self): + self.conn.commit() + + def close(self): + self.cur.close() + self.conn.close() + + def __get_data__(self): + return Data(self, self.id) + + # General + # getter + def get_table_list(self): + self.__connect__() + tables = self.cur.execute("SELECT name FROM sqlite_master WHERE type='table'").fetchall() + self.close() + return tables + + # setter + def __reset_table_data__(self, tablename, data): + self.__connect__() + self.cur.execute(f'DELETE FROM {tablename}') + script = f'INSERT INTO {tablename} VALUES({",".join(["?"]*len(data[0]))})' + self.cur.executemany(script, data) + self.__commit__() + self.close() + + # 관리 + # getter + # 유저존재 확인 + def check_user(self, id): + self.__connect__() + user = self.cur.execute(f'SELECT count(*) FROM user WHERE id ={id} ').fetchone()[0] + self.close() + if user != 0: + return True + else: + return False + + def get_user_info(self): + self.__connect__() + userdata = self.cur.execute(f'SELECT * from user').fetchall() + self.close() + return userdata + + # 유저 목록 + def get_user_list(self): + self.__connect__() + userdata = self.cur.execute('SELECT * FROM user').fetchall() + self.close() + return userdata + + # 회원가입 가능여부 + def check_register_available(self): + self.__connect__() + result = self.cur.execute('SELECT value from setting WHERE type="accept_user"').fetchone()[0] + self.close() + return True if result == "True" else False + + # 설정정보 + def get_setting_info(self): + self.__connect__() + result = self.cur.execute('SELECT * FROM setting').fetchall() + self.close() + return result + + def __get_setting_value__(self, name): + self.__connect__() + value = self.cur.execute('SELECT value FROM setting WHERE type = (?)',(name,)).fetchone()[0] + self.close() + return value + + # 서버 관련 정보 가져오기 + def get_server_info(self): + self.__connect__() + self.close() + + # setter + # 설정 + def __set_setting_value__(self, name, value): + print('setting value set') + try: + self.__connect__() + self.cur.execute('UPDATE setting SET value=(?) WHERE type = (?)',(value, name)) + self.__commit__() + self.close() + + self.data.setting.__data__[name] = value + print('setting complete') + except Exception as e: + print(e) + print(traceback.format_exc()) + print('error occured') + raise ConnectionError('설정 변경 실패', name) + + # 유저정보 등록/갱신 + def set_user(self, id, name, private_thread_id): + self.__connect__() + self.cur.execute(f'INSERT INTO user VALUES(?,?,?,?)',(id, name, private_thread_id, "")) + self.__commit__() + self.close() + + self.data.user.data[id] = User((id, name, private_thread_id, '')) + + def update_user(self, id, name): + self.__connect__() + self.cur.execute(f'UPDATE user SET name={name} WHERE id = {id}') + + self.__commit__() + self.close() + + self.data.user.data[id].name = name + + # 유저정보 삭제 + def delete_user(self, id): + if self.check_user(id): + self.__connect__() + self.cur.execute(f'DELETE FROM user WHERE id = {id}') + self.__commit__() + self.close() + + if self.data.user.data[id].charas: + self.delete_charactor_data(self.data.user.data[id].charas, id) + del self.data.user.data[id] + + # 낚시 관련 + # getter + # 물고기 정보 가져오기 + def get_fish_data(self): + self.__connect__() + fish_data = self.cur.execute('SELECT * FROM fishing_data').fetchall() + self.close() + return fish_data + + # 최대 낚시 횟수 확인 + def get_max_fishing(self): + self.__connect__() + max_count = int(self.cur.execute('SELECT value FROM setting WHERE type = "max_fishing"').fetchone()[0]) + self.close() + return max_count + + # 낚시 이력 확인 + def get_fishing_history(self, id, channel, date): + try: + self.__connect__() + count = self.cur.execute(f"SELECT COUNT(*) FROM fishing_history WHERE user = {id} and fishdate LIKE '{date}%'").fetchone()[0] + self.close() + return count + except Exception as e: + print(e) + print(traceback.format_exc()) + raise ConnectionError('낚시 이력 조회 실패') + + # 물고기 존재 확인 + def check_fish(self, name): + self.__connect__() + result = self.cur.execute('SELECT count(*) FROM fishign_data WHERE name=(?)',(name)).fetchone()[0] + self.close() + return True if result > 0 else False + + # setter + # 낚시 결과 기록 + def set_fishing_history(self, now, user_id, loc, name, length): + try: + self.__connect__() + print('connect DB') + self.cur.execute('INSERT INTO fishing_history VALUES(?, ?, ?, ?, ?)', (now, user_id, loc, name, length)) + self.__commit__() + self.close() + print('connection closed') + except Exception as e: + print(e) + print(traceback.format_exc()) + raise FishingError('낚시 이력 등록 실패') + + # 최대 낚시 횟수 설정 + def set_max_fishing(self, value): + try: + self.__set_setting_value__("max_fishing", value) + except Exception as e: + print(e) + print(traceback.format_exc()) + raise ConnectionError('최대 낚시 횟수 등록 실패') + + # 물고기 추가 + def add_fish_data(self, name, desc, min, max, baseprice, loc): + try: + self.__connect__() + count = self.cur.execute('SELECT count(*) FROM fishing_data').fetchone()[0] + + code = str(count+1001) + code_list = self.cur.execute(f'SELECT code FROM fishing_data ORDER BY code').fetchall() + if code in code_list: + recent_code = code_list.pop()[0] + code = str(int(recent_code)+1) + + self.cur.execute('INSERT INTO fishing_data VALUES(?,?,?,?,?,?)',(code, name, min, max, baseprice, loc)) + self.cur.execute('INSERT INTO item VALUES(?,?,?,?,?)', (code, name, desc, 1, baseprice)) + self.__commit__() + self.close() + self.reg_item(code, name, desc, 1, baseprice) + + locs = list(self.data.fishing.data.keys()) + for location in loc.split(','): + if location in locs: + self.data.fishing.data[location] = [] + self.data.fishing.data[location].append(code) + self.data.fishing.fish[code] = Fish((code, name, min, max, baseprice, loc)) + except Exception as e: + print(e) + print(traceback.format_exc()) + raise CannotAddFish('물고기 추가 실패') + + # 물고기 변경 + def change_fish_data(self, code, desc, min, max, baseprice, loc): + try: + self.__connect__() + self.cur.execute('UPDATE fishing_data SET min=(?), max=(?), baseprice=(?), loc=(?) WHERE code = (?)',(min, max, baseprice, loc, code)) + self.cur.execute('UPDATE item SET description=(?), price=(?) WHERE code=(?)', (desc, baseprice, code)) + self.__commit__() + self.close() + locs = list(self.data.fishing.data.keys()) + for location in loc.split(','): + if location in locs : + if code not in self.data.fishing.data[location]: + self.data.fishing.data[location].append(code) + else: + self.data.fishing.data[location] = [] + self.data.fishing.data[location].append(code) + fish = self.data.fishing.fish[code] + fish.desc = desc + fish.min = min + fish.max = max + fish.baseprice = baseprice + item = self.data.items.data[code] + item.desc = desc + item.baseprice = baseprice + except Exception as e: + print(e) + print(traceback.format_exc()) + raise CannotAddFish('물고기 변경 실패') + + # 물고기 삭제 + def delete_fish(self, code): + self.__connect__() + self.cur.execute(f'DELETE FROM fishing_data WHERE code={code}') + self.cur.execute(f'DELETE FROM item WHERE code={code}') + self.__commit__() + self.close() + self.delete_item_from_inventory(code) + + for loc in list(self.data.fishing.data.keys()): + if code in self.data.fishing.data[loc]: + self.data.fishing.data[loc].remove(code) + del self.data.fishing.fish[code] + + # 낚시 이력 삭제 + def delete_fishing_history(self, id): + try: + self.__connect__() + self.cur.execute(f"DELETE FROM fishing_history WHERE user={id}") + self.__commit__() + self.close() + return True + except Exception as e: + print(e) + print(traceback.format_exc()) + raise ConnectionError('낚시 이력 삭제 실패') + + # 낚시터 변경 + def change_fishing_channel(self, before, after): + self.__connect__() + list = self.cur.execute("SELECT code, loc FROM fishing_data WHERE loc LIKE '(?)'",(f'%{before}%',)).fetchall() + for item in list: + code = item[0] + loc = item[1] + after_loc = loc.replace(before, after) + self.cur.execute("UPDATE fishing_data SET loc=(?) WHERE code=(?)",(after_loc, code)) + self.__commit__() + self.close() + + self.data.fishing.data[after] = deepcopy(self.data.fishing.data[before]) + del self.data.fishing.data[before] + + # 아이템 관련 + # getter + # 아이템 정보 수집 + def get_items_info(self): + self.__connect__() + item = self.cur.execute(f'SELECT * FROM item').fetchall() + self.close() + return item + + # 아이템 정보 확인 + def get_item_info(self, code): + self.__connect__() + item = self.cur.execute(f'SELECT * FROM item WHERE code={code}').fetchone() + self.close() + return item + + # setter + # 아이템 등록 + def reg_item(self, code, name, desc, number, price): + self.__connect__() + self.cur.execute('INSERT INTO item VALUES(?,?,?,?,?)',(code, name, desc, number, price)) + self.__commit__() + self.close() + + self.data.items.data[code] = Item((code, name, desc, number, price)) + + # 아이템 수정 + def update_item(self, code, name, desc, number, price): + self.__connect__() + self.cur.execute('UPDATE item SET name=(?), description=(?), price=(?) WHERE code=(?)',(name, desc, price, code)) + self.__commit__() + self.close() + + self.data.items.data[code].name = name + self.data.items.data[code].desc = desc + self.data.items.data[code].number = number + self.data.items.data[code].price = price + + # 인벤토리 관련 + # getter + # 인벤토리 정보 확인 + def get_inventory_data(self): + try: + self.__connect__() + result = self.cur.execute('SELECT * FROM inventory').fetchall() + self.close() + return result + except Exception as e: + print(e) + print(traceback.format_exc()) + raise ConnectionError('인벤토리 정보 확인 실패') + + # setter + # 아이템 보관 + def store_item(self, code, itemcode, num): + inventory = self.data.inventory.data[code] + before_items = ','.join(inventory.items) + items = f'{before_items},{itemcode}' if before_items else f'{itemcode}' + self.__connect__() + self.cur.execute(f'UPDATE inventory SET item=? WHERE code=?',(items, code)) + self.__commit__() + self.close() + + self.data.inventory.data[code].items.append(itemcode) + + # 아이템사용 + def use_item(self, code, itemcode): + self.__connect__() + item = self.cur.execute(f'SELECT item from inventory WHERE code=?',(code,)).fetchone() + items = item[0].split(',') + items.remove(itemcode) + item_after = ','.join(items) + self.cur.execute(f"UPDATE inventory SET item=(?) WHERE code=(?)",(item_after, code)) + self.cur.execute(f"DELETE FROM item WHERE code=(?)",(itemcode,)) + self.__commit__() + self.close() + + self.data.inventory.data[code].items.remove(itemcode) + + # 아이템 부여 + def spawn_item(self, code, itemcode, item): + itemcode = f'{itemcode}-1-{str(random())[2:]}' + name = item.name + desc = item.desc + price = item.price + self.reg_item(itemcode, name, desc, 1, price) + self.store_item(code, itemcode, 1) + + # 아이템판매 + def sold_item(self, code, itemcode, gold): + self.use_item(code, itemcode) + self.add_gold(code, gold) + + # 골드 추가 + def add_gold(self, code, gold): + try: + before_gold = self.data.inventory.data[code].gold + print(before_gold) + after_gold = before_gold + float(gold) + self.__connect__() + self.cur.execute('UPDATE inventory SET gold = (?) WHERE code = (?)', (after_gold, code)) + self.__commit__() + self.close() + + self.data.inventory.data[code].gold = after_gold + except Exception as e: + print(e) + print(traceback.format_exc()) + raise SellingError('골드 추가 실패') + + # 인벤토리 업데이트 + def update_chara_inv_size(self, code, size): + try: + self.__connect__() + before_size = self.data.inventory.data[code].size + after_size = before_size+size + self.cur.execute(f'UPDATE inventory SET size=(?) WHERE code=(?)',(after_size, code)) + self.__commit__() + self.close() + + self.data.inventory.data[code].size = after_size + except Exception as e: + print(e) + print(traceback.format_exc()) + raise SellingError('인벤토리 업데이트 실패') + + # 인벤토리에서 아이템 삭제 + def delete_item_from_inventory(self, code): + self.__connect__() + self.cur.execute(f"DELETE FROM item WHERE code LIKE '{'%-'+code+'-%'}'") + self.__commit__() + inventory = self.cur.execute(f"SELECT code, item FROM inventory WHERE items LIKE '{'%-'+code+'-%'}'").fetchall() + + for inv in inventory: + items = inv[1].split(',') + mod_items = [] + for item in items: + if f'-{code}-' not in item: + mod_items.append(item) + self.cur.execute(f'UPDATE inventory SET item={",".join(mod_items)} WHERE code={inv[0]}') + self.__commit__() + self.close() + + for code in list(self.data.inventory.data.keys()): + itemcodes = list(filter(lambda x: code.split('_')[0] == code,list(self.data.inventory.data[code].keys()))) + if len(itemcodes) > 0: + for itemcode in itemcodes: + del self.data.inventory[code][itemcode] + + # 캐릭터 관련 + # getter + # NPC존재 확인 + def check_NPC(self, type): + self.__connect__() + result = self.cur.execute("SELECT count(*) FROM chara WHERE code LIKE '(?)'",(f'%{type}%',)).fetchall()[0] + self.close() + return True if result > 0 else False + + # 캐릭터 존재 확인 + def check_chara(self, id): + self.__connect__() + code = self.cur.execute('SELECT charas FROM user WHERE id=(?)',(id,)).fetchone()[0] + result = self.cur.execute(f"SELECT count(*) FROM chara WHERE code=?",(code,)).fetchone()[0] + self.close() + return result + + # NPC정보 가져오기 + def get_NPC_info(self): + self.__connect__() + info = self.cur.execute("SELECT * FROM NPC").fetchall() + vers = self.cur.execute("SELECT * FROM NPC_vers").fetchall() + self.close() + return info, vers + + # 캐릭터 정보 가져오기 + def get_charactor_data(self): + self.__connect__() + data = self.cur.execute("SELECT * FROM chara").fetchall() + self.close() + return data + + # 유저 캐릭터 가져오기 + def get_user_chara(self, id): + self.__connect__() + data = self.cur.execute("SELECT code FROM chara WHERE code LIKE '(?)'",(f'{id}%',)).fetchall()[0] + self.close() + return data + + # setter + # NPC정보 입력 + def set_NPC_info(self, code, name, color=0): + self.__connect__() + self.cur.execute("INSERT INTO NPC VALUES(?,?,?)",(code, name, color)) + self.__commit__() + self.close() + + self.data.npc.data[code] = NPC((code, name, color), []) + + # NPC대사 입력 + def set_NPC_vers(self, code, keyword, vers): + self.__connect__() + self.cur.execute("INSERT INTO NPC_vers VALUES(?,?,?)",(code, keyword, vers)) + self.__commit__() + self.close() + + self.data.npc.data[code].number += 1 + self.data.npc.data[code].case = self.data.npc.data[code].case + f',{keyword}' if self.data.npc.data[code].case else keyword + self.data.npc.data[code].vers.append(vers) + + # NPC정보 수정 + def update_NPC_info(self, code, name, color): + self.__connect__() + self.cur.execute("UPDATE NPC SET name=(?), color=(?) WHERE code=(?)",(name, color, code)) + self.__commit__() + self.close() + + self.data.npc.data[code].name = name + self.data.npc.data[code].color = color + + # NPC대사 입력 + def update_NPC_vers(self, code, before_keyword, after_keyword, vers): + self.__connect__() + self.cur.execute("UPDATE NPC_vers SET keyword=(?), vers=(?) WHERE code=(?) and keyword=(?)",(after_keyword, vers, code, before_keyword)) + self.__commit__() + self.close() + + self.data.npc.data[code].case = ','.join([after_keyword if case == before_keyword else case for case in self.data.npc.data[code].case.split(',')]) + self.data.npc.data[code].vers[self.data.npc.data[code].case.split(',').index(after_keyword)] = vers + + # NPC 정보 삭제 + def delete_NPC_data(self, code): + self.__connect__() + self.cur.execute("DELETE FROM NPC WHERE code=(?)",(code,)) + self.cur.execute(f'DELETE FROM NPC_vers WHERE code=(?)',(code,)) + self.__commit__() + self.close() + + del self.data.npc.data[code] + + # 캐릭터 정보 입력 + def set_charactor_data(self, id, code, name, keyword, desc, link): + stat = [1,1,1,1,1,1] + point = 4 + gold = self.data.setting.data.base_inv_gold + size = self.data.setting.data.base_inv_size + self.__connect__() + self.cur.execute("INSERT INTO chara VALUES(?,?,?,?,?,?)",(code, name, keyword, desc, link, None)) + self.cur.execute("INSERT INTO stat VALUES(?,?,?,?,?,?,?,?,?)",(code, point, None, *stat)) + self.cur.execute(f'INSERT INTO inventory VALUES(?,?,?,?)',(code, gold, size, "")) + self.cur.execute("UPDATE user SET charas=(?) WHERE id=(?)",(code, id)) + self.__commit__() + self.close() + + self.data.user.data[id].charas = code + self.data.chara.data[code] = Chara((code, name, keyword, desc, link, None),(code, point, None, *stat),','.join(self.data.setting.data.stat_names)) + self.data.inventory.data[code] = Backpack((code, gold, size, "")) + + # 캐릭터 정보 갱신 + def update_charactor_data(self, code, name, keyword, desc, link): + self.__connect__() + self.cur.execute("UPDATE chara SET name=(?), keyword=(?), desc=(?), link=(?) WHERE code=(?)",(name, keyword, desc, link, code)) + self.__commit__() + self.close() + + self.data.chara.data[code].name = name + self.data.chara.data[code].keyword = keyword + self.data.chara.data[code].desc = desc + self.data.chara.data[code].link = link + + # 캐릭터 색상 갱신 + def update_charactor_color(self, code, color): + self.__connect__() + self.cur.execute("UPDATE chara SET color=(?) WHERE code=(?)",(color, code)) + self.__commit__() + self.close() + + self.data.chara.data[code].color = color + + # 캐릭터 인벤토리 갱신 + def update_charactor_inventory(self, code, gold=0, size=0): + self.__connect__() + basegold, basesize = self.cur.execute('SELECT gold, size FROM inventory WHERE code = (?)',(code,)).fetchone() + result_gold = basegold+gold + result_size = basesize+size + self.cur.execute('UPDATE inventory SET gold=(?), size=(?) WHERE code=(?)',(result_gold, result_size, code)) + self.__commit__() + self.close() + + self.data.inventory.data[code].gold = result_gold + self.data.inventory.data[code].size = result_size + + # 캐릭터 정보 삭제 + def delete_charactor_data(self, code, id): + self.__connect__() + self.cur.execute("DELETE FROM chara WHERE code=(?)",(code,)) + self.cur.execute(f'DELETE FROM inventory WHERE code = {code}') + self.cur.execute('UPDATE user SET charas=(?) WHERE id=(?)',(code, id)) + self.__commit__() + self.close() + + del self.data.inventory.data[id] + del self.data.chara.data[code] + self.data.user.data[id].charas = '' + + # 스탯 관련 + # getter + def get_stat_data(self): + self.__connect__() + result = self.cur.execute('SELECT * FROM stat').fetchall() + self.close() + return result + + def get_base_stat_names(self): + self.__connect__() + result = self.cur.execute("SELECT value FROM setting WHERE id LIKE 'stat%'").fetchall() + self.close() + return result + + # setter + def update_stat(self, code, names:list[str], point, stat:list[int]): + self.__connect__() + self.cur.execute('UPDATE stat SET stat1=(?), stat2=(?), stat3=(?), stat4=(?), stat5=(?), stat6=(?), point=(?), statname=(?) WHERE code=(?)', (*stat, point, ','.join(names), code)) + self.__commit__() + self.close() + + self.data.chara.data[code].stat.point = point + self.data.chara.data[code].stat.stat_names = names + self.data.chara.data[code].stat.stat1 = stat[0] + self.data.chara.data[code].stat.stat2 = stat[1] + self.data.chara.data[code].stat.stat3 = stat[2] + self.data.chara.data[code].stat.stat4 = stat[3] + self.data.chara.data[code].stat.stat5 = stat[4] + self.data.chara.data[code].stat.stat6 = stat[5] + + # 스탯명 변경 + def update_stat_name(self, statname): + if not statname: + for chara in list(self.data.chara.data.keys()): + self.data.chara.data[chara].stat=None + else: + if not self.data.setting.data.stat_names[0]: + for stat_data in self.get_stat_data(): + chara = stat_data[0] + self.data.chara.data[chara].stat = Stat(stat_data, statname) + self.__set_setting_value__('stat_names', statname) + +# Data Object +class Data(): + def __init__(self, conn:Connector, guild_id) -> None: + self.guild_id = guild_id + self.setting=Setting(conn.get_setting_info()) + self.user=Users(conn.get_user_info()) + self.fishing=Fishing(conn.get_fish_data()) + self.items=Items(conn.get_items_info()) + self.inventory=Inventory(conn.get_inventory_data()) + self.chara=Charas(conn.get_charactor_data(), conn.get_stat_data(), conn.__get_setting_value__('stat_names')) + self.npc=NPCs(conn.get_NPC_info()) + def __str__(self) -> str: + content = [] + content.append(str(self.setting)) + content.append(str(self.user)) + content.append(str(self.fishing)) + content.append(str(self.items)) + content.append(str(self.inventory)) + content.append(str(self.chara)) + content.append(str(self.npc)) + return '\n'.join(content) + def showDB(self)->list: + content = [] + content.append(str(self.setting)) + content.append(str(self.user)) + content.append(str(self.fishing)) + content.append(str(self.items)) + content.append(str(self.inventory)) + content.append(str(self.chara)) + content.append(str(self.npc)) + return content + +# User data +class Users(): + def __init__(self, data) -> None: + self.data : dict[int, User] = {} + for user in data: + code = user[0] + self.data[code] = User(user) + def __str__(self) -> str: + content = [] + content.append('User data') + for code in list(self.data.keys()): + content.append('-'*30) + content.append(str(self.data[code])) + return return_string(content) + +class User(): + def __init__(self, data) -> None: + self.id:int = data[0] + self.name:str = data[1] + self.thread:int = data[2] + self.charas:str = data[3] + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('id', self.id)) + content.append('{0:<20} : {1}'.format('name', self.name)) + content.append('{0:<20} : {1}'.format('thread', self.thread)) + content.append('{0:<20} : {1}'.format('charas', self.charas)) + return '\n'.join(content) + +# setting +class Setting(): + def __init__(self, data) -> None: + self.__data__:dict[str, str] = {} + + for item in data: + key, value = item + self.__data__[key] = value + + self.data=Vars(self.__data__) + self.channel=Channel(self.__data__) + self.role=Roles(self.__data__) + def __str__(self) -> str: + content = [] + content.append('Current Setting') + content.append('-'*30) + content.append(str(self.data)) + content.append('-'*30) + content.append(str(self.channel)) + content.append('-'*30) + content.append(str(self.role)) + return return_string(content) + +class Vars: + def __init__(self, data) -> None: + self.__data__:dict[str, str] = data + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('max_fishing', self.max_fishing)) + content.append('{0:<20} : {1}'.format('max_gather', self.max_gather)) + content.append('{0:<20} : {1}'.format('base_inv_size', self.base_inv_size)) + content.append('{0:<20} : {1}'.format('base_inv_gold', self.base_inv_gold)) + content.append('{0:<20} : {1}'.format('accept_user', self.accept_user)) + content.append('{0:<20} : {1}'.format('random_box', self.random_box)) + content.append('{0:<20} : {1}'.format('gspread_url', self.gspread_url)) + content.append('{0:<20} : {1}'.format('stat_names', self.stat_names)) + return '\n'.join(content) + + @property + def max_fishing(self) -> int: + return int(self.__data__["max_fishing"]) + @property + def max_gather(self) -> int: + return int(self.__data__["max_gather"]) + @property + def base_inv_size(self) -> int: + return int(self.__data__["base_inv_size"]) + @property + def base_inv_gold(self) -> int: + return int(self.__data__["base_inv_gold"]) + @property + def accept_user(self) -> bool: + return True if self.__data__["accept_user"]=="True" else False + @property + def random_box(self) -> int: + return int(self.__data__["random_box"]) + @property + def gspread_url(self) -> str: + return self.__data__["gspread_url"] + @property + def stat_names(self) -> list[str]: + return self.__data__["stat_names"].split(',') + +class Channel: + def __init__(self, data) -> None: + self.__data__:dict[str, str] = data + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('anon channel', self.anon)) + content.append('{0:<20} : {1}'.format('manage channel', self.manage)) + content.append('{0:<20} : {1}'.format('join channel', self.join)) + content.append('{0:<20} : {1}'.format('store channel', self.store)) + content.append('{0:<20} : {1}'.format('community channel', self.community)) + content.append('{0:<20} : {1}'.format('qna channel', self.qna)) + return '\n'.join(content) + + @property + def anon(self) -> str: + return self.__data__["anon"] + @property + def manage(self) -> str: + return self.__data__["manage"] + @property + def join(self) -> str: + return self.__data__["join"] + @property + def store(self) -> str: + return self.__data__["store"] + @property + def community(self) -> str: + return self.__data__["community"] + @property + def qna(self) -> str: + return self.__data__["qna"] + +class Roles: + def __init__(self, data) -> None: + self.__data__:dict[str, str] = data + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('visitor role', self.visitor)) + content.append('{0:<20} : {1}'.format('registered role', self.registered)) + content.append('{0:<20} : {1}'.format('admin role', self.admin)) + return '\n'.join(content) + + @property + def visitor(self) -> str: + return self.__data__["visitor"] + @property + def registered(self) -> str: + return self.__data__["registered"] + @property + def admin(self) -> str: + return self.__data__["admin"] + +# fishing data +class Fishing(): + def __init__(self, data) -> None: + self.data:dict[str, list[str]] = {} + self.fish:dict[str, Fish] = {} + for fish in data: + code = fish[0] + fishdata = Fish(fish) + locs = fish[5].split(',') + for loc in locs: + try: + self.data[loc].append(code) + except: + self.data[loc]=[] + self.data[loc].append(code) + self.fish[code] = fishdata + self.data = self.data + def __str__(self) -> str: + content = [] + content.append('Fish data') + for loc in list(self.data.keys()): + content.append('-'*30) + content.append(f'place: {loc}') + for fish in self.data[loc]: + content.append('-'*20) + content.append(f'[{fish}]') + content.append(str(self.fish[fish])) + return return_string(content) + +class Fish(): + def __init__(self, fish) -> None: + self.name:str = fish[1] + self.min:int = fish[2] + self.max:int = fish[3] + self.baseprice:int = fish[4] + self.loc:str = fish[5] + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('name', self.name)) + content.append('{0:<20} : {1}'.format('min', self.min)) + content.append('{0:<20} : {1}'.format('max', self.max)) + content.append('{0:<20} : {1}'.format('baseprice', self.baseprice)) + content.append('{0:<20} : {1}'.format('loc', self.loc)) + return '\n'.join(content) + +# Item data +class Items(): + def __init__(self, data) -> None: + self.data:dict[str,Item] = {} + for item in data: + code = item[0] + itemdata = Item(item) + self.data[code] = itemdata + def __str__(self) -> str: + content = [] + content.append('Item data') + for code in list(self.data.keys()): + content.append('-'*30) + content.append(f'[{code}]') + content.append(str(self.data[code])) + return return_string(content) + +class Item(): + def __init__(self, data) -> None: + self.name:str = data[1] + self.desc:str = data[2] + self.number:int = data[3] + self.price:int = data[4] + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('name', self.name)) + content.append('{0:<20} : {1}'.format('desc', self.desc)) + content.append('{0:<20} : {1}'.format('number', self.number)) + content.append('{0:<20} : {1}'.format('price', self.price)) + return '\n'.join(content) + +# Inventory data +class Inventory(): + def __init__(self, data) -> None: + self.data:dict[str, Backpack] = {} + for invdata in data: + id = invdata[0] + self.data[id] = Backpack(invdata) + def __str__(self) -> str: + content = [] + content.append('Inventory data') + for user in list(self.data.keys()): + content.append('-'*30) + content.append(f'User: {user}') + content.append('-'*20) + content.append(str(self.data[user])) + return return_string(content) + +class Backpack(): + def __init__(self, inv_data) -> None: + self.gold:int = inv_data[1] + self.size:int = inv_data[2] + self.items:list[str] = [] + if inv_data[3] != '': + for itemcode in inv_data[3].split(','): + code = itemcode.strip() + self.items.append(code) + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('gold', self.gold)) + content.append('{0:<20} : {1}'.format('max size', self.size)) + content.append('{0:<20} :'.format('items')) + content.append('-'*15) + for item in self.items: + content.append('{0:<15} : {1}'.format('code', item)) + return '\n'.join(content) + +# Store data +class Store(): + def __init__(self, data, iteminfo) -> None: + self.data:dict[str, StoreItem] = {} + for item in data: + code = item[0] + self.data[code] = StoreItem(item, iteminfo) + def __str__(self) -> str: + content = [] + content.append('Store data') + for code in list(self.data.keys()): + content.append('-'*30) + content.append(f'[{code}]') + content.append(str(self.data[code])) + return return_string(content) + +class StoreItem(): + def __init__(self, data, iteminfo) -> None: + code = data[0] + self.name:str = iteminfo[code].name + self.sale:bool = True if data[1]=='True' else False + self.desc:str = iteminfo[code].desc + self.price:int = data[2] + self.discount:int = data[3] + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('name', self.name)) + content.append('{0:<20} : {1}'.format('sale', self.sale)) + content.append('{0:<20} : \n{1}'.format('desc', self.desc)) + content.append('{0:<20} : {1}'.format('price', self.price)) + content.append('{0:<20} : {1}'.format('discount', self.discount)) + return '\n'.join(content) + +# NPC data +class NPCs(): + def __init__(self, data) -> None: + self.data:dict[str,NPC] = {} + info = data[0] + vers = data[1] + for chara in info: + code = chara[0] + npc_vers = list(filter(lambda x: x[0]==code,vers)) + self.data[code] = NPC(chara, npc_vers) + def __str__(self) -> str: + content = [] + content.append('NPC data') + for code in list(self.data.keys()): + content.append('-'*30) + content.append(f'[{code}]') + content.append(str(self.data[code])) + return return_string(content) + +class NPC(): + def __init__(self, data, vers:list) -> None: + self.name:str = data[1] + self.color:int = data[2] + self.number:int = len(vers) + self.case:list[str] = [] + self.vers:list[str] = [] + if vers: + for item in vers: + self.case.append(item[1]) + self.vers.append(item[2]) + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('name', self.name)) + content.append('-'*15) + content.append('{0:<15} : {1}'.format('number', self.number)) + content.append('{0:<15} :'.format('vers')) + for i in range(len(self.vers)): + content.append(self.case[i] + '-'*10) + for vers in self.vers[i].split('\n'): + content.append(f' {vers}') + return '\n'.join(content) + +# Chara data +class Charas(): + def __init__(self, data, stat, stat_default) -> None: + self.data:dict[str,Chara] = {} + for chara in data: + code = chara[0] + stat_data = list(filter(lambda x: x[0]==code ,stat))[0] + self.data[code] = Chara(chara, stat_data, stat_default) + def __str__(self) -> str: + content = [] + content.append('Chara data') + for code in list(self.data.keys()): + content.append('-'*30) + content.append(f'[{code}]') + content.append(str(self.data[code])) + return return_string(content) + +class Stat(): + def __init__(self, data, default) -> None: + self.stat1:int = int(data[3]) + self.stat2:int = int(data[4]) + self.stat3:int = int(data[5]) + self.stat4:int = int(data[6]) + self.stat5:int = int(data[7]) + self.stat6:int = int(data[8]) + self.point:int = data[1] + self.stat_names = data[2].split(',') if data[2] else default.split(',') + def __str__(self) -> str: + content = [] + content.append('{0:<15} : {1}'.format(self.stat_names[0], self.stat1)) + content.append('{0:<15} : {1}'.format(self.stat_names[1], self.stat2)) + content.append('{0:<15} : {1}'.format(self.stat_names[2], self.stat3)) + content.append('{0:<15} : {1}'.format(self.stat_names[3], self.stat4)) + content.append('{0:<15} : {1}'.format(self.stat_names[4], self.stat5)) + content.append('{0:<15} : {1}'.format(self.stat_names[5], self.stat6)) + content.append('='*5) + content.append('{0:<15} : {1}'.format('point', self.point)) + return '\n'.join(content) + +class Chara(): + def __init__(self, data, stat, stat_default) -> None: + self.name:str = data[1] + self.keyword:str = data[2] + self.desc:str = data[3] + self.link:str = data[4] + self.color:int = data[5] + self.stat:Stat = Stat(stat, stat_default) if stat_default else None + def __str__(self) -> str: + content = [] + content.append('{0:<20} : {1}'.format('name', self.name)) + content.append('{0:<20} : {1}'.format('keyword', self.keyword)) + content.append('{0:<20} : \n{1}'.format('desc', self.desc)) + content.append('{0:<20} : {1}'.format('link', self.link)) + if self.stat: + content.append('-'*15) + content.append(str(self.stat)) + return '\n'.join(content) + \ No newline at end of file diff --git a/JAS/resources/Embeds.py b/JAS/resources/Embeds.py new file mode 100644 index 0000000..598fb3e --- /dev/null +++ b/JAS/resources/Embeds.py @@ -0,0 +1,267 @@ +from discord import Embed, Color, File +from JAS.resources.Addons import os, currency, desc_converter, IMG_FOLDER_PATH, filename +import JAS.resources.Connector as Conn + +# 공용 +def general(title, desc=None, color=Color.dark_grey(), footer=None): + embed = Embed(title=title, description=desc, color=color) + if footer: embed.set_footer(text=footer) + return embed + +def warning(title, desc=None): + return Embed(title=title, description=desc, color=Color.dark_red()) + +def error(errorcode=None): + title = "예상치 못한 오류가 발생하였습니다." + desc = "지속적으로 발생하는 경우 관리자에게 문의 바랍니다." + if errorcode: + desc = desc+f'\n오류 내용: {errorcode}' + return Embed(title=title, description=desc, color=Color.red()) + +def store(title, desc=None): + return Embed(title=title, description=desc, color=Color.teal()) + +# 관리 +def setting(title, desc=None, footer=None): + embed = Embed(title=title, description=desc, color=Color.gold()) + if footer: + embed.set_footer(text=footer) + return embed + +def show_user(manage:Conn.Connector, id): + user_name = manage.data.user.data[id].name + code = manage.data.user.data[id].charas + chara_data = manage.data.chara.data[code] + chara_name = chara_data.name + title = f'{user_name}의 정보입니다.' + desc = f'id: {id}' + embed = Embed(title=title, description=desc, color=Color.gold()) + embed.add_field(name="캐릭터",value=f'{chara_name}\n==========\n{str(chara_data)}') + return embed + +def show_system(title, manage:Conn.Vars, footer): + embed = Embed(title=title, color=Color.gold()) + data = { + "회원가입 가능여부": "허용" if manage.accept_user else "차단", + "최대 낚시 횟수": manage.max_fishing, + "최대 채집 횟수": manage.max_gather, + "기본 가방 크기": manage.base_inv_size, + "최초 소지 골드": manage.base_inv_gold, + "랜덤 박스 가격": manage.random_box, + "구글 연동 링크": manage.gspread_url or '_', + "스탯명": str(manage.stat_names), + } + for key in list(data.keys()): + embed.add_field(name=key, value=f'`{data[key]}`', inline=True) + embed.set_footer(text=footer) + return embed + +def user_accept(manage:Conn.Connector, footer): + title = "회원가입 가능여부를 설정" + desc = "회원가입을 허용하시겠습니까?" + embed = Embed(title=title, description=desc, color=Color.gold()) + value="허용" if manage.data.setting.data.accept_user else "차단" + embed.add_field(name="현재 설정", value=value) + embed.set_footer(text=footer) + return embed + +def guide_user(name): + title = f"{name}님 환영합니다." + desc = "해당 익명 스레드의 사용법을 설명드리겠습니다." + embed = Embed(title=title, description=desc, color=Color.gold()) + guide_list={ + "익명 게시판 작성":"해당 스레드에 메세지를 보내시면 해당 메세지는 익명게시판에 작성됩니다.", + "익명 문의":"/문의 명령어를 이용하시면 비공개 문의를 진행할 수 있습니다.", + "명령어 사용":"다른 게시판에서와 마찬가지로 명령어의 사용이 가능합니다." + } + for name in list(guide_list.keys()): + value = guide_list[name] + embed.add_field(name=name, value=value, inline=False) + + return embed + +# 낚시 +def fishing(type:int): + types = { + 0:["자리를 준비하는 중...",Color.dark_grey()], + 1:["낚싯대를 드리우는 중...", Color.green()], + 2:["흐르는 물을 바라보는 중...", Color.blue()], + 3:["스쳐 지나가는 물고기 그림자를 바라보는 중...", Color.teal()], + 4:["월척의 꿈을 꾸는 중...",Color.green()], + 9:["앗 느낌이?!",Color.gold()] + } + title = types[type][0] + color = types[type][1] + embed = Embed(title=title, description=None, color=color) + return embed + +def fishing_result(title, desc, color, result=None): + image = None + embed = Embed(title=title, description=desc, color=color) + context = "" + if result: + for item in result: + context = context + ' / ' + str(item) if context else "🐟 "+str(item) + embed.set_footer(text = context) + # embed.add_field(name='크기', value=result["length"], inline=True) + # embed.add_field(name='가격', value=result["price"], inline=True) + # file = File(f'resources/img/{image}') + return embed + +def fish_data(title, data:dict[str, list['fishcode':str]], fishinfo:dict[str, Conn.Fish], isOnePlace): + embed = Embed(title=title, description=None, color=Color.green()) + for place in list(data.keys()): + context = "" + name = "물고기 목록" if isOnePlace else place + for fish in data[place]: + context = context+','+ fishinfo[fish].name if context else fishinfo[fish].name + embed.add_field(name=name, value=context, inline=False) + return embed + +def add_fish(title, name, desc, min, max, baseprice, loc): + embed = Embed(title=title,color=Color.yellow()) + embed.add_field(name="이름",value=name, inline=False) + embed.add_field(name="설명",value=desc, inline=False) + embed.add_field(name="최소 길이",value=min, inline=False) + embed.add_field(name="최고 길이",value=max, inline=False) + embed.add_field(name="기본 가격",value=baseprice, inline=False) + embed.add_field(name="등장위치",value=loc, inline=False) + return embed + +# 인텐토리 +def inventory(title, desc, inventory:Conn.Backpack, items:dict[str, Conn.Item]): + embed = Embed(title=title, description=desc, color=Color.teal()) + # index = 0 + gold = inventory.gold + if inventory.items: + for item in inventory.items: + itemcode = item.split('_')[0] + name = items[itemcode].name + price = '가격: '+currency(0, str(items[itemcode].price)) if items[itemcode].price else "" + embed.add_field(name=name, value=price, inline=False) + else: + name = "" + value = "" + # embed.add_field(name=name, value=value) + embed.set_footer(text=f"{currency(0, gold)}") + return embed + +def sold_item(gold): + title="아이템을 판매하였습니다." + desc=f"총 가격: {gold}" + color = Color.teal() + return Embed(title=title, description=desc, color=color) + +def random_box(title, count:int): + spin = [ + "/","―","\\","|" + ] + desc = spin[count%4] + embed = Embed(title=title, description=desc, color=Color.brand_red()) + return embed + +# 캐릭터 +def chara_talk(data:Conn.Chara, iconpath, content, color): + name = data.name + # embed = Embed(title=name, description=desc_converter(content), color=color) + embed = Embed(title=None, description=desc_converter(content), color=color) + icon = 'icon.png' + try: + file = File(iconpath, filename=icon) + except: + file = File(os.path.join(IMG_FOLDER_PATH, 'sample.png'), filename=icon) + embed.set_thumbnail(url=f"attachment://{icon}") + return embed, file + +def show_chara(data:Conn.Chara, imgpath="img/sample.png"): + name = data.name + keyword = data.keyword.replace(',',' / ') + desc = data.desc + link = f'[신청서 링크]({data.link})' + color = data.color or Color.light_grey().value + embed = Embed(title=name, color=Color(color)) + icon = 'icon.png' + try: + file = File(imgpath, filename=icon) + except: + file = File("img/sample.png", filename=icon) + embed.set_image(url=f"attachment://{icon}") + embed.add_field(name="키워드", value=keyword, inline=False) + embed.add_field(name="소개", value=desc, inline=False) + if data.stat: + stat_names = data.stat.stat_names + embed.add_field(name=stat_names[0], value=data.stat.stat1, inline=True) + embed.add_field(name=stat_names[1], value=data.stat.stat2, inline=True) + embed.add_field(name=stat_names[2], value=data.stat.stat3, inline=True) + embed.add_field(name=stat_names[3], value=data.stat.stat4, inline=True) + embed.add_field(name=stat_names[4], value=data.stat.stat5, inline=True) + embed.add_field(name=stat_names[5], value=data.stat.stat6, inline=True) + embed.add_field(name="스탯포인트", value=data.stat.point, inline=False) + embed.add_field(name="신청서", value=link, inline=False) + return embed, file + +def show_npc(data:Conn.NPC): + name = data.name + number = data.number + cases = data.case.split(',') + vers = data.vers + color = data.color or Color.light_grey().value + desc = '총 대사 수: ' + str(number) + embed = Embed(title=name, description=desc, color=Color(color)) + for case in cases: + embed.add_field(name=case, value=vers[cases.index(case)], inline=False) + return embed + +def show_stat_result(data:Conn.Chara): + title = f"{data.name}의 스탯적용을 완료하였습니다." + desc = f"남은 포인트: {data.stat.point}" + embed = Embed(title=title, description=desc, color=Color.brand_green()) + embed.add_field(name='체력', value=data.stat.stat1) + embed.add_field(name='힘', value=data.stat.stat2) + embed.add_field(name='지능', value=data.stat.stat3) + embed.add_field(name='관찰', value=data.stat.stat4) + embed.add_field(name='민첩', value=data.stat.stat5) + embed.add_field(name='운', value=data.stat.stat6) + return embed + +def anon_qna(question, answer): + title = "익명문의" + embed = Embed(title=title, description=question, color=Color.dark_gold()) + embed.add_field(name="답변", value=answer) + return embed + +# 투표 +def vote(timeout:int, op1:str, op2:str, op3:str, op4:str, op5:str): + embed = Embed(title='투표', color=Color.brand_green()) + if '/' in op1: + embed.add_field(name='1. '+op1.split('/')[0], value=op1.split('/')[1], inline=False) + else: + embed.add_field(name='1. '+op1.split('/')[0], value='', inline=False) + if '/' in op2: + embed.add_field(name='2. '+op2.split('/')[0], value=op2.split('/')[1], inline=False) + else: + embed.add_field(name='2. '+op2.split('/')[0], value='', inline=False) + if op3: + if '/' in op3: + embed.add_field(name='3. '+op3.split('/')[0], value=op3.split('/')[1], inline=False) + else: + embed.add_field(name='3. '+op3.split('/')[0], value='', inline=False) + if op4: + if '/' in op4: + embed.add_field(name='4. '+op4.split('/')[0], value=op4.split('/')[1], inline=False) + else: + embed.add_field(name='4. '+op4.split('/')[0], value='', inline=False) + if op5: + if '/' in op5: + embed.add_field(name='5. '+op5.split('/')[0], value=op5.split('/')[1], inline=False) + else: + embed.add_field(name='5. '+op5.split('/')[0], value='', inline=False) + time = '{0}시간 {1}분'.format(*divmod(timeout,60)) if timeout >= 60 else f'{timeout}분' + embed.set_footer(text=f'진행시간: {time}') + return embed + +def vote_result(total:int, data:dict[str, list[int, int]]): + embed = Embed(title="투표 결과 입니다", description=f"총 투표수: {total}", color=Color.light_grey()) + for key in list(data.keys()): + embed.add_field(name=f'{key} ({data[key][0]}%)', value='`'+'■'*(data[key][0]//10)+'□'*(10-data[key][0]//10)+' '+str(data[key][1])+'`', inline=False) + return embed diff --git a/JAS/resources/Exceptions.py b/JAS/resources/Exceptions.py new file mode 100644 index 0000000..f1429c9 --- /dev/null +++ b/JAS/resources/Exceptions.py @@ -0,0 +1,181 @@ +import traceback + +def exception_msg(code, str): + codeline = f'>> Error: {code}' + errorline = f'==={str[0]}===' + return codeline+' : '+errorline + +# General 00 +class ConnectionError(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E00-000' + def __str__(self): + return exception_msg(self.code, self.args) + +class NoToken(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E00-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class IncorrectPlace(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E00-003' + def __str__(self): + return exception_msg(self.code, self.args) + +# Manage 09 +class NoUser(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E09-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class VaildationError(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E09-002' + def __str__(self): + return exception_msg(self.code, self.args) + +# fishing 01 +class FishingLimit(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E01-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class FishingError(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E01-002' + def __str__(self): + return exception_msg(self.code, self.args) + +class CannotAddFish(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E01-003' + def __str__(self): + return exception_msg(self.code, self.args) + +class CannotChangeFish(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E01-004' + def __str__(self): + return exception_msg(self.code, self.args) + +class CannotDeleteFish(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E01-005' + def __str__(self): + return exception_msg(self.code, self.args) + +class FishNotFOund(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E01-404' + def __str__(self): + return exception_msg(self.code, self.args) + +# gather 02 +class GatherLimit(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E02-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class GatherError(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E02-002' + def __str__(self): + return exception_msg(self.code, self.args) + +class CannotAddGather(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E02-003' + def __str__(self): + return exception_msg(self.code, self.args) + +class CannotChangeGather(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E02-004' + def __str__(self): + return exception_msg(self.code, self.args) + +class CannotDeleteGather(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E02-005' + def __str__(self): + return exception_msg(self.code, self.args) + +# inventory 03 +class NotEnoughGold(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E03-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class NotEnoughSpace(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E03-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class SellingError(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E03-002' + def __str__(self): + return exception_msg(self.code, self.args) + +class ItemNotFound(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E03-003' + def __str__(self): + return exception_msg(self.code, self.args) + +# Community 05 +class NoChara(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E05-001' + def __str__(self): + return exception_msg(self.code, self.args) + +# Licensing 90 +class NoLicense(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E90-000' + def __str__(self): + return exception_msg(self.code, self.args) + +class IncorrectLicense(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E90-001' + def __str__(self): + return exception_msg(self.code, self.args) + +class ExpiredLicense(Exception): + def __init__(self, *args: object): + self.args = args + self.code = 'E90-002' + def __str__(self): + return exception_msg(self.code, self.args) \ No newline at end of file diff --git a/JAS/resources/Selects.py b/JAS/resources/Selects.py new file mode 100644 index 0000000..baf8c83 --- /dev/null +++ b/JAS/resources/Selects.py @@ -0,0 +1,301 @@ +import discord +from discord import Interaction +from discord.components import SelectOption +from discord.interactions import Interaction +from discord.ui import Select + +class UserSelect(Select): + def __init__(self, data): + options = self.options(data) + super().__init__(placeholder="대상을 선택해 주세요", options=options, row=0) + + def options(self, data): + options = [] + for user in data: + label = user[1] + value = user[0] + option = discord.SelectOption(label=label, value=value) + options.append(option) + return options + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class UserManageSelect(Select): + def __init__(self): + menus = {"유저 정보":"유저정보 확인"} + options = [] + for i in menus.keys(): + label = menus[i] + option = SelectOption(label=label, value=i) + options.append(option) + + super().__init__(options=options) + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class InventoryManageSelect(Select): + def __init__(self): + menus = {"인벤토리 골드":"유저 골드 추가", "인벤토리 크기":"인벤토리 크기 조정"} + options = [] + for i in menus.keys(): + label = menus[i] + option = SelectOption(label=label, value=i) + options.append(option) + + super().__init__(options=options) + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class InventoryGoldSelect(Select): + def __init__(self): + placeholder = "지급할 금액을 선택해 주세요" + + options = [] + for i in range(20,-21, -5): + if i != 0: + label = str(i) + option = SelectOption(label=label, value=i) + options.append(option) + + super().__init__(options=options, placeholder=placeholder, row=1) + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class InventorySizeSelect(Select): + def __init__(self): + placeholder = "변경할 인벤토리 크기를 선택해 주세요" + + options = [] + for i in range(5,-6, -1): + if i != 0: + label = str(i) + option = SelectOption(label=label, value=i) + options.append(option) + + super().__init__(options=options, placeholder=placeholder, row=1) + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class StatManageSelect(Select): + def __init__(self): + menus = {"스탯 변경":"스탯명 변경", } + options = [] + for i in menus.keys(): + label = menus[i] + option = SelectOption(label=label, value=i) + options.append(option) + + super().__init__(options=options) + + async def callback(self, interaction:Interaction): + print(self.values) + await interaction.response.defer() + +class SystemManageSelect(Select): + def __init__(self): + menus = {"시스템 확인":"설정확인", "시스템 회원가입":"화원가입 허용 여부 변경", "시스템 변경":"기본수치 변경"} + options = [] + for i in menus.keys(): + label = menus[i] + option = SelectOption(label=label, value=i) + options.append(option) + + super().__init__(options=options) + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class ValueChange(Select): + def __init__(self, type, manage): + if type == 1: + placeholder = "최대 낚시 횟수" + base=manage.max_fishing + valuerange = range(1,11) + elif type == 2: + placeholder = "최대 수집 횟수" + base=manage.max_gather + valuerange = range(1,11) + elif type == 3: + placeholder = "기본 가방 크기" + base=manage.base_inv_size + valuerange = range(5,21,5) + elif type == 4: + placeholder = "최초 소지 골드" + base=manage.base_inv_gold + valuerange = range(5,31,5) + + options = [] + for i in valuerange: + label = str(i) + default = True if i == base else False + option = SelectOption(label=label, default=default) + options.append(option) + + super().__init__(options=options, placeholder=placeholder) + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +class SettingValueSelect(Select): + def __init__(self): + placeholder = '변경하고자 하는 항목을 선택해 주세요.' + options = self.options() + super().__init__(placeholder=placeholder, options=options, row=0) + + def options(self): + data = [ + ['구글 연동 링크','gspread_key','DB 데이터가 연동될 구글 스프레드 시트 링크'], + ['최대 낚시 횟수','max_fishing','하루에 최대로 낚시 가능한 기본 횟수'], + ['최대 채집 횟수','max_gather','하루에 최대로 채집 가능한 기본 횟수'], + ['기본 가방 크기','base_inv_size','캐릭터 생성시 부여받게되는 기본 인벤토리 크기'], + ['최초 소지 골드','base_inv_gold','캐릭터 생성시 부여받게되는 기본 골드'], + ['랜덤 박스 가격','random_box','랜덤 박스 가격'], + ] + + options = [] + for item in data: + options.append(SelectOption(label=item[0], value=item[1], description=item[2])) + return options + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +# NPC 선택 +class NPCSelect(Select): + def __init__(self, data) -> None: + placeholder = 'NPC를 선택해 주세요' + options = self.options(data) + super().__init__(placeholder=placeholder, options=options, row=0) + + def options(self, data): + options = [] + for item in data: + options.append(SelectOption(label=item[0], value=item[1])) + return options + + async def callback(self, interaction:Interaction): + print(self.values) + await interaction.response.defer() + +# 물고기 선택 +class FishSelect(Select): + def __init__(self, manage) -> None: + self.manage = manage + placeholder = "물고기를 선택해 주세요." + options = self.get_options() + super().__init__(placeholder=placeholder, min_values=1, max_values=1, options=options, row=0) + + def get_options(self): + options = [] + for fish in list(self.manage.keys()): + label = self.manage[fish].name + code = fish + options.append(SelectOption(label=label, value=code)) + return options + + async def callback(self, interaction): + print(self.values) + await interaction.response.defer() + +# 인벤토리 아이템 선택 +class InventoryItemSelect(Select): + def __init__(self, data, items, count=None) -> None: + placeholder = "아이템을 선택해 주세요" + options = self.options(data, items) + max_values = count or len(data) + super().__init__(placeholder=placeholder, min_values=1, max_values=max_values, options=options, row=0) + + def options(self, data:dict, items): + options = [] + for item in data: + label = items[item].name + value = item + desc = f'{items[item].price} {items[item].desc}' + option = SelectOption(label=label,value=value,description=desc) + options.append(option) + return options + + async def callback(self, interaction: Interaction) -> any: + print(self.values) + await interaction.response.defer() + +# 전체 아이템 선택 +class ItemSelect(Select): + def __init__(self, items) -> None: + placeholder = "아이템을 선택해 주세요" + options = self.options(items) + max_values = 5 + super().__init__(placeholder=placeholder, min_values=1, max_values=max_values, options=options, row=0) + + def options(self, items): + options = [] + print(items) + for item in items: + if "_" in item: + continue + label = items[item].name + value = item + desc = f'{items[item].price} {items[item].desc}' + option = SelectOption(label=label,value=value,description=desc) + options.append(option) + return options + + async def callback(self, interaction: Interaction) -> any: + print(self.values) + await interaction.response.defer() + +# 색상 선택 +class ColorSelect(Select): + def __init__(self): + placeholder = "원하는 색상을 선택해 주세요" + options = self.options() + super().__init__(placeholder=placeholder, min_values=1, max_values=1, options=options, row=0) + + def options(self): + return [ + SelectOption(label="teal",value=discord.Color.teal().value), + SelectOption(label="dark_teal",value=discord.Color.dark_teal().value), + SelectOption(label="brand_green",value=discord.Color.brand_green().value), + SelectOption(label="green",value=discord.Color.green().value), + SelectOption(label="dark_green",value=discord.Color.dark_green().value), + SelectOption(label="blue",value=discord.Color.blue().value), + SelectOption(label="purple",value=discord.Color.purple().value), + SelectOption(label="dark_purple",value=discord.Color.dark_purple().value), + SelectOption(label="magenta",value=discord.Color.magenta().value), + SelectOption(label="dark_magenta",value=discord.Color.dark_magenta().value), + SelectOption(label="gold",value=discord.Color.gold().value), + SelectOption(label="dark_gold",value=discord.Color.dark_gold().value), + SelectOption(label="orange",value=discord.Color.orange().value), + SelectOption(label="dark_orange",value=discord.Color.dark_orange().value), + SelectOption(label="brand_red",value=discord.Color.brand_red().value), + SelectOption(label="lighter_grey",value=discord.Color.lighter_grey().value), + SelectOption(label="dark_grey",value=discord.Color.dark_grey().value), + SelectOption(label="light_grey",value=discord.Color.light_grey().value), + SelectOption(label="darker_grey",value=discord.Color.darker_grey().value), + SelectOption(label="og_blurple",value=discord.Color.og_blurple().value), + SelectOption(label="blurple",value=discord.Color.blurple().value), + SelectOption(label="greyple",value=discord.Color.greyple().value), + SelectOption(label="fuchsia",value=discord.Color.fuchsia().value), + SelectOption(label="yellow",value=discord.Color.yellow().value), + SelectOption(label="pink",value=discord.Color.pink().value), + ] + + async def callback(self, interaction: Interaction) -> any: + print(self.values[0]) + await interaction.response.defer() + # color = discord.Color(int(self.values[0])) + # emb = discord.Embed(title='원하시는 색상을 선택해 주세요',color=color) + # await interaction.edit_original_response(embed=emb) diff --git a/JAS/resources/Views.py b/JAS/resources/Views.py new file mode 100644 index 0000000..8258e7a --- /dev/null +++ b/JAS/resources/Views.py @@ -0,0 +1,1040 @@ +import discord +from discord.enums import ButtonStyle +from discord.interactions import Interaction +from discord.ui import View, TextInput, Modal, Button +from discord.ext.commands import Cog +from discord import ButtonStyle, TextStyle, ui +from JAS.resources import Buttons, Embeds, Selects +from JAS.resources.Exceptions import * +import JAS.resources.Connector as Conn + +async def cancel_message(interaction:Interaction): + await interaction.message.delete() + # return await interaction.response.send_message(embed=Embeds.general('작업을 취소합니다.'), delete_after=5, ephemeral=True) + +async def delete_message(interaction:Interaction): + await interaction.response.defer() + await interaction.message.delete() + +timeout = 300 + +class templateView(View): + def __init__(self): + super().__init__(timeout=timeout) + + @ui.button(label="선택", style=ButtonStyle.primary, row=1) + async def add_callback(self, interaction:Interaction, button): + self.finish = True + await delete_message(interaction) + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction:Interaction, button): + self.stop() + +class templateModal(Modal): + def __init__(self): + title = "제목" + super().__init__(title=title, timeout=timeout) + + self.item = TextInput( + label="제목", + placeholder="설명", + max_length=100, + required=True, + ) + self.add_item(self.item) + + async def on_submit(self, interaction:Interaction) -> None: + await interaction.response.defer() + self.stop() + +# 송금 뷰 +class TransferView(View): + def __init__(self): + super().__init__(timeout=timeout) + self.cancel = False + # self.select = select.UserSelect(data) + # self.add_item(self.select) + + @ui.button(label="송금",row=1) + async def button_callback(self, interaction=discord.Interaction, button=Buttons): + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary,row=1) + async def select_callback(self, interaction=Interaction, button=Buttons): + self.cancel = True + # await interaction.edit_original_response(embed=embed.general('작업을 취소합니다.'), delete_after=5) + self.stop() + +class ManageView(View): + def __init__(self): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + + @ui.button(label="유저 관리", custom_id="User Management", row=0) + async def user_manage_callback(self, interaction:Interaction, button): + await interaction.response.defer() + self.result="유저" + self.stop() + + @ui.button(label="인벤토리 관리", custom_id="Inventory Management", row=1) + async def inventory_manage_callback(self, interaction:Interaction, button): + await interaction.response.defer() + self.result="인벤토리" + self.stop() + + @ui.button(label="스탯 관리", custom_id="Stat Management", row=1) + async def inventory_manage_callback(self, interaction:Interaction, button): + await interaction.response.defer() + self.result="스탯" + self.stop() + + # @ui.button(label="이벤트 관리", custom_id="Event Management", row=2) + # async def event_manage_callback(self, interaction:Interaction, button): + # await interaction.response.defer() + # self.result="이벤트" + # self.stop() + + @ui.button(label="시스템 관리", custom_id="System Management", row=3) + async def system_manage_callback(self, interaction:Interaction, button): + await interaction.response.defer() + self.result="시스템" + self.stop() + + @ui.button(label="취소", custom_id="cancel", row=4) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +# sub Views +# 유저 관리 뷰 +class UserManageView(View): + def __init__(self): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.select = Selects.UserManageSelect() + self.add_item(self.select) + + @ui.button(label="확인", style=ButtonStyle.primary, row=1) + async def confirm_callback(self, interaction:Interaction, button): + self.result = self.select.values[0] + await interaction.response.defer() + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=1) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +class UserInfo(View): + def __init__(self, manage:Conn.Connector, data): + self.result = None + self.cancel = None + self.manage = manage + super().__init__(timeout=timeout) + self.select_user = Selects.UserSelect(data) + self.add_item(self.select_user) + + @ui.button(label="선택", style=ButtonStyle.primary, row=1) + async def select_callback(self, interaction:Interaction, button): + selected_user=self.select_user.values[0] + id = int(selected_user.split(',')[0]) + await interaction.response.edit_message(embed=Embeds.show_user(self.manage, id), view=None) + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=1) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +# 인벤토리 관리 뷰 +class InventoryManageView(View): + def __init__(self, manage:Conn.Connector): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + self.select = Selects.InventoryManageSelect() + self.add_item(self.select) + + @ui.button(label="확인", style=ButtonStyle.primary, row=1) + async def confirm_callback(self, interaction:discord.Interaction, button): + self.result = self.select.values[0] + await interaction.response.defer() + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=1) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +class InventoryGoldChange(View): + def __init__(self, manage:Conn.Connector, data): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + self.data = data + self.user = Selects.UserSelect(data) + self.gold = Selects.InventoryGoldSelect() + self.add_item(self.user) + self.add_item(self.gold) + + @ui.button(label="확인", style=ButtonStyle.primary, row=2) + async def confirm_callback(self, interaction:discord.Interaction, button): + user = int(self.user.values[0]) + # name = list(filter(lambda x:x[0]==user, self.data))[0][1] + code = self.manage.data.user.data[user].charas + chara = self.manage.data.chara.data[code] + name = chara.name + gold = int(self.gold.values[0]) + self.manage.update_charactor_inventory(code=code, gold=gold) + after_gold = self.manage.data.inventory.data[code].gold + await interaction.response.edit_message(embed=Embeds.setting(f'{name}에게 G {gold}를 전달하였습니다', f'소지금액: {after_gold}'), view=None) + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=2) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +class InventorySizeChange(View): + def __init__(self, manage:Conn.Connector, data): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + self.data = data + self.user = Selects.UserSelect(data) + self.size = Selects.InventorySizeSelect() + self.add_item(self.user) + self.add_item(self.size) + + @ui.button(label="확인", style=ButtonStyle.primary, row=2) + async def confirm_callback(self, interaction:discord.Interaction, button): + user = int(self.user.values[0]) + # name = list(filter(lambda x:x[0]==user, self.data))[0][1] + code = self.manage.data.user.data[user].charas + chara = self.manage.data.chara.data[code] + name = chara.name + size = int(self.size.values[0]) + self.manage.update_charactor_inventory(code=code, size=size) + after_size = self.manage.data.inventory.data[code].size + await interaction.response.edit_message(embed=Embeds.setting(f'{name}의 가방 공간이 {size} 변경 되었습니다.', f'가방크기: {after_size}'), view=None) + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=2) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +# 스탯 관리 뷰 +class StatManageView(View): + def __init__(self, manage:Conn.Connector): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + self.select = Selects.StatManageSelect() + self.add_item(self.select) + + @ui.button(label="확인", style=ButtonStyle.primary, row=1) + async def confirm_callback(self, interaction:Interaction, button): + self.result = self.select.values[0] + await interaction.response.defer() + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=1) + async def cancel_callback(self, interaction, button): + self.cancel=True + await cancel_message(interaction) + self.stop() + +class StatNameChange(View): + def __init__(self, manage:Conn.Connector): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + + @ui.button(label="확인", style=ButtonStyle.primary, row=1) + async def confirm_callback(self, interaction:Interaction, button): + modal = ChangeStatName(','.join(self.manage.data.setting.data.stat_names)) + await interaction.response.send_modal(modal) + await modal.wait() + if modal.finish: + values = [name.strip() for name in modal.setting.value.split(',')] + if len(values) != 6 and not (len(values) == 1 and not values[0]): + for _ in range(len(values),6): + values.append('-') + value = ','.join(values) + self.manage.update_stat_name(value) + await interaction.followup.edit_message(interaction.message.id, embed=Embeds.setting('스탯 변경완료',f'변경된 스탯이 적용되었습니다.\n{value}'), view=None) + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=1) + async def cancel_callback(self, interaction, button): + self.cancel=True + await cancel_message(interaction) + self.stop() + +class ChangeStatName(Modal): + def __init__(self, default) -> None: + self.finish = False + super().__init__(title="스탯명 변경", timeout=timeout) + + self.setting = TextInput( + label='스탯명\n쉼표로 구분하여 기입해 주세요', + required=False, + max_length=300, + default=default + ) + self.add_item(self.setting) + + async def on_submit(self, interaction:Interaction): + self.finish = True + await interaction.response.defer() + + +# 시스템 관리 뷰 +class SystemManageView(View): + def __init__(self): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.add_item(Selects.SystemManageSelect()) + + @ui.button(label="확인", style=ButtonStyle.primary, row=1) + async def confirm_callback(self, interaction:Interaction, button): + self.result = self.children[2].values[0] + await interaction.response.defer() + self.stop() + + @ui.button(label="취소", style=ButtonStyle.secondary, row=1) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +class RegView(View): + def __init__(self, manage:Conn.Connector): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + + @ui.button(label="O") + async def open_callback(self, interaction:Interaction, button): + self.manage.__set_setting_value__("accept_user", "True") + await interaction.response.edit_message(embed=Embeds.setting('회원가입을 허용합니다.'),view=None) + self.stop() + + @ui.button(label="X") + async def close_callback(self, interaction:Interaction, button): + self.manage.__set_setting_value__("accept_user", "False") + await interaction.response.edit_message(embed=Embeds.setting('회원가입을 차단합니다.'),view=None) + self.stop() + +class ChangeSettingView(View): + def __init__(self, manage:Conn.Connector): + self.result = None + self.cancel = None + super().__init__(timeout=timeout) + self.manage = manage + self.select = Selects.SettingValueSelect() + self.add_item(self.select) + + @ui.button(label="확인", row=1) + async def open_callback(self, interaction:Interaction, button): + key = self.select.values[0] + values = { + "gspread_key":[self.manage.data.setting.data.gspread_key,'구글 연동 링크',2000], + "max_fishing":[self.manage.data.setting.data.max_fishing,'최대 낚시 횟수',2], + "max_gather":[self.manage.data.setting.data.max_gather,'최대 채집 횟수',2], + "base_inv_size":[self.manage.data.setting.data.base_inv_size,'기본 인벤토리 크기',2], + "base_inv_gold":[self.manage.data.setting.data.base_inv_gold,'기본 인벤토리 골드',2], + "random_box":[self.manage.data.setting.data.random_box,'랜덤박스 비용',2], + } + default, label, max_length = values[key] + modal = ChangeSetting(default, label, max_length) + await interaction.response.send_modal(modal) + await modal.wait() + if modal.finish: + await interaction.message.delete() + # if key == 'gspread_key': + # result = modal.setting.value + # else: + # result = int(modal.setting.value) + self.manage.__set_setting_value__(key, modal.setting.value) + await interaction.channel.send(embed=Embeds.show_system('설정 적용이 완료되었습니다.', self.manage.data.setting.data, '[관리 ▶ 시스템 ▶ 변경]')) + self.stop() + + @ui.button(label="취소", row=1) + async def close_callback(self, interaction:Interaction, button): + self.cancel = True + await interaction.response.defer() + self.stop() + +class ChangeSetting(Modal): + def __init__(self, default, label, max_length) -> None: + self.finish = False + super().__init__(title="설정 변경", timeout=timeout) + + self.setting = TextInput( + label=label, + required=True, + max_length=max_length, + default=default + ) + self.add_item(self.setting) + + async def on_submit(self, interaction:Interaction): + self.finish = True + await interaction.response.defer() + +# 낚시 +class FishingView(View): + label = "label" + style = ButtonStyle.secondary + + def __init__(self, hooked): + super().__init__(timeout=1.5) + self.hooked=hooked + self.result = False + if self.hooked: + self.children[0].label = "지금이야!" + self.children[0].style = ButtonStyle.primary + + @ui.button(label = "대기중...") + async def callback(self, interaction, button): + self.stop() + self.result = True + +class GetFishInfo(Modal): + def __init__(self, data=None) -> None: + title = "물고기 정보" + super().__init__(title=title, timeout=timeout) + + self.desc = TextInput( + label="물고기 설명", + placeholder="물고기에 대한 설명을 입력해 주세요", + default=data.desc if data else None, + style=TextStyle.paragraph, + max_length=100, + required=True, + ) + self.min = TextInput( + label="최소 길이", + placeholder="물고기의 최소 길이를 입력해 주세요", + default=data.min if data else None, + max_length=3, + required=True, + ) + self.max = TextInput( + label="최대 길이", + placeholder="물고기의 최대 길이를 입력해 주세요", + default=data.max if data else None, + max_length=3, + required=True + ) + self.baseprice = TextInput( + label="기본 가격", + placeholder="물고기가 팔릴 가격을 입력해 주세요", + default=data.baseprice if data else None, + max_length=20, + min_length=1, + required=True + ) + self.loc = TextInput( + label="등장위치(쉼표로 구분)", + placeholder="물고기가 등장할 낚시터 채널명을 입력해 주세요.", + default=data.loc if data else None, + required=True, + max_length=20 + ) + self.add_item(self.desc) + self.add_item(self.min) + self.add_item(self.max) + self.add_item(self.baseprice) + self.add_item(self.loc) + + async def on_submit(self, interaction) -> None: + await interaction.response.defer() + self.stop() + +class AddFishView(View): + def __init__(self): + super().__init__(timeout=timeout) + self.cancel = False + self.modal = GetFishInfo() + + @ui.button(label="추가", style=ButtonStyle.primary) + async def add_callback(self, interaction, button): + await interaction.message.delete() + await interaction.response.send_modal(self.modal) + await self.modal.wait() + self.stop() + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class SelectFishView(View): + def __init__(self, manage): + super().__init__(timeout=timeout) + self.fish = Selects.FishSelect(manage) + self.add_item(self.fish) + self.cancel = False + + @ui.button(label="선택", style=ButtonStyle.primary, row=1) + async def add_callback(self, interaction:Interaction, button): + await delete_message(interaction) + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + + +class ChangeFishView(View): + def __init__(self, data): + super().__init__(timeout=timeout) + self.modal = GetFishInfo(data) + self.data = data + self.cancel = False + + @ui.button(label="변경", style=ButtonStyle.primary) + async def change_callback(self, interaction, button): + await interaction.message.delete() + await interaction.response.send_modal(self.modal) + await self.modal.wait() + self.stop() + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class ConfirmFishDelete(View): + def __init__(self): + self.cancel = False + super().__init__(timeout=timeout) + + @ui.button(label="삭제", style=ButtonStyle.danger) + async def delete_callback(self, interaction, button): + await delete_message(interaction) + self.stop() + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +# 캐릭터 +class CharaInfo(Modal): + def __init__(self, title, data=None) -> None: + self.finish = False + super().__init__(title=title, timeout=timeout) + + self.name = TextInput( + label="이름", + placeholder="캐릭터의 이름", + default=data.name if data else None, + required=True, + max_length=20 + ) + self.keyword = TextInput( + label="키워드(쉼표로 구분)", + placeholder="캐릭터를 나타내는 키워드를 입력해 주세요", + default=data.keyword if data else None, + style=TextStyle.paragraph, + required=False, + max_length=100 + ) + self.desc = TextInput( + label="간단설명", + placeholder="캐릭터에 대한 간단한 설명 및 소개말", + default=data.desc if data else None, + style=TextStyle.paragraph, + required=False, + max_length=1000 + ) + self.link = TextInput( + label="신청서 링크", + placeholder="신청서 링크", + default=data.link if data else None, + required=True, + max_length=1000 + ) + self.add_item(self.name) + self.add_item(self.keyword) + self.add_item(self.desc) + self.add_item(self.link) + + async def on_submit(self, interaction) -> None: + self.finish = True + await interaction.response.defer() + self.stop() + + +class AddChara(View): + def __init__(self): + title = f'캐릭터 정보' + self.info = CharaInfo(title) + self.cancel = False + super().__init__(timeout=timeout) + + @ui.button(label="등록", style=ButtonStyle.primary) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.message.delete() + await interaction.response.send_modal(self.info) + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class ChangeChara(View): + def __init__(self, name, data): + self.cancel = False + title = f'{name}를 변경합니다.' + self.info = CharaInfo(title, data) + super().__init__(timeout=timeout) + + @ui.button(label="변경", style=ButtonStyle.primary) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.message.delete() + await interaction.response.send_modal(self.info) + await self.info.wait() + self.stop() + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class RemoveChara(View): + def __init__(self, name): + self.name = name + self.delete = False + self.cancel = False + super().__init__(timeout=timeout) + + @ui.button(label="삭제", style=ButtonStyle.danger) + async def add_callback(self, interaction:discord.Interaction, button): + self.delete = True + await interaction.response.edit_message(embed=Embeds.setting(f"{self.name}을/를 삭제하였습니다."), view=None) + self.stop() + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class NPCInfo(Modal): + def __init__(self, data:Conn.NPC=None) -> None: + title = "NPC 정보" + self.finish=False + self.data = data + vers_placeholder = '(상황)/(대사) 와 같은 형식으로 기입해 주세요' + super().__init__(title=title, timeout=timeout) + + self.name = TextInput( + label="NPC 이름", + default=data.name if data else '', + required=True, + max_length=20 + ) + self.vers1 = TextInput( + label="캐릭터의 대사1", + style=TextStyle.paragraph, + default='' if not data else data.case.split(',')[0]+'/'+data.vers[0] if data.number>=0 else '', + placeholder=vers_placeholder, + max_length=500 + ) + self.vers2 = TextInput( + label="캐릭터의 대사2", + style=TextStyle.paragraph, + default='' if not data else data.case.split(',')[1]+'/'+data.vers[1] if data.number>=1 else '', + placeholder=vers_placeholder, + required=False, + max_length=500 + ) + self.vers3 = TextInput( + label="캐릭터의 대사3", + style=TextStyle.paragraph, + default='' if not data else data.case.split(',')[2]+'/'+data.vers[2] if data.number>=2 else '', + placeholder=vers_placeholder, + required=False, + max_length=500 + ) + self.vers4 = TextInput( + label="캐릭터의 대사4", + style=TextStyle.paragraph, + default='' if not data else data.case.split(',')[3]+'/'+data.vers[3] if data.number>=3 else '', + placeholder=vers_placeholder, + required=False, + max_length=500 + ) + self.add_item(self.name) + self.add_item(self.vers1) + self.add_item(self.vers2) + self.add_item(self.vers3) + self.add_item(self.vers4) + + async def on_submit(self, interaction:Interaction): + self.finish = True + await interaction.response.defer() + self.stop() + +class AddNPC(View): + def __init__(self): + title = f'캐릭터 정보' + self.info = CharaInfo(title) + self.cancel = False + super().__init__(timeout=timeout) + + @ui.button(label="등록", style=ButtonStyle.primary) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.message.delete() + await interaction.response.send_modal(self.info) + await self.info.wait() + self.stop() + + @ui.button(label="취소") + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class UpdateNPC(View): + def __init__(self, npc_list, npc_data): + self.cancel = False + self.data = npc_data + self.select = Selects.NPCSelect(npc_list) + super().__init__(timeout=timeout) + self.add_item(self.select) + + @ui.button(label="선택", style=ButtonStyle.primary, row=1) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.message.delete() + self.modal = NPCInfo(self.data[self.select.values[0]]) + await interaction.response.send_modal(self.modal) + await self.modal.wait() + if not self.modal.finish: + self.cancel = True + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction:discord.Interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class RemoveNPC(View): + def __init__(self, npc_list): + self.cancel = False + self.select = Selects.NPCSelect(npc_list) + super().__init__(timeout=timeout) + self.add_item(self.select) + + @ui.button(label="삭제", style=ButtonStyle.danger, row=1) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.message.delete() + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction:discord.Interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class SelectNPC(View): + def __init__(self, npc_list): + self.cancel = False + self.select = Selects.NPCSelect(npc_list) + super().__init__(timeout=timeout) + self.add_item(self.select) + + @ui.button(label="선택", style=ButtonStyle.primary, row=1) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.message.delete() + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction:discord.Interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class CharaColor(View): + def __init__(self): + self.finish = False + super().__init__(timeout=300) + self.select = Selects.ColorSelect() + self.add_item(self.select) + + @ui.button(label="선택", style=ButtonStyle.primary, row=1) + async def finish_callback(self, interaction:Interaction, button): + self.finish = True + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction, button): + self.stop() + +# 인벤토리 +class SpawnItem(View): + def __init__(self, items): + self.cancel = False + super().__init__(timeout=timeout) + self.select = Selects.ItemSelect(items) + self.add_item(self.select) + + @ui.button(label="부여", style=ButtonStyle.primary, row=1) + async def confirm_callback(self, interaction:Interaction, button): + await delete_message(interaction) + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + +class SoldItemView(View): + def __init__(self, data, items): + self.cancel = False + super().__init__(timeout=timeout) + self.select = Selects.InventoryItemSelect(data, items) + self.add_item(self.select) + + @ui.button(label="판매", style=ButtonStyle.primary, row=1) + async def buy_callback(self, interaction:Interaction, button:Button): + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction:Interaction, button): + self.cancel = True + self.stop() + +# 스탯 +class SpendStat(View): + def __init__(self, user:discord.Member): + self.cancel = False + self.finish = False + self.user_id = user.id + super().__init__(timeout=timeout) + + @ui.button(label="적용", style=ButtonStyle.primary, row=1) + async def add_callback(self, interaction:discord.Interaction, button): + if self.user_id != interaction.user.id: + return await interaction.response.send_message('스탯은 본인만 조정할 수 있습니다', ephemeral=True) + self.finish = True + await interaction.response.defer() + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction:discord.Interaction, button): + if self.user_id != interaction.user.id: + return await interaction.response.send_message('스탯은 본인만 조정할 수 있습니다', ephemeral=True) + self.cancel = True + await cancel_message(interaction) + self.stop() + +class AllStat(View): + def __init__(self, type, data:Conn.Stat, user:discord.Member): + super().__init__(timeout=timeout) + self.user_id = user.id + self.point = data.point + if type == 1: + self.stat1_origin = data.stat1 + self.stat1_value = data.stat1 + self.stat2_origin = data.stat2 + self.stat2_value = data.stat2 + self.stat3_origin = data.stat3 + self.stat3_value = data.stat3 + self.stat1_sub = Buttons.sub_stat(0, user) + self.stat1_current = Buttons.current_stat(data.stat_names[0], self.stat1_value,0) + self.stat1_add = Buttons.add_stat(0, user) + self.stat2_sub = Buttons.sub_stat(1, user) + self.stat2_current = Buttons.current_stat(data.stat_names[1],self.stat2_value,1) + self.stat2_add = Buttons.add_stat(1, user) + self.stat3_sub = Buttons.sub_stat(2, user) + self.stat3_current = Buttons.current_stat(data.stat_names[2],self.stat3_value,2) + self.stat3_add = Buttons.add_stat(2, user) + if self.stat1_origin==self.stat1_value: + self.stat1_sub.disabled=True + if self.stat2_origin==self.stat2_value: + self.stat2_sub.disabled=True + if self.stat3_origin==self.stat3_value: + self.stat3_sub.disabled=True + if self.point==0: + self.stat1_add.disabled=True + self.stat2_add.disabled=True + self.stat3_add.disabled=True + self.add_item(self.stat1_sub) + self.add_item(self.stat1_current) + self.add_item(self.stat1_add) + self.add_item(self.stat2_sub) + self.add_item(self.stat2_current) + self.add_item(self.stat2_add) + self.add_item(self.stat3_sub) + self.add_item(self.stat3_current) + self.add_item(self.stat3_add) + elif type == 2: + self.stat4_origin = data.stat4 + self.stat4_value = data.stat4 + self.stat5_origin = data.stat5 + self.stat5_value = data.stat5 + self.stat6_origin = data.stat6 + self.stat6_value = data.stat6 + self.stat4_sub = Buttons.sub_stat(0, user) + self.stat4_current = Buttons.current_stat(data.stat_names[3], self.stat4_value,0) + self.stat4_add = Buttons.add_stat(0, user) + self.stat5_sub = Buttons.sub_stat(1, user) + self.stat5_current = Buttons.current_stat(data.stat_names[4],self.stat5_value,1) + self.stat5_add = Buttons.add_stat(1, user) + self.stat6_sub = Buttons.sub_stat(2, user) + self.stat6_current = Buttons.current_stat(data.stat_names[5],self.stat6_value,2) + self.stat6_add = Buttons.add_stat(2, user) + if self.stat4_origin==self.stat4_value: + self.stat4_sub.disabled=True + if self.stat5_origin==self.stat5_value: + self.stat5_sub.disabled=True + if self.stat6_origin==self.stat6_value: + self.stat6_sub.disabled=True + if self.point==0: + self.stat4_add.disabled=True + self.stat5_add.disabled=True + self.stat6_add.disabled=True + self.add_item(self.stat4_sub) + self.add_item(self.stat4_current) + self.add_item(self.stat4_add) + self.add_item(self.stat5_sub) + self.add_item(self.stat5_current) + self.add_item(self.stat5_add) + self.add_item(self.stat6_sub) + self.add_item(self.stat6_current) + self.add_item(self.stat6_add) + elif type == 3: + self.stat1_origin = data.stat1 + self.stat1_value = data.stat1 + self.stat2_origin = data.stat2 + self.stat2_value = data.stat2 + self.stat3_origin = data.stat3 + self.stat3_value = data.stat3 + self.stat4_origin = data.stat4 + self.stat4_value = data.stat4 + self.stat5_origin = data.stat5 + self.stat5_value = data.stat5 + self.stat1_sub = Buttons.sub_stat(0, user) + self.stat1_current = Buttons.current_stat(data.stat_names[0], self.stat1_value,0) + self.stat1_add = Buttons.add_stat(0, user) + self.stat2_sub = Buttons.sub_stat(1, user) + self.stat2_current = Buttons.current_stat(data.stat_names[1],self.stat2_value,1) + self.stat2_add = Buttons.add_stat(1, user) + self.stat3_sub = Buttons.sub_stat(2, user) + self.stat3_current = Buttons.current_stat(data.stat_names[2],self.stat3_value,2) + self.stat3_add = Buttons.add_stat(2, user) + self.stat4_sub = Buttons.sub_stat(0, user) + self.stat4_current = Buttons.current_stat(data.stat_names[3], self.stat4_value,0) + self.stat4_add = Buttons.add_stat(0, user) + self.stat5_sub = Buttons.sub_stat(1, user) + self.stat5_current = Buttons.current_stat(data.stat_names[4],self.stat5_value,1) + self.stat5_add = Buttons.add_stat(1, user) + if self.stat1_origin==self.stat1_value: + self.stat1_sub.disabled=True + if self.stat2_origin==self.stat2_value: + self.stat2_sub.disabled=True + if self.stat3_origin==self.stat3_value: + self.stat3_sub.disabled=True + if self.stat4_origin==self.stat4_value: + self.stat4_sub.disabled=True + if self.stat5_origin==self.stat5_value: + self.stat5_sub.disabled=True + if self.point==0: + self.stat1_add.disabled=True + self.stat2_add.disabled=True + self.stat3_add.disabled=True + self.stat4_add.disabled=True + self.stat5_add.disabled=True + self.add_item(self.stat1_sub) + self.add_item(self.stat1_current) + self.add_item(self.stat1_add) + self.add_item(self.stat2_sub) + self.add_item(self.stat2_current) + self.add_item(self.stat2_add) + self.add_item(self.stat3_sub) + self.add_item(self.stat3_current) + self.add_item(self.stat3_add) + self.add_item(self.stat4_sub) + self.add_item(self.stat4_current) + self.add_item(self.stat4_add) + self.add_item(self.stat5_sub) + self.add_item(self.stat5_current) + self.add_item(self.stat5_add) + +class Ticket(View): + def __init__(self, data): + self.cancel = False + super().__init__(timeout=timeout) + self.modal = self.Response(data) + + @ui.button(label="답변", style=ButtonStyle.primary, row=1) + async def add_callback(self, interaction:discord.Interaction, button): + await interaction.response.send_modal(self.modal) + await self.modal.wait() + if self.modal.finish: + await interaction.message.edit(view = None) + self.stop() + + @ui.button(label="취소", row=1) + async def cancel_callback(self, interaction, button): + self.cancel = True + await cancel_message(interaction) + self.stop() + + class Response(Modal): + def __init__(self, data=None) -> None: + self.finish = False + title = "답변 작성" + super().__init__(title=title, timeout=timeout) + + self.question = TextInput( + label="질문내용", + default=data, + style=TextStyle.paragraph, + ) + self.content = TextInput( + label="답변", + placeholder="질문에 대한 답변", + style=TextStyle.paragraph, + required=True, + max_length=1000 + ) + self.add_item(self.question) + self.add_item(self.content) + + async def on_submit(self, interaction:Interaction) -> None: + await interaction.response.defer() + self.finish = True + self.stop() \ No newline at end of file diff --git a/JAS/resources/__init__.py b/JAS/resources/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/JAS/setup.py b/JAS/setup.py new file mode 100644 index 0000000..858e6c8 --- /dev/null +++ b/JAS/setup.py @@ -0,0 +1,461 @@ +import discord, os, sqlite3, traceback, shutil +from JAS.resources.Addons import DB_FOLDER_PATH, IMG_FOLDER_PATH, resource_path, filename + +class Setup: + def __init__(self): + pass + + def check(self, guild_id): + if os.path.exists(os.path.join(DB_FOLDER_PATH, f'{guild_id}.db')): + return True + else: + print('creating guild folder') + os.makedirs(os.path.join(IMG_FOLDER_PATH, str(guild_id)), exist_ok=True) + return False + + def connect(self, guild_id): + return sqlite3.connect(os.path.join(DB_FOLDER_PATH, f'{guild_id}.db')) + + def update(self, conn:sqlite3.Connection, field:dict, table): + """ + DB TABLE 업데이트 + """ + conn.row_factory = sqlite3.Row + cur = conn.cursor() + data_cur = cur.execute(f'SELECT * FROM {table}') + cols = [col[0] for col in data_cur.description] + data = [dict(row) for row in data_cur.fetchall()] + + if list(field.keys())==cols: + return + + temp_name = f'{table}_temp' + cur.execute(f'ALTER TABLE {table} rename to {temp_name}') + + try: + script = "" + column = list(field.keys()) + for col in column: + script = script + f'{col} {field[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE {table}({script})") + values = [] + script = f'INSERT INTO {table} VALUES({",".join(["?"]*len(column))})' + for i in range(len(data)): + row = data[i] + temp_val = [] + for colname in column: + value = row[colname] if colname in cols else None + temp_val.append(value) + values.append(temp_val) + print(f'Script: {script}') + print(values) + cur.executemany(script, values) + cur.execute(f'DROP TABLE {temp_name}') + conn.commit() + except Exception as e: + cur.execute(f'ALTER TABLE {temp_name} rename to {table}') + + + def setup(self, guild_id): + self.check(guild_id) + print(' check complete') + self.create_DB(guild_id) + print(' create db complete') + return self.connect(guild_id) + + def create_DB(self,guild_id): + conn = None + try: + print('start Setup Process') + conn = sqlite3.connect(os.path.join(DB_FOLDER_PATH, f'{guild_id}.db')) + self.creat_user(conn) + self.create_chara(conn) + self.create_NPC(conn, guild_id) + self.create_store(conn) + self.default_setting(conn) + self.create_fishing(conn) + self.creat_gather(conn) + self.create_merge(conn) + print('finish Setup Process') + except Exception as e: + print(e) + print(traceback.format_exc()) + finally: + if conn: + conn.close() + + def creat_user(self, conn:sqlite3.Connection): + cur = conn.cursor() + user_list = ("id","user","loc","fish","size") + inventory_list = ("id","gold","size","item") + user = { + "id":"integer primary key", + "name":"text", + "thread":"integer", + "charas":"text", + } + check_user = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='user'").fetchone()[0] + if check_user != 1: + print('creat user') + script = "" + for col in list(user.keys()): + script = script + f'{col} {user[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE user({script})") + conn.commit() + else: + self.update(conn, user, 'user') + + def create_chara(self, conn:sqlite3.Connection): + cur = conn.cursor() + chara_list = ("id","name","keyword","desc") + chara={ + "code":"text primary key", + "name":"real", + "keyword":"integer", + "desc":"text", + "link":"text", + "color":"integer", + } + stat = { + "code":"text primary key", + "point":"integer", + "statname":"text", + "stat1":"integer", + "stat2":"integer", + "stat3":"integer", + "stat4":"integer", + "stat5":"integer", + "stat6":"integer", + } + inventory={ + "code":"text primary key", + "gold":"real", + "size":"integer", + "item":"text" + } + check_chara = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='chara'").fetchone()[0] + check_stat = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='stat'").fetchone()[0] + check_inventory = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='inventory'").fetchone()[0] + if check_chara != 1: + print('creat chara') + script = "" + for col in list(chara.keys()): + script = script + f'{col} {chara[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE chara({script})") + conn.commit() + else: + self.update(conn, chara, 'chara') + + if check_stat != 1: + print('creat stat') + script = "" + for col in list(stat.keys()): + script = script + f'{col} {stat[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE stat({script})") + conn.commit() + else: + self.update(conn, stat, 'stat') + + if check_inventory != 1: + print('creat inventory') + script = "" + for col in list(inventory.keys()): + script = script + f'{col} {inventory[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE inventory({script})") + conn.commit() + else: + self.update(conn, inventory, 'inventory') + + def create_NPC(self, conn:sqlite3.Connection, guils_id): + cur = conn.cursor() + NPC = { + "code":"text primary key", + "name":"text", + "color":"integer", + } + NPC_vers = { + "code":"text", + "type":"text", + "vers":"text" + } + check_NPC = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='NPC'").fetchone()[0] + check_NPC_vers = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='NPC_vers'").fetchone()[0] + if check_NPC != 1: + print('creat NPC') + script = "" + for col in list(NPC.keys()): + script = script + f'{col} {NPC[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE NPC({script})") + + code = "NPC_store001" + name = "상점NPC" + + cur.execute('INSERT INTO NPC VALUES(?,?,?)',(code, name, 0)) + cases = ["환영","판매성공","구매취소","잔액부족"] + + for case in cases: + shutil.copy(resource_path('img','sample.png'), os.path.join(IMG_FOLDER_PATH,str(guils_id),filename(f'{name}_{case}.png'))) + conn.commit() + else: + self.update(conn, NPC, 'NPC') + + if check_NPC_vers != 1: + print('creat NPC') + script = "" + for col in list(NPC_vers.keys()): + script = script + f'{col} {NPC_vers[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE NPC_vers({script})") + + code = "NPC_store001" + + cur.executescript(f''' + INSERT INTO NPC_vers VALUES("{code}","환영","상점에 온걸 환영해!\n사고 싶은걸 골라봐~"); + INSERT INTO NPC_vers VALUES("{code}","판매성공","구매해 줘서 고마워!"); + INSERT INTO NPC_vers VALUES("{code}","구매취소","다음에 또 방문해줘"); + INSERT INTO NPC_vers VALUES("{code}","잔액부족","앗! 금액이 충분하지 않아!\n다음에 다시 찾아와줘~"); + ''') + conn.commit() + else: + self.update(conn, NPC_vers, 'NPC_vers') + + def create_store(self, conn:sqlite3.Connection): + cur = conn.cursor() + # cur.execute('CREATE TABLE item(code text, name text, description text, number integer, price real)') + # cur.execute('CREATE TABLE store(code text, sale text, price integer, discount real)') + + item_list = ("code","name","description","number","price") + store_list = ("code","sale","price","discount") + item = { + "code":"text primary key", + "name":"text", + "description":"str", + "number":"text", + "price":"integer" + } + store={ + "code":"text primary key", + "sale":"text", + "price":"integer", + "discount":"integer" + } + check_item = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='item'").fetchone()[0] + check_store = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='store'").fetchone()[0] + if check_item != 1: + print('creat item data') + script = "" + for col in list(item.keys()): + script = script + f'{col} {item[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE item({script})") + conn.commit() + else: + self.update(conn, item, 'item') + + if check_store != 1: + print('creat store data') + script = "" + for col in list(store.keys()): + script = script + f'{col} {store[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE store({script})") + + cur.executescript(''' + INSERT INTO store values("3001", "True", 10, 1); + INSERT INTO store values("3002", "True", 20, 1); + INSERT INTO item values("3001", "칫솔", "빳빳한 칫솔\n양치질은 중요해!", 1, 5); + INSERT INTO item values("3002", "보석", "아름다운 보석\n선물해 주기 딱 좋다.", 1, 10); + ''') + conn.commit() + else: + self.update(conn, store, 'store') + + def create_fishing(self, conn:sqlite3.Connection): + cur = conn.cursor() + history_list = ("fishdate","user","loc","fish","size") + data_list = ("code","name","min","max","baseprice","loc") + fishing_history = { + "fishdate":"text", + "user":"integer", + "loc":"str", + "fish":"text", + "size":"real" + } + fishing_data={ + "code":"text primary key", + "name":"text", + "min":"integer", + "max":"integer", + "baseprice":"integer", + "loc":"text" + } + check_data = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='fishing_data'").fetchone()[0] + check_history = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='fishing_history'").fetchone()[0] + if check_data != 1: + print('creat fishing data') + script = "" + for col in list(fishing_data.keys()): + script = script + f'{col} {fishing_data[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE fishing_data({script})") + + cur.executescript(''' + INSERT INTO fishing_data values("1001", "멸치", 1, 3, 5, "기본 낚시터"); + INSERT INTO fishing_data values("1002", "고등어", 2, 10, 20, "기본 낚시터"); + INSERT INTO item values("1001", "멸치", "작은 멸치\n볶음으로 만들어 먹기에 딱 좋은 크기이다.", 1, 5); + INSERT INTO item values("1002", "고등어", "고등어\n구워먹으면 맛있다.", 1, 20); + ''') + conn.commit() + else: + self.update(conn, fishing_data, 'fishing_data') + + if check_history != 1: + print('creat fishing history') + script = "" + for col in list(fishing_history.keys()): + script = script + f'{col} {fishing_history[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE fishing_history({script})") + conn.commit() + else: + self.update(conn, fishing_history, 'fishing_history') + + def creat_gather(self, conn:sqlite3.Connection): + cur = conn.cursor() + history_list = ("gatherdate","user","loc","item","size") + data_list = ("code","name","min","max","baseprice","loc") + gather_history = { + "gatherdate":"text", + "user":"integer", + "loc":"str", + "item":"text", + "size":"real" + } + gather_data={ + "code":"text primary key", + "name":"text", + "min":"integer", + "max":"integer", + "baseprice":"integer", + "loc":"text" + } + check_data = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='gather_data'").fetchone()[0] + if check_data != 1: + print('creat gather data') + script = "" + for col in list(gather_data.keys()): + script = script + f'{col} {gather_data[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE gather_data({script})") + cur.executescript(''' + INSERT INTO gather_data values("2001", "사과", 1, 1, 5, "기본 채집터"); + INSERT INTO gather_data values("2002", "복숭아", 1, 1, 10, "기본 채집터"); + INSERT INTO item values("2001", "사과", "맛있어 보이는 사과\n밤에는 먹지말자.", 1, 5); + INSERT INTO item values("2002", "복숭아", "복숭아\n털이 많다.", 1, 10); + ''') + + conn.commit() + else: + self.update(conn, gather_data, 'gather_data') + + check_history = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='gather_history'").fetchone()[0] + if check_history != 1: + print('creat gather history') + script = "" + for col in list(gather_history.keys()): + script = script + f'{col} {gather_history[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE gather_history({script})") + conn.commit() + else: + self.update(conn, gather_data, 'gather_data') + + def create_merge(self, conn:sqlite3.Connection): + cur = conn.cursor() + store_list = ("code","sale","price","discount") + recipe = { + "code":"text primary key", + "name":"text", + "merge":"text", + "rate":"integer" + } + check_recipe = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='recipe'").fetchone()[0] + if check_recipe != 1: + print('creat recipe data') + script = "" + for col in list(recipe.keys()): + script = script + f'{col} {recipe[col]}, ' + script = script[:-2] + cur.execute(f"CREATE TABLE recipe({script})") + + cur.executescript(''' + INSERT INTO recipe values("4001","물고기죽","1001,1002",100); + INSERT INTO item values("4001", "물고기죽", "여러 물고기를 섞어 끓인 탕.\n맛은 보장할 수 없다.", 1, 10); + ''') + conn.commit() + else: + self.update(conn, recipe, 'recipe') + + def default_setting(self, conn:sqlite3.Connection): + cur = conn.cursor() + setting = { + "type":"text primary key", + "value":"text" + } + + check_history = cur.execute("SELECT count(name) FROM sqlite_master WHERE type='table' AND name='setting'").fetchone()[0] + if check_history != 1: + print('creat setting') + script = "" + for col in list(setting.keys()): + script = script + f'{col} {setting[col]}, ' + script = script[:-2] + + cur.execute(f"CREATE TABLE setting({script})") + else: + self.update(conn, setting, 'setting') + + values = { + "max_fishing":"3", + "max_gather":"3", + "base_inv_size":"10", + "base_inv_gold":"10", + "accept_user":"True", + "random_box":"5", + "gspread_url":"", + "anon":"익명게시판", + "manage":"관리", + "join":"가입", + "store":"상점", + "community":"캐입역극", + "qna":"질의응답", + "visitor":"방문자", + "registered":"가입자", + "admin":"관리자", + "stat_names":"체력,힘,지능,관찰,민첩,운", + } + + for type in list(values.keys()): + try: + cur.execute(f'INSERT INTO setting VALUES("{type}","{values[type]}")') + except Exception as e: + # ignore unique error + e + + conn.commit() + + # def get_db_list(self, conn): + # tables = [] + # cur = conn.cursor() + # cur.execute("SELECT name FROM sqlite_master WHERE type IN ('table', 'view') AND name NOT LIKE 'sqlite_%' UNION ALL SELECT name FROM sqlite_temp_master WHERE type IN ('table', 'view') ORDER BY 1;") + + # for table in cur: + # tables.append(table[0]) + + # return tables diff --git a/app.py b/app.py new file mode 100644 index 0000000..06fec89 --- /dev/null +++ b/app.py @@ -0,0 +1,90 @@ +import os, shutil, sys, json +import sqlite3 +import logging, logging.handlers, multiprocessing +from io import BytesIO +from PIL import Image +from random import choice, randrange, random +from math import floor +from asyncio import sleep +from copy import deepcopy +from time import strftime,localtime, sleep +from traceback import format_exc + +from discord import CustomActivity, Intents +from JAS.resources.Addons import read_json + +def run_discord(receive_queue:multiprocessing.Queue, send_queue:multiprocessing.Queue): + from JAS.resources.Base import JAS, logger, handler + while True: + if not receive_queue.empty(): + queuedata = receive_queue.get() + print('from main discord process >>',queuedata) + if 'stop process' == queuedata: + print(' stop process') + break + elif 'start bot' == queuedata: + try: + print('startup process') + data = read_json() + + intents = Intents.default() + intents.message_content = True + intents.members = True + intents.guilds = True + + activity = CustomActivity(name="Running DEMO...") + bot = JAS(intents=intents, command_prefix='!', activity=activity, logger=logger) + + bot.rec_queue = receive_queue + bot.send_queue = send_queue + bot.TOKEN = data["TOKEN"] + bot.PROFILE = data["PROFILE"] + bot.TEST_SERVER_ID = int(data["TEST_SERVER_ID"]) + bot.AUTH_JSON_PATH = data["AUTH_JSON_PATH"] + print(bot.TOKEN, bot.PROFILE, bot.TEST_SERVER_ID, bot.AUTH_JSON_PATH) + print('launching discord bot') + bot.run(bot.TOKEN, log_handler=handler) + send_queue.put_nowait('bot terminated') + print('bot terminated') + except Exception as e: + send_queue.put('error') + send_queue.put(str(e)) + else: + if queuedata != 'stop bot': + receive_queue.put(queuedata) + else: + sleep(1) + print('end discord process') + pass + +def run_GUI(receive_queue:multiprocessing.Queue, send_queue:multiprocessing.Queue): + from JAS.UI import QApplication, ComuBotAPP + app = QApplication(sys.argv) + main_window = ComuBotAPP() + main_window.rec_queue = receive_queue + main_window.send_queue = send_queue + main_window.show() + + app.exec() + send_queue.close() + receive_queue.close() + print('end GUI process') + pass + + +if __name__ == '__main__': + multiprocessing.freeze_support() + + # construct env + from JAS.resources.Addons import prep_env + prep_env() + + queue1 = multiprocessing.Queue() + queue2 = multiprocessing.Queue() + discord_process = multiprocessing.Process(target=run_discord, args=(queue1,queue2)) + pyGUI_process = multiprocessing.Process(target=run_GUI, args=(queue2,queue1)) + + pyGUI_process.start() + discord_process.start() + discord_process.join() + pyGUI_process.join() diff --git a/discord_app.py b/discord_app.py new file mode 100644 index 0000000..b37f85d --- /dev/null +++ b/discord_app.py @@ -0,0 +1,40 @@ +#run.py +import discord, traceback, os, logging, logging.handlers +from discord.ext import tasks + +#get libs +from JAS.resources.Exceptions import * +from JAS.resources.Addons import read_json, prep_env + +if __name__ == '__main__': + prep_env() + from JAS.resources.Base import JAS, logger, handler + from JAS.mailing import Mailer + + #intents + intents = discord.Intents.default() + intents.message_content = True + intents.members = True + intents.guilds = True + while True: + data = read_json() + SMTP_SERVER = data['SMTP_SERVER'] + SMTP_PORT = data['SMTP_PORT'] + MAIL_ID = data['MAIL_ID'] + MAIL_PW = data['MAIL_PW'] + mail_sender = Mailer(SMTP_SERVER, SMTP_PORT, MAIL_ID, MAIL_PW) + + activity = discord.CustomActivity(name="⚙️ 위이잉 치킨") + bot = JAS(intents=intents, command_prefix='!', activity=activity, logger=logger) + bot.TOKEN = data["TOKEN"] + bot.PROFILE = data["PROFILE"] + bot.TEST_SERVER_ID = int(data["TEST_SERVER_ID"]) + bot.AUTH_JSON_PATH = data["AUTH_JSON_PATH"] + + bot.run(bot.TOKEN, log_handler=handler) + + if bot.is_quit: + break + else: + # print('error') + mail_sender.send_email(0,'봇이 의도치 않게 중단되었습니다. 로그를 확인하여 주시기 바랍니다.') \ No newline at end of file diff --git a/img/UI/disk.png b/img/UI/disk.png new file mode 100644 index 0000000..51bf067 Binary files /dev/null and b/img/UI/disk.png differ diff --git a/img/UI/exit.png b/img/UI/exit.png new file mode 100644 index 0000000..1e42767 Binary files /dev/null and b/img/UI/exit.png differ diff --git a/img/UI/folder.png b/img/UI/folder.png new file mode 100644 index 0000000..b45470c Binary files /dev/null and b/img/UI/folder.png differ diff --git a/img/UI/icon.ico b/img/UI/icon.ico new file mode 100644 index 0000000..a5c29cd Binary files /dev/null and b/img/UI/icon.ico differ diff --git a/img/UI/icon.png b/img/UI/icon.png new file mode 100644 index 0000000..05b5b6f Binary files /dev/null and b/img/UI/icon.png differ diff --git a/img/UI/play.png b/img/UI/play.png new file mode 100644 index 0000000..8317550 Binary files /dev/null and b/img/UI/play.png differ diff --git a/img/UI/settings.png b/img/UI/settings.png new file mode 100644 index 0000000..7a85563 Binary files /dev/null and b/img/UI/settings.png differ diff --git a/img/UI/stop.png b/img/UI/stop.png new file mode 100644 index 0000000..568fe72 Binary files /dev/null and b/img/UI/stop.png differ diff --git a/img/sample.png b/img/sample.png new file mode 100644 index 0000000..82b2882 Binary files /dev/null and b/img/sample.png differ diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..8a0d265 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,6 @@ +discord=2.3.2 +Pillow=10.0.0 +PySide6=6.5.3 +gspread=5.11.2 +clipboard=0.0.4 +cryptography=41.0.4 \ No newline at end of file