Skip to content

Commit

Permalink
Merge pull request #43 from tetsuya-ki/develop
Browse files Browse the repository at this point in the history
リアクション待ち時間の改善、ログレベル見直し、ラジコ検索機能の追加
  • Loading branch information
tetsuya-ki authored Jan 14, 2021
2 parents 21e3954 + fba1e12 commit 764eac5
Show file tree
Hide file tree
Showing 10 changed files with 240 additions and 72 deletions.
80 changes: 41 additions & 39 deletions cogs/admincog.py

Large diffs are not rendered by default.

31 changes: 31 additions & 0 deletions cogs/messagecog.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from discord.ext import commands # Bot Commands Frameworkのインポート
from .modules.grouping import MakeTeam
from .modules.radiko import Radiko
from logging import getLogger

import discord
Expand Down Expand Up @@ -80,6 +81,30 @@ async def poll(self, ctx, arg1=None, *args):
for emoji, arg in zip(POLL_CHAR, args):
await message.add_reaction(emoji)

@commands.command(aliases=['rs','radiko','radikoKensaku','rk'], description='Radikoの番組表を検索する機能です')
async def radikoSearch(self, ctx, *args):
"""
このコマンドを実行すると、Radikoの番組表を検索することができます。
1番目の引数(キーワード): 検索する対象。**半角スペースがある場合、"(二重引用符)で囲って**ください。
2番目の引数(検索対象): 過去(past)、未来(future)を検索対象とします。未指定か不明な場合、allが採用されます
3番目の引数(地域): XX県かJP01(数字は県番号)と指定すると、その地域の番組表を検索します。未指定か不明の場合はデフォルトの地域が採用されます。
*あんまり検索結果が多いと困るので、一旦5件に制限しています。
"""
usage = '/radikoSearchの使い方\n 例:`/radikoSearch 福山雅治 東京都`\nRadikoの番組表を検索した結果(件数や番組の時間など)をチャンネルへ投稿します。詳しい使い方は`/help radikoSearch`で調べてください'

# 引数の数をチェック
if len(args) == 0:
await ctx.channel.send(usage)
elif len(args) > 3:
await ctx.channel.send(f'引数は3件までです!\n{usage}')
else:
radiko = Radiko()
embed = await radiko.radiko_search(*args)
if not radiko.r_err:
await ctx.channel.send(content=radiko.content, embed=embed)
else:
await ctx.channel.send(radiko.r_err)

@team.error
async def team_error(self, ctx, error):
if isinstance(error, commands.CommandError):
Expand All @@ -92,5 +117,11 @@ async def group_error(self, ctx, error):
logger.error(error)
await ctx.send(error)

@radikoSearch.error
async def radikoSearch_error(self, ctx, error):
if isinstance(error, commands.CommandError):
logger.error(error)
await ctx.send(error)

def setup(bot):
bot.add_cog(MessageCog(bot)) # MessageCogにBotを渡してインスタンス化し、Botにコグとして登録する
2 changes: 2 additions & 0 deletions cogs/modules/auditlogchannel.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,7 @@ async def get_ch(self, guild:discord.Guild):
logger.debug(self.channel)
if self.channel is not None:
return True

self.alc_err = '管理用のチャンネルが登録されていません。'
logger.error(self.alc_err)
return False
123 changes: 123 additions & 0 deletions cogs/modules/radiko.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
from logging import getLogger

import aiohttp
import datetime, random, hashlib
import discord
import re
import mojimoji

logger = getLogger(__name__)

class Radiko:
PREF_CD = {'北海道':'01','青森県':'02','岩手県':'03','宮城県':'04','秋田県':'05','山形県':'06','福島県':'07','茨城県':'08','栃木県':'09','群馬県':'10','埼玉県':'11','千葉県':'12','東京都':'13','神奈川県':'14','新潟県':'15','富山県':'16','石川県':'17','福井県':'18','山梨県':'19','長野県':'20','岐阜県':'21','静岡県':'22','愛知県':'23','三重県':'24','滋賀県':'25','京都府':'26','大阪府':'27','兵庫県':'28','奈良県':'29','和歌山県':'30','鳥取県':'31','島根県':'32','岡山県':'33','広島県':'34','山口県':'35','徳島県':'36','香川県':'37','愛媛県':'38','高知県':'39','福岡県':'40','佐賀県':'41','長崎県':'42','熊本県':'43','大分県':'44','宮崎県':'45','鹿児島県':'46','沖縄県':'47'}
RADIKO_URL = 'http://radiko.jp/v3/api/program/search'
LIMIT_NUM = 5
DEFAULT_AREA = 'JP13'

def __init__(self):
self.content = ''
self.r_err = ''

async def radiko_search(self, keyword='', filter='', areaName=''):
# どっちも空文字になるなら、入れ替えて検索する
if self.convert_filter(filter) == self.convert_prefCd(areaName) == '':
filter,areaName = areaName,filter
response = await self.search_radiko_result(keyword, self.convert_filter(filter), self.convert_prefCd(areaName))

if response is None:
return

result_count = response['meta']['result_count']
self.content = f"検索結果は、{result_count}件です"
if result_count > self.LIMIT_NUM:
self.content += f'(ただし、{self.LIMIT_NUM}件を超えた部分については表示されません)'
elif result_count == 0:
return

embed_field = ''

count = 1
for key in response['data']:
data = ''
data += f"**title:{key['title']}**"
if key['start_time'][:10] == key['end_time'][:10]:
start2end = key['start_time'][:16] + '-' + key['end_time'][11:16]
else:
start2end = key['start_time'][:16] + '-' + key['end_time'][:16]
data += '\ndatetime:' + start2end
data += '\nstation_id:' + key['station_id']
data += '\nperformer:' + key['performer'] if key['performer'] != '' else '\nperformer:(なし)'

info_data = re.sub(r'<br *?/>', '@@', key['info'])
info_data = re.sub(r'<[^>]*?>', '', info_data)
info_data = re.sub(r'\t|\n|(&[lg]t; *)', '', info_data)
info_data = re.sub(r'@+ +?@+', '', info_data)
info_data = re.sub(r'@{2,}', ' ', info_data)
info_data = info_data.strip()
info_data = mojimoji.zen_to_han(info_data, kana=False)
if len(embed_field + data + '\ninfo:' + info_data) > 1700:
data += '\ninfo:(文字数超えのため削除)'
logger.debug('文字数超で削除されたinfo:' + info_data)
else:
data += f'\ninfo:{info_data}'

embed_field += f'{str(count)}件目: {data}\n'
count = count + 1
if count > self.LIMIT_NUM:
break

embed = discord.Embed(title = f'keyword:{keyword}, filter:{filter}, areaName:{areaName} の結果です', description=embed_field,type='rich', inline=False)
return embed

def generate_uid(self):
"""
rdk_uid生成関数
"""
rnd = random.random() * 1000000000
ms = datetime.timedelta.total_seconds(datetime.datetime.now() - datetime.datetime(1970, 1, 1)) * 1000
return hashlib.md5(str(rnd + ms).encode('utf-8')).hexdigest()

def convert_prefCd(self, prefName):
if ('都' in prefName or '道' in prefName or '府' in prefName or '県' in prefName) and self.PREF_CD[prefName].isdecimal():
return 'JP' + self.PREF_CD[prefName]
elif 'JP' in prefName:
return prefName
else:
return ''

def convert_filter(self, filter):
if '未来' in filter or 'future' in filter:
return 'future'
elif '過去' in filter or 'past' in filter:
return 'past'
else:
return ''

async def search_radiko_result(self, keyword='', filter='', area_id=DEFAULT_AREA):
# ref. https://turtlechan.hatenablog.com/entry/2019/06/25/195451
area_id_with_defalut_area = self.DEFAULT_AREA if area_id == '' else area_id
params = {
'key': keyword,
'filter': filter,
'start_day': '',
'end_day': '',
'area_id': area_id_with_defalut_area,
'region_id': '',
'cul_area_id': area_id_with_defalut_area,
'page_idx': '0',
'uid': self.generate_uid(),
'row_limit': '10',
'app_id': 'pc',
'action_id': '0',
}
response = None

async with aiohttp.ClientSession() as session:
async with session.get(self.RADIKO_URL, params=params) as r:
if r.status == 200:
response = await r.json()
return response
else:
self.r_err = 'Radikoの番組表検索に失敗しました。'
logger.warn(self.r_err)
return
11 changes: 7 additions & 4 deletions cogs/modules/reactionchannel.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ async def get_discord_attachment_file(self):
Attachment_file_date = last_message[0].created_at
file_path = join(dirname(__file__), 'files' + os.sep + self.FILE)
await last_message[0].attachments[0].save(file_path)
logger.debug(f'channel_file_save:{guild.name}')
logger.info(f'channel_file_save:{guild.name}')
else:
logger.warn(f'{guild}: に所定のチャンネルがありません')
else:
Expand Down Expand Up @@ -91,7 +91,7 @@ async def set_discord_attachment_file(self, guild:discord.Guild):
overwrites = dict(zip(target, permissions))

try:
logger.debug(f'***{self.REACTION_CHANNEL}を作成しました!***')
logger.info(f'***{self.REACTION_CHANNEL}を作成しました!***')
get_control_channel = await guild.create_text_channel(name=self.REACTION_CHANNEL, overwrites=overwrites)
except discord.errors.Forbidden:
logger.error(f'***{self.REACTION_CHANNEL}の作成に失敗しました!***')
Expand All @@ -105,7 +105,7 @@ async def set_discord_attachment_file(self, guild:discord.Guild):
# チャンネルにファイルを添付する
file_path = join(dirname(__file__), 'files' + os.sep + self.FILE)
await get_control_channel.send(self.FILE, file=discord.File(file_path))
logger.debug(f'***{get_control_channel.name}へファイルを添付しました!***')
logger.info(f'***{get_control_channel.name}へファイルを添付しました!***')

logger.debug('set_discord_attachment_file is over!')

Expand Down Expand Up @@ -167,6 +167,7 @@ async def save(self, guild:discord.Guild):
except pickle.PickleError:
# 書き込みに失敗したらなにもしない
self.rc_err = '保管に失敗しました。'
logger.error(self.rc_err)

# 追加するリアクションチャネルが問題ないかチェック
def check(self, ctx, reaction:str, channel:str):
Expand Down Expand Up @@ -241,7 +242,9 @@ async def add(self, ctx, reaction:str, channel:str):
if await self.save(guild) is False:
return self.rc_err

return f'リアクションチャンネルの登録に成功しました!\n{reaction} → <#{get_channel.id}>'
msg = f'リアクションチャンネルの登録に成功しました!\n{reaction} → <#{get_channel.id}>'
logger.info(msg)
return msg

async def list(self, ctx):
guild = ctx.guild
Expand Down
36 changes: 19 additions & 17 deletions cogs/modules/scrapboxsidandpnames.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,8 @@ def setup(self, guild:discord.Guild):
if len(self.targets) > 0:
return True

self.sbsu_err = '展開対象のSCRAPBOXのSID、プロジェクトが登録されていません。'
self.sbsu_err = '展開対象のScrapboxのSID、プロジェクトが登録されていません。'
logger.error(self.sbsu_err)
return False

def check(self, message:discord.Message):
Expand All @@ -76,24 +77,25 @@ async def expand(self, message:discord.Message):

json = None
target_url = ''
if mo is not None:
if mo.group(1):
target_url = 'https://scrapbox.io/api/pages/'+ self.target_project + '/' + mo.group(1)
if mo is not None and mo.group(1):
target_url = 'https://scrapbox.io/api/pages/'+ self.target_project + '/' + mo.group(1)

async with aiohttp.ClientSession(cookies=cookies) as session:
async with session.get(target_url) as r:
if r.status == 200:
json = await r.json()
async with aiohttp.ClientSession(cookies=cookies) as session:
async with session.get(target_url) as r:
if r.status == 200:
json = await r.json()

if json is not None:
embed = discord.Embed(title = json['title'], description = " ".join(json['descriptions']), url=mo.group(0), type='rich')
embed.set_author(name=json['user']['displayName'], icon_url=json['user']['photo'])
if json is not None:
embed = discord.Embed(title = json['title'], description = " ".join(json['descriptions']), url=mo.group(0), type='rich')
embed.set_author(name=json['user']['displayName'], icon_url=json['user']['photo'])

if json['image']:
embed.set_thumbnail(url=json['image'])
if json['image']:
embed.set_thumbnail(url=json['image'])

updated = datetime.datetime.fromtimestamp(json['updated'])
embed.add_field(name='Updated', value=updated.strftime('%Y/%m/%d(%a) %H:%M:%S'))

return embed
updated = datetime.datetime.fromtimestamp(json['updated'])
embed.add_field(name='Updated', value=updated.strftime('%Y/%m/%d(%a) %H:%M:%S'))

return embed
else:
logger.debug('展開対象外のため、対応しない')
return
8 changes: 4 additions & 4 deletions cogs/onmessagecog.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,17 +91,17 @@ async def on_message(self, message: discord.Message):
if message.author == botUser:# 自分は無視する
return

if self.scrapboxSidAndPnames.SCRAPBOX_URL_PATTERN in message.clean_content:
if self.scrapboxSidAndPnames.setup(message.guild):
await self.scrapbox_url_expand(message)
if self.scrapboxSidAndPnames.SCRAPBOX_URL_PATTERN in message.clean_content and self.scrapboxSidAndPnames.setup(message.guild):
await self.scrapbox_url_expand(message)
else:
return

# ScrapboxのURLを展開
async def scrapbox_url_expand(self, targetMessage: discord.Message):
if (self.scrapboxSidAndPnames.check(targetMessage)):
embed = await self.scrapboxSidAndPnames.expand(targetMessage)
await targetMessage.channel.send(embed=embed)
if embed is not None:
await targetMessage.channel.send(embed=embed)
else:
return

Expand Down
15 changes: 8 additions & 7 deletions cogs/reactionchannelercog.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ class ReactionChannelerCog(commands.Cog, name="リアクションチャンネラ
リアクションチャンネラー機能のカテゴリ(リアクションをもとに実行するアクション含む)。
"""
SPLIT_SIZE = 1900
TIMEOUT_TIME = 30.0

# ReactionChannelerCogクラスのコンストラクタ。Botを受取り、インスタンス変数として保持。
def __init__(self, bot):
Expand Down Expand Up @@ -53,7 +54,7 @@ async def add(self, ctx, reaction:str=None, channel:str=None):
"""
# リアクション、チャンネルがない場合は実施不可
if reaction is None or channel is None:
await ctx.channel.purge(limit=1)
await ctx.message.delete()
await ctx.channel.send('リアクションとチャンネルを指定してください。\nあなたのコマンド:`{0}`'.format(ctx.message.clean_content))
return
msg = await self.reaction_channel.add(ctx, reaction, channel)
Expand All @@ -74,23 +75,23 @@ async def list(self, ctx):
async def purge(self, ctx):
"""
リアクションチャンネラー(*)で反応する絵文字を全て削除します。
10秒以内に👌(ok_hand)のリアクションをつけないと実行されませんので、素早く対応ください。
30秒以内に👌(ok_hand)のリアクションをつけないと実行されませんので、素早く対応ください。
*指定した絵文字でリアクションされた時、チャンネルに通知する機能のこと
"""
command_author = ctx.author
# 念の為、確認する
confirm_text = f'全てのリアクションチャンネラーを削除しますか?\n 問題ない場合、10秒以内に👌(ok_hand)のリアクションをつけてください。\nあなたのコマンド:`{ctx.message.clean_content}`'
await ctx.channel.purge(limit=1)
confirm_text = f'全てのリアクションチャンネラーを削除しますか?\n 問題ない場合、30秒以内に👌(ok_hand)のリアクションをつけてください。\nあなたのコマンド:`{ctx.message.clean_content}`'
await ctx.message.delete()
await ctx.channel.send(confirm_text)

def check(reaction, user):
return user == command_author and str(reaction.emoji) == '👌'

# リアクション待ち
try:
reaction, user = await self.bot.wait_for('reaction_add', timeout=10.0, check=check)
reaction, user = await self.bot.wait_for('reaction_add', timeout=self.TIMEOUT_TIME, check=check)
except asyncio.TimeoutError:
await ctx.channel.send('→リアクションがなかったのでキャンセルしました!')
await ctx.channel.send('→リアクションがなかったので、リアクションチャンネラーの全削除をキャンセルしました!')
else:
msg = await self.reaction_channel.purge(ctx)
await ctx.channel.send(msg)
Expand All @@ -105,7 +106,7 @@ async def delete(self, ctx, reaction:str=None, channel:str=None):
"""
# リアクション、チャンネルがない場合は実施不可
if reaction is None or channel is None:
await ctx.channel.purge(limit=1)
await ctx.message.delete()
await ctx.channel.send('リアクションとチャンネルを指定してください。\nあなたのコマンド:`{0}`'.format(ctx.message.clean_content))
return
msg = await self.reaction_channel.delete(ctx, reaction, channel)
Expand Down
3 changes: 3 additions & 0 deletions readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ Discord用のBot。discord.pyのBot Commands Frameworkを使用して実装。
`/vcmembers` ボイスチャンネルに接続しているメンバーリストを取得
![image(vcmembers)](https://github.com/tetsuya-ki/images/blob/main/discord-bot-heroku/vcmembers.png?raw=true)

`/radikoSearch` ラジコの番組表を検索する機能
![image(radikoSearch)](https://github.com/tetsuya-ki/images/blob/main/discord-bot-heroku/radiko_search.png?raw=true)

### 管理用カテゴリ(admincog.pyで実装)

`/channel` チャンネルを操作するコマンド(サブコマンド必須)。チャンネルの操作権限を渡すと、削除も可能だから嫌だなと思って作ったコマンド。
Expand Down
3 changes: 2 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,5 @@ typing-extensions==3.7.4.3
websockets==6.0
wrapt==1.12.1
yarl==1.5.1
zope.interface==5.1.0
zope.interface==5.1.0
mojimoji==0.0.11

0 comments on commit 764eac5

Please sign in to comment.