Skip to content

Commit

Permalink
Feat: additional settings (#2280)
Browse files Browse the repository at this point in the history
* Feat: additional settings

* change default

* format

* space

* subcommand

* feat: parse space-separated-subcommands

* chore: reconstruct dir structure

* add: nowplayingnotificationlevel

* change: useConfig -> getConfig

* feat: change skipvote

* fix: fix

* improve: introduce asyncLocalStorage

* feat: nowplaying notification

* feat: queueManager#addQueue supports suitable locale

* chore: fix test

* fix: documentation

* rename
  • Loading branch information
mtripg6666tdr authored Jun 1, 2024
1 parent a5014d6 commit 53b07c7
Show file tree
Hide file tree
Showing 118 changed files with 1,203 additions and 570 deletions.
2 changes: 2 additions & 0 deletions docs/docs/guide/commands/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@
|[キュー内を検索](./searchqueue.md)|キュー内を検索します。引数にキーワードを指定します。|
|[サウンドクラウドを検索](./searchsoundcloud.md)|曲をSoundCloudで検索します。|
|[シーク](./seek.md)|楽曲をシークします。|
|[設定>現在再生中](./setting>nowplaying.md)|現在再生中パネルの表示モードの設定をします。何も指定しないと現在の設定を確認できます。|
|[設定>スキップ投票](./setting>skipvote.md)|スキップ投票の有効・無効の設定をします。何も指定しないと現在の設定を確認できます。|
|[シャッフル](./shuffle.md)|キューの内容をシャッフルします。|
|[スキップ](./skip.md)|状況に応じて現在再生中の曲をスキップするか、スキップ投票を開始します。|
|[サムネイル](./thumbnail.md)|現在再生中のサムネイルを表示します。検索パネルが開いていて検索パネル中の番号が指定された場合にはその曲のサムネイルを表示します。|
Expand Down
21 changes: 21 additions & 0 deletions docs/docs/guide/commands/setting_nowplaying.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
sidebar_label: 設定>現在再生中
---
# `設定>現在再生中`コマンド
現在再生中パネルの表示モードの設定をします。何も指定しないと現在の設定を確認できます。

スラッシュコマンドでは、`/setting nowplaying`を使用してください。

## 別名
`設定>現在再生中`以外にも以下の別名を使用できます。

- setting>nowplaying




## 実行に必要な権限
同じボイスチャンネルに接続していてかつDJロールを保持していること, ボイスチャンネルの唯一のユーザーであること, サーバーの管理権限を持っていることのいずれか

※管理者権限や、サーバーの管理権限、チャンネルの管理権限、および管理者権限を持つユーザーはこの権限を満たしていなくてもいつでもこのコマンドを実行できます。

21 changes: 21 additions & 0 deletions docs/docs/guide/commands/setting_skipvote.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
sidebar_label: 設定>スキップ投票
---
# `設定>スキップ投票`コマンド
スキップ投票の有効・無効の設定をします。何も指定しないと現在の設定を確認できます。

スラッシュコマンドでは、`/setting skipvote`を使用してください。

## 別名
`設定>スキップ投票`以外にも以下の別名を使用できます。

- setting>skipvote




## 実行に必要な権限
同じボイスチャンネルに接続していてかつDJロールを保持していること, ボイスチャンネルの唯一のユーザーであること, サーバーの管理権限を持っていることのいずれか

※管理者権限や、サーバーの管理権限、チャンネルの管理権限、および管理者権限を持つユーザーはこの権限を満たしていなくてもいつでもこのコマンドを実行できます。

35 changes: 34 additions & 1 deletion locales/ja/commands.json
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@
"player": "音楽プレイヤー制御系",
"playlist": "プレイリスト操作系",
"utility": "ユーティリティ系",
"bot": "ボット操作全般"
"bot": "ボット操作全般",
"settings": "設定"
},
"commandList": "コマンド一覧",
"toLearnMoreMessage": "`{{prefix}}コマンド 再生`のように、コマンド名を引数につけて、そのコマンドの詳細を表示できます。",
Expand Down Expand Up @@ -489,6 +490,38 @@
"success": "シークしました",
"failed": "シークに失敗しました"
},
"setting": {
"name": "設定",
"description": "ボットの設定を変更します"
},
"setting>skipvote": {
"name": "設定>スキップ投票",
"description": "スキップ投票の有効・無効の設定をします。何も指定しないと現在の設定を確認できます。",
"args": {
"enabled": {
"description": "スキップ投票を有効にするかどうか。"
}
},
"currentState": "スキップ投票は現在 **{{status}}** の設定になっています。",
"changed": "スキップ投票を **{{status}}** にしました。"
},
"setting>nowplaying": {
"name": "設定>現在再生中",
"description": "現在再生中パネルの表示モードの設定をします。何も指定しないと現在の設定を確認できます。",
"args": {
"level": {
"description": "表示モードを指定します。",
"choices": {
"normal": "通常",
"silent": "サイレント",
"disabled": "無効"
}
}
},
"invalidLevel": "指定されたモードが正しくありません。normal, silent, disabledのいずれかを指定してください。",
"changed": "現在再生中パネルの表示モードを **{{level}}** に設定しました。",
"currentState": "現在再生中パネルの表示モードは現在 **{{level}}** に設定されています。"
},
"shuffle": {
"name": "シャッフル",
"description": "キューの内容をシャッフルします。",
Expand Down
4 changes: 3 additions & 1 deletion locales/ja/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,5 +94,7 @@
"attachmentNotFound": "添付ファイルが見つかりません",
"invalidUrl": "有効なURLを指定してください。キーワードで再生する場合は`検索`コマンドを使用してください。"
},
"system": "システム"
"system": "システム",
"enabled": "有効",
"disabled": "無効"
}
9 changes: 4 additions & 5 deletions src/AudioSource/audiosource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@

import type { YouTube } from "./youtube";
import type { LoggerObject } from "../logger";
import type { i18n } from "i18next";
import type { EmbedField } from "oceanic.js";
import type { Readable } from "stream";

Expand Down Expand Up @@ -140,13 +139,13 @@ export abstract class AudioSource<T extends ThumbnailType, U extends AudioSource
}

/** 現在再生中の曲を示すEmbedFieldを生成します。 */
abstract toField(verbose: boolean, t: i18n["t"]): EmbedField[];
abstract toField(verbose: boolean): EmbedField[];
/** クラスを非同期で初期化します。 */
abstract init(url: string, prefetched: U | null, t: i18n["t"]): Promise<AudioSource<T, U>>;
abstract init(url: string, prefetched: U | null): Promise<AudioSource<T, U>>;
/** 再生するためのストリームをフェッチします。 */
abstract fetch(url?: boolean, t?: i18n["t"]): Promise<StreamInfo>;
abstract fetch(url?: boolean): Promise<StreamInfo>;
/** 現在再生中の曲に関する追加データを生成します。 */
abstract npAdditional(t: i18n["t"]): string;
abstract npAdditional(): string;
/** データをプレーンなオブジェクトにエクスポートします。 */
abstract exportData(): U;

Expand Down
11 changes: 8 additions & 3 deletions src/AudioSource/bestdori.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
import type { AudioSourceBasicJsonFormat, UrlStreamInfo } from ".";

import candyget from "candyget";
import { i18n } from "i18next";

import { AudioSource } from "./audiosource";
import { getCommandExecutionContext } from "../Commands";

export class BestdoriS extends AudioSource<string, BestdoriJsonFormat> {
protected artist = "";
Expand All @@ -31,7 +31,9 @@ export class BestdoriS extends AudioSource<string, BestdoriJsonFormat> {
protected arranger: string;
private id: number;

async init(url: string, prefetched: BestdoriJsonFormat, t: i18n["t"]){
async init(url: string, prefetched: BestdoriJsonFormat){
const { t } = getCommandExecutionContext();

this.url = url;
const id = BestdoriApi.instance.getAudioId(url);
if(!id) throw new Error("Invalid streamable url");
Expand Down Expand Up @@ -68,11 +70,14 @@ export class BestdoriS extends AudioSource<string, BestdoriJsonFormat> {
};
}

toField(_: boolean, t: i18n["t"]){
toField(_: boolean){
const { t } = getCommandExecutionContext();

const typeMap = {
anime: "カバー",
normal: "アニメ",
};

return [
{
name: "バンド名",
Expand Down
10 changes: 7 additions & 3 deletions src/AudioSource/custom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,19 @@
*/

import type { AudioSourceBasicJsonFormat, StreamInfo } from ".";
import type { i18n } from "i18next";

import { AudioSource } from "./audiosource";
import { getCommandExecutionContext } from "../Commands";
import { createFragmentalDownloadStream, downloadAsReadable, isAvailableRawAudioURL, requestHead, retrieveRemoteAudioInfo } from "../Util";

export class CustomStream extends AudioSource<string, AudioSourceBasicJsonFormat> {
constructor(){
super({ isCacheable: false });
}

async init(url: string, prefetched: AudioSourceBasicJsonFormat | null, t: i18n["t"]){
async init(url: string, prefetched: AudioSourceBasicJsonFormat | null){
const { t } = getCommandExecutionContext();

if(prefetched){
this.title = prefetched.title || t("audioSources.customStream");
this.url = url;
Expand Down Expand Up @@ -69,7 +71,9 @@ export class CustomStream extends AudioSource<string, AudioSourceBasicJsonFormat
};
}

toField(_: boolean, t: i18n["t"]){
toField(_: boolean){
const { t } = getCommandExecutionContext();

return [
{
name: ":link:URL",
Expand Down
10 changes: 7 additions & 3 deletions src/AudioSource/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@
*/

import type { AudioSourceBasicJsonFormat, ReadableStreamInfo } from "./audiosource";
import type { i18n } from "i18next";

import * as fs from "fs";
import * as path from "path";

import { AudioSource } from "./audiosource";
import { getCommandExecutionContext } from "../Commands";
import { retrieveRemoteAudioInfo } from "../Util";

export class FsStream extends AudioSource<string, AudioSourceBasicJsonFormat> {
constructor(){
super({ isCacheable: false });
}

async init(url: string, _: AudioSourceBasicJsonFormat | null, t: i18n["t"]){
async init(url: string, _: AudioSourceBasicJsonFormat | null){
const { t } = getCommandExecutionContext();

this.url = url;
const info = await retrieveRemoteAudioInfo(url);
this.title = info.displayTitle || t("audioSources.customStream");
Expand All @@ -46,7 +48,9 @@ export class FsStream extends AudioSource<string, AudioSourceBasicJsonFormat> {
};
}

toField(_: boolean, t: i18n["t"]){
toField(_: boolean){
const { t } = getCommandExecutionContext();

return [
{
name: `:asterisk:${t("moreInfo")}`,
Expand Down
10 changes: 7 additions & 3 deletions src/AudioSource/googledrive.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,22 @@
*/

import type { AudioSourceBasicJsonFormat, UrlStreamInfo } from ".";
import type { i18n } from "i18next";

import candyget from "candyget";
import * as htmlEntities from "html-entities";

import { AudioSource } from "./audiosource";
import { getCommandExecutionContext } from "../Commands";
import { retrieveHttpStatusCode, retrieveRemoteAudioInfo } from "../Util";

export class GoogleDrive extends AudioSource<string, AudioSourceBasicJsonFormat> {
constructor(){
super({ isCacheable: false });
}

async init(url: string, prefetched: AudioSourceBasicJsonFormat | null, t: i18n["t"]){
async init(url: string, prefetched: AudioSourceBasicJsonFormat | null){
const { t } = getCommandExecutionContext();

if(prefetched){
this.title = prefetched.title || t("audioSources.driveStream");
this.url = url;
Expand All @@ -56,7 +58,9 @@ export class GoogleDrive extends AudioSource<string, AudioSourceBasicJsonFormat>
};
}

toField(_: boolean, t: i18n["t"]){
toField(_: boolean){
const { t } = getCommandExecutionContext();

return [
{
name: `:asterisk:${t("moreInfo")}`,
Expand Down
14 changes: 10 additions & 4 deletions src/AudioSource/niconico.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,13 @@
*/

import type { AudioSourceBasicJsonFormat, ReadableStreamInfo } from ".";
import type { i18n } from "i18next";
import type { Readable } from "stream";

import { convert as htmlToText } from "html-to-text";
import NiconicoDL, { isValidURL } from "niconico-dl.js";

import { AudioSource } from "./audiosource";
import { getCommandExecutionContext } from "../Commands";
import { createPassThrough } from "../Util";

export class NicoNicoS extends AudioSource<string, NiconicoJsonFormat> {
Expand All @@ -35,7 +35,9 @@ export class NicoNicoS extends AudioSource<string, NiconicoJsonFormat> {
super({ isSeekable: false });
}

async init(url: string, prefetched: NiconicoJsonFormat, t: i18n["t"]){
async init(url: string, prefetched: NiconicoJsonFormat){
const { t } = getCommandExecutionContext();

this.url = url;
this.nico = new NiconicoDL(url, /* quality */ "high");
if(prefetched){
Expand Down Expand Up @@ -75,7 +77,9 @@ export class NicoNicoS extends AudioSource<string, NiconicoJsonFormat> {
};
}

toField(verbose: boolean, t: i18n["t"]){
toField(verbose: boolean){
const { t } = getCommandExecutionContext();

return [
{
name: `:cinema:${t("audioSources.videoAuthor")}`,
Expand All @@ -97,7 +101,9 @@ export class NicoNicoS extends AudioSource<string, NiconicoJsonFormat> {
];
}

npAdditional(t: i18n["t"]){
npAdditional(){
const { t } = getCommandExecutionContext();

return `${t("audioSources.videoAuthor")}: ` + this.author;
}

Expand Down
19 changes: 9 additions & 10 deletions src/AudioSource/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,10 @@

import type { KnownAudioSourceIdentifer } from "../Component/queueManager";
import type { SourceCache } from "../Component/sourceCache";
import type { i18n } from "i18next";

import * as AudioSource from ".";
import { isAvailableRawAudioURL } from "../Util";
import { useConfig } from "../config";
import { getConfig } from "../config";
import { getLogger } from "../logger";

type AudioSourceBasicInfo = {
Expand All @@ -32,10 +31,10 @@ type AudioSourceBasicInfo = {
forceCache: boolean,
};

const { isDisabledSource } = useConfig();
const { isDisabledSource } = getConfig();
const logger = getLogger("Resolver");

export async function resolve(info: AudioSourceBasicInfo, cacheManager: SourceCache, preventSourceCache: boolean, t: i18n["t"]){
export async function resolve(info: AudioSourceBasicInfo, cacheManager: SourceCache, preventSourceCache: boolean){
let basicInfo: AudioSource.AudioSource<any, any> | null = null;

const type = info.type;
Expand Down Expand Up @@ -66,29 +65,29 @@ export async function resolve(info: AudioSourceBasicInfo, cacheManager: SourceCa
basicInfo = await AudioSource.initYouTube(url, gotData as AudioSource.YouTubeJsonFormat, cache);
}else if(!isDisabledSource("custom") && (type === "custom" || type === "unknown" && isAvailableRawAudioURL(url))){
// カスタムストリーム
basicInfo = await new AudioSource.CustomStream().init(url, info.knownData, t);
basicInfo = await new AudioSource.CustomStream().init(url, info.knownData);
}else if(!isDisabledSource("soundcloud") && (type === "soundcloud" || AudioSource.SoundCloudS.validateUrl(url))){
// soundcloud
basicInfo = await new AudioSource.SoundCloudS().init(url, gotData as AudioSource.SoundcloudJsonFormat, t);
basicInfo = await new AudioSource.SoundCloudS().init(url, gotData as AudioSource.SoundcloudJsonFormat);
}else if(!isDisabledSource("spotify") && (type === "spotify" || AudioSource.Spotify.validateTrackUrl(url)) && AudioSource.Spotify.available){
// spotify
basicInfo = await new AudioSource.Spotify().init(url, gotData as AudioSource.SpotifyJsonFormat);
}else if(type === "unknown"){
// google drive
if(!isDisabledSource("googledrive") && AudioSource.GoogleDrive.validateUrl(url)){
basicInfo = await new AudioSource.GoogleDrive().init(url, info.knownData, t);
basicInfo = await new AudioSource.GoogleDrive().init(url, info.knownData);
}else if(!isDisabledSource("streamable") && AudioSource.StreamableApi.getVideoId(url)){
// Streamable
basicInfo = await new AudioSource.Streamable().init(url, gotData as AudioSource.StreamableJsonFormat);
}else if(process.env.BD_ENABLE && AudioSource.BestdoriApi.instance.getAudioId(url)){
// Bestdori
basicInfo = await new AudioSource.BestdoriS().init(url, gotData as AudioSource.BestdoriJsonFormat, t);
basicInfo = await new AudioSource.BestdoriS().init(url, gotData as AudioSource.BestdoriJsonFormat);
}else if(!isDisabledSource("niconico") && AudioSource.NicoNicoS.validateUrl(url)){
// NicoNico
basicInfo = await new AudioSource.NicoNicoS().init(url, gotData as AudioSource.NiconicoJsonFormat, t);
basicInfo = await new AudioSource.NicoNicoS().init(url, gotData as AudioSource.NiconicoJsonFormat);
}else if(!isDisabledSource("twitter") && AudioSource.Twitter.validateUrl(url)){
// Twitter
basicInfo = await new AudioSource.Twitter().init(url, gotData as AudioSource.TwitterJsonFormat, t);
basicInfo = await new AudioSource.Twitter().init(url, gotData as AudioSource.TwitterJsonFormat);
}
}

Expand Down
Loading

0 comments on commit 53b07c7

Please sign in to comment.