---
# System prepended metadata

title: 進階程式設計期末專題 - Discord Bot
tags: [report]

---

---
tags: report
---

> 建議從網站看: h<!---->ttps://hackmd.io/@konchin/2021finalreport

# 進階程式設計期末專題 - Discord Bot

## 動機

因為在家遠距學習很閒，而且那時候messenger越改越爛，我覺得群組換到discord會比較好

但因為discord自訂貼圖的上限偏小，而且沒付錢不能用，所以就想自己寫一個有這功能的bot

## 目標

* 在聊天頻道傳出特定訊息時，上傳圖片
* 用指令管理身分組
* 在身分組頻道點選表情符號加入身分組
* 伺服器加入審核
* 實用指令集

## 實作

### 概念

用 `discord.py` 這個discord官方提供的函式庫實作
用 `json` 格式來儲存所需的資料

### 示意圖

![](https://i.imgur.com/mydYeq3.png)


### code

上傳圖片的程式碼如下
```python=!
@commands.Cog.listener()
async def on_message(self, msg):
    if msg.author == self.bot.user:
        return
    with open("./jsons/roles.json",'r',encoding='utf8') as jfile:
        jrole = json.load(jfile)
    if msg.author.id in jrole["banned"]:
        return
    with open("./jsons/pictures_v2.json", 'r', encoding="utf8") as jfile:
        jdata = json.load(jfile)
    msg_fixed = msg.content.replace(' ', '').lower()
    msg_fixed = msg_fixed.replace('　', '')
    if msg_fixed in jdata["keys"]:
        async with msg.channel.typing():
            image = discord.File(f"./pictures/{choice(jdata['keys'][msg_fixed])}")
            await msg.channel.send(file=image)
```

`json` 文本示意如下
```json=!
{
    "keys": {
        "gay": [
            "160.jpg"
        ]
    },
    "pictures": {
        "160.jpg": [
            "gay"
        ]
}
```

加入圖片程式碼如下

```python=!
@picture.command(aliases=['u'])
async def upload(self, ctx, url: str= ''):
    if not is_permitted(ctx.author):
        await ctx.send("Command denied. Out of permission.", delete_after=3)
        return
    if not len(ctx.message.attachments) and not len(url):
        await ctx.send("No attachments or url.", delete_after=3)
        return 
    with open("./jsons/pictures_v2.json", 'r', encoding="utf8") as jfile:
        jdata = json.load(jfile)
    with open("./jsons/setting.json", 'r', encoding="utf8") as jfile:
        jsdata = json.load(jfile)
    path = "./pictures/"
    for attachment in ctx.message.attachments:
        filename = f"{jsdata['Picture_count']}.{attachment.filename.split('.')[-1].lower()}"
        jdata['pictures'][filename] = []
        jsdata['Picture_count'] += 1
        await attachment.save(path + filename)
        await ctx.send(f"{filename} saved.")
    with open("./jsons/pictures_v2.json", "w", encoding="utf8") as jfile:
        json.dump(jdata, jfile, indent=4)
    with open("./jsons/setting.json", 'w', encoding="utf8") as jfile:
        json.dump(jsdata, jfile, indent=4)
    if url == '':
        return 
    if not get(url):
        await ctx.send("Url error, please check your url.")
        return 
    res = get(url, stream=True)
    if res.status_code != 200:
        await ctx.send("Upload failed, please check your url.", delete_after=3)
        return 
    res.raw.decode_content = True
    filename = f"{jsdata['Picture_count']}.{url.split('.')[-1].lower()}"
    with open(path + filename, 'wb') as file:
        shutil.copyfileobj(res.raw, file)
    jdata['pictures'][filename] = []
    jsdata['Picture_count'] += 1
    await ctx.send(f"{filename} saved.")
    with open("./jsons/pictures_v2.json", "w", encoding="utf8") as jfile:
        json.dump(jdata, jfile, indent=4)
    with open("./jsons/setting.json", 'w', encoding="utf8") as jfile:
        json.dump(jsdata, jfile, indent=4)
        
@picture.command(aliases=['b'])
async def bond(self, ctx, key, file):
    if not is_permitted(ctx.author):
        await ctx.send("Command denied. Out of permission.", delete_after=3)
        return
    with open("./jsons/pictures_v2.json", 'r', encoding="utf8") as jfile:
        jdata = json.load(jfile)
    if file not in jdata["pictures"]:
        await ctx.send("File not exist.")
        return 
    if key not in jdata["keys"]:
        jdata["keys"][key] = []
    jdata["keys"][key].append(file)
    jdata["pictures"][file].append(key)
    with open("./jsons/pictures_v2.json", "w", encoding="utf8") as jfile:
        json.dump(jdata, jfile, indent=4)
    await ctx.send(f"{file} has bonded to {key}.")
    channel = ctx.guild.get_channel(jsdata["Picture_list_ch"])
    async with channel.typing():
        image = discord.File(f"./pictures/{file}")
        await channel.send(f"**alias:** {key}",file=image)
```

### 實際使用情況

**1.**
同學傳了一張圖，我想對這張圖表達我的想法
在聊天頻道打`gay`，discord bot就會隨機上傳一張`gay`這個標籤相應的圖

![](https://i.imgur.com/lkKMMaX.png =500x)

**2.**
在同一個訊息中，文字打`$picture upload`(或是簡寫`$p u`)，同時上傳圖片，discord bot 的伺服器端就會下載那張照片(或動圖、影片)
接下來透過`$picture bond`(簡寫`$p b`)將伺服器端圖片編號和標籤進行綁定
(同一張圖片可以綁定多個標籤，反之亦然，同個標籤也可以綁定多張圖片)
之後就可以在discord bot運行的頻道使用

![](https://i.imgur.com/fYHqDGr.png =500x)

如下圖
![](https://i.imgur.com/NidKkgc.png =500x)



## 運行平台

- Raspberry pi 4 (4GB RAM)
- SanDisk Extreme PRO microSDXC UHS-I Card (128GB)
- Raspberry pi OS

## 外部連結

[Discord bot - Shiki Natsume 完整使用說明](/@konchin/discord_bot_commands)

## 參考資料

[discord.py API Reference](https://discordpy.readthedocs.io/en/stable/api.html)

[Youtube playlist: Proladon - Code a discord bot](https://youtube.com/playlist?list=PLSCgthA1Anif1w6mKM3O6xlBGGypXtrtN)