1
0

refactor: for public repository

This commit is contained in:
オスカー、 2023-11-24 09:15:54 +09:00
parent 323b6a7a86
commit 05caae7cf8
No known key found for this signature in database
GPG Key ID: B1EFBBF5C93FF78F
4 changed files with 100 additions and 55 deletions

4
.env.example Normal file
View File

@ -0,0 +1,4 @@
MISSKEY_TOKEN = YOUR_ACCESS_TOKEN_HERE
MISSKEY_ORIGIN = YOUR_MISSKEY_SERVER_HERE
MAX_DUPLICATE_COUNT = 3
SPREADSHEET_URL = https://docs.google.com/spreadsheets/d/1KXt5mcAsdT87j94d8yHJWcdmHG-MadDKpOWmCfK8OGI/edit

View File

@ -1,2 +1,68 @@
# Autopost # Misskey Autoposter
I want some random lines for my timeline 간단한 Misskey (또는 CherryPick!) 전용 자동 노트 봇
⚠️ **이 봇을 사용할 때에는 봇 친화적인 서버, 또는 개인 서버에서 사용하시는 것을 권장합니다.**
도움이 필요하시다면 [@ALPINE_SECTOR@phater.live](https://phater.live/@ALPINE_SECTOR)로 알려주세요.
본 소스 코드의 수정 및 이용은 자유이나, 재배포 시애는 해당 리포지토리의 링크를 걸어주세요!
## What
Google 스프레드시트를 기반으로 지정된 문구들을 자동적으로 노트로 작성합니다.
현재 이 소스 코드를 기반으로 작동하고 있는 봇을 [여기에서 확인](https://phater.live/@Stainless)하실 수 있습니다!
## Features
* 최근 등장한 n개의 문구를 스킵할 수 있는 기능
* @멘션 시 입력된 문구 정보와 함께 즉시 문구를 출력할 수 있는 기능
## Options
.env.example 파일을 참조하여 .env 파일을 작성해주세요.
```
MISSKEY_TOKEN : 계정 액세스 토큰
MISSKEY_ORIGIN : 호스트 서버 이름 (misskey.io, phater.live...)
MAX_DUPLICATE_COUNT : 중복 시 출력되지 않게 할 간격
(3으로 설정 시 다음 3개의 문구가 나올 동안은 출력되지 않음)
WORKSHEET_URL : 문구를 불러올 스프레드시트 URL
```
## How
### Requirements
* Git
* Google Cloud Service Account
* 구글 스프레드시트 연동을 위해 작업이 필요합니다. ([gspread 문서 참조](https://docs.gspread.org/en/latest/oauth2.html))
* Ubuntu 20.04+ or Windows 10+
* Python 3.10+
* Google 스프레드시트 ([예시 스프레드시트](https://docs.google.com/spreadsheets/d/1nO70lwFFkyyK8AtVE4fWO7lW7KDtM5pNudGJydTaQdk/edit))
* 예시에서 복사본을 생성하여 수정하세요. 양식이 맞지 않으면 오류가 발생합니다.
### Clone source code
GitHub에서 소스 코드를 복사 후, 해당 디렉토리로 이동합니다.
```sh
> git clone https://github.com/Unstarrified/Autopost.git
> cd Autopost
```
### Setup venv (Optional)
프로덕션 서버에서 작동하는 경우, 아래 명령어로 venv를 설정하시는 것을 권장합니다.
```sh
> python3 -m venv .venv
# Ubuntu의 경우 가상 환경을 불러오려면 이 명령어를 실행하세요.
> source ./.venv/bin/activate
# Windows의 경우 가상 환경을 불러오려면 이 명령어를 실행하세요.
> call ./.venv/Scripts/activate
```
### Install dependencies
아래 명령어를 실행해 봇이 필요한 의존성 패키지를 설치합니다.
```sh
> pip install -r requirements.txt
```
### Run
아래 명령어를 실행하면 봇이 동작하기 시작합니다.
```sh
> python3 main.py
```
끝입니다! 계속 실행되도록 하시려면 nohup이나 systemd 등을 사용해주세요.
### Update
추후 봇 코드가 변경되어 업데이트가 필요한 경우, 아래 명령어를 실행하세요.
```sh
> git pull
```

View File

@ -2,10 +2,9 @@ from datetime import datetime
from mipa.ext import commands, tasks from mipa.ext import commands, tasks
from mipa.ext.commands.bot import Bot from mipa.ext.commands.bot import Bot
# from mipa.ext.commands.context import Context
class Autopost(commands.Cog): class Post(commands.Cog):
def __init__(self, bot: Bot) -> None: def __init__(self, bot: Bot) -> None:
self.bot = bot self.bot = bot
self.posted = [] self.posted = []
@ -13,10 +12,10 @@ class Autopost(commands.Cog):
@tasks.loop(seconds=60) @tasks.loop(seconds=60)
async def _postLine(self) -> None: async def _postLine(self) -> None:
now = datetime.now() now = datetime.now()
if now.minute not in [30, 00]: if now.minute not in [30, 00]: # Only post in n:30 and n:00
return return
line = self.bot.get_line() line = self.bot.get_random_line()
while line in self.posted: while line in self.posted:
line = self.bot.get_line() line = self.bot.get_line()
await self.bot.client.note.action.send(content=line, visibility="home") await self.bot.client.note.action.send(content=line, visibility="home")
@ -26,6 +25,6 @@ class Autopost(commands.Cog):
async def setup(bot: Bot): async def setup(bot: Bot):
cog = Autopost(bot) cog = Post(bot)
await cog._postLine.start() await cog._postLine.start()
await bot.add_cog(cog) await bot.add_cog(cog)

72
main.py
View File

@ -1,49 +1,21 @@
import asyncio import asyncio
import os import os
import random import random
#from typing import Optional
#import aiomysql
import gspread import gspread
from aiohttp import ClientWebSocketResponse from aiohttp import ClientWebSocketResponse
from gspread.worksheet import Worksheet from gspread.worksheet import Worksheet
from mipac.models.notification import NotificationNote from mipac.models.notification import NotificationNote
from mipa.ext import commands from mipa.ext import commands
# from mipac.models.note import Note
from dotenv import load_dotenv from dotenv import load_dotenv
load_dotenv() load_dotenv()
COGS = [
"exts.tasks"
]
class MyBot(commands.Bot): class Autoposter(commands.Bot):
def __init__(self): def __init__(self):
super().__init__() super().__init__()
self.max_count = int(os.getenv("MAX_COUNT")) self.max_count = int(os.getenv("MAX_DUPLICATE_COUNT"))
# async def create_pool(self) -> None:
# pool = await aiomysql.create_pool(
# minsize=5,
# maxsize=20,
# host=os.getenv("MYSQL_HOST"),
# port=int(os.getenv("MYSQL_PORT")),
# user=os.getenv("MYSQL_USER"),
# password=os.getenv("MYSQL_PASSWORD"),
# db="autopost",
# autocommit=True
# )
# self.pool = pool
# async def query(self, query: str, fetch: bool = False) -> Optional[dict]:
# async with self.pool.acquire() as conn:
# async with conn.cursor(aiomysql.DictCursor) as cur:
# await cur.execute(query)
# if fetch:
# return await cur.fetchall()
# else:
# return "Query Successful"
def get_worksheet(self) -> Worksheet: def get_worksheet(self) -> Worksheet:
gc = gspread.service_account() gc = gspread.service_account()
@ -51,7 +23,7 @@ class MyBot(commands.Bot):
worksheet = sh.get_worksheet(0) worksheet = sh.get_worksheet(0)
return worksheet return worksheet
def get_line(self) -> str: def get_random_line(self) -> str:
sheet: Worksheet = self.get_worksheet() sheet: Worksheet = self.get_worksheet()
response = sheet.get("F4") response = sheet.get("F4")
if response is None or response == "": if response is None or response == "":
@ -62,35 +34,39 @@ class MyBot(commands.Bot):
res = sheet.get(f"D{number}") res = sheet.get(f"D{number}")
text = res[0][0].strip() text = res[0][0].strip()
return text return text
def get_info(self, line: str) -> dict:
sheet = self.get_worksheet()
result = sheet.find(line, in_column=4)
number = result.row - 2
where = sheet.get(f"C{result.row}")
where = where[0][0]
return {"number": number, "from": where}
async def _connect_channel(self): async def _connect_channel(self):
await self.router.connect_channel(['main', 'global']) await self.router.connect_channel(['main', 'global'])
async def on_ready(self, ws: ClientWebSocketResponse): async def on_ready(self, ws: ClientWebSocketResponse):
print(f'connected: {self.user.username}') print(f"Connected as @{self.user.username}@{self.user.host}")
await self._connect_channel() await self._connect_channel()
for cog in COGS: extensions = [
await self.load_extension(cog) "exts.post"
]
for extension in extensions:
await self.load_extension(extension)
async def on_reconnect(self, ws: ClientWebSocketResponse): async def on_reconnect(self, ws: ClientWebSocketResponse):
print('Disconnected from server. Will try to reconnect.') print("Disconnected from server.")
await self._connect_channel() await self._connect_channel()
# async def on_note(self, note: Note):
# print(f'{note.author.username}: {note.content}')
async def on_mention(self, notice: NotificationNote): async def on_mention(self, notice: NotificationNote):
sheet = self.get_worksheet() line = self.get_random_line()
line = self.get_line() info = self.get_info(line)
result = sheet.find(line, in_column=4) await notice.note.api.action.reply(content=f"{line}\n \n<small>- {info['where']}에서 발췌됨. ({info['number']}번 대사)</small>", reply_id=notice.note.id)
number = result.row - 2
where = sheet.get(f"C{result.row}")
where = where[0][0]
await notice.note.api.action.reply(content=f"{line}\n \n<small>- {where}에서 발췌됨. ({number}번 대사)</small>", visibility="home", reply_id=notice.note.id)
if __name__ == '__main__': if __name__ == '__main__':
bot = MyBot() bot = Autoposter()
loop = asyncio.get_event_loop() loop = asyncio.get_event_loop()
# loop.run_until_complete(bot.create_pool()) origin = os.getenv("MISSKEY_ORIGIN")
loop.run_until_complete(bot.start(os.getenv("MISSKEY_ORIGIN"), os.getenv("MISSKEY_TOKEN"))) loop.run_until_complete(bot.start(f"wss://{origin}/streaming", os.getenv("MISSKEY_TOKEN")))