diff --git a/.env.example b/.env.example
index 47b0cab2..b2338a9f 100644
--- a/.env.example
+++ b/.env.example
@@ -1 +1,2 @@
DISCORD_TOKEN=
+MAIN_CHANNEL_ID=
diff --git a/.gitignore b/.gitignore
index 5391269e..0752c5ff 100644
--- a/.gitignore
+++ b/.gitignore
@@ -118,3 +118,6 @@ dist
# ts
build/
+
+# WebStorm
+.idea/
diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index b58b603f..00000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# Default ignored files
-/shelf/
-/workspace.xml
-# Editor-based HTTP Client requests
-/httpRequests/
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
deleted file mode 100644
index 1f0d6674..00000000
--- a/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,83 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
deleted file mode 100644
index 79ee123c..00000000
--- a/.idea/codeStyles/codeStyleConfig.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/discord.xml b/.idea/discord.xml
deleted file mode 100644
index d8e95616..00000000
--- a/.idea/discord.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/git_toolbox_prj.xml b/.idea/git_toolbox_prj.xml
deleted file mode 100644
index b3820067..00000000
--- a/.idea/git_toolbox_prj.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 03d9549e..00000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsLibraryMappings.xml b/.idea/jsLibraryMappings.xml
deleted file mode 100644
index d23208fb..00000000
--- a/.idea/jsLibraryMappings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/jsLinters/eslint.xml b/.idea/jsLinters/eslint.xml
deleted file mode 100644
index 541945bb..00000000
--- a/.idea/jsLinters/eslint.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 197fcc23..00000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/oreorebot2.iml b/.idea/oreorebot2.iml
deleted file mode 100644
index 0c8867d7..00000000
--- a/.idea/oreorebot2.iml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/prettier.xml b/.idea/prettier.xml
deleted file mode 100644
index 727b8b53..00000000
--- a/.idea/prettier.xml
+++ /dev/null
@@ -1,7 +0,0 @@
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7f..00000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/adaptor/discord-output.ts b/src/adaptor/discord-output.ts
new file mode 100644
index 00000000..3c2e4f9e
--- /dev/null
+++ b/src/adaptor/discord-output.ts
@@ -0,0 +1,53 @@
+import { Client, MessageEmbed } from 'discord.js';
+import type { EmbedMessage } from '../model/embed-message';
+import type { StandardOutput } from '../service/voice-diff';
+
+export class DiscordOutput implements StandardOutput {
+ constructor(
+ private readonly client: Client,
+ private readonly channelId: string
+ ) {}
+
+ async sendEmbed(embed: EmbedMessage): Promise {
+ const channel = await this.client.channels.fetch(this.channelId);
+ if (!channel || !channel.isText()) {
+ throw new Error(`the channel (${this.channelId}) is not text channel`);
+ }
+
+ const made = buildEmbed(embed);
+ await channel.send({
+ embeds: [made]
+ });
+ }
+}
+
+function buildEmbed(embed: EmbedMessage) {
+ const makeEmbed = new MessageEmbed();
+ const { title, color, description, fields, url, footer, thumbnail, author } =
+ embed;
+ if (author) {
+ makeEmbed.setAuthor({ name: author.name, iconURL: author.iconUrl });
+ }
+ if (color) {
+ makeEmbed.setColor(color);
+ }
+ if (description) {
+ makeEmbed.setDescription(description);
+ }
+ if (fields) {
+ makeEmbed.setFields(fields);
+ }
+ if (footer) {
+ makeEmbed.setFooter({ text: footer });
+ }
+ if (title) {
+ makeEmbed.setTitle(title);
+ }
+ if (url) {
+ makeEmbed.setURL(url);
+ }
+ if (thumbnail) {
+ makeEmbed.setThumbnail(thumbnail.url);
+ }
+ return makeEmbed;
+}
diff --git a/src/adaptor/discord-participant.ts b/src/adaptor/discord-participant.ts
new file mode 100644
index 00000000..24ed4931
--- /dev/null
+++ b/src/adaptor/discord-participant.ts
@@ -0,0 +1,22 @@
+import type { VoiceChannelParticipant } from '../service/voice-diff';
+import type { VoiceState } from 'discord.js';
+
+export class DiscordParticipant implements VoiceChannelParticipant {
+ constructor(private voiceState: VoiceState) {}
+
+ get userName(): string {
+ return this.voiceState.member?.displayName ?? '名無し';
+ }
+
+ get userAvatar(): string {
+ const avatarURL = this.voiceState.member?.displayAvatarURL();
+ if (!avatarURL) {
+ throw new Error('アバターが取得できませんでした。');
+ }
+ return avatarURL;
+ }
+
+ get channelName(): string {
+ return this.voiceState.channel?.name ?? '名無し';
+ }
+}
diff --git a/src/adaptor/index.ts b/src/adaptor/index.ts
index a3541c13..255a88b0 100644
--- a/src/adaptor/index.ts
+++ b/src/adaptor/index.ts
@@ -8,3 +8,5 @@ export * from './mock-voice';
export * from './random';
export * from './transformer';
export * from './voice-room-proxy';
+export * from './discord-participant';
+export * from './discord-output';
diff --git a/src/adaptor/voice-room-proxy.ts b/src/adaptor/voice-room-proxy.ts
index f0fd0fdf..f7655f06 100644
--- a/src/adaptor/voice-room-proxy.ts
+++ b/src/adaptor/voice-room-proxy.ts
@@ -10,11 +10,14 @@ type ObserveExpectation = 'ChangingIntoFalsy' | 'ChangingIntoTruthy' | 'All';
* @class VoiceRoomProxy
* @implements {VoiceRoomEventProvider}
*/
-export class VoiceRoomProxy implements VoiceRoomEventProvider {
- constructor(private readonly client: Client) {}
+export class VoiceRoomProxy implements VoiceRoomEventProvider {
+ constructor(
+ private readonly client: Client,
+ private readonly map: (voiceState: VoiceState) => V
+ ) {}
private registerHandler(
- handler: (v: VoiceState) => Promise,
+ handler: (v: V) => Promise,
toObserve: keyof VoiceState,
expected: ObserveExpectation
): void {
@@ -28,32 +31,32 @@ export class VoiceRoomProxy implements VoiceRoomEventProvider {
(expected === 'ChangingIntoTruthy' && !!newState[toObserve]) ||
expected === 'All')
) {
- await handler(newState);
+ await handler(this.map(newState));
}
});
}
- onJoin(handler: (voiceState: VoiceState) => Promise): void {
+ onJoin(handler: (voiceState: V) => Promise): void {
this.registerHandler(handler, 'channelId', 'ChangingIntoTruthy');
}
- onLeave(handler: (voiceState: VoiceState) => Promise): void {
+ onLeave(handler: (voiceState: V) => Promise): void {
this.registerHandler(handler, 'channelId', 'ChangingIntoFalsy');
}
- onMute(handler: (voiceState: VoiceState) => Promise): void {
+ onMute(handler: (voiceState: V) => Promise): void {
this.registerHandler(handler, 'mute', 'ChangingIntoTruthy');
}
- onDeafen(handler: (voiceState: VoiceState) => Promise): void {
+ onDeafen(handler: (voiceState: V) => Promise): void {
this.registerHandler(handler, 'deaf', 'ChangingIntoTruthy');
}
- onUnmute(handler: (voiceState: VoiceState) => Promise): void {
+ onUnmute(handler: (voiceState: V) => Promise): void {
this.registerHandler(handler, 'mute', 'ChangingIntoFalsy');
}
- onUndeafen(handler: (voiceState: VoiceState) => Promise): void {
+ onUndeafen(handler: (voiceState: V) => Promise): void {
this.registerHandler(handler, 'deaf', 'ChangingIntoFalsy');
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index c894ee5b..84e89f82 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -1,5 +1,7 @@
import {
ActualClock,
+ DiscordOutput,
+ DiscordParticipant,
DiscordVoiceConnectionFactory,
InMemoryReservationRepository,
InMemoryTypoRepository,
@@ -9,14 +11,17 @@ import {
MathRandomGenerator,
MessageProxy,
MessageUpdateProxy,
- DiscordVoiceRoomController
+ DiscordVoiceRoomController,
+ VoiceRoomProxy
} from '../adaptor';
import { Client, Intents, version } from 'discord.js';
import {
MessageResponseRunner,
MessageUpdateResponseRunner,
- ScheduleRunner
+ ScheduleRunner,
+ VoiceRoomResponseRunner
} from '../runner';
+import { VoiceChannelParticipant, VoiceDiff } from '../service/voice-diff';
import {
allCommandResponder,
allMessageEventResponder,
@@ -30,7 +35,8 @@ import { join } from 'path';
dotenv.config();
const token = process.env.DISCORD_TOKEN;
-if (!token) {
+const mainChannelId = process.env.MAIN_CHANNEL_ID;
+if (!token || !mainChannelId) {
throw new Error(
'Error> Failed to start. You did not specify any environment variables.'
);
@@ -101,6 +107,15 @@ commandRunner.addResponder(
)
);
+const provider = new VoiceRoomProxy(
+ client,
+ (voiceState) => new DiscordParticipant(voiceState)
+);
+const voiceRunner = new VoiceRoomResponseRunner(provider);
+voiceRunner.addResponder(
+ new VoiceDiff(new DiscordOutput(client, mainChannelId))
+);
+
client.once('ready', () => {
readyLog(client);
});
diff --git a/src/service/voice-diff.test.ts b/src/service/voice-diff.test.ts
new file mode 100644
index 00000000..feed1fda
--- /dev/null
+++ b/src/service/voice-diff.test.ts
@@ -0,0 +1,47 @@
+import { StandardOutput, VoiceDiff } from './voice-diff';
+
+test('use case of VoiceDiff', async () => {
+ const outputJoin: StandardOutput = {
+ sendEmbed(message) {
+ expect(message).toStrictEqual({
+ title: 'めるが限界に入りました',
+ description: '何かが始まる予感がする。',
+ color: 0x1e63e9,
+ author: { name: 'はらちょからのお知らせ' },
+ thumbnail: {
+ url: 'https://cdn.discordapp.com/avatars/586824421470109716/9eb541e567f0ce82d34e55a37213c524.webp'
+ }
+ });
+ return Promise.resolve();
+ }
+ };
+ const outputLeave: StandardOutput = {
+ sendEmbed(message) {
+ expect(message).toStrictEqual({
+ title: 'めるが限界から抜けました',
+ description: 'あいつは良い奴だったよ...',
+ color: 0x1e63e9,
+ author: { name: 'はらちょからのお知らせ' },
+ thumbnail: {
+ url: 'https://cdn.discordapp.com/avatars/586824421470109716/9eb541e567f0ce82d34e55a37213c524.webp'
+ }
+ });
+ return Promise.resolve();
+ }
+ };
+
+ const responderJoin = new VoiceDiff(outputJoin);
+ const responderLeave = new VoiceDiff(outputLeave);
+ await responderJoin.on('JOIN', {
+ userName: 'める',
+ channelName: '限界',
+ userAvatar:
+ 'https://cdn.discordapp.com/avatars/586824421470109716/9eb541e567f0ce82d34e55a37213c524.webp'
+ });
+ await responderLeave.on('LEAVE', {
+ userName: 'める',
+ channelName: '限界',
+ userAvatar:
+ 'https://cdn.discordapp.com/avatars/586824421470109716/9eb541e567f0ce82d34e55a37213c524.webp'
+ });
+});
diff --git a/src/service/voice-diff.ts b/src/service/voice-diff.ts
new file mode 100644
index 00000000..bbaa6c00
--- /dev/null
+++ b/src/service/voice-diff.ts
@@ -0,0 +1,46 @@
+import { VoiceRoomEvent, VoiceRoomEventResponder } from '../runner';
+import { EmbedMessage } from '../model/embed-message';
+
+export interface VoiceChannelParticipant {
+ userName: string;
+ userAvatar: string;
+ channelName: string;
+}
+
+export interface StandardOutput {
+ sendEmbed(embed: EmbedMessage): Promise;
+}
+
+export class VoiceDiff
+ implements VoiceRoomEventResponder
+{
+ constructor(private readonly stdout: StandardOutput) {}
+
+ async on(
+ event: VoiceRoomEvent,
+ voiceState: VoiceChannelParticipant
+ ): Promise {
+ if (event === 'JOIN') {
+ // VoiceChannel 入室時
+ const { userName, userAvatar, channelName } = voiceState;
+ await this.stdout.sendEmbed({
+ title: userName + 'が' + channelName + 'に入りました',
+ description: '何かが始まる予感がする。',
+ color: 0x1e63e9,
+ author: { name: 'はらちょからのお知らせ' },
+ thumbnail: { url: userAvatar }
+ });
+ }
+ if (event === 'LEAVE') {
+ // VoiceChannel 退出時
+ const { userName, userAvatar, channelName } = voiceState;
+ await this.stdout.sendEmbed({
+ title: userName + 'が' + channelName + 'から抜けました',
+ description: 'あいつは良い奴だったよ...',
+ color: 0x1e63e9,
+ author: { name: 'はらちょからのお知らせ' },
+ thumbnail: { url: userAvatar }
+ });
+ }
+ }
+}
diff --git a/yarn.lock b/yarn.lock
index 88d02eca..acbd46c5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1736,9 +1736,9 @@ discord-api-types@^0.26.0, discord-api-types@^0.26.1:
integrity sha512-T5PdMQ+Y1MEECYMV5wmyi9VEYPagEDEi4S0amgsszpWY0VB9JJ/hEvM6BgLhbdnKky4gfmZEXtEEtojN8ZKJQQ==
discord.js@^13.5.0:
- version "13.5.0"
- resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.5.0.tgz#f9ca9e629f2de0fb138e8c916fa93e40d70631f5"
- integrity sha512-K+ZcB0f+wA1ZzDhz3hlaAi4Ap7jSvVEUZ+U29T4DMoiNNUv22F4vu1byrOq8GyyLLDFiZ3iSudea0MvSHu3fQA==
+ version "13.6.0"
+ resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.6.0.tgz#d8a8a591dbf25cbcf9c783d5ddf22c4694860475"
+ integrity sha512-tXNR8zgsEPxPBvGk3AQjJ9ljIIC6/LOPjzKwpwz8Y1Q2X66Vi3ZqFgRHYwnHKC0jC0F+l4LzxlhmOJsBZDNg9g==
dependencies:
"@discordjs/builders" "^0.11.0"
"@discordjs/collection" "^0.4.0"