Discordの音楽botを改造した

ゲームサークルのDiscordサーバーで音楽botをよく使うのですが、使いやすいbotがなかなか見つけられずにいました。そこで見つけたのが[Discord-SimpleMusicBot](https://github.com/mtripg6666tdr/Discord-SimpleMusicBot)なのですが、使っている上で少々不便な点がありました。せっかくオープンソースなので、改造して使うことにしました。
完成したbotが[Тро́йка](https://github.com/zawanume/Troika)です。リポジトリ名にキリル文字が使えなかったのでTroikaになっています。

# キューが空になっても退出しない

```typescript:PlayManager.ts
export class PlayManager extends ServerManagerBase {
    // 中略

    async onStreamFinished(){
        // 中略    

        if(this.server.queue.isEmpty){
            // キューがなくなったら待機 オリジナルでは切断
            this.Log("Queue empty");
            if(this.server.boundTextChannel){
                const ch = this.server.bot.client.getChannel(this.server.boundTextChannel) as TextChannel;
                if(!ch) return;
                await ch.createMessage(":raised_hand:キューが空になりました").catch(e => Util.logger.log(e, "error"));
            }
            this.stop();
        }else{
            // なくなってないなら再生開始！
            this.play();
        }
    }
    // 中略

}
```

オリジナルでは`disconnect()`が呼ばれて切断されていた箇所で`stop()`を呼ぶようにして退出しないようにました。

# NowPlayingを表示しない

```typescript:PlayManager.ts
export class PlayManager extends ServerManagerBase {
    // 中略

    async play(time:number = 0):Promise<PlayManager>{
        // 中略

        try{
            // 中略
            /*if(mes){
                再生開始メッセージを表示しない
                中略

            }*/
            // 中略

        }
        // 中略

    }
    // 中略

}
```

次の曲が再生される度にNowPlayingが表示されてDiscordが混み合うので、表示させている箇所をコメントアウトして表示させないようにしました。

# アクティビティで音量を注意喚起

```typescript:bot.ts
export class MusicBot extends MusicBotBase {
    // 中略

    private async onReady(){
        // 中略

        if(!this.maintenance){
          client.editStatus({
            type: discord.Constants.ActivityTypes.GAME, // オリジナルではLISTENING
            name: "音量注意"
          });
        }
        // 中略

    }
    // 中略

}
```

オリジナルでは「音楽を再生中」と表示されますが、これを変更して「音量注意をプレイ中」と表示されるようにしました。

# ボイスチャットに誰もいなくなったら切断

```typescript
export class MusicBot extends MusicBotBase {
    // 中略

    private async onVoiceChannelLeave(member:discord.Member, oldChannel:discord.TextVoiceChannel){
        const server = this.data[oldChannel.guild.id];
        if(!server || !server.connection) return;
        if(member.id === this._client.user.id){
              // サーバー側からのボットの切断
              server.player.disconnect();
              const bound = this._client.getChannel(server.boundTextChannel);
              if(!bound) return;
              await this._client.createMessage(bound.id, ":postbox: 正常に切断しました").catch(e => this.Log(e));
        }else if(oldChannel.voiceMembers.has(this._client.user.id) && oldChannel.voiceMembers.size === 1){
              if(server.queue instanceof QueueManagerWithBgm && server.queue.isBGM){
              server.player.disconnect();
        }else{
            // 誰も聞いてる人がいない場合、キューを空にして切断 オリジナルでは一時停止
            server.queue.removeAll();
            server.player.disconnect();
            await this._client.createMessage(server.boundTextChannel, ":wave:ボイスチャンネルから誰もいなくなったため切断します").catch(e => this.Log(e));
        }
    }
}

```

オリジナルではサーバーに誰もいなくなったとき一時停止するだけでしたが、キューを空にして切断するようにしました。

# サーバーの管理者権限が無くてもbotの管理者ならresetコマンドを使える

```typescript:reset.ts
export default class Reset extends BaseCommand {
    // 中略

    async run(message:CommandMessage, options:CommandArgs){
        if(message.member.id == Util.config.adminId){
            // config.jsonのadminIdに登録されたユーザー
            Util.logger.log("[reset] Command \"reset\" is used by adminId");
        }else if(message.member.permissions.has("manageGuild")){
            // サーバーの管理権限を持つユーザー
            Util.logger.log("[reset] Command \"reset\" is used by member who has permission \"manageGuild\"");
        }else{
            message.reply("この操作を実行する権限がありません").catch(e => Util.logger.log(e, "error"));
            return;
        }
        // 中略

    }
    // 中略

}
```

オリジナルだとサーバー上で強い権限を持つユーザーしか`reset`コマンドは実行できなかったが、`config.json`の`adminId`に登録されたユーザーなら権限が無くても実行できるようにした。

# デフォルトの音量を下げる

```typescript:PlayManager.ts
export class PlayManager extends ServerManagerBase {
    protected readonly retryLimit = 3;
    protected _seek = 0;
    protected _errorReportChannel = null as TextChannel;
    protected _volume = 35; // オリジナルでは100
    protected _errorCount = 0;
    protected _errorUrl = "";
    protected _preparing = false;
    protected _currentAudioInfo = null as AudioSource;
    protected _cost = 0;
    readonly onStreamFinishedBindThis:any = null;

    // 略

}
```

音量が大きいことを知らずにボイスチャットに入ってきて鼓膜をやられる人が多発したので、デフォルトの音量を35まで下げました。

# その他
他にも、ちょっとしたネタコマンドが搭載されています。今後も気が向いたら増やすかもしれません。