Skip to content

Commit

Permalink
Merge pull request #61 from tetsuya-ki/develop
Browse files Browse the repository at this point in the history
Developから取り込み(リアクションチャンネラー機能拡張(Webhook)、キーワード指定削除機能追加)
  • Loading branch information
tetsuya-ki authored Feb 23, 2021
2 parents 88637f7 + 571fcf8 commit 6daca7d
Show file tree
Hide file tree
Showing 8 changed files with 181 additions and 31 deletions.
3 changes: 2 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ FIRST_REACTION_CHECK=True \
SCRAPBOX_SID_AND_PROJECTNAME1=xxx:sid@pjname \
COUNT_RANK_SETTING=5 \
PURGE_TARGET_IS_ME_AND_BOT=False \
OHGIRI_JSON_URL=XXXXXXXXXX
OHGIRI_JSON_URL=XXXXXXXXXX\
REACTION_CHANNELER_PERMIT_WEBHOOK_ID=99999999

WORKDIR $dir

Expand Down
36 changes: 36 additions & 0 deletions cogs/admincog.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,42 @@ def check(reaction, user):
else:
await confirm_msg.reply(f'チャンネル「{ctx.channel.name}」からロール**「{targetRole}」**の閲覧権限を削除しました!')

# 指定した文章を含むメッセージを削除するコマンド
@commands.command(aliases=['dm','dem','delm'],description='指定した文章を含むメッセージを削除します')
async def deleteMessage(self, ctx, keyword:str, limit_num:str='1'):
"""
自分かBOTの指定した文章を含むメッセージを削除します。
削除対象のキーワード(必須)、削除対象とするメッセージの数(任意。デフォルトは1)
なお、BOTにメッセージの管理権限、メッセージの履歴閲覧権限、メッセージの閲覧権限がない場合は失敗します。
"""
self.command_author = ctx.author
# botかコマンドの実行主かチェックし、キーワードを含むメッセージのみ削除
def is_me_and_contain_keyword(m):
return (self.command_author == m.author or (m.author.bot and settings.PURGE_TARGET_IS_ME_AND_BOT)) and keyword in m.clean_content

# 指定がない、または、不正な場合は、コマンドを削除。そうではない場合、コマンドを削除し、指定数だけメッセージを走査し、キーワードを含むものだけ削除する
if keyword is None:
await ctx.message.delete()
await ctx.channel.send('削除対象のキーワードを指定してください(削除対象とするメッセージ数を続けて指定してください)。\nあなたのコマンド:`{0}`'.format(ctx.message.clean_content))
return
if limit_num.isdecimal():
limit_num = int(limit_num) + 1
else:
await ctx.message.delete()
await ctx.channel.send('有効な数字ではないようです。削除数は1以上の数値を指定してください。\nあなたのコマンド:`{0}`'.format(ctx.message.clean_content))
return

if limit_num > 1000:
limit_num = 1000
elif limit_num < 2:
await ctx.message.delete()
await ctx.channel.send('削除数は1以上の数値を指定してください。\nあなたのコマンド:`{0}`'.format(ctx.message.clean_content))
return

# 違和感を持たせないため、コマンドを削除した分を省いた削除数を通知する。
deleted = await ctx.channel.purge(limit=limit_num, check=is_me_and_contain_keyword)
await ctx.channel.send('{0}個のメッセージを削除しました。\nあなたのコマンド:`{1}`'.format(len(deleted) - 1, ctx.message.clean_content))

# チャンネル作成時に実行されるイベントハンドラを定義
@commands.Cog.listener()
async def on_guild_channel_create(self, channel: discord.abc.GuildChannel):
Expand Down
1 change: 1 addition & 0 deletions cogs/modules/files/.env-docker.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ SCRAPBOX_SID_AND_PROJECTNAME=all:scrapbox_sid@projectname1,projectname2;guild1:s
COUNT_RANK_SETTING=5
PURGE_TARGET_IS_ME_AND_BOT=False
OHGIRI_JSON_URL=ohgiri_json_url
REACTION_CHANNELER_PERMIT_WEBHOOK_ID=99999999;99999999
1 change: 1 addition & 0 deletions cogs/modules/files/.env.sample
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ SCRAPBOX_SID_AND_PROJECTNAME = "all:scrapbox_sid@projectname1,projectname2;guild
COUNT_RANK_SETTING = 5
PURGE_TARGET_IS_ME_AND_BOT=False
OHGIRI_JSON_URL = "ohgiri_json_url"
REACTION_CHANNELER_PERMIT_WEBHOOK_ID = 99999999;99999999
106 changes: 90 additions & 16 deletions cogs/modules/reactionchannel.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import re
from discord.message import Attachment
from discord.utils import get
from os.path import join, dirname
Expand All @@ -10,12 +11,15 @@
import base64
import json
import datetime
import aiohttp

logger = getLogger(__name__)

class ReactionChannel:
FILE = 'reaction-channel.json'
REACTION_CHANNEL = 'reaction_channel_control'
WEBHOOK_URL = 'discord.com/api/webhooks/'
NOT_PERMIT_WEBHOOK_MESSAGE = '※環境変数に未登録のWebhookのため、実行されません。環境変数`REACTION_CHANNELER_PERMIT_WEBHOOK_ID`にWebhook IDか、「all」を記載ください(allの場合はすべてのWebhookが許可されます)。'

def __init__(self, guilds, bot):
self.guilds = guilds
Expand Down Expand Up @@ -135,7 +139,27 @@ async def set_rc(self, guild:discord.Guild):
with open(file_path, mode='r') as f:
dict = json.load(f)
serialize = dict["pickle"]
self.reaction_channels = pickle.loads(base64.b64decode(serialize.encode()))
reaction_channels = pickle.loads(base64.b64decode(serialize.encode()))

# Wenhook対応
reaction_channeler_permit_webhook_id_list = settings.REACTION_CHANNELER_PERMIT_WEBHOOK_ID.replace(' ', '').split(';')
for rc in reaction_channels:
# rc[3](チャンネル名が入るところ)が空ではない場合、通常のリアクションチャンネラーのためそのまま追加。そうではない場合はWebhookのため、有効か確認する
if rc[3] != '':
self.reaction_channels.append(rc)
else:
# 環境変数に登録されているものかチェック
ch_guild_id = str(re.search(self.WEBHOOK_URL+r'(\d+)/', rc[2]).group(1))
l_in = [s for s in reaction_channeler_permit_webhook_id_list if (ch_guild_id in s or 'all' in s.lower())]
# 環境変数に登録されていないものの場合、先頭に「※」を付与
if len(l_in) == 0:
logger.info(f'{rc[0]}{rc[1]}{rc[2]}は有効になっていません({self.NOT_PERMIT_WEBHOOK_MESSAGE})。')
rc[2] = re.sub('^※?', '※', rc[2])
# 含まれる場合、先頭の「※」を削除
else:
rc[2] = re.sub('^※?', '', rc[2])
self.reaction_channels.append(rc)

self.guild_reaction_channels = [rc[1:] for rc in self.reaction_channels if str(guild.id) in map(str, rc)]
# joinするので文字列に変換し、リストに追加する
self.guild_rc_txt_lists = []
Expand Down Expand Up @@ -170,7 +194,7 @@ async def save(self, guild:discord.Guild):
logger.error(self.rc_err)

# 追加するリアクションチャネルが問題ないかチェック
def check(self, ctx, reaction:str, channel:str):
async def check(self, ctx, reaction:str, channel:str, is_webhook:bool = False):
reaction_id = None
if reaction.count(':') == 2:
reaction_id = reaction.split(':')[1]
Expand All @@ -188,11 +212,21 @@ def check(self, ctx, reaction:str, channel:str):
self.rc_err = f'この絵文字を本Botで使用しているため、登録できません。(reaction: {reaction})'
return False

# チャンネルが不正な場合
get_channel = discord.utils.get(guild.text_channels, name=channel)
if get_channel is None:
self.rc_err = 'チャンネルが不正なので登録できません。'
return False
# webhookの場合のチェック
if is_webhook:
async with aiohttp.ClientSession() as session:
async with session.get(channel) as r:
logger.debug(channel)
if r.status != 200:
self.rc_err = 'Webhookが不正なので登録できません。'
logger.info(self.rc_err)
return False
else:
# チャンネルが不正(ギルドに存在しないチャンネル)な場合
get_channel = discord.utils.get(guild.text_channels, name=channel)
if get_channel is None:
self.rc_err = 'チャンネルが不正なので登録できません。'
return False

# リアクションチャンネルが未登録ならチェックOK
if self.rc_len == 0:
Expand Down Expand Up @@ -222,15 +256,35 @@ async def add(self, ctx, reaction:str, channel:str):
if channel_info is not None:
channel = channel_info.name

if self.check(ctx, reaction, channel) is False:
is_webhook = False
if self.WEBHOOK_URL in channel:
is_webhook = True
if await self.check(ctx, reaction, channel, is_webhook) is False:
return self.rc_err
get_channel = discord.utils.get(guild.text_channels, name=channel)

succeeded_channel_or_webhook = ''
addItem = []
addItem.append(guild.id)
addItem.append(reaction)
addItem.append(get_channel.name)
addItem.append(get_channel.id)
if is_webhook:
# 環境変数に登録されているものかチェック
ch_guild_id = str(re.search(self.WEBHOOK_URL+r'(\d+)/', channel).group(1))
reaction_channeler_permit_webhook_id_list = settings.REACTION_CHANNELER_PERMIT_WEBHOOK_ID.replace(' ', '').split(';')
l_in = [s for s in reaction_channeler_permit_webhook_id_list if (ch_guild_id or 'all') in s.lower()]
# 環境変数に登録されていないものの場合、先頭に「※」を付与
add_messsage = ''
webhook_url = channel
if len(l_in) == 0:
webhook_url = re.sub('^※?', '※', webhook_url)
add_messsage = self.NOT_PERMIT_WEBHOOK_MESSAGE
addItem.append(webhook_url)
addItem.append('')
succeeded_channel_or_webhook = f'{webhook_url}\n{add_messsage}'
else:
addItem.append(get_channel.name)
addItem.append(get_channel.id)
succeeded_channel_or_webhook = f'<#{get_channel.id}>'

# 追加
self.reaction_channels.append(addItem)
Expand All @@ -242,7 +296,7 @@ async def add(self, ctx, reaction:str, channel:str):
if await self.save(guild) is False:
return self.rc_err

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

Expand All @@ -252,11 +306,17 @@ async def list(self, ctx):
logger.debug(f'**リスト**, {self.guild_reaction_channels}')
text = ''
for list in self.guild_reaction_channels:
text = f'{text} リアクション:{list[0]} → <#{list[2]}>\n'

# list[2]が空文字でない場合、チャンネルとして出力。そうではない場合、Webhookのためlist[1]を出力
if list[2] != '':
text = f'{text} リアクション:{list[0]} → <#{list[2]}>\n'
else:
text = f'{text} リアクション:{list[0]}{list[1]}\n'
if text == '':
return f'*現在登録されているリアクションチャンネルはありません!'
else:
# 有効でないWebhookがある場合、説明を付与
if '※' in text:
text = text + f'\n{self.NOT_PERMIT_WEBHOOK_MESSAGE}'
return f'*現在登録されているリアクションチャンネルの一覧です!({self.rc_len}種類)\n{text}'

# 全削除
Expand Down Expand Up @@ -300,17 +360,31 @@ async def delete(self, ctx, reaction:str, channel:str):
deleteItem = []
deleteItem.append(guild.id)
deleteItem.append(reaction)
deleteItem.append(get_channel.name)
deleteItem.append(get_channel.id)
channel_or_webhook_msg = ''
if self.WEBHOOK_URL in channel:
deleteItem.append(channel)
deleteItem.append('')
channel_or_webhook_msg = f'{channel}'
else:
deleteItem.append(get_channel.name)
deleteItem.append(get_channel.id)
channel_or_webhook_msg = f'<#{get_channel.id}>'

# 削除
self.reaction_channels = [s for s in self.reaction_channels if s != deleteItem]
self.guild_reaction_channels = [s for s in self.guild_reaction_channels if s != deleteItem[1:]]
self.guild_rc_txt_lists = [s for s in self.guild_rc_txt_lists if s != '+'.join(map(str, deleteItem[1:]))]
self.rc_len = len(self.guild_reaction_channels)
# Webhookの場合、先頭に「※」をつけて再度削除する(有効でない時は※付与するため...)
if self.WEBHOOK_URL in channel:
deleteItem[2] = '※' + channel
self.reaction_channels = [s for s in self.reaction_channels if s != deleteItem]
self.guild_reaction_channels = [s for s in self.guild_reaction_channels if s != deleteItem[1:]]
self.guild_rc_txt_lists = [s for s in self.guild_rc_txt_lists if s != '+'.join(map(str, deleteItem[1:]))]
self.rc_len = len(self.guild_reaction_channels)

# 保管
if await self.save(guild) is False:
return self.rc_err

return f'リアクションチャンネラーの削除に成功しました!\n{reaction} <#{get_channel.id}>'
return f'リアクションチャンネラーの削除に成功しました!\n{reaction}{channel_or_webhook_msg}'
1 change: 1 addition & 0 deletions cogs/modules/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,4 @@ def num_env(param):
COUNT_RANK_SETTING = num_env(os.environ.get('COUNT_RANK_SETTING'))
PURGE_TARGET_IS_ME_AND_BOT = if_env(os.environ.get('PURGE_TARGET_IS_ME_AND_BOT'))
OHGIRI_JSON_URL = os.environ.get('OHGIRI_JSON_URL')
REACTION_CHANNELER_PERMIT_WEBHOOK_ID = os.environ.get('REACTION_CHANNELER_PERMIT_WEBHOOK_ID')
47 changes: 36 additions & 11 deletions cogs/reactionchannelercog.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@
from .modules import settings
from .onmessagecog import OnMessageCog
from logging import getLogger
from discord import Webhook, AsyncWebhookAdapter

import discord
import datetime
import asyncio
import aiohttp

logger = getLogger(__name__)

Expand Down Expand Up @@ -191,27 +193,50 @@ async def reaction_channeler(self, payload: discord.RawReactionActionEvent):
logger.debug('Already reaction added. emoji_count:'+ str(message_reaction.count))
return

contents = [message.clean_content[i: i+200] for i in range(0, len(message.clean_content), 200)]
if len(contents) != 1 :
contents = [message.clean_content[i: i+1980] for i in range(0, len(message.clean_content), 1980)]
if len(contents) == 0:
return
elif len(contents) > 1:
contents[0] += ' *長いので分割しました*'
embed = discord.Embed(title = contents[0], description = '<#' + str(message.channel.id) + '>', type='rich')

is_webhook = False
channel = ''
# Webhookの場合
if reaction[2] == '':
is_webhook = True
channel = f'{message.guild.name} / #{message.channel.name}'
else:
channel = f'<#{message.channel.id}>'

embed = discord.Embed(description = contents[0], type='rich')
embed.set_author(name=reaction[0] + ' :reaction_channeler', url='https://github.com/tetsuya-ki/discord-bot-heroku/')
embed.set_thumbnail(url=message.author.avatar_url)

created_at = message.created_at.replace(tzinfo=datetime.timezone.utc)
created_at_jst = created_at.astimezone(datetime.timezone(datetime.timedelta(hours=9)))

embed.add_field(name='作成日時', value=created_at_jst.strftime('%Y/%m/%d(%a) %H:%M:%S'))
embed.add_field(name='元のチャンネル', value=channel)

if len(contents) != 1 :
for addText in contents[1:]:
embed.add_field(name='addText', value=addText + ' *長いので分割しました*', inline=False)

to_channel = guild.get_channel(int(reaction[2]))
logger.debug('setting:'+str(reaction[2]))
logger.debug('to_channel: '+str(to_channel))

await to_channel.send(reaction[1] + ': ' + message.jump_url, embed=embed)
embed.set_footer(text=contents[1] + ' *長いので分割しました(以降省略)*')

# リアクションチャンネラーがWebhookだった場合の処理
if is_webhook and '※' not in reaction[1]:
async with aiohttp.ClientSession() as session:
webhook = Webhook.from_url(reaction[1], adapter=AsyncWebhookAdapter(session))
try:
await webhook.send('ReactionChanneler(Webhook): ' + message.jump_url, embed=embed, username='ReactionChanneler', avatar_url=message.author.avatar_url)
except (discord.HTTPException,discord.NotFound,discord.Forbidden,discord.InvalidArgument) as e:
logger.error(e)
elif '※' in reaction[1]:
logger.info('環境変数に登録されていないギルドIDをもつWebhookのため、実行されませんでした。')
# 通常のリアクションチャンネラー機能の実行
else:
to_channel = guild.get_channel(int(reaction[2]))
logger.debug('setting:'+str(reaction[2]))
logger.debug('to_channel: '+str(to_channel))
await to_channel.send(reaction[1] + ': ' + message.jump_url, embed=embed)

# 画像を保存
async def save_file(self, payload: discord.RawReactionActionEvent):
Expand Down
Loading

0 comments on commit 6daca7d

Please sign in to comment.