commit 6835bf8fb0474ebe9bf88219dec7e04e9f6e667b Author: Soumt Nam Date: Fri Sep 8 09:49:37 2023 +0900 initial diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1e3381d --- /dev/null +++ b/.gitignore @@ -0,0 +1,180 @@ +# Created by https://www.toptal.com/developers/gitignore/api/python,dotenv +# Edit at https://www.toptal.com/developers/gitignore?templates=python,dotenv + +### dotenv ### +.env + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +.vim/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +### Python Patch ### +# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration +poetry.toml + +# ruff +.ruff_cache/ + +# LSP config files +pyrightconfig.json + +# End of https://www.toptal.com/developers/gitignore/api/python,dotenv diff --git a/DummyModule.py b/DummyModule.py new file mode 100644 index 0000000..f7e23a5 --- /dev/null +++ b/DummyModule.py @@ -0,0 +1,12 @@ +from module_interface import ModuleInterface +class MyModule(ModuleInterface): + def __init__(self): + super().__init__() + self.name = "DummyModule1" + self.regex_pattern = None #r"my_pattern" + + #print("[Dummy1] DummyModule1 loaded.") + + def execute_module(self, ctx): + #print(f"[Dummy1] {self.name} 실행: {ctx}") + pass diff --git a/example.env b/example.env new file mode 100644 index 0000000..8f55c2e --- /dev/null +++ b/example.env @@ -0,0 +1,2 @@ +BOT_INSTANCE=(Your Misskey Instance) +BOT_KEY=(Your Misskey api key) diff --git a/minya.py b/minya.py new file mode 100644 index 0000000..66cc8af --- /dev/null +++ b/minya.py @@ -0,0 +1,90 @@ +from pyfiglet import Figlet +f = Figlet(font='small') + +import os +import importlib.util +import re +import inspect +from dotenv import load_dotenv + +from module_interface import ModuleInterface, ModuleManager + +import asyncio + +from aiohttp import ClientWebSocketResponse +from mipac.models.notification import NotificationNote +from mipa.ext.commands.bot import Bot + +def load_modules(): + module_dir = "modules" + modules = [] + for filename in os.listdir(module_dir): + if filename.endswith(".py"): + module_name = os.path.splitext(filename)[0] + module_path = os.path.join(module_dir, filename) + + spec = importlib.util.spec_from_file_location(module_name, module_path) + module = importlib.util.module_from_spec(spec) + spec.loader.exec_module(module) + + for attribute_name in dir(module): + attribute = getattr(module, attribute_name) + if inspect.isclass(attribute) and issubclass(attribute, ModuleInterface) and attribute != ModuleInterface: + modules.append(attribute()) + return modules + +class MiNya(Bot): + def __init__(self): + super().__init__() + self.__modules = [] + self.__manager = ModuleManager(self) + + async def _connect_channel(self): + await self.router.connect_channel(['main']) + async def on_reconnect(self, ws: ClientWebSocketResponse): + await self._connect_channel() + + async def on_ready(self, ws: ClientWebSocketResponse): + await self._connect_channel() + print("[Main] Bot System Ready.") + print("") + print("[Main] Loading Modules...") + print("") + self.__modules = load_modules() + print("") + print(f"[Main] Loaded Modules : {len(self.__modules)}") + print("") + + print("[Main] Registering Modules...") + + for module in self.__modules: + self.__manager.register_module(module) + print("") + + async def on_mention(self, notice: NotificationNote): + print("[Main] Got Mentioned!") + for _, module in self.__manager.modules.items(): + if module.regex_pattern and re.match(module.regex_pattern, notice.note.content): + print("[Main] Regex Matching Module found : ", module.name) + if await module.execute_module(notice.note): + break + + +def main(): + print(f.renderText('MiNya V1')) + print('"MIsskey Networked Yet Ambitious"') + print('') + print("[Main] Minya V1 Bootstap...") + print("[Main] Loading settings...") + load_dotenv() + instance = os.environ.get('BOT_INSTANCE') + key = os.environ.get('BOT_KEY') + + if not (instance and key): + print("[MAIN] ERR! please check .env or os environment!") + return + bot = MiNya() + asyncio.run(bot.start(f'wss://{instance}/streaming', key)) + +if __name__ == "__main__": + main() diff --git a/module_interface.py b/module_interface.py new file mode 100644 index 0000000..41d1fca --- /dev/null +++ b/module_interface.py @@ -0,0 +1,38 @@ +from mipa.ext.commands.bot import Bot + +class ModuleInterface: + def __init__(self): + self.name = None + self.regex_pattern = None + self.funcs = {} + self.manager: (ModuleManager | None) = None + + def set_manager(self, manager): + self.manager = manager + self.module_ready() + + def get_execute_pattern(self, regex_pattern): + return self.__regex_pattern + + def module_ready(self): + pass + + async def execute_module(self, ctx): + pass + + def get_func(self, name): + return self.funcs[name] + + +class ModuleManager: + def __init__(self, main_bot = None): + self.modules = {} + self.bot: (Bot | None) = main_bot + + def require(self, name): + return self.modules[name] + + def register_module(self, module): + if module.name not in self.modules: + self.modules[module.name] = module + module.set_manager(self) diff --git a/modules/Ai_tem.py b/modules/Ai_tem.py new file mode 100644 index 0000000..14a9317 --- /dev/null +++ b/modules/Ai_tem.py @@ -0,0 +1,247 @@ +from module_interface import ModuleInterface +import random + +itemPrefixes = [ + '플라티나로 만든', + '신선한', + '최신의', + '고대의', + '수제', + '시계장치의', + '전설의', + '구이', + '날것의', + '미냐가 만든', + '포켓 사이즈', + '사흘 전의', + '그 근처의', + '짭', + '사용된', + '고장난', + '시판되는', + '주문제작된', + '업무용의', + 'Microsoft제', + 'Apple제', + '인류 기술의 결정체인', + '2021년산', + '500kg정도의', + '고오급', + '썩은', + '인공지능 탑재', + '블록체인 탑재', + '반중력', + '접이식', + '휴대용', + '유전자 재조합', + '돌연변이로 비행 능력이 있는', + '순금으로 만든', + '투명한', + '빛나는', + '하트 모양의', + '움직이는', + '반으로 잘린', + 'USB 커넥터가 달린', + '지난 날의', + '저주받은', + '인챈트된', + '하루치의 비타민이 들어간', + '손을 댄', + '환상의', + '가상의', + '원자력', + '고도로 훈련받은', + '유전자 조작이 아닌', + '런던 중심부에서 발견된', + '이세계의', + '다른 별의', + '수수께끼의', + '시공을 일그러뜨리는', + '이상한 소리가 나는', + '무산된', + '플라즈마화된', + '충격을 주면 낮은 확률로 폭발하는', + '주키니로 변신한', + '가설의', + '독이 있는', + '진짜', + '궁극의', + '초코가 들어간', + '악취가 나는', + '4차원', + '박동하는', + '정체를 알 수 없는', + '네모난', + '날뛰는', + '꿈의', + '어둠의', + '암흑의', + '봉인된', + '죽음의', + '얼어버린', + '마의', + '금단의', + '홀로그래픽', + '유압식', +] + +items = [ + '가지', + '토마토', + '오이', + '감자', + '볶음국수', + '허리', + '초밥', + '호박', + '유키치', + '금괴', + '알루미늄', + '나트륨', + '마그네슘', + '플루토늄', + '작은 금속', + '우유팩', + '페트병', + '쿠키', + '초콜릿', + '메이드복', + '오렌지', + '니삭스', + '반물질 콘덴서', + '입자가속기', + '마이크로프로세서 (8코어 16스레드)', + '원자력 발전소', + 'L4 스위치', + '완충 체인', + '양전자 두뇌', + '행성', + '테르민', + '충치차', + '마운터', + '버킷 휠 익스커베이터', + '데몬 코어', + '게임보이 어드밴스', + '양자컴퓨터', + '아나몰픽 렌즈', + '벽장에서 나온 수수께끼의 생물', + '스마트폰', + '시계', + '푸딩', + '가브리엘의 나팔', + '맹거의 스펀지', + '피젯 스피너', + '초입방체', + '건축물', + '에너지 드링크', + '마우스 커서', + '안경', + '참치', + '쓰레기통', + '이쑤시개', + '도시락에 들어가는 초록색 칸막이같은 녀석', + '나무젓가락', + '환기구', + '페트병의 뚜껑', + '소파 블럭', + '피자', + '치약', + '깡통', + '열쇠고리', + '금발 벽안의 미소녀', + 'SD카드', + '립 크림', + '초코 없는 초코소라빵', + '조류독감', + '자판기', + '무거운 것', + '노트북', + '육포', + '연어 치즈', + '다이아몬드', + '물체', + '월석', + '특이점', + '중성자별', + '액체', + '위성', + '주키니', + '검은 것', + '흰 것', + '빨간 것', + '동그란 것', + '네모난 것', + '카드 모양의 것', + '기체', + '연필', + '지우개', + '양날검', + '막대 모양의 것', + '농산물', + '메탈 슬라임', + '문어발', + '버섯', + '나메코', + '호로요이', + '손톱깎기', + '귓속말', + '인형', + '티라노사우르스', + '요로결석', + '엔터 키', + '항아리', + '수은', + 'DHMO', + '물', + '토지', + '대륙', + '주사위', + '실외기', + '유압잭', + '타피오카', + 'PSP', + '화장지 심지', + '골판지 상자', + '하니와', + '볼펜', + '샤펜', + '원자', + '우주', + '소립자', + '참기름', + 'undefined', + 'None', + 'NAN', + '[object Object]' +] + +ands = [ + '에 빙의한', + '에 들어가는', + '에 묻힌', + '을 연상시키는', + ' 같은', + '로 가장한', + '에 올려진', + ' 옆에 있는', +]; + +from mipac.models import Note +class AiTem(ModuleInterface): + def __init__(self): + super().__init__() + self.name = "Ai-tem" + self.regex_pattern = ".*Test_AI-tem" + + print("[Ai-tem] Ai-like item generator, Ai-tem V1 loaded.") + + async def execute_module(self, ctx: Note): + print("[Ai-tem] test Generate Note") + await ctx.api.action.reply(str(self._generate()), visibility="home") + + def _generate(self): + return random.choice(itemPrefixes) + ' ' + random.choice(items) + ((random.choice(ands) + ' ' + random.choice(itemPrefixes) + ' ' + random.choice(items)) if random.choice([True, False]) else "") + + def module_ready(self): + self.funcs["generate"] = self._generate + print("[Ai-tem] Module Ready.") diff --git a/modules/Amumal.py b/modules/Amumal.py new file mode 100644 index 0000000..e36bd5f --- /dev/null +++ b/modules/Amumal.py @@ -0,0 +1,53 @@ +from module_interface import ModuleInterface +from mipa.ext import tasks +import random + +Amumals = { + "notes": [ + "아무말 모듈 테스트 중이에요!", + "별거 아닌 일에는 '어?' 금지에요!", + "미레라도의 아이돌, 미냐에요!", + "빙구르르...", + "잠이여 물러가세요!", + "좋아... 이 버튼을 누르면..! 어라..?", + "겍...", + "리아님이 안놀아줘요...", + "미레라도? 미래라도? 헷갈려요!", + "배가 고파서 저기 메모리에 굴러다니던 푸딩을 먹었어요!", + "어라라, 분명 뭔가 하려고 했었는데..??", + #"가끔씩은 이웃집에도 놀러가보고 싶어요♪", + "피곤하신가요? 오늘도 수고하셨어요!", + "이불 밖은 던전이에요!", + "미냐 안자요", + "옆집에는 좋은 이름을 가진 친구가 있는 것 같아요!", + "아이와 친구가 되고 싶어요... 일본어는 못하지만요...", + "코...", + "리아님 놀아주세요!", + "저는 심연은 아니지만 여러분을 들여다 볼 수 있어요.", + "꾸벅...", + "레라님 일하세요!", + "집에 가고싶나요? 저도 가고싶어요!" + ] + + } + + +class Amumal(ModuleInterface): + def __init__(self): + super().__init__() + self.name = "Amumal" + self.regex_pattern = r"//" + + print("[Amumal] Random message module, Amumal V1 loaded.") + + def module_ready(self): + print("[Amumal] Module Ready.") + self.amumal_task.start() + + @tasks.loop(seconds=600.0) + async def amumal_task(self): + aitem = str(self.manager.require("Ai-tem").get_func("generate")()) + if random.choice([True, False]): + await self.manager.bot.client.note.action.send(random.choice(Amumals["notes"] + [aitem + "이 가지고 싶어요...", "오늘 산책 중에 " + aitem + "을 봤어요!"]), visibility="home") + print("[Amumal] Sent Amumal!") + diff --git a/modules/Birthday.py b/modules/Birthday.py new file mode 100644 index 0000000..e96da2f --- /dev/null +++ b/modules/Birthday.py @@ -0,0 +1,20 @@ +from module_interface import ModuleInterface +from mipa.ext import tasks +import random + +class Birthday(ModuleInterface): + def __init__(self): + super().__init__() + self.name = "Birthday" + self.regex_pattern = r"//" + + print("[Birthday] Happy birthday message module, Birthday V1 loaded.") + + def module_ready(self): + print("[Birthday] Module Ready.") + self.birthday_task.start() + + @tasks.loop(seconds=600.0) + async def birthday_task(self): + #awa + pass diff --git a/modules/Follow.py b/modules/Follow.py new file mode 100644 index 0000000..4f57bf5 --- /dev/null +++ b/modules/Follow.py @@ -0,0 +1,20 @@ +from module_interface import ModuleInterface +from mipac.models import Note + +class Follow(ModuleInterface): + def __init__(self): + super().__init__() + self.name = "Follow" + self.regex_pattern = r".*팔로우\s*(부탁\s*해|해?\s*(주(\s*십\s*시\s*오|세요)|줘))" + + print("[Follow] Auto follow module, Follow V1 loaded.") + + async def execute_module(self, ctx: Note): + print(f"[Follow] Got Follow Related Message : {ctx.content}") + + usr = await self.manager.bot.user.api.action.get(ctx.author.id) + if usr.is_following: + await ctx.api.action.reply("이미 팔로우 되어있어요..! 저를 잊으셨나요..?") + else: + await usr.api.follow.action.add() + await ctx.api.action.reply("잘 부탁드려요!") diff --git a/modules/Timer.py b/modules/Timer.py new file mode 100644 index 0000000..cab16c1 --- /dev/null +++ b/modules/Timer.py @@ -0,0 +1,31 @@ +import re +import asyncio +from module_interface import ModuleInterface +class Timer(ModuleInterface): + def __init__(self): + super().__init__() + self.name = "Timer" + self.regex_pattern = r"((.|\n)*)(타이머\s.*(맞춰줘|설정해줘))|.*(뒤에\s*알려줘)" + + print("[Timer] Settable timer module, Timer V1 loaded.") + + async def execute_module(self, ctx): + print(f"[Timer] Got Timer Related Message : {ctx.content}") + + text = ctx.content + seconds_query = re.search(r'([0-9]+)초', text) + minutes_query = re.search(r'([0-9]+)분', text) + hours_query = re.search(r'([0-9]+)(시간)', text) + + seconds = int(seconds_query.group(1)) if seconds_query else 0 + minutes = int(minutes_query.group(1)) if minutes_query else 0 + hours = int(hours_query.group(1)) if hours_query else 0 + + if ((seconds + minutes + hours) == 0): + await ctx.api.action.reply("올바른 값이 아니에요...") + + time = (seconds) + (60 * minutes) + (60 * 60 * hours) + + await ctx.api.action.reply("타이머를 설정할게요!") + await asyncio.sleep(int(time)) + await ctx.api.action.reply("시간이 다 되었어요!")