テキスト読み上げを行えるようにした
This commit is contained in:
parent
b968a8f4bc
commit
a5149d5ca1
@ -1,5 +1,7 @@
|
|||||||
FROM node:23.1.0-alpine
|
FROM node:23.1.0-alpine
|
||||||
|
|
||||||
|
RUN apk add --no-cache ffmpeg
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package*.json ./
|
COPY package*.json ./
|
||||||
|
@ -5,9 +5,15 @@ services:
|
|||||||
NODE_ENV: development
|
NODE_ENV: development
|
||||||
env_file:
|
env_file:
|
||||||
- config/development.env
|
- config/development.env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=development
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
- /app/node_modules
|
- /app/node_modules
|
||||||
environment:
|
|
||||||
- NODE_ENV=development
|
|
||||||
command: ["npm", "run", "dev"]
|
command: ["npm", "run", "dev"]
|
||||||
|
depends_on:
|
||||||
|
- voicevox
|
||||||
|
voicevox:
|
||||||
|
image: voicevox/voicevox_engine:latest
|
||||||
|
ports:
|
||||||
|
- "50021:50021"
|
||||||
|
@ -6,8 +6,8 @@ services:
|
|||||||
NODE_ENV: production
|
NODE_ENV: production
|
||||||
env_file:
|
env_file:
|
||||||
- config/production.env
|
- config/production.env
|
||||||
|
environment:
|
||||||
|
- NODE_ENV=production
|
||||||
volumes:
|
volumes:
|
||||||
- .:/app
|
- .:/app
|
||||||
restart: always
|
restart: always
|
||||||
environment:
|
|
||||||
- NODE_ENV=production
|
|
||||||
|
132
package-lock.json
generated
132
package-lock.json
generated
@ -10,8 +10,11 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.17.0",
|
"@discordjs/voice": "^0.17.0",
|
||||||
|
"axios": "^1.7.7",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.3",
|
||||||
"dotenv": "^16.4.5"
|
"dotenv": "^16.4.5",
|
||||||
|
"libsodium-wrappers": "^0.7.15",
|
||||||
|
"uuid": "^11.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.7"
|
"nodemon": "^3.1.7"
|
||||||
@ -268,6 +271,23 @@
|
|||||||
"node": ">= 8"
|
"node": ">= 8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/asynckit": {
|
||||||
|
"version": "0.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
|
||||||
|
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/axios": {
|
||||||
|
"version": "1.7.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.7.tgz",
|
||||||
|
"integrity": "sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"follow-redirects": "^1.15.6",
|
||||||
|
"form-data": "^4.0.0",
|
||||||
|
"proxy-from-env": "^1.1.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/balanced-match": {
|
"node_modules/balanced-match": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||||
@ -337,6 +357,18 @@
|
|||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/combined-stream": {
|
||||||
|
"version": "1.0.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
|
||||||
|
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"delayed-stream": "~1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/concat-map": {
|
"node_modules/concat-map": {
|
||||||
"version": "0.0.1",
|
"version": "0.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||||
@ -362,6 +394,15 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/delayed-stream": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.4.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/discord-api-types": {
|
"node_modules/discord-api-types": {
|
||||||
"version": "0.37.100",
|
"version": "0.37.100",
|
||||||
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.100.tgz",
|
"resolved": "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.37.100.tgz",
|
||||||
@ -425,6 +466,40 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/follow-redirects": {
|
||||||
|
"version": "1.15.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
|
||||||
|
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"type": "individual",
|
||||||
|
"url": "https://github.com/sponsors/RubenVerborgh"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=4.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"debug": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/form-data": {
|
||||||
|
"version": "4.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.1.tgz",
|
||||||
|
"integrity": "sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"asynckit": "^0.4.0",
|
||||||
|
"combined-stream": "^1.0.8",
|
||||||
|
"mime-types": "^2.1.12"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/fsevents": {
|
"node_modules/fsevents": {
|
||||||
"version": "2.3.3",
|
"version": "2.3.3",
|
||||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
|
||||||
@ -516,6 +591,21 @@
|
|||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/libsodium": {
|
||||||
|
"version": "0.7.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium/-/libsodium-0.7.15.tgz",
|
||||||
|
"integrity": "sha512-sZwRknt/tUpE2AwzHq3jEyUU5uvIZHtSssktXq7owd++3CSgn8RGrv6UZJJBpP7+iBghBqe7Z06/2M31rI2NKw==",
|
||||||
|
"license": "ISC"
|
||||||
|
},
|
||||||
|
"node_modules/libsodium-wrappers": {
|
||||||
|
"version": "0.7.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/libsodium-wrappers/-/libsodium-wrappers-0.7.15.tgz",
|
||||||
|
"integrity": "sha512-E4anqJQwcfiC6+Yrl01C1m8p99wEhLmJSs0VQqST66SbQXXBoaJY0pF4BNjRYa/sOQAxx6lXAaAFIlx+15tXJQ==",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"libsodium": "^0.7.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/lodash": {
|
"node_modules/lodash": {
|
||||||
"version": "4.17.21",
|
"version": "4.17.21",
|
||||||
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
|
||||||
@ -534,6 +624,27 @@
|
|||||||
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==",
|
"integrity": "sha512-/k20Lg2q8LE5xiaaSkMXk4sfvI+9EGEykFS4b0CHHGWqDYU0bGUFSwchNOMA56D7TCs9GwVTkqe9als1/ns8UQ==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/mime-db": {
|
||||||
|
"version": "1.52.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
|
||||||
|
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/mime-types": {
|
||||||
|
"version": "2.1.35",
|
||||||
|
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
|
||||||
|
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"mime-db": "1.52.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 0.6"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/minimatch": {
|
"node_modules/minimatch": {
|
||||||
"version": "3.1.2",
|
"version": "3.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||||
@ -632,6 +743,12 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/proxy-from-env": {
|
||||||
|
"version": "1.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
|
||||||
|
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/pstree.remy": {
|
"node_modules/pstree.remy": {
|
||||||
"version": "1.1.8",
|
"version": "1.1.8",
|
||||||
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
"resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz",
|
||||||
@ -748,6 +865,19 @@
|
|||||||
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/uuid": {
|
||||||
|
"version": "11.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-11.0.2.tgz",
|
||||||
|
"integrity": "sha512-14FfcOJmqdjbBPdDjFQyk/SdT4NySW4eM0zcG+HqbHP5jzuH56xO3J1DGhgs/cEMCfwYi3HQI1gnTO62iaG+tQ==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/esm/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "8.18.0",
|
"version": "8.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
|
||||||
|
@ -12,8 +12,11 @@
|
|||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@discordjs/voice": "^0.17.0",
|
"@discordjs/voice": "^0.17.0",
|
||||||
|
"axios": "^1.7.7",
|
||||||
"discord.js": "^14.16.3",
|
"discord.js": "^14.16.3",
|
||||||
"dotenv": "^16.4.5"
|
"dotenv": "^16.4.5",
|
||||||
|
"libsodium-wrappers": "^0.7.15",
|
||||||
|
"uuid": "^11.0.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.7"
|
"nodemon": "^3.1.7"
|
||||||
|
@ -4,20 +4,28 @@ const { joinVoiceChannel } = require('@discordjs/voice');
|
|||||||
module.exports = {
|
module.exports = {
|
||||||
data: new SlashCommandBuilder()
|
data: new SlashCommandBuilder()
|
||||||
.setName('join')
|
.setName('join')
|
||||||
.setDescription('Join the voice channel you are in'),
|
.setDescription('Join the voice channel you are in and listen to this text channel'),
|
||||||
async execute(interaction) {
|
async execute(interaction) {
|
||||||
const voiceChannel = interaction.member.voice.channel;
|
const voiceChannel = interaction.member.voice.channel;
|
||||||
|
const textChannel = interaction.channel;
|
||||||
|
|
||||||
if (!voiceChannel) {
|
if (!voiceChannel) {
|
||||||
return interaction.reply('ボイスチャンネルに接続されていません。');
|
return interaction.reply('ボイスチャンネルに接続されていません。');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ボイスチャンネルに参加
|
||||||
joinVoiceChannel({
|
joinVoiceChannel({
|
||||||
channelId: voiceChannel.id,
|
channelId: voiceChannel.id,
|
||||||
guildId: voiceChannel.guild.id,
|
guildId: voiceChannel.guild.id,
|
||||||
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
|
adapterCreator: voiceChannel.guild.voiceAdapterCreator,
|
||||||
});
|
});
|
||||||
|
|
||||||
await interaction.reply('ボイスチャンネルに接続しました。');
|
// サーバーごとにチャンネル情報を保存
|
||||||
|
interaction.client.guildChannels.set(interaction.guild.id, {
|
||||||
|
textChannelId: textChannel.id,
|
||||||
|
voiceChannelId: voiceChannel.id
|
||||||
|
});
|
||||||
|
|
||||||
|
await interaction.reply('ボイスチャンネルに接続し、このテキストチャンネルのメッセージを読み上げます。');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -1,11 +1,27 @@
|
|||||||
|
const { createAudioPlayer, createAudioResource, getVoiceConnection } = require('@discordjs/voice');
|
||||||
|
const { synthesizeSpeech } = require('../services/tts');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
name: 'messageCreate',
|
name: 'messageCreate',
|
||||||
execute(message) {
|
async execute(message) {
|
||||||
|
const client = message.client;
|
||||||
|
|
||||||
|
// サーバーごとのチャンネル情報を取得
|
||||||
|
const guildChannels = client.guildChannels.get(message.guild.id);
|
||||||
|
if (!guildChannels) return;
|
||||||
|
|
||||||
|
// 記憶されたテキストチャンネルのメッセージのみ処理
|
||||||
|
if (message.channel.id !== guildChannels.textChannelId) return;
|
||||||
if (message.author.bot) return;
|
if (message.author.bot) return;
|
||||||
|
|
||||||
if (message.mentions.has(message.client.user)) {
|
const connection = getVoiceConnection(message.guild.id);
|
||||||
const response = message.content.replace(`<@${message.client.user.id}>`, '').trim();
|
if (!connection) return;
|
||||||
message.channel.send(response || "メッセージを受け取りました!");
|
|
||||||
}
|
const audioUrl = await synthesizeSpeech(message.content);
|
||||||
|
const resource = createAudioResource(audioUrl);
|
||||||
|
const player = createAudioPlayer();
|
||||||
|
|
||||||
|
player.play(resource);
|
||||||
|
connection.subscribe(player);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -12,6 +12,9 @@ const client = new Client({
|
|||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// サーバーごとのテキストチャンネルとボイスチャンネルの情報を保持
|
||||||
|
client.guildChannels = new Map();
|
||||||
|
|
||||||
// コマンドの読み込み
|
// コマンドの読み込み
|
||||||
client.commands = new Collection();
|
client.commands = new Collection();
|
||||||
const commands = [];
|
const commands = [];
|
||||||
|
23
src/services/tts.js
Normal file
23
src/services/tts.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { v4: uuidv4 } = require('uuid');
|
||||||
|
|
||||||
|
async function synthesizeSpeech(text) {
|
||||||
|
if (process.env.NODE_ENV === 'development') {
|
||||||
|
console.log('[development] Synthesizing speech...');
|
||||||
|
const voicevoxUrl = `http://voicevox:50021/audio_query?text=${encodeURIComponent(text)}&speaker=1`;
|
||||||
|
const { data } = await axios.post(voicevoxUrl);
|
||||||
|
|
||||||
|
const synthesisUrl = `http://voicevox:50021/synthesis?speaker=1`;
|
||||||
|
const response = await axios.post(synthesisUrl, data, { responseType: 'arraybuffer' });
|
||||||
|
const filePath = path.join('/tmp', `${uuidv4()}.mp3`);
|
||||||
|
fs.writeFileSync(filePath, Buffer.from(response.data));
|
||||||
|
return filePath;
|
||||||
|
} else {
|
||||||
|
console.log('[production] Synthesizing speech...');
|
||||||
|
return path.join(__dirname, 'dummy.mp3'); // 本番用の音声生成処理
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { synthesizeSpeech };
|
Loading…
Reference in New Issue
Block a user