0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

discord bot(python + replit) 個人的な修正点&コピペまとめ

Last updated at Posted at 2022-12-03

replitでdiscordの身内用botを何度か作り直したときの副産物である、コードの修正や個人的に使いやすかったコピペをまとめておく。
replitでほぼ24時間動くdiscord botを作る方法はこちらを参照。ただし、discord.pyの更新により、そのままでは実行できない。

実際に自分が作ったコードはここから確認できる。ただし、身内ノリの関係でう〇こが大量に出てくるので注意。

- #赤で表示されている部分を消す
+ #緑で表示されている部分を追加する

修正

intentsの適用

- client=discord.Client()
+ intents=discord.Intents.default()
+ intents.message_content=True
+ client=discord.Client(intents=intents)

いつぞやのdiscord.pyの更新で、そのままだとTypeError : __init__() missing 1 required keyword_only argument : 'intents'が出てくるが、client=discord.Client(intents=discord.Intents.default())と直しても(少なくともreplit上で起動した場合は)discord上の発言を読み取れない。

トークンの隠蔽

- TOKEN = os.getenv("TOKEN")
- client.run(TOKEN)
+ client.run(os.getenv('TOKEN'))

bot内に管理者以外が使えるevalexecがある場合、TOKENを定義してしまうとglobals()を実行するだけで簡単にTOKENが盗まれる。

再起動スムーズ化

- keep_alive()
- client.run(os.getenv('TOKEN'))
+ try:
+   keep_alive()
+   client.run(os.getenv('TOKEN'))
+ except Exception as e:
+   print(str(e))
+   os.system("kill 1")

429 Too Many Requests (error code: 0)などで長時間起動しないような事態がかなり減る。

オンライン表示

恐らく、keep_alive.pyみたいなファイル名に以下のようなコードが書いてあると思う。

from flask import Flask
from threading import Thread

app = Flask('')

@app.route('/')
def main():
  return 'Bot is aLive!'
  
def run():
  app.run(host="0.0.0.0", port=8080)
  
def keep_alive():
  server = Thread(target=run)
  server.start()

これをそのままmain.pyに移植すると、便利な表示機能が作れる。

main.py
- import keep_alive
+ from flask import Flask
+ from threading import Thread
+ 
+ app = Flask('')
+ @app.route('/')
+ def main():
+   #HTMLコードをreturnする
+   return "<html>Hello</html>"
+  
+ def run():
+   app.run(host="0.0.0.0", port=8080)
+ def keep_alive():
+   server = Thread(target=run)
+   server.start()
実装例

ffadata.charadataに格納されているセーブデータについて、レベルによる順位付けをオンラインで表示する。def main():以外は同じ

def main():
  rankdata = sorted([[id,ffadata.charadata[id]["lv"]] for id in list(ffadata.charadata.keys())], key=lambda x:x[1], reverse=True)
  t="<br><midashi>FFAもどき レベルランキング</midashi><br><table><thead><tr><td></td><td></td><td></td></tr></thead><tbody>"
  for i in range(len(rankdata)):
    data=rankdata[i]
    try:
      t+="<tr><td>"+str(i+1)+"位</td><td>"+iroiro.idname[data[0]]+"さん</td><td>lv"+"{:,}".format(data[1])+"</td></tr>"
    except:
      t+="<tr><td>"+str(i+1)+"位</td><td>id"+str(data[0])+"さん</td><td>lv"+"{:,}".format(data[1])+"</td></tr>"
  return """<!DOCTYPE html><html><head>
<title>Atmk Bot 24h Online Review</title>
<style type="text/css">
midashi{font.size=150%; font-weight: bold;}
</style></head><body>
<midashi>Atmk bot 24h オンライン表示</midashi><div id="DateTimeDisp"></div>
※1分経過で自動更新されます<br>
"""+t+"""<script>
setTimeout(function () {location.reload();}, 60000);var now = new Date();
var target = document.getElementById("DateTimeDisp");
var Year = now.getFullYear();
var Month = now.getMonth()+1;
var Date = now.getDate();
var Hour = now.getHours();
var Min = now.getMinutes();
var Sec = now.getSeconds();
target.innerHTML = Year + "/" + Month + "/" + Date + " " + Hour + ":" + Min + ":" + Sec;
</script></body></html>"""

便利コピペ

db使用セーブデータクラス

class Savedata:
  def __init__(self,name):
    self.savename=[name]
    if self.savename[0] in db:
      self.load()
  
  def save(self):
    db[self.savename[0]]="\n".join(["self."+attr+"="+str(eval("self."+attr)) for attr in dir(self) if not callable(getattr(self,attr)) and not attr.startswith("__")])

  def load(self):
    exec(db[self.savename[0]])
使用例
class Iroiro(Savedata):
  def __init__(self,name="iroiro"):
    self.counta=dict()
    super().__init__(name)

iroiro=Iroiro()

@client.event
async def on_message(message):
  if message.content=="":
    if not message.author.id in iroiro.counta:
      iroiro.counta[message.author.id]=0
    iroiro.counta[message.author.id]+=1
    iroiro.save()
小分けにするとめんどくさいsave, loadをまとめて実装できる。

with open(ファイル名) as f:などの構文を利用すれば、replit未使用でも利用可。

このSavedataクラスを使う場合、str型をデータに含める場合は必ず["hogehoge"]など、str型でない形 にすること。(上記のsave関数では、str型をstr型として保存できない。)

- #NG
- self.strvalue = "hogehoge"
- #save()後にload()すると以下のように解釈され、エラーとなる
- self.strvalue = hogehoge

+ #OK
+ self.strvalue = ["hogehoge"]
+ #この場合は正常に保存・読込が行われる。

txtファイルのみの発言の際、txtファイルの中身を本文と解釈する

@client.event
async def on_message(message):
  CONT=message.content
  if CONT=="":
    try:
      r=requests.get(message.attachments[0].url, stream=True, timeout=(3,7))
      r.encoding="utf-8"
      if r.status_code == 200:
        CONT=r.text.replace("\r","")
    except:
      pass

これを入れた後、message.contentの代わりにCONTを使えばよい。

管理者用デバッグ機能

@client.event
async def on_message(message):
  if ID==管理者のID:
    if CONT.startswith(">debug"):
      if not "\n" in CONT:
        try:
          await sendmes(message,str(eval(CONT[7:])))
        except Exception as e:
          await sendmes(message,str(e))
      elif CONT.endswith("```"):
        try:
          codes = "\n".join(CONT.split("\n")[2:-1])
          exec("mes=''\ndef print(text):\n  global mes\n  mes+=str(text)+'\\n'\n"+codes)
          global mes
          if mes=="":
            await sendmes(message,"DONE")
          else:
            await sendmes(message,mes)
        except Exception as e:
          await sendmes(message,str(e))
      else:
        try:
          await sendmes(message,CONT[7:])
        except:
          pass

>debug 変数名と入れれば変数名の中身が出力され、

>debug
```python
pythonコード
```

と入力すればpythonコードが実行される。また、

>debug
文章

と入力すれば文章をそのまま出力する。

2000文字超過のテキストをtxtファイルに変換 & 編集・削除でbotの発言を更新

class Zatuyou:
  def __init__(self):
    self.meslist=dict()

zatuyou=Zatuyou()

@client.event
async def sendmes(message,text,memo=False):
  if memo==False and len(text)<2000:
    botmes=await message.channel.send(text)
  else:
    with open("file/message.txt",mode="w",encoding='utf-8') as textfile:
      textfile.write(text)
    botmes=await message.channel.send(file=discord.File("file/message.txt"))
  if message.author.id!=このbotのID:
    if not message.id in zatuyou.meslist:
      zatuyou.meslist[message.id]=set()
    zatuyou.meslist[message.id].add(botmes)
    if len(zatuyou.meslist)>=100: #ここの数字はメモリ上限が許す限り任意
      zatuyou.meslist=dict()

@client.event
async def trydelete(message,text=""):
  try:
    await message.delete()
  except:
    if text=="":
      pass
    else:
      await sendmes(message,text)

@client.event
async def on_message_edit(before, after):
  if not after.author.bot:
    if after.id in zatuyou.meslist:
      for MES in zatuyou.meslist[after.id]:
        await trydelete(MES)
      del zatuyou.meslist[after.id]
    await on_message(after)

@client.event
async def on_message_delete(message):
  if not message.author.bot:
    if message.id in zatuyou.meslist:
      for MES in zatuyou.meslist[message.id]:
        await trydelete(MES)
      del zatuyou.meslist[message.id]

sendmesの後半部分では、botの発言内容をメッセージid毎に保存する機能がある。保存したmessageを参照して、可能ならば消す機能(trydelete)をon_message_edit, on_message_deleteに入れてある。

ランキング

class Iroiro(Savedata):
  def __init__(self,name="iroiro"):
    self.idname=dict()
    super().__init__(name)

iroiro = Iroiro()

@client.event
async def sendrank(message,data,title,tanni):
  rankdata = sorted(data.items(), key=lambda x:x[1],reverse=True)
  text=title+"\n"
  j=1
  for i in range(len(rankdata)):
    data=rankdata[i]
    try:
      user=iroiro.idname[data[0]]
      if data[0]==message.author.id:
        text+="**"+str(j)+""+user+" さん : "+"{:,}".format(data[1])+" "+tanni+"**\n"
      else:
        text+=str(j)+""+user+" さん : "+"{:,}".format(data[1])+" "+tanni+"\n"
    except:
      text+=str(j)+" 位 id "+str(data[0])+" さん : "+"{:,}".format(data[1])+" "+tanni+"\n"
    if i<len(rankdata)-1 and not data[1]==rankdata[i+1][1]:
      j+=1
  await sendmes(message,text)

@client.event
async def on_message(message):
  ID=message.author.id
  NAME=message.author.name
  if not message.author.bot:
    if not ID in iroiro.idname:
      iroiro.idname[ID]=NAME
      iroiro.save()

例えばawait sendrank(message,iroiro.counta,"「あ」発言回数ランキング", "回")のように使う。

自作エラー

class BotError(Exception):
  pass

import traceback

@client.event
async def on_message(message):
  if not message.author.bot:
    try:
      if message.content=="「あ」大好き":
        if iroiro.counta[message.author.id]<100:
          raise BotError("もっと「あ」と発言してください")
        await message.channel.send("そうですね、あなたは実際"+str(iroiro.counta[message.author.id])+"回「あ」と発言しました")
    except:
      await message.channel.send(message.author.name+"さん"+"`"*3+"\n"+traceback.format_exc()+"`"*3)

メッセージの反応幅を広げる

正規表現を使うとよい。ここを見れば全コマンドが分かる。ビジュアライザーもある。

import re

class Zatuyou:
  def __init__(self):
    self.multia=re.compile(u"^[aAあア]+$")

zatuyou = Zatuyou()

@client.event
async def on_message(message):
  if zatuyou.taika.match(CONT):
    if ID in iroiro.counta:
      iroiro.counta[ID]+=1
    else:
      iroiro.counta[ID]=1

上記の場合、a,A,あ,アの4種類のみからなる文章全てに反応する。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?