init commit

This commit is contained in:
kimpure 2026-02-09 18:31:56 +00:00
commit dcf0853e20
54 changed files with 7720 additions and 0 deletions

7
.dockerignore Normal file
View file

@ -0,0 +1,7 @@
dist
node_modules
.env
.git
.trash
packages/db/generated

1
.editorconfig Normal file
View file

@ -0,0 +1 @@
tab=4

9
.gitignore vendored Normal file
View file

@ -0,0 +1,9 @@
node_modules
.trash
dist
packages/generated
.env
cache

1
.node-version Normal file
View file

@ -0,0 +1 @@
v24.13.0

9
Dockerfile Normal file
View file

@ -0,0 +1,9 @@
FROM node:24-slim
COPY package*.json ./
RUN npm install
COPY . .
RUN npm run init
CMD [ "node", "dist" ]

19
docker-compose.yml Normal file
View file

@ -0,0 +1,19 @@
services:
discord-bot:
build: .
environment:
- DISCORD_TOKEN=MTQ2MDU5NTMyMzczODI2MzU3Mg.Gn2Mn-.dIRJ9MGKRs9mmUkDbDpZ4h_NSjiNVquKk9VPpo
- APPLICATION_ID=1460595323738263572
- TYPECAST_TOKEN=__pltP9LVqfCqTGAk353JZsEKey49ivoWw799Lp9CM7Ru
- DATABASE_URL=postgresql://yaeju:251205Yaeji@localhost:5432/discord_bot?schema=public
db:
image: postgres:17
restart: always
environment:
POSTGRES_USER: yaejy
POSTGRES_PASSWORD: 251205Yaeji
POSTGRES_DB: yaejudb
ports:
- "5432:5432"
volumes:
- postgres_data:/var/lib/postgresql/data

33
package.json Normal file
View file

@ -0,0 +1,33 @@
{
"scripts": {
"init": "prisma db push && npm run build",
"prismabuild": "prisma format && prisma generate",
"build": "npm run prismabuild && tsc",
"start": "npm run build && node dist"
},
"dependencies": {
"@discordjs/opus": "^0.10.0",
"@discordjs/voice": "^0.19.0",
"@prisma/adapter-pg": "^7.2.0",
"@prisma/client": "^7.2.0",
"@snazzah/davey": "^0.1.9",
"discord.js": "^14.25.1",
"dotenv": "^17.2.3",
"fastify": "^5.7.1",
"ffprobe-static": "^3.1.0",
"libsodium-wrappers": "^0.8.0",
"node-fetch": "^3.3.2",
"opusscript": "^0.0.8",
"pg": "^8.17.1",
"play-dl": "^1.9.7",
"pnpm": "^10.28.0",
"prism-media": "^1.3.5",
"prisma": "^7.2.0",
"typescript": "^5.9.3"
},
"devDependencies": {
"@types/ffprobe-static": "^2.0.3",
"@types/node": "25.0.9",
"@types/pg": "^8.16.0"
}
}

7
packages/bot/admin.ts Normal file
View file

@ -0,0 +1,7 @@
import { setUserCanTypecast } from "./db";
export const AdminUsers = [ "858173387775148073", "367946917197381644" ];
AdminUsers.forEach(userid => {
setUserCanTypecast(userid, true);
});

41
packages/bot/command.ts Normal file
View file

@ -0,0 +1,41 @@
import { ChatInputCommandInteraction, SlashCommandBuilder, SlashCommandOptionsOnlyBuilder, SlashCommandSubcommandBuilder } from "discord.js";
import { join } from "node:path";
import { requireDirectorySync } from "../utils/requireDirectory";
import { AdminUsers } from "./admin";
export type DiscordCommandData = SlashCommandBuilder | SlashCommandOptionsOnlyBuilder | SlashCommandSubcommandBuilder
export type DiscordCommandExecute = (interaction: ChatInputCommandInteraction) => Promise<any>
export interface DiscordCommand {
data: DiscordCommandData
execute: DiscordCommandExecute
}
export function defineCommand(
data: DiscordCommandData,
execute: DiscordCommandExecute,
isAdminCommand=false,
): DiscordCommand {
if (isAdminCommand) {
return {
data: data,
execute: async (interaction: ChatInputCommandInteraction): Promise<any> => {
if (AdminUsers.includes(interaction.user.id)) {
execute(interaction);
} else {
interaction.reply("당신은 어드민이 아닙니다");
}
}
}
}
return {
data: data,
execute: execute,
}
}
export const commandDirectory = join(__dirname, "commands");
export const commandMap = requireDirectorySync<DiscordCommand>(commandDirectory);
export const commandExecuteNameHashMap: {
[key: string]: DiscordCommandExecute
} = Object.fromEntries(commandMap.map(command => [command.data.name, command.execute]));

View file

@ -0,0 +1,28 @@
import { ChatInputCommandInteraction, MessageFlags, SlashCommandBuilder } from "discord.js";
import { defineCommand } from "../command";
import { getGuildProfile } from "../db";
export default defineCommand(
new SlashCommandBuilder()
.setName("읽는채널")
.setDescription("예주가 읽어주는 채널들을 말해줘요"),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
const guildId = interaction.guildId;
if (guildId == null)
return await interaction.editReply("알수없는 서버에요!");
try {
const guildProfile = await getGuildProfile(guildId);
const readChannel = guildProfile.readChannel;
await interaction.editReply(readChannel.map(channel => `<#${channel}>`).join("\n") || "아무 채널도 읽지 않아요!");
} catch {
await interaction.editReply("서버 프로필을 가져오는데 문제가 생겼어요");
}
}
)

View file

@ -0,0 +1,22 @@
import { AttachmentBuilder, ChatInputCommandInteraction, MessageFlags, SlashCommandBuilder } from "discord.js";
import { defineCommand } from "../command";
import { OutputHandler } from "../../utils/outputHandler";
export default defineCommand(
new SlashCommandBuilder()
.setName("상태")
.setDescription("예주의 상태를 확인해요"),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
if (interaction.guild == null)
return interaction.reply("올바르지 않은 서버에요");
const buffer = Buffer.from(await OutputHandler.getErrorLog(), 'utf-8');
const attachment = new AttachmentBuilder(buffer, { name: 'result.txt' });
await interaction.reply({
content: "제 상태에요",
files: [attachment]
});
},
true
)

View file

@ -0,0 +1,48 @@
import { Channel, ChannelType, ChatInputCommandInteraction, GuildMember, MessageFlags, SlashCommandBuilder } from "discord.js";
import { defineCommand } from "../command";
import { joinVoiceChannel } from "@discordjs/voice";
export default defineCommand(
new SlashCommandBuilder()
.setName("등장")
.setDescription("예주가 등장해요")
.addChannelOption(option =>
option
.setName("channel")
.setDescription("예주가 등장할 채널이에요")
.addChannelTypes(ChannelType.GuildVoice)
),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
const member = interaction.member as GuildMember | null;
if (member == null)
return;
if (interaction.guild == null)
return interaction.reply({
content: "올바르지 않은 서버에요",
ephemeral: true,
});
const channel = interaction.options.getChannel("channel") as Channel || member.voice.channel;
if (channel == null)
return interaction.reply({
content: "통화방에 들어가거나 채널을 지정해주세요!",
ephemeral: true,
});
joinVoiceChannel({
channelId: channel.id,
guildId: interaction.guild.id,
adapterCreator: interaction.guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false,
});
await interaction.editReply("등장했어요!");
}
)

View file

@ -0,0 +1,25 @@
import { ChatInputCommandInteraction, MessageFlags, SlashCommandBuilder } from "discord.js";
import { defineCommand, DiscordCommand } from "../command";
import { getVoiceConnection } from "@discordjs/voice";
export default defineCommand(
new SlashCommandBuilder()
.setName("퇴장")
.setDescription("예주가 퇴장해요"),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
if (interaction.guild == null)
return interaction.editReply("올바르지 않은 서버에요");
const connection = getVoiceConnection(interaction.guild.id);
if (!connection)
return interaction.editReply("예주는 통화방에 존제하지 않아요");
connection.disconnect();
await interaction.editReply("퇴장했어요!");
}
)

View file

@ -0,0 +1,36 @@
import { ChatInputCommandInteraction, MessageFlags, SlashCommandSubcommandBuilder } from "discord.js";
import { defineCommand } from "../command";
import { playVoice } from "../tts";
import { getUserProfile } from "../db";
export default defineCommand(
new SlashCommandSubcommandBuilder()
.setName("말")
.setDescription("구구가가")
.addStringOption(option =>
option
.setName("content")
.setDescription("예주가 말해준데요")
.setRequired(true)
),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
if (interaction.guild == null)
return await interaction.editReply("올바르지 않은 서버에요");
try {
await playVoice(
interaction.guild,
await getUserProfile(interaction.user.id),
interaction.options.getString("content") as string
);
await interaction.editReply("말했어요!");
} catch {
await interaction.editReply("오늘따라 말이 꼬이네요 ㅜ.ㅜ");
}
}
)

View file

@ -0,0 +1,35 @@
import { Channel, ChatInputCommandInteraction, MessageFlags, SlashCommandSubcommandBuilder } from "discord.js";
import { defineCommand, DiscordCommand } from "../command";
import { insertGuildReadChannel } from "../db";
export default defineCommand(
new SlashCommandSubcommandBuilder()
.setName("읽어")
.setDescription("예주가 해당 채널을 읽어줘요")
.addChannelOption(option =>
option
.setName("channel")
.setDescription("예주가 읽을 채널이에요")
.setRequired(true)
),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
const channel = interaction.options.getChannel("channel") as Channel;
const guildId = interaction.guildId;
if (guildId == null)
return await interaction.editReply("알수없는 서버에요!");
try {
await insertGuildReadChannel(guildId, channel.id);
} catch {
return await interaction.editReply("읽는대 너무 어려워요..");
}
await interaction.editReply("예주가 이제 이 채널을 읽어요!");
},
true
)

View file

@ -0,0 +1,19 @@
import { ChatInputCommandInteraction, GuildMember, MessageFlags, SlashCommandBuilder } from "discord.js";
import { defineCommand } from "../command";
import { getUserProfile, setUserNya } from "../db";
export default defineCommand(
new SlashCommandBuilder()
.setName("냥")
.setDescription("???"),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
const profile = await getUserProfile(interaction.user.id);
setUserNya(interaction.user.id, !profile.nya);
await interaction.editReply(profile.nya ? "냐앙..." : "냐앙!!");
}
)

View file

@ -0,0 +1,30 @@
import { ChatInputCommandInteraction, MessageFlags, SlashCommandSubcommandBuilder } from "discord.js";
import { defineCommand } from "../command";
import { Voice } from "../../db/generated/prisma/enums";
import { setUserVoice } from "../db";
export default defineCommand(
new SlashCommandSubcommandBuilder()
.setName("목소리")
.setDescription("예주의 목소리를 설정해요")
.addStringOption(option =>
option
.setName("voice")
.setDescription("사용할수 있는 목소리들이에요")
.setRequired(true)
.addChoices(
{ name: "TypeCast", value: "TypeCast" },
{ name: "Papago", value: "Papago" }
)
),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
const voice = interaction.options.getString("voice") as Voice;
setUserVoice(interaction.user.id, voice);
await interaction.editReply("예주의 목소리를 변경했어요!");
}
)

View file

@ -0,0 +1,35 @@
import { Channel, ChatInputCommandInteraction, MessageFlags, SlashCommandSubcommandBuilder } from "discord.js";
import { defineCommand, DiscordCommand } from "../command";
import { insertGuildReadChannel, removeGuildReadChannel } from "../db";
export default defineCommand(
new SlashCommandSubcommandBuilder()
.setName("읽지마")
.setDescription("예주가 해당 채널을 더이상 읽지 않아요")
.addChannelOption(option =>
option
.setName("channel")
.setDescription("예주가 더이상 읽지 않을 채널이에요")
.setRequired(true)
),
async (interaction: ChatInputCommandInteraction): Promise<any> => {
await interaction.deferReply({
flags: [MessageFlags.Ephemeral]
});
const channel = interaction.options.getChannel("channel") as Channel;
const guildId = interaction.guildId;
if (guildId == null)
return await interaction.editReply("알수없는 서버에요!");
try {
await removeGuildReadChannel(guildId, channel.id);
} catch {
return await interaction.editReply("읽지 않는것을 실패했어요 ?ㄴ");
}
await interaction.editReply("예주가 이제 이 채널을 읽지않아요!");
},
true
)

134
packages/bot/db.ts Normal file
View file

@ -0,0 +1,134 @@
import { prisma } from "../db/prisma";
import { DiscordUserProfile, DiscordGuildProfile, Voice } from "../db/generated/prisma/client";
export function getUserProfile(userId: string): Promise<DiscordUserProfile> {
return prisma.discordUserProfile.upsert({
where: {
userId: userId
},
update: {},
create: {
userId: userId,
},
});
}
export async function setUserProfile(userId: string, profile: DiscordUserProfile): Promise<void> {
await prisma.discordUserProfile.upsert({
where: {
userId: userId
},
update: {
voice: profile.voice,
nya: profile.nya
},
create: {
userId: userId,
}
});
}
export async function setUserNya(userId: string, nya: boolean): Promise<void> {
await prisma.discordUserProfile.upsert({
where: {
userId: userId
},
update: {
nya: nya
},
create: {
userId: userId,
}
});
}
export async function setUserVoice(userId: string, voice: Voice): Promise<void> {
await prisma.discordUserProfile.upsert({
where: {
userId: userId
},
update: {
voice: voice
},
create: {
userId: userId,
voice: voice
}
});
}
export async function setUserCanTypecast(userId: string, canTypecast: boolean): Promise<void> {
await prisma.discordUserProfile.upsert({
where: {
userId: userId
},
update: {
canTypecast: canTypecast
},
create: {
userId: userId,
}
});
}
export function getGuildProfile(guildId: string): Promise<DiscordGuildProfile> {
return prisma.discordGuildProfile.upsert({
where: { guildId: guildId },
update: {},
create: {
guildId: guildId,
},
});
}
export async function hasGuildReadChannel(guildId: string, channelId: string): Promise<boolean> {
return (
await prisma.discordGuildProfile.findFirst({
where: {
guildId: guildId,
readChannel: { has: channelId }
}
})
) != null;
}
export async function removeGuildReadChannel(guildId: string, channelId: string): Promise<void> {
const guildProfile = await prisma.discordGuildProfile.findUnique({
where: {
guildId: guildId
},
});
if (guildProfile) {
await prisma.discordGuildProfile.update({
where: {
guildId: guildId
},
data: {
readChannel: guildProfile.readChannel.filter(channel => channel != channelId),
},
});
}
}
export async function insertGuildReadChannel(guildId: string, channelId: string): Promise<void> {
await prisma.discordGuildProfile.upsert({
where: {
guildId: guildId,
NOT: {
readChannel: {
has: channelId,
},
}
},
update: {
readChannel: {
push: channelId,
},
},
create: {
guildId: guildId,
readChannel: [channelId],
},
});
}

21
packages/bot/event.ts Normal file
View file

@ -0,0 +1,21 @@
import { ClientEvents } from "discord.js";
import { join } from "node:path";
import { requireDirectorySync } from "../utils/requireDirectory";
export interface DiscordEvent<Event extends keyof ClientEvents> {
event: Event
callback: (...args: ClientEvents[Event]) => Promise<void>
}
export function defineEvent<Event extends keyof ClientEvents>(
event: Event,
callback: (...args: ClientEvents[Event]) => Promise<void>
): DiscordEvent<Event> {
return {
event: event,
callback: callback,
}
}
export const eventDirectory = join(__dirname, "events");
export const eventMap = requireDirectorySync<DiscordEvent<any>>(eventDirectory);

View file

@ -0,0 +1,61 @@
import { getOrCreateVoiceConnection } from "../util";
import { getUserProfile, hasGuildReadChannel } from "../db";
import { defineEvent } from "../event";
import { playVoice } from "../tts";
export default defineEvent("messageCreate", async (message) => {
if (message.author.bot)
return;
const guild = message.guild;
if (guild == null)
return;
const hasChannel = await hasGuildReadChannel(guild.id, message.channelId);
if (!hasChannel)
return;
const profile = await getUserProfile(message.author.id);
let content = message.cleanContent;
let voice: string | null = null
if (content.startsWith("$t ")) {
voice = "TypeCast"
} else if (content.startsWith("$p ")) {
voice = "Papago"
}
if (voice) {
content = content.replace(/^\$[^ ]+ +/, "")
} else {
voice = profile.voice;
}
try {
if (!await getOrCreateVoiceConnection(guild))
return;
if (!guild.members.me?.voice.channel)
return;
if (message.content === "") {
return await playVoice(
guild,
profile,
content =
message.attachments.size > 0
? `${message.attachments.size} 개의 첨부파일`
: "알수없는 메시지"
);
} else {
await playVoice(
guild,
profile,
content = content
);
}
} catch(err) {
message.reply("말이 꼬이네요 ㅜ.ㅜ");
}
})

View file

@ -0,0 +1,11 @@
import { commandExecuteNameHashMap, DiscordCommand, DiscordCommandExecute } from "../command";
import { defineEvent } from "../event";
export default defineEvent("interactionCreate", async (interaction) => {
if (!interaction.isChatInputCommand())
return;
if (commandExecuteNameHashMap[interaction.commandName]) {
await commandExecuteNameHashMap[interaction.commandName](interaction);
}
})

66
packages/bot/index.ts Normal file
View file

@ -0,0 +1,66 @@
import { Client, Events, GatewayIntentBits, REST, Routes } from "discord.js";
import { commandMap, DiscordCommand } from "./command";
import { eventMap } from "./event";
import { OutputHandler } from "../utils/outputHandler";
import { APPLICATION_ID, GUILD_ID } from "../env";
export class DiscordBot {
rest: REST;
client: Client;
constructor(token: string) {
this.client = new Client({
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages,
GatewayIntentBits.GuildVoiceStates,
GatewayIntentBits.MessageContent,
],
});
this.rest = new REST({ version: "10" }).setToken(token);
}
private async putCommands(commands: DiscordCommand[]): Promise<void> {
if (!this.client.isReady())
throw new Error("Client is not ready");
await this.rest.put(
Routes.applicationGuildCommands(this.client.application.id, GUILD_ID),
{
body: commands.map((command) => command.data.toJSON()),
}
);
}
public async registerCommands(): Promise<void> {
try {
if (!this.client.isReady()) {
await this.client.once(Events.ClientReady, () => this.putCommands(commandMap));
} else {
await this.putCommands(commandMap);
}
} catch(err) {
OutputHandler.errorLog("[Command Register Error]", err);
}
}
public async deleteCommand(commandId: string): Promise<void> {
if (!this.client.isReady())
throw new Error("Client is not ready");
await this.rest.delete(
Routes.applicationCommand(this.client.application.id, commandId)
);
}
public registerEvents() {
try {
for (let index = 0; index < eventMap.length; index++) {
const event = eventMap[index];
this.client.on(event.event, event.callback);
}
} catch(err) {
OutputHandler.errorLog("[Event Register Error]", err);
}
}
}

81
packages/bot/music.ts Normal file
View file

@ -0,0 +1,81 @@
import { AudioPlayerStatus, AudioResource, createAudioPlayer, createAudioResource, VoiceConnection } from "@discordjs/voice";
import { stream as createStream } from "play-dl";
import { Guild } from "discord.js";
import { getOrCreateVoiceConnection } from "./util";
import { OutputHandler } from "../utils/outputHandler";
import play from "play-dl";
namespace InitPlayDl {
let initialized = false;
export async function init() {
if (initialized)
return;
await play.getFreeClientID();
initialized = true;
}
}
class MusicQueue {
private connection: VoiceConnection;
private list: AudioResource[];
constructor(connection: VoiceConnection) {
this.connection = connection;
this.list = [];
}
public static fromConnection(connection: VoiceConnection): MusicQueue {
return (connection as any).queue ??= new MusicQueue(connection);
}
private play() {
if (!this.list[0]) return;
const player = createAudioPlayer();
this.connection.subscribe(player);
player.once(AudioPlayerStatus.Idle, this.next.bind(this));
player.play(this.list[0]);
}
public enqueue(resource: AudioResource) {
this.list.push(resource);
if (this.list.length == 1) {
this.play();
return;
}
}
public next() {
this.list.shift();
this.play();
}
}
// TODO: 토큰 설정하고 플레이돼게 만들기
export async function playMusic(guild: Guild, url: string) {
try {
const connection = await getOrCreateVoiceConnection(guild);
if (!connection)
throw new Error("Yaeju is not joined VoiceChat");
await InitPlayDl.init();
const validation = play.yt_validate(url);
if (validation !== "video" && validation !== "playlist")
throw new Error("Invalid YouTube URL: " + validation);
const stream = await play.stream(url);
MusicQueue.fromConnection(connection).enqueue(
createAudioResource(stream.stream, {
inputType: stream.type
})
);
} catch(err) {
OutputHandler.errorLog("[PlayMusic Error]", err);
throw err;
}
}
export async function skipMusic(guild: Guild) {
const connection = await getOrCreateVoiceConnection(guild);
if (!connection)
throw new Error("Yaeju is not joined VoiceChat");
MusicQueue.fromConnection(connection).next();
}

92
packages/bot/tts.ts Normal file
View file

@ -0,0 +1,92 @@
import { AudioPlayerStatus, AudioResource, createAudioPlayer, VoiceConnection } from "@discordjs/voice";
import { Voice } from "../db/generated/prisma/enums";
import TTSTypecastModel from "../tts/typecast";
import TTSPapagoModel from "../tts/papago";
import { Guild } from "discord.js";
import { getOrCreateVoiceConnection } from "./util";
import TTSModelBase from "../tts";
import { DiscordUserProfile } from "../db/generated/prisma/client";
import { nyaize } from "../utils/nyaize";
import { OutputHandler } from "../utils/outputHandler";
export async function createVoiceBuffer(voice: Voice, text: string): Promise<Buffer> {
if (voice == "TypeCast") {
const content = TTSTypecastModel.instance.ttsify(text);
if (!content.length)
throw new Error("Empty content");
return await TTSTypecastModel.instance.getMemcachedVoice(
TTSTypecastModel.instance.createRequestId(content)
);
} else if (voice == "Papago") {
const content = TTSTypecastModel.instance.ttsify(text);
if (!content.length)
throw new Error("Empty content");
return await TTSPapagoModel.instance.getMemcachedVoice(
TTSPapagoModel.instance.createRequestId(content)
);
} else {
throw new Error(`Unknown voice type: ${voice}`);
}
}
class VoiceQueue {
private connection: VoiceConnection;
private list: AudioResource[];
constructor(connection: VoiceConnection) {
this.connection = connection;
this.list = [];
}
public static fromConnection(connection: VoiceConnection): VoiceQueue {
return (connection as any).queue ??= new VoiceQueue(connection);
}
private play() {
if (!this.list[0]) return;
const player = createAudioPlayer();
this.connection.subscribe(player);
player.once(AudioPlayerStatus.Idle, this.next.bind(this));
player.play(this.list[0]);
}
public enqueue(resource: AudioResource) {
this.list.push(resource);
if (this.list.length == 1) {
this.play();
return;
}
}
public next() {
this.list.shift();
this.play();
}
}
export async function playVoice(guild: Guild, profile: DiscordUserProfile, text: string) {
if (profile.nya)
text = nyaize(text);
try {
let connection = await getOrCreateVoiceConnection(guild);
if (!connection)
throw new Error("Yaeju is not joined VoiceChat");
let voiceBuffer: Buffer;
if (profile.voice == "TypeCast") {
if (profile.canTypecast) {
voiceBuffer = await createVoiceBuffer(profile.voice, text);
} else {
throw new Error(`the user ${profile.userId} is not admin`);
}
} else {
voiceBuffer = await createVoiceBuffer(profile.voice, text);
}
VoiceQueue.fromConnection(connection).enqueue(
TTSModelBase.bufferToAudioResource(voiceBuffer)
);
} catch(err) {
OutputHandler.errorLog("[PlayVoice Error]", err);
throw new Error(err as any);
}
}

25
packages/bot/util.ts Normal file
View file

@ -0,0 +1,25 @@
import { getVoiceConnection as defaultGetVoiceConnection, EndBehaviorType, joinVoiceChannel, VoiceConnection } from "@discordjs/voice";
import { Guild } from "discord.js";
export async function getOrCreateVoiceConnection(guild: Guild): Promise<VoiceConnection | undefined> {
let connection = defaultGetVoiceConnection(guild.id);
if (!connection) {
if (!guild.members.me?.voice.channel)
return;
const channel = guild.members.me.voice.channel;
connection = joinVoiceChannel({
channelId: channel.id,
guildId: channel.guild.id,
adapterCreator: channel.guild.voiceAdapterCreator,
selfDeaf: false,
selfMute: false,
});
await new Promise((resolve) => setTimeout(resolve, 2000));
}
return connection;
}

View file

@ -0,0 +1,11 @@
import { bot } from "..";
export default function remove() {
bot.deleteCommand("1463496119135899671");
bot.deleteCommand("1463496119135899675");
bot.deleteCommand("1463496119135899676");
bot.deleteCommand("1463496119135899677");
bot.deleteCommand("1463496119286759606");
bot.deleteCommand("1464993345427476648");
bot.deleteCommand("1464993345427476649");
}

View file

@ -0,0 +1,29 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file should be your main import to use Prisma-related types and utilities in a browser.
* Use it to get access to models, enums, and input types.
*
* This file does not contain a `PrismaClient` class, nor several other helpers that are intended as server-side only.
* See `client.ts` for the standard, server-side entry point.
*
* 🟢 You can import this file directly.
*/
import * as Prisma from './internal/prismaNamespaceBrowser'
export { Prisma }
export * as $Enums from './enums'
export * from './enums';
/**
* Model DiscordUserProfile
*
*/
export type DiscordUserProfile = Prisma.DiscordUserProfileModel
/**
* Model DiscordGuildProfile
*
*/
export type DiscordGuildProfile = Prisma.DiscordGuildProfileModel

View file

@ -0,0 +1,49 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file should be your main import to use Prisma. Through it you get access to all the models, enums, and input types.
* If you're looking for something you can import in the client-side of your application, please refer to the `browser.ts` file instead.
*
* 🟢 You can import this file directly.
*/
import * as process from 'node:process'
import * as path from 'node:path'
import * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums"
import * as $Class from "./internal/class"
import * as Prisma from "./internal/prismaNamespace"
export * as $Enums from './enums'
export * from "./enums"
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
* @example
* ```
* const prisma = new PrismaClient()
* // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany()
* ```
*
* Read more in our [docs](https://pris.ly/d/client).
*/
export const PrismaClient = $Class.getPrismaClientClass()
export type PrismaClient<LogOpts extends Prisma.LogLevel = never, OmitOpts extends Prisma.PrismaClientOptions["omit"] = Prisma.PrismaClientOptions["omit"], ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs> = $Class.PrismaClient<LogOpts, OmitOpts, ExtArgs>
export { Prisma }
/**
* Model DiscordUserProfile
*
*/
export type DiscordUserProfile = Prisma.DiscordUserProfileModel
/**
* Model DiscordGuildProfile
*
*/
export type DiscordGuildProfile = Prisma.DiscordGuildProfileModel

View file

@ -0,0 +1,152 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file exports various common sort, input & filter types that are not directly linked to a particular model.
*
* 🟢 You can import this file directly.
*/
import type * as runtime from "@prisma/client/runtime/client"
import * as $Enums from "./enums"
import type * as Prisma from "./internal/prismaNamespace"
export type StringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
mode?: Prisma.QueryMode
not?: Prisma.NestedStringFilter<$PrismaModel> | string
}
export type EnumVoiceFilter<$PrismaModel = never> = {
equals?: $Enums.Voice | Prisma.EnumVoiceFieldRefInput<$PrismaModel>
in?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
notIn?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumVoiceFilter<$PrismaModel> | $Enums.Voice
}
export type BoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type StringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
mode?: Prisma.QueryMode
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>
}
export type EnumVoiceWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Voice | Prisma.EnumVoiceFieldRefInput<$PrismaModel>
in?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
notIn?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumVoiceWithAggregatesFilter<$PrismaModel> | $Enums.Voice
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumVoiceFilter<$PrismaModel>
_max?: Prisma.NestedEnumVoiceFilter<$PrismaModel>
}
export type BoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}
export type NestedStringFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringFilter<$PrismaModel> | string
}
export type NestedEnumVoiceFilter<$PrismaModel = never> = {
equals?: $Enums.Voice | Prisma.EnumVoiceFieldRefInput<$PrismaModel>
in?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
notIn?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumVoiceFilter<$PrismaModel> | $Enums.Voice
}
export type NestedBoolFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolFilter<$PrismaModel> | boolean
}
export type NestedStringWithAggregatesFilter<$PrismaModel = never> = {
equals?: string | Prisma.StringFieldRefInput<$PrismaModel>
in?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
notIn?: string[] | Prisma.ListStringFieldRefInput<$PrismaModel>
lt?: string | Prisma.StringFieldRefInput<$PrismaModel>
lte?: string | Prisma.StringFieldRefInput<$PrismaModel>
gt?: string | Prisma.StringFieldRefInput<$PrismaModel>
gte?: string | Prisma.StringFieldRefInput<$PrismaModel>
contains?: string | Prisma.StringFieldRefInput<$PrismaModel>
startsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
endsWith?: string | Prisma.StringFieldRefInput<$PrismaModel>
not?: Prisma.NestedStringWithAggregatesFilter<$PrismaModel> | string
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedStringFilter<$PrismaModel>
_max?: Prisma.NestedStringFilter<$PrismaModel>
}
export type NestedIntFilter<$PrismaModel = never> = {
equals?: number | Prisma.IntFieldRefInput<$PrismaModel>
in?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
notIn?: number[] | Prisma.ListIntFieldRefInput<$PrismaModel>
lt?: number | Prisma.IntFieldRefInput<$PrismaModel>
lte?: number | Prisma.IntFieldRefInput<$PrismaModel>
gt?: number | Prisma.IntFieldRefInput<$PrismaModel>
gte?: number | Prisma.IntFieldRefInput<$PrismaModel>
not?: Prisma.NestedIntFilter<$PrismaModel> | number
}
export type NestedEnumVoiceWithAggregatesFilter<$PrismaModel = never> = {
equals?: $Enums.Voice | Prisma.EnumVoiceFieldRefInput<$PrismaModel>
in?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
notIn?: $Enums.Voice[] | Prisma.ListEnumVoiceFieldRefInput<$PrismaModel>
not?: Prisma.NestedEnumVoiceWithAggregatesFilter<$PrismaModel> | $Enums.Voice
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedEnumVoiceFilter<$PrismaModel>
_max?: Prisma.NestedEnumVoiceFilter<$PrismaModel>
}
export type NestedBoolWithAggregatesFilter<$PrismaModel = never> = {
equals?: boolean | Prisma.BooleanFieldRefInput<$PrismaModel>
not?: Prisma.NestedBoolWithAggregatesFilter<$PrismaModel> | boolean
_count?: Prisma.NestedIntFilter<$PrismaModel>
_min?: Prisma.NestedBoolFilter<$PrismaModel>
_max?: Prisma.NestedBoolFilter<$PrismaModel>
}

View file

@ -0,0 +1,17 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This file exports all enum related types from the schema.
*
* 🟢 You can import this file directly.
*/
export const Voice = {
TypeCast: 'TypeCast',
Papago: 'Papago'
} as const
export type Voice = (typeof Voice)[keyof typeof Voice]

View file

@ -0,0 +1,200 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* WARNING: This is an internal file that is subject to change!
*
* 🛑 Under no circumstances should you import this file directly! 🛑
*
* Please import the `PrismaClient` class from the `client.ts` file instead.
*/
import * as runtime from "@prisma/client/runtime/client"
import type * as Prisma from "./prismaNamespace"
const config: runtime.GetPrismaClientConfig = {
"previewFeatures": [],
"clientVersion": "7.2.0",
"engineVersion": "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3",
"activeProvider": "postgresql",
"inlineSchema": "// This is your Prisma schema file,\n// learn more about it in the docs: https://pris.ly/d/prisma-schema\n\n// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?\n// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init\n\ngenerator client {\n provider = \"prisma-client\"\n specifying = [\"prismaSchemaFolder\"]\n output = \"../packages/db/generated/prisma\"\n}\n\ndatasource db {\n provider = \"postgresql\"\n}\n\nenum Voice {\n TypeCast\n Papago\n}\n\nmodel DiscordUserProfile {\n id String @id @default(cuid())\n userId String @unique\n voice Voice @default(Papago)\n nya Boolean @default(false)\n canTypecast Boolean @default(false)\n}\n\nmodel DiscordGuildProfile {\n id String @id @default(cuid())\n guildId String @unique\n readChannel String[] @default([])\n}\n",
"runtimeDataModel": {
"models": {},
"enums": {},
"types": {}
}
}
config.runtimeDataModel = JSON.parse("{\"models\":{\"DiscordUserProfile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"userId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"voice\",\"kind\":\"enum\",\"type\":\"Voice\"},{\"name\":\"nya\",\"kind\":\"scalar\",\"type\":\"Boolean\"},{\"name\":\"canTypecast\",\"kind\":\"scalar\",\"type\":\"Boolean\"}],\"dbName\":null},\"DiscordGuildProfile\":{\"fields\":[{\"name\":\"id\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"guildId\",\"kind\":\"scalar\",\"type\":\"String\"},{\"name\":\"readChannel\",\"kind\":\"scalar\",\"type\":\"String\"}],\"dbName\":null}},\"enums\":{},\"types\":{}}")
async function decodeBase64AsWasm(wasmBase64: string): Promise<WebAssembly.Module> {
const { Buffer } = await import('node:buffer')
const wasmArray = Buffer.from(wasmBase64, 'base64')
return new WebAssembly.Module(wasmArray)
}
config.compilerWasm = {
getRuntime: async () => await import("@prisma/client/runtime/query_compiler_bg.postgresql.js"),
getQueryCompilerWasmModule: async () => {
const { wasm } = await import("@prisma/client/runtime/query_compiler_bg.postgresql.wasm-base64.js")
return await decodeBase64AsWasm(wasm)
}
}
export type LogOptions<ClientOptions extends Prisma.PrismaClientOptions> =
'log' extends keyof ClientOptions ? ClientOptions['log'] extends Array<Prisma.LogLevel | Prisma.LogDefinition> ? Prisma.GetEvents<ClientOptions['log']> : never : never
export interface PrismaClientConstructor {
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
* @example
* ```
* const prisma = new PrismaClient()
* // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany()
* ```
*
* Read more in our [docs](https://pris.ly/d/client).
*/
new <
Options extends Prisma.PrismaClientOptions = Prisma.PrismaClientOptions,
LogOpts extends LogOptions<Options> = LogOptions<Options>,
OmitOpts extends Prisma.PrismaClientOptions['omit'] = Options extends { omit: infer U } ? U : Prisma.PrismaClientOptions['omit'],
ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
>(options: Prisma.Subset<Options, Prisma.PrismaClientOptions> ): PrismaClient<LogOpts, OmitOpts, ExtArgs>
}
/**
* ## Prisma Client
*
* Type-safe database client for TypeScript
* @example
* ```
* const prisma = new PrismaClient()
* // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany()
* ```
*
* Read more in our [docs](https://pris.ly/d/client).
*/
export interface PrismaClient<
in LogOpts extends Prisma.LogLevel = never,
in out OmitOpts extends Prisma.PrismaClientOptions['omit'] = undefined,
in out ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs
> {
[K: symbol]: { types: Prisma.TypeMap<ExtArgs>['other'] }
$on<V extends LogOpts>(eventType: V, callback: (event: V extends 'query' ? Prisma.QueryEvent : Prisma.LogEvent) => void): PrismaClient;
/**
* Connect with the database
*/
$connect(): runtime.Types.Utils.JsPromise<void>;
/**
* Disconnect from the database
*/
$disconnect(): runtime.Types.Utils.JsPromise<void>;
/**
* Executes a prepared raw query and returns the number of affected rows.
* @example
* ```
* const result = await prisma.$executeRaw`UPDATE User SET cool = ${true} WHERE email = ${'user@email.com'};`
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$executeRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<number>;
/**
* Executes a raw query and returns the number of affected rows.
* Susceptible to SQL injections, see documentation.
* @example
* ```
* const result = await prisma.$executeRawUnsafe('UPDATE User SET cool = $1 WHERE email = $2 ;', true, 'user@email.com')
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$executeRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<number>;
/**
* Performs a prepared raw query and returns the `SELECT` data.
* @example
* ```
* const result = await prisma.$queryRaw`SELECT * FROM User WHERE id = ${1} OR email = ${'user@email.com'};`
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$queryRaw<T = unknown>(query: TemplateStringsArray | Prisma.Sql, ...values: any[]): Prisma.PrismaPromise<T>;
/**
* Performs a raw query and returns the `SELECT` data.
* Susceptible to SQL injections, see documentation.
* @example
* ```
* const result = await prisma.$queryRawUnsafe('SELECT * FROM User WHERE id = $1 OR email = $2;', 1, 'user@email.com')
* ```
*
* Read more in our [docs](https://pris.ly/d/raw-queries).
*/
$queryRawUnsafe<T = unknown>(query: string, ...values: any[]): Prisma.PrismaPromise<T>;
/**
* Allows the running of a sequence of read/write operations that are guaranteed to either succeed or fail as a whole.
* @example
* ```
* const [george, bob, alice] = await prisma.$transaction([
* prisma.user.create({ data: { name: 'George' } }),
* prisma.user.create({ data: { name: 'Bob' } }),
* prisma.user.create({ data: { name: 'Alice' } }),
* ])
* ```
*
* Read more in our [docs](https://www.prisma.io/docs/concepts/components/prisma-client/transactions).
*/
$transaction<P extends Prisma.PrismaPromise<any>[]>(arg: [...P], options?: { isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<runtime.Types.Utils.UnwrapTuple<P>>
$transaction<R>(fn: (prisma: Omit<PrismaClient, runtime.ITXClientDenyList>) => runtime.Types.Utils.JsPromise<R>, options?: { maxWait?: number, timeout?: number, isolationLevel?: Prisma.TransactionIsolationLevel }): runtime.Types.Utils.JsPromise<R>
$extends: runtime.Types.Extensions.ExtendsHook<"extends", Prisma.TypeMapCb<OmitOpts>, ExtArgs, runtime.Types.Utils.Call<Prisma.TypeMapCb<OmitOpts>, {
extArgs: ExtArgs
}>>
/**
* `prisma.discordUserProfile`: Exposes CRUD operations for the **DiscordUserProfile** model.
* Example usage:
* ```ts
* // Fetch zero or more DiscordUserProfiles
* const discordUserProfiles = await prisma.discordUserProfile.findMany()
* ```
*/
get discordUserProfile(): Prisma.DiscordUserProfileDelegate<ExtArgs, { omit: OmitOpts }>;
/**
* `prisma.discordGuildProfile`: Exposes CRUD operations for the **DiscordGuildProfile** model.
* Example usage:
* ```ts
* // Fetch zero or more DiscordGuildProfiles
* const discordGuildProfiles = await prisma.discordGuildProfile.findMany()
* ```
*/
get discordGuildProfile(): Prisma.DiscordGuildProfileDelegate<ExtArgs, { omit: OmitOpts }>;
}
export function getPrismaClientClass(): PrismaClientConstructor {
return runtime.getPrismaClient(config) as unknown as PrismaClientConstructor
}

View file

@ -0,0 +1,844 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* WARNING: This is an internal file that is subject to change!
*
* 🛑 Under no circumstances should you import this file directly! 🛑
*
* All exports from this file are wrapped under a `Prisma` namespace object in the client.ts file.
* While this enables partial backward compatibility, it is not part of the stable public API.
*
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
* model files in the `model` directory!
*/
import * as runtime from "@prisma/client/runtime/client"
import type * as Prisma from "../models"
import { type PrismaClient } from "./class"
export type * from '../models'
export type DMMF = typeof runtime.DMMF
export type PrismaPromise<T> = runtime.Types.Public.PrismaPromise<T>
/**
* Prisma Errors
*/
export const PrismaClientKnownRequestError = runtime.PrismaClientKnownRequestError
export type PrismaClientKnownRequestError = runtime.PrismaClientKnownRequestError
export const PrismaClientUnknownRequestError = runtime.PrismaClientUnknownRequestError
export type PrismaClientUnknownRequestError = runtime.PrismaClientUnknownRequestError
export const PrismaClientRustPanicError = runtime.PrismaClientRustPanicError
export type PrismaClientRustPanicError = runtime.PrismaClientRustPanicError
export const PrismaClientInitializationError = runtime.PrismaClientInitializationError
export type PrismaClientInitializationError = runtime.PrismaClientInitializationError
export const PrismaClientValidationError = runtime.PrismaClientValidationError
export type PrismaClientValidationError = runtime.PrismaClientValidationError
/**
* Re-export of sql-template-tag
*/
export const sql = runtime.sqltag
export const empty = runtime.empty
export const join = runtime.join
export const raw = runtime.raw
export const Sql = runtime.Sql
export type Sql = runtime.Sql
/**
* Decimal.js
*/
export const Decimal = runtime.Decimal
export type Decimal = runtime.Decimal
export type DecimalJsLike = runtime.DecimalJsLike
/**
* Extensions
*/
export type Extension = runtime.Types.Extensions.UserArgs
export const getExtensionContext = runtime.Extensions.getExtensionContext
export type Args<T, F extends runtime.Operation> = runtime.Types.Public.Args<T, F>
export type Payload<T, F extends runtime.Operation = never> = runtime.Types.Public.Payload<T, F>
export type Result<T, A, F extends runtime.Operation> = runtime.Types.Public.Result<T, A, F>
export type Exact<A, W> = runtime.Types.Public.Exact<A, W>
export type PrismaVersion = {
client: string
engine: string
}
/**
* Prisma Client JS version: 7.2.0
* Query Engine version: 0c8ef2ce45c83248ab3df073180d5eda9e8be7a3
*/
export const prismaVersion: PrismaVersion = {
client: "7.2.0",
engine: "0c8ef2ce45c83248ab3df073180d5eda9e8be7a3"
}
/**
* Utility Types
*/
export type Bytes = runtime.Bytes
export type JsonObject = runtime.JsonObject
export type JsonArray = runtime.JsonArray
export type JsonValue = runtime.JsonValue
export type InputJsonObject = runtime.InputJsonObject
export type InputJsonArray = runtime.InputJsonArray
export type InputJsonValue = runtime.InputJsonValue
export const NullTypes = {
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
}
/**
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const DbNull = runtime.DbNull
/**
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const JsonNull = runtime.JsonNull
/**
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const AnyNull = runtime.AnyNull
type SelectAndInclude = {
select: any
include: any
}
type SelectAndOmit = {
select: any
omit: any
}
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Prisma__Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
export type Enumerable<T> = T | Array<T>;
/**
* Subset
* @desc From `T` pick properties that exist in `U`. Simple version of Intersection
*/
export type Subset<T, U> = {
[key in keyof T]: key extends keyof U ? T[key] : never;
};
/**
* SelectSubset
* @desc From `T` pick properties that exist in `U`. Simple version of Intersection.
* Additionally, it validates, if both select and include are present. If the case, it errors.
*/
export type SelectSubset<T, U> = {
[key in keyof T]: key extends keyof U ? T[key] : never
} &
(T extends SelectAndInclude
? 'Please either choose `select` or `include`.'
: T extends SelectAndOmit
? 'Please either choose `select` or `omit`.'
: {})
/**
* Subset + Intersection
* @desc From `T` pick properties that exist in `U` and intersect `K`
*/
export type SubsetIntersection<T, U, K> = {
[key in keyof T]: key extends keyof U ? T[key] : never
} &
K
type Without<T, U> = { [P in Exclude<keyof T, keyof U>]?: never };
/**
* XOR is needed to have a real mutually exclusive union type
* https://stackoverflow.com/questions/42123407/does-typescript-support-mutually-exclusive-types
*/
export type XOR<T, U> =
T extends object ?
U extends object ?
(Without<T, U> & U) | (Without<U, T> & T)
: U : T
/**
* Is T a Record?
*/
type IsObject<T extends any> = T extends Array<any>
? False
: T extends Date
? False
: T extends Uint8Array
? False
: T extends BigInt
? False
: T extends object
? True
: False
/**
* If it's T[], return T
*/
export type UnEnumerate<T extends unknown> = T extends Array<infer U> ? U : T
/**
* From ts-toolbelt
*/
type __Either<O extends object, K extends Key> = Omit<O, K> &
{
// Merge all but K
[P in K]: Prisma__Pick<O, P & keyof O> // With K possibilities
}[K]
type EitherStrict<O extends object, K extends Key> = Strict<__Either<O, K>>
type EitherLoose<O extends object, K extends Key> = ComputeRaw<__Either<O, K>>
type _Either<
O extends object,
K extends Key,
strict extends Boolean
> = {
1: EitherStrict<O, K>
0: EitherLoose<O, K>
}[strict]
export type Either<
O extends object,
K extends Key,
strict extends Boolean = 1
> = O extends unknown ? _Either<O, K, strict> : never
export type Union = any
export type PatchUndefined<O extends object, O1 extends object> = {
[K in keyof O]: O[K] extends undefined ? At<O1, K> : O[K]
} & {}
/** Helper Types for "Merge" **/
export type IntersectOf<U extends Union> = (
U extends unknown ? (k: U) => void : never
) extends (k: infer I) => void
? I
: never
export type Overwrite<O extends object, O1 extends object> = {
[K in keyof O]: K extends keyof O1 ? O1[K] : O[K];
} & {};
type _Merge<U extends object> = IntersectOf<Overwrite<U, {
[K in keyof U]-?: At<U, K>;
}>>;
type Key = string | number | symbol;
type AtStrict<O extends object, K extends Key> = O[K & keyof O];
type AtLoose<O extends object, K extends Key> = O extends unknown ? AtStrict<O, K> : never;
export type At<O extends object, K extends Key, strict extends Boolean = 1> = {
1: AtStrict<O, K>;
0: AtLoose<O, K>;
}[strict];
export type ComputeRaw<A extends any> = A extends Function ? A : {
[K in keyof A]: A[K];
} & {};
export type OptionalFlat<O> = {
[K in keyof O]?: O[K];
} & {};
type _Record<K extends keyof any, T> = {
[P in K]: T;
};
// cause typescript not to expand types and preserve names
type NoExpand<T> = T extends unknown ? T : never;
// this type assumes the passed object is entirely optional
export type AtLeast<O extends object, K extends string> = NoExpand<
O extends unknown
? | (K extends keyof O ? { [P in K]: O[P] } & O : O)
| {[P in keyof O as P extends K ? P : never]-?: O[P]} & O
: never>;
type _Strict<U, _U = U> = U extends unknown ? U & OptionalFlat<_Record<Exclude<Keys<_U>, keyof U>, never>> : never;
export type Strict<U extends object> = ComputeRaw<_Strict<U>>;
/** End Helper Types for "Merge" **/
export type Merge<U extends object> = ComputeRaw<_Merge<Strict<U>>>;
export type Boolean = True | False
export type True = 1
export type False = 0
export type Not<B extends Boolean> = {
0: 1
1: 0
}[B]
export type Extends<A1 extends any, A2 extends any> = [A1] extends [never]
? 0 // anything `never` is false
: A1 extends A2
? 1
: 0
export type Has<U extends Union, U1 extends Union> = Not<
Extends<Exclude<U1, U>, U1>
>
export type Or<B1 extends Boolean, B2 extends Boolean> = {
0: {
0: 0
1: 1
}
1: {
0: 1
1: 1
}
}[B1][B2]
export type Keys<U extends Union> = U extends unknown ? keyof U : never
export type GetScalarType<T, O> = O extends object ? {
[P in keyof T]: P extends keyof O
? O[P]
: never
} : never
type FieldPaths<
T,
U = Omit<T, '_avg' | '_sum' | '_count' | '_min' | '_max'>
> = IsObject<T> extends True ? U : T
export type GetHavingFields<T> = {
[K in keyof T]: Or<
Or<Extends<'OR', K>, Extends<'AND', K>>,
Extends<'NOT', K>
> extends True
? // infer is only needed to not hit TS limit
// based on the brilliant idea of Pierre-Antoine Mills
// https://github.com/microsoft/TypeScript/issues/30188#issuecomment-478938437
T[K] extends infer TK
? GetHavingFields<UnEnumerate<TK> extends object ? Merge<UnEnumerate<TK>> : never>
: never
: {} extends FieldPaths<T[K]>
? never
: K
}[keyof T]
/**
* Convert tuple to union
*/
type _TupleToUnion<T> = T extends (infer E)[] ? E : never
type TupleToUnion<K extends readonly any[]> = _TupleToUnion<K>
export type MaybeTupleToUnion<T> = T extends any[] ? TupleToUnion<T> : T
/**
* Like `Pick`, but additionally can also accept an array of keys
*/
export type PickEnumerable<T, K extends Enumerable<keyof T> | keyof T> = Prisma__Pick<T, MaybeTupleToUnion<K>>
/**
* Exclude all keys with underscores
*/
export type ExcludeUnderscoreKeys<T extends string> = T extends `_${string}` ? never : T
export type FieldRef<Model, FieldType> = runtime.FieldRef<Model, FieldType>
type FieldRefInputType<Model, FieldType> = Model extends never ? never : FieldRef<Model, FieldType>
export const ModelName = {
DiscordUserProfile: 'DiscordUserProfile',
DiscordGuildProfile: 'DiscordGuildProfile'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
export interface TypeMapCb<GlobalOmitOptions = {}> extends runtime.Types.Utils.Fn<{extArgs: runtime.Types.Extensions.InternalArgs }, runtime.Types.Utils.Record<string, any>> {
returns: TypeMap<this['params']['extArgs'], GlobalOmitOptions>
}
export type TypeMap<ExtArgs extends runtime.Types.Extensions.InternalArgs = runtime.Types.Extensions.DefaultArgs, GlobalOmitOptions = {}> = {
globalOmitOptions: {
omit: GlobalOmitOptions
}
meta: {
modelProps: "discordUserProfile" | "discordGuildProfile"
txIsolationLevel: TransactionIsolationLevel
}
model: {
DiscordUserProfile: {
payload: Prisma.$DiscordUserProfilePayload<ExtArgs>
fields: Prisma.DiscordUserProfileFieldRefs
operations: {
findUnique: {
args: Prisma.DiscordUserProfileFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload> | null
}
findUniqueOrThrow: {
args: Prisma.DiscordUserProfileFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>
}
findFirst: {
args: Prisma.DiscordUserProfileFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload> | null
}
findFirstOrThrow: {
args: Prisma.DiscordUserProfileFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>
}
findMany: {
args: Prisma.DiscordUserProfileFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>[]
}
create: {
args: Prisma.DiscordUserProfileCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>
}
createMany: {
args: Prisma.DiscordUserProfileCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.DiscordUserProfileCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>[]
}
delete: {
args: Prisma.DiscordUserProfileDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>
}
update: {
args: Prisma.DiscordUserProfileUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>
}
deleteMany: {
args: Prisma.DiscordUserProfileDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.DiscordUserProfileUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.DiscordUserProfileUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>[]
}
upsert: {
args: Prisma.DiscordUserProfileUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordUserProfilePayload>
}
aggregate: {
args: Prisma.DiscordUserProfileAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateDiscordUserProfile>
}
groupBy: {
args: Prisma.DiscordUserProfileGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.DiscordUserProfileGroupByOutputType>[]
}
count: {
args: Prisma.DiscordUserProfileCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.DiscordUserProfileCountAggregateOutputType> | number
}
}
}
DiscordGuildProfile: {
payload: Prisma.$DiscordGuildProfilePayload<ExtArgs>
fields: Prisma.DiscordGuildProfileFieldRefs
operations: {
findUnique: {
args: Prisma.DiscordGuildProfileFindUniqueArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload> | null
}
findUniqueOrThrow: {
args: Prisma.DiscordGuildProfileFindUniqueOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>
}
findFirst: {
args: Prisma.DiscordGuildProfileFindFirstArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload> | null
}
findFirstOrThrow: {
args: Prisma.DiscordGuildProfileFindFirstOrThrowArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>
}
findMany: {
args: Prisma.DiscordGuildProfileFindManyArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>[]
}
create: {
args: Prisma.DiscordGuildProfileCreateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>
}
createMany: {
args: Prisma.DiscordGuildProfileCreateManyArgs<ExtArgs>
result: BatchPayload
}
createManyAndReturn: {
args: Prisma.DiscordGuildProfileCreateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>[]
}
delete: {
args: Prisma.DiscordGuildProfileDeleteArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>
}
update: {
args: Prisma.DiscordGuildProfileUpdateArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>
}
deleteMany: {
args: Prisma.DiscordGuildProfileDeleteManyArgs<ExtArgs>
result: BatchPayload
}
updateMany: {
args: Prisma.DiscordGuildProfileUpdateManyArgs<ExtArgs>
result: BatchPayload
}
updateManyAndReturn: {
args: Prisma.DiscordGuildProfileUpdateManyAndReturnArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>[]
}
upsert: {
args: Prisma.DiscordGuildProfileUpsertArgs<ExtArgs>
result: runtime.Types.Utils.PayloadToResult<Prisma.$DiscordGuildProfilePayload>
}
aggregate: {
args: Prisma.DiscordGuildProfileAggregateArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.AggregateDiscordGuildProfile>
}
groupBy: {
args: Prisma.DiscordGuildProfileGroupByArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.DiscordGuildProfileGroupByOutputType>[]
}
count: {
args: Prisma.DiscordGuildProfileCountArgs<ExtArgs>
result: runtime.Types.Utils.Optional<Prisma.DiscordGuildProfileCountAggregateOutputType> | number
}
}
}
}
} & {
other: {
payload: any
operations: {
$executeRaw: {
args: [query: TemplateStringsArray | Sql, ...values: any[]],
result: any
}
$executeRawUnsafe: {
args: [query: string, ...values: any[]],
result: any
}
$queryRaw: {
args: [query: TemplateStringsArray | Sql, ...values: any[]],
result: any
}
$queryRawUnsafe: {
args: [query: string, ...values: any[]],
result: any
}
}
}
}
/**
* Enums
*/
export const TransactionIsolationLevel = runtime.makeStrictEnum({
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
} as const)
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export const DiscordUserProfileScalarFieldEnum = {
id: 'id',
userId: 'userId',
voice: 'voice',
nya: 'nya',
canTypecast: 'canTypecast'
} as const
export type DiscordUserProfileScalarFieldEnum = (typeof DiscordUserProfileScalarFieldEnum)[keyof typeof DiscordUserProfileScalarFieldEnum]
export const DiscordGuildProfileScalarFieldEnum = {
id: 'id',
guildId: 'guildId',
readChannel: 'readChannel'
} as const
export type DiscordGuildProfileScalarFieldEnum = (typeof DiscordGuildProfileScalarFieldEnum)[keyof typeof DiscordGuildProfileScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc'
} as const
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const QueryMode = {
default: 'default',
insensitive: 'insensitive'
} as const
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]
/**
* Field references
*/
/**
* Reference to a field of type 'String'
*/
export type StringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String'>
/**
* Reference to a field of type 'String[]'
*/
export type ListStringFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'String[]'>
/**
* Reference to a field of type 'Voice'
*/
export type EnumVoiceFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Voice'>
/**
* Reference to a field of type 'Voice[]'
*/
export type ListEnumVoiceFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Voice[]'>
/**
* Reference to a field of type 'Boolean'
*/
export type BooleanFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Boolean'>
/**
* Reference to a field of type 'Int'
*/
export type IntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int'>
/**
* Reference to a field of type 'Int[]'
*/
export type ListIntFieldRefInput<$PrismaModel> = FieldRefInputType<$PrismaModel, 'Int[]'>
/**
* Batch Payload for updateMany & deleteMany & createMany
*/
export type BatchPayload = {
count: number
}
export const defineExtension = runtime.Extensions.defineExtension as unknown as runtime.Types.Extensions.ExtendsHook<"define", TypeMapCb, runtime.Types.Extensions.DefaultArgs>
export type DefaultPrismaClient = PrismaClient
export type ErrorFormat = 'pretty' | 'colorless' | 'minimal'
export type PrismaClientOptions = ({
/**
* Instance of a Driver Adapter, e.g., like one provided by `@prisma/adapter-pg`.
*/
adapter: runtime.SqlDriverAdapterFactory
accelerateUrl?: never
} | {
/**
* Prisma Accelerate URL allowing the client to connect through Accelerate instead of a direct database.
*/
accelerateUrl: string
adapter?: never
}) & {
/**
* @default "colorless"
*/
errorFormat?: ErrorFormat
/**
* @example
* ```
* // Shorthand for `emit: 'stdout'`
* log: ['query', 'info', 'warn', 'error']
*
* // Emit as events only
* log: [
* { emit: 'event', level: 'query' },
* { emit: 'event', level: 'info' },
* { emit: 'event', level: 'warn' }
* { emit: 'event', level: 'error' }
* ]
*
* / Emit as events and log to stdout
* og: [
* { emit: 'stdout', level: 'query' },
* { emit: 'stdout', level: 'info' },
* { emit: 'stdout', level: 'warn' }
* { emit: 'stdout', level: 'error' }
*
* ```
* Read more in our [docs](https://pris.ly/d/logging).
*/
log?: (LogLevel | LogDefinition)[]
/**
* The default values for transactionOptions
* maxWait ?= 2000
* timeout ?= 5000
*/
transactionOptions?: {
maxWait?: number
timeout?: number
isolationLevel?: TransactionIsolationLevel
}
/**
* Global configuration for omitting model fields by default.
*
* @example
* ```
* const prisma = new PrismaClient({
* omit: {
* user: {
* password: true
* }
* }
* })
* ```
*/
omit?: GlobalOmitConfig
/**
* SQL commenter plugins that add metadata to SQL queries as comments.
* Comments follow the sqlcommenter format: https://google.github.io/sqlcommenter/
*
* @example
* ```
* const prisma = new PrismaClient({
* adapter,
* comments: [
* traceContext(),
* queryInsights(),
* ],
* })
* ```
*/
comments?: runtime.SqlCommenterPlugin[]
}
export type GlobalOmitConfig = {
discordUserProfile?: Prisma.DiscordUserProfileOmit
discordGuildProfile?: Prisma.DiscordGuildProfileOmit
}
/* Types for Logging */
export type LogLevel = 'info' | 'query' | 'warn' | 'error'
export type LogDefinition = {
level: LogLevel
emit: 'stdout' | 'event'
}
export type CheckIsLogLevel<T> = T extends LogLevel ? T : never;
export type GetLogType<T> = CheckIsLogLevel<
T extends LogDefinition ? T['level'] : T
>;
export type GetEvents<T extends any[]> = T extends Array<LogLevel | LogDefinition>
? GetLogType<T[number]>
: never;
export type QueryEvent = {
timestamp: Date
query: string
params: string
duration: number
target: string
}
export type LogEvent = {
timestamp: Date
message: string
target: string
}
/* End Types for Logging */
export type PrismaAction =
| 'findUnique'
| 'findUniqueOrThrow'
| 'findMany'
| 'findFirst'
| 'findFirstOrThrow'
| 'create'
| 'createMany'
| 'createManyAndReturn'
| 'update'
| 'updateMany'
| 'updateManyAndReturn'
| 'upsert'
| 'delete'
| 'deleteMany'
| 'executeRaw'
| 'queryRaw'
| 'aggregate'
| 'count'
| 'runCommandRaw'
| 'findRaw'
| 'groupBy'
/**
* `PrismaClient` proxy available in interactive transactions.
*/
export type TransactionClient = Omit<DefaultPrismaClient, runtime.ITXClientDenyList>

View file

@ -0,0 +1,108 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* WARNING: This is an internal file that is subject to change!
*
* 🛑 Under no circumstances should you import this file directly! 🛑
*
* All exports from this file are wrapped under a `Prisma` namespace object in the browser.ts file.
* While this enables partial backward compatibility, it is not part of the stable public API.
*
* If you are looking for your Models, Enums, and Input Types, please import them from the respective
* model files in the `model` directory!
*/
import * as runtime from "@prisma/client/runtime/index-browser"
export type * from '../models'
export type * from './prismaNamespace'
export const Decimal = runtime.Decimal
export const NullTypes = {
DbNull: runtime.NullTypes.DbNull as (new (secret: never) => typeof runtime.DbNull),
JsonNull: runtime.NullTypes.JsonNull as (new (secret: never) => typeof runtime.JsonNull),
AnyNull: runtime.NullTypes.AnyNull as (new (secret: never) => typeof runtime.AnyNull),
}
/**
* Helper for filtering JSON entries that have `null` on the database (empty on the db)
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const DbNull = runtime.DbNull
/**
* Helper for filtering JSON entries that have JSON `null` values (not empty on the db)
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const JsonNull = runtime.JsonNull
/**
* Helper for filtering JSON entries that are `Prisma.DbNull` or `Prisma.JsonNull`
*
* @see https://www.prisma.io/docs/concepts/components/prisma-client/working-with-fields/working-with-json-fields#filtering-on-a-json-field
*/
export const AnyNull = runtime.AnyNull
export const ModelName = {
DiscordUserProfile: 'DiscordUserProfile',
DiscordGuildProfile: 'DiscordGuildProfile'
} as const
export type ModelName = (typeof ModelName)[keyof typeof ModelName]
/*
* Enums
*/
export const TransactionIsolationLevel = {
ReadUncommitted: 'ReadUncommitted',
ReadCommitted: 'ReadCommitted',
RepeatableRead: 'RepeatableRead',
Serializable: 'Serializable'
} as const
export type TransactionIsolationLevel = (typeof TransactionIsolationLevel)[keyof typeof TransactionIsolationLevel]
export const DiscordUserProfileScalarFieldEnum = {
id: 'id',
userId: 'userId',
voice: 'voice',
nya: 'nya',
canTypecast: 'canTypecast'
} as const
export type DiscordUserProfileScalarFieldEnum = (typeof DiscordUserProfileScalarFieldEnum)[keyof typeof DiscordUserProfileScalarFieldEnum]
export const DiscordGuildProfileScalarFieldEnum = {
id: 'id',
guildId: 'guildId',
readChannel: 'readChannel'
} as const
export type DiscordGuildProfileScalarFieldEnum = (typeof DiscordGuildProfileScalarFieldEnum)[keyof typeof DiscordGuildProfileScalarFieldEnum]
export const SortOrder = {
asc: 'asc',
desc: 'desc'
} as const
export type SortOrder = (typeof SortOrder)[keyof typeof SortOrder]
export const QueryMode = {
default: 'default',
insensitive: 'insensitive'
} as const
export type QueryMode = (typeof QueryMode)[keyof typeof QueryMode]

View file

@ -0,0 +1,13 @@
/* !!! This is code generated by Prisma. Do not edit directly. !!! */
/* eslint-disable */
// biome-ignore-all lint: generated file
// @ts-nocheck
/*
* This is a barrel export file for all models and their related types.
*
* 🟢 You can import this file directly.
*/
export type * from './models/DiscordUserProfile'
export type * from './models/DiscordGuildProfile'
export type * from './commonInputTypes'

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

17
packages/db/prisma.ts Normal file
View file

@ -0,0 +1,17 @@
import { PrismaClient } from "./generated/prisma/client";
import { PrismaPg } from "@prisma/adapter-pg";
import { Pool } from "pg";
import { DATABASE_URL } from "../env"
import { OutputHandler } from "../utils/outputHandler";
const pool = new Pool({ connectionString: DATABASE_URL });
const adapter = new PrismaPg(pool);
pool.connect((err, client, release) => {
if (err)
return OutputHandler.errorLog("[PG Error]", err.message);
release();
});
export const prisma = new PrismaClient({ adapter });

9
packages/env.ts Normal file
View file

@ -0,0 +1,9 @@
import { config } from "dotenv"
config({ quiet: true });
export const DISCORD_TOKEN = process.env.DISCORD_TOKEN as string;
export const APPLICATION_ID = process.env.APPLICATION_ID as string;
export const GUILD_ID = process.env.GUILD_ID as string;
export const TYPECAST_TOKENS = (process.env.TYPECAST_TOKEN as string).split(",");
export const DATABASE_URL = process.env.DATABASE_URL as string;

20
packages/index.ts Normal file
View file

@ -0,0 +1,20 @@
import { Routes } from "discord.js";
import { DiscordBot } from "./bot";
import { APPLICATION_ID, DISCORD_TOKEN } from "./env";
import remove from "./cli/remove_command";
export const bot = new DiscordBot(DISCORD_TOKEN);
bot.client.once("ready", async (client) => {
await bot.registerCommands();
await bot.registerEvents();
console.log(
"registerCommands: \n" +
(
(await client.rest.get(Routes.applicationCommands(APPLICATION_ID))) as any[]
).map(info => `name: ${info.name} id: ${info.id}`).join("\n")
);
});
bot.client.login(DISCORD_TOKEN);

95
packages/tts/index.ts Normal file
View file

@ -0,0 +1,95 @@
import { writeFile, mkdir, stat, readFile } from "fs/promises";
import { dirname } from "path";
import { AudioResource, createAudioResource, StreamType } from "@discordjs/voice";
import { Readable } from "stream";
import { createHash } from "node:crypto";
import { join } from "node:path";
import { existsSync } from "node:fs";
export abstract class TTSModelBase<RequestId> {
public ttsify(input: string): string {
return input
.replace(/:[^:]+:/g, (text: string): string => (TTSModelBase.EMOJI_MAP[text] ?? "이모지"))
.replace(/[:*"<>|]/g, "")
.replace(/[\t\n]/g, " ")
}
public abstract createRequestId(text: string): RequestId
public abstract getVoiceBuffer(id: RequestId): Promise<ArrayBuffer>
public abstract getVoicePath(id: RequestId): string
/**
* id로
*
*/
public async createVoice(id: RequestId, audioPath?: string): Promise<Buffer> {
const voiceBuffer = await this.getVoiceBuffer(id);
audioPath ??= this.getVoicePath(id);
const buffer = Buffer.from(voiceBuffer);
await mkdir(dirname(audioPath), { recursive: true });
await writeFile(audioPath, buffer);
return buffer;
}
/**
* id로
*/
public async getVoice(id: RequestId, audioPath?: string): Promise<Buffer> {
audioPath ??= this.getVoicePath(id);
if (existsSync(audioPath)) {
const buffer = await readFile(audioPath);
return buffer;
}
return this.createVoice(id, audioPath);
}
/**
* id로 ,
* ,
*/
protected abstract cachedVoice: Map<String, Promise<Buffer>>
public async getMemcachedVoice(id: RequestId): Promise<Buffer> {
const path = this.getVoicePath(id);
const cached = this.cachedVoice.get(path);
if (cached) {
return cached;
}
const waitter = this.getVoice(id);
this.cachedVoice.set(path, waitter);
setTimeout(
() => this.cachedVoice.delete(path),
TTSModelBase.MemCacheTTL
);
return await waitter;
}
}
export namespace TTSModelBase {
export const EMOJI_MAP: { [key: string]: string } = {
":heart:": "하트",
":huck:": "헉헉!",
":star:": "초롱초롱!"
}
export const AudioCachePath = join(
process.cwd(),
"cache",
"audio",
);
export function bufferToAudioResource(buf: Buffer): AudioResource {
const stream = Readable.from(buf);
const resource = createAudioResource(stream, {
inlineVolume: true,
inputType: StreamType.Arbitrary,
});
resource.volume?.setVolume(0.3);
return resource;
}
export function hashAudioFile(audio: string, suffix: string = ""): string {
return createHash("md5").update(audio).digest("hex") + suffix + ".mp3";
}
export const MemCacheTTL = 60 * 60 * 1000
}
export default TTSModelBase;

101
packages/tts/papago.ts Normal file
View file

@ -0,0 +1,101 @@
import { createHmac } from "crypto";
import { join } from "path";
import fetch from "../utils/fetch";
import TTSModelBase from ".";
export class TTSPapagoModel extends TTSModelBase<TTSPapagoModel.RequestId> {
protected cachedVoice: Map<String, Promise<Buffer>>
constructor() {
super()
this.cachedVoice = new Map();
}
public getVoicePath(id: TTSPapagoModel.RequestId): string {
const audioFileName = TTSModelBase.hashAudioFile(id.text, `.${id.speaker}.${id.speed.replace(/\-/g, "_")}`);
const audioPath = join(
TTSPapagoModel.PapagoAudioCachePath,
audioFileName
);
return audioPath;
}
async getVoiceBuffer(id: TTSPapagoModel.RequestId, voiceId?: string): Promise<ArrayBuffer> {
voiceId ??= await TTSPapagoModel.getVoiceId(id)
const response = await fetch(`https://papago.naver.com/apis/tts/${voiceId}`);
return await response.arrayBuffer();
}
createRequestId(text: string, speaker?: string, speed?: string): TTSPapagoModel.RequestId {
return {
text,
speed: speed ?? "-1",
speaker: speaker ?? "kyuri",
};
}
}
export namespace TTSPapagoModel {
export const instance = new TTSPapagoModel();
export type RequestId = {
speaker: string;
speed: string;
text: string;
};
export const GenerateTokenKey = "v1.9.3_3bdf0438a8";
export function hmacMD5(key: string, plaintext: string) {
const hmac = createHmac("md5", key);
const data = hmac.update(plaintext);
return data.digest("base64");
}
export function generateToken(time: number) {
const e = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (e) => {
var t = (time + 16 * Math.random()) % 16 | 0;
return (time = Math.floor(time / 16)), ("x" === e ? t : (3 & t) | 8).toString(16);
});
const plain = `${e}\n${"https://papago.naver.com/apis/tts/makeID"}\n${time}`;
return `PPG ${e}:${hmacMD5(GenerateTokenKey, plain)}`;
}
export async function getVoiceId(id: RequestId): Promise<string> {
const input = {
alpha: "0",
pitch: "0",
speaker: id.speaker,
speed: id.speed,
text: id.text,
};
const time = new Date().getTime();
const token = TTSPapagoModel.generateToken(time);
const reqbody = new URLSearchParams(Object.entries(input)).toString();
const response = await fetch("https://papago.naver.com/apis/tts/makeID", {
headers: {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
Accept: "application/json",
"Accept-Language": "en",
"Sec-GPC": "1",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "no-cors",
"Sec-Fetch-Site": "same-origin",
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
Authorization: token,
Timestamp: time.toString(),
Pragma: "no-cache",
"Cache-Control": "no-cache",
},
referrer: "https://papago.naver.com/",
body: reqbody,
method: "POST",
});
if (!response.ok) {
throw new Error(`TTS makeID request failed: ${response.status}: ${await response.text()}`);
}
return ((await response.json()) as any).id;
}
export const PapagoAudioCachePath = join(
TTSModelBase.AudioCachePath,
"papago"
);
}
export default TTSPapagoModel;

176
packages/tts/typecast.ts Normal file
View file

@ -0,0 +1,176 @@
import { join } from "path";
import { TYPECAST_TOKENS } from "../env";
import fetch from "../utils/fetch";
import TTSModelBase from ".";
import CallingNumberKorean from "../utils/callingNumberKorean";
import IntegerKorean from "../utils/integerKorean";
import FloatKorean from "../utils/floatKorean";
import { readFileSync, writeFileSync } from "fs";
import { cwd, env } from "process";
export class TTSTypecastModel extends TTSModelBase<TTSTypecastModel.RequestId> {
protected cachedVoice: Map<String, Promise<Buffer>>
private lastUseApiKeyPath: string
constructor() {
super()
this.cachedVoice = new Map();
this.lastUseApiKeyPath = join(cwd(), "cache", "typecast", "lastUseApiToken");
}
ttsify(input: string): string {
return super.ttsify(
input
.replace(/\.+$/, "")
.replace(/\.\.+/g, "")
.replace(/\.[ \t]/g, " ")
.replace(/^[\?\!\'\"]+$/, (total)=>(
[...total].map(element => TTSTypecastModel.IsolatedSymbolMap[
element as keyof typeof TTSTypecastModel.IsolatedSymbolMap
]).join("")
))
.replace(/\`\`\`.+?\`\`\`/g, "코드블럭")
.replace(/https\S+/g, "링크")
.replace(/ㄴㄴ/g, "노노")
.replace(/ㅇㅋ/g, "오키")
.replace(/ㅜㅜ/g, "눙물")
.replace(/빵/g, "빵 크크")
.replace(/[\?]+ *ㄴ/g, "물음표ㄴ")
.replace(/(\d+)[ \t\n]*([개살])/g, (_, num: string, postfix: string)=>{
const intNum = parseInt(num)
if (CallingNumberKorean.canConvert(intNum)) {
return CallingNumberKorean.convert(intNum) + postfix;
} else {
return IntegerKorean.convertFromString(num) + postfix;
}
})
.replace(/(v?)([\d\.]+)([ab]?)/g, (_, suffix: string, num: string, postfix: string) => {
const dotCount = [...num.matchAll(/\./g)].length;
const hasNoSuffix = suffix == "";
if (hasNoSuffix && dotCount == 0) {
return IntegerKorean.convertFromString(num) + postfix;
} else if (hasNoSuffix && dotCount == 1) {
const [intPart, floatPart] = num.split(/\./);
return (
IntegerKorean.convertFromString(intPart)
+ "쩜"
+ FloatKorean.convert(floatPart)
+ postfix
)
} else if (suffix == "v") {
return (
"버전"
+ FloatKorean.convert(num)
+ (TTSTypecastModel.VersionPostfix[
postfix as keyof typeof TTSTypecastModel.VersionPostfix
] ?? "")
);
} else {
return FloatKorean.convert(num) + postfix;
}
})
.replace(/[\%\^\&\*\#\@\.\-\+\_\=\/\\♡\$]/g, (t) => (
TTSTypecastModel.SymbolMap[t as keyof typeof TTSTypecastModel.SymbolMap]
))
.replace(/\?+/g, "?")
.replace(/\!+/g, "!")
)
}
private async getTypecastResponse(apiKey: string, voiceId: TTSTypecastModel.RequestId) {
const payload = {
text: voiceId.text,
model: "ssfm-v21",
voice_id: voiceId.voiceId,
language: "kor",
prompt: {
emotion_preset: "happy", // Options: normal, happy, sad, angry, tonemid, toneup
emotion_intensity: 1 // Range: 0.0 to 2.0
},
output: {
volume: 45, // Range: 0 to 200
audio_pitch: 1, // Range: -12 to +12 semitones
audio_tempo: 1, // Range: 0.5x to 2.0x
audio_format: "mp3" // Options: wav, mp3
},
seed: 22 // For reproducible results
};
return await fetch(TTSTypecastModel.TypecastApiUrl, {
method: "POST",
headers: {
"X-API-KEY": apiKey,
"Content-Type": "application/json"
},
body: JSON.stringify(payload)
});
}
async getVoiceBuffer(voiceId: TTSTypecastModel.RequestId): Promise<ArrayBuffer> {
let response: Response | undefined;
for (let i = 0; i < TYPECAST_TOKENS.length; i++) {
response = await this.getTypecastResponse(readFileSync(this.lastUseApiKeyPath, "utf-8"), voiceId) as Response;
if (response.ok)
return await response.arrayBuffer();;
if (response.status === 402) {
writeFileSync(this.lastUseApiKeyPath, TYPECAST_TOKENS[i]);
} else {
throw new Error(`TTS makeID request failed: ${response.status}: ${await response.text()}`);
}
}
throw new Error("Typecast Api use all credit");
}
public getVoicePath(id: TTSTypecastModel.RequestId): string {
const audioFileName = TTSModelBase.hashAudioFile(id.text);
const audioPath = join(
TTSTypecastModel.TypecastAudioCachePath,
id.voiceId,
audioFileName
);
return audioPath;
}
public createRequestId(text: string, voiceId?: string): TTSTypecastModel.RequestId {
return {
text,
voiceId: voiceId ?? TTSTypecastModel.DefaultVoiceId,
};
}
}
export namespace TTSTypecastModel {
export const IsolatedSymbolMap = {
"?": "물음표",
"!": "느낌표",
"'": "쿼트",
"\"": "더블쿼트",
}
export const SymbolMap = {
"%": "퍼센트",
"$": "달러싸인",
"^": "캐럿",
"&": "엠퍼센드",
"*": "스타",
"#": "해시",
"@": "엣",
".": "쩜",
"-": "마이너스",
"+": "플러스",
"_": "언더바",
"=": "이퀄",
"/": "슬래쉬",
"\\": "역슬래쉬",
"♡": "하투 ",
};
export const VersionPostfix = {
"a": "알파",
"b": "베타",
};
export const instance = new TTSTypecastModel();
export type RequestId = { text: string, voiceId: string };
export const TypecastAudioCachePath = join(TTSModelBase.AudioCachePath, "typecast");
export const TypecastApiUrl = "https://api.typecast.ai/v1/text-to-speech";
export const DefaultVoiceId = "tc_6731b292d944a485bc406efb";
}
export default TTSTypecastModel;

View file

@ -0,0 +1,31 @@
export default class CallingNumberKorean {
// 개, 살 이 붙는 경우 발음법
static SecondDigit = [
"열", "스물", "서른", "마흔", "쉰",
"예순", "일흔", "여든", "아흔",
]
static FirstDigit = [
"", "한", "두", "세", "네", "다섯",
"여섯", "일곱", "여덟", "아홉", "열",
]
static FirstDigitSingle = [
"영", "한", "두", "세", "네", "다섯",
"여섯", "일곱", "여덟", "아홉", "열",
]
static canConvert(num: number): boolean {
return num < 100 && num >= 0 && !Number.isInteger(num)
}
static convert(num: number): string {
const firstDigit = num % 10;
const secondDigit = Math.floor(num / 10);
if (secondDigit) {
return (
this.SecondDigit[secondDigit]
+ this.FirstDigit[firstDigit]
);
} else {
return this.FirstDigitSingle[firstDigit];
}
}
}

15
packages/utils/fetch.ts Normal file
View file

@ -0,0 +1,15 @@
import { Response as NodeFetchResponse, RequestInit, RequestInfo } from "node-fetch";
import defaultFetch from "node-fetch";
export default async function(url: URL | RequestInfo, request: RequestInit={}, time: number=5000): Promise<NodeFetchResponse | Response> {
const controller = new AbortController();
const timeout = setTimeout(() => controller.abort(), time);
request.signal ??= controller.signal;
try {
return await defaultFetch(url, request);;
} finally {
clearTimeout(timeout);
}
}

View file

@ -0,0 +1,16 @@
export default class FloatKorean {
static Digits = [
"영", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구"
];
static convert(num: string): string {
const buf = new Array(num.length);
for (let idx = 0; idx < num.length; idx++) {
if (num[idx] == ".") {
buf[idx] = "쩜";
} else {
buf[idx] = this.Digits[+num[idx]];
}
}
return buf.join("");
}
}

View file

@ -0,0 +1,109 @@
export default class IntegerKorean {
static DigitName = [ "영", "일", "이", "삼", "사", "오", "육", "칠", "팔", "구" ];
static DigitModifier = ["", "십", "백", "천"];
static Unit = [ "", "만", "억", "조", "경", "해", "자", "양", "구", "간", "정", "재", "극", "항하사", "아승기", "나유타", "불가사의", "무량대수" ];
private static stringifyKDigits(
first: number, second: number, third: number, forth: number
): string {
const buf = [];
if (forth) {
if (forth >= 2) buf.push(this.DigitName[forth]);
buf.push(this.DigitModifier[3]);
}
if (third) {
if (third >= 2) buf.push(this.DigitName[third]);
buf.push(this.DigitModifier[2]);
}
if (second) {
if (second >= 2) buf.push(this.DigitName[second]);
buf.push(this.DigitModifier[1]);
}
if (first || (!forth && !third && !second)) {
buf.push(this.DigitName[first]);
}
return buf.join("");
}
private static parseKDigitsFromNumber(num: number): string {
const first = num % 10;
const second = Math.floor(num / 10) % 10;
const third = Math.floor(num / 100) % 10;
const forth = Math.floor(num / 1000) % 10;
return this.stringifyKDigits(first, second, third, forth);
}
private static parseKDigitsFromString(num: string, offset: number): string {
const first = +num[offset];
const second = offset >= 1 ? +num[offset - 1] : 0;
const third = offset >= 2 ? +num[offset - 2] : 0;
const forth = offset >= 3 ? +num[offset - 3] : 0;
return this.stringifyKDigits(first, second, third, forth);
}
static convertFromString(num: string): string {
let isNegative = false;
if (num.startsWith("-")) {
num = num.slice(1, -1);
isNegative = true;
}
if (num == "0") {
return isNegative ? "마이너스영" : "영";
}
const unitStack = [];
let offset = num.length - 1;
while (offset >= 0) {
unitStack.push(this.parseKDigitsFromString(num, offset));
offset -= 4;
}
const buf = [];
if (isNegative) buf.push("마이너스");
for (let i = unitStack.length - 1; i >= 0; i--) {
const currUnit = this.Unit[i];
let currKDigits = unitStack[i];
if (currKDigits == "영") continue;
if (i == 1 && currKDigits == "일")
currKDigits = "";
buf.push(currKDigits + currUnit);
}
return buf.join("");
}
static convertFromNumber(num: number): string {
let isNegative = false;
if (num < 0) {
isNegative = true;
num *= -1;
}
if (num == 0) {
return "영";
}
const unitStack = [];
while (num) {
unitStack.push(this.parseKDigitsFromNumber(num));
num = Math.floor(num / 10000);
}
const buf = [];
if (isNegative) buf.push("마이너스");
for (let i = unitStack.length - 1; i >= 0; i--) {
const currUnit = this.Unit[i];
let currKDigits = unitStack[i];
if (currKDigits == "영") continue;
if (i == 1 && currKDigits == "일")
currKDigits = "";
buf.push(currKDigits + currUnit);
}
return buf.join("");
}
}

137
packages/utils/nyaize.ts Normal file
View file

@ -0,0 +1,137 @@
export const nyaWords = {
'나': "냐",
'낙': "냑",
'낚': "냒",
'낛': "냓",
'난': "냔",
'낝': "냕",
'낞': "냖",
'낟': "냗",
'날': "냘",
'낡': "냙",
'낢': "냚",
'낣': "냛",
'낤': "냜",
'낥': "냝",
'낦': "냞",
'낧': "냟",
'남': "냠",
'납': "냡",
'낪': "냢",
'낫': "냣",
'났': "냤",
'낭': "냥",
'낮': "냦",
'낯': "냧",
'낰': "냨",
'낱': "냩",
'낲': "냪",
'낳': "냫",
};
export const nyaWords2 = {
'내': "냥",
'넹': "냥",
'넴': "냥",
'넵': "냥",
'냐': "냥",
'님': "냥",
'니': "냥",
'다': "다냥",
'까': "까냥",
'네': "네냥",
'야': "야냥",
'꺼': "꺼냥",
'래': "래냥",
'해': "해냥",
'지': "지냥",
'라': "라냥",
'요': "요냥",
'가': "가냥",
'데': "데냥",
'돼': "돼냥",
'줘': "줘냥",
'마': "마냥",
'와': "와냥",
'어': "어냥",
'자': "자냥",
'죠': "죠냥",
'서': "서냥",
'게': "게냥",
};
function replacePunctuation(input: string): string {
return input.replace(/(^|\s)([?!,.;~^@()]+)/g, (match, p1, p2) => {
const firstChar = p2[0];
let transformed;
if (firstChar === '?') transformed = '냥?';
else if (firstChar === '!') transformed = '냥!';
else if (firstChar === ',') transformed = '냥,';
else if (firstChar === '.') transformed = '냥.';
else if (firstChar === ';') transformed = '냥;';
else if (firstChar === '~') transformed = '냥~';
else if (firstChar === '^') transformed = '냥^';
else if (firstChar === '@') transformed = '냥@';
else if (firstChar === '(') transformed = '냥(';
else if (firstChar === ')') transformed = '냥)';
else transformed = p2;
return p1 + transformed + p2.slice(1);
});
}
function addNyangAtMWord(sentence: string): string {
return sentence.split(' ').map((word) => {
const match = word.match(/^([가-힣]+)([?!,.;~^@()]*)$/);
if (!match) return word;
const baseWord = match[1];
const punctuation = match[2];
const lastChar = baseWord[baseWord.length - 1];
const charCode = lastChar.charCodeAt(0);
if (charCode >= 0xAC00 && charCode <= 0xD7A3) {
const baseCode = charCode - 0xAC00;
const jongseong = baseCode % 28;
if (jongseong === 16) {
return baseWord + "냥" + punctuation;
}
}
return word;
}).join(' ');
}
export function nyaize(text: string): string {
for (let key in nyaWords2) {
text = text.replaceAll(key + ".", nyaWords2[key as keyof typeof nyaWords2] + "."); // yeah I gotta optimize these
text = text.replaceAll(key + ",", nyaWords2[key as keyof typeof nyaWords2] + ",");
text = text.replaceAll(key + "?", nyaWords2[key as keyof typeof nyaWords2] + "?");
text = text.replaceAll(key + "!", nyaWords2[key as keyof typeof nyaWords2] + "!");
text = text.replaceAll(key + ";", nyaWords2[key as keyof typeof nyaWords2] + ";");
text = text.replaceAll(key + "~", nyaWords2[key as keyof typeof nyaWords2] + "~");
text = text.replaceAll(key + "^", nyaWords2[key as keyof typeof nyaWords2] + "^");
text = text.replaceAll(key + "@", nyaWords2[key as keyof typeof nyaWords2] + "@");
text = text.replaceAll(key + "(", nyaWords2[key as keyof typeof nyaWords2] + "(");
text = text.replaceAll(key + ")", nyaWords2[key as keyof typeof nyaWords2] + ")");
text = text.replaceAll(key + " ", nyaWords2[key as keyof typeof nyaWords2] + " ");
if (text.endsWith(key)) {
text = text.slice(0, -1) + nyaWords2[key as keyof typeof nyaWords2];
}
}
for (let key in nyaWords) {
text = text.replaceAll(key, nyaWords[key as keyof typeof nyaWords]);
}
text = replacePunctuation(text);
text = addNyangAtMWord(text);
return text;
}

View file

@ -0,0 +1,40 @@
import { dirname, join } from "path";
import { mkdir, open, readFile } from "fs/promises"
export namespace OutputHandler {
export const LogCachePath = join(
process.cwd(),
"cache",
"log",
);
export const ErrorLogPath = join(LogCachePath, "error.log" );
export function getErrorOutput(...args: any[]) {
const timestamp = new Date().toISOString();
const message = args
.map(arg => {
if (arg instanceof Error) {
return `${arg.name}: ${arg.message}\n${arg.stack}`;
}
if (typeof arg === 'object') {
return JSON.stringify(arg);
}
return String(arg);
})
.join(' ');
return `[${timestamp}] ${message}`;
}
export async function errorLog(...args: any[]): Promise<void> {
const output = getErrorOutput(...args);
await mkdir(dirname(ErrorLogPath), { recursive: true });
const fileHandle = await open(ErrorLogPath, "a");
fileHandle.write(output + "\n");
fileHandle.close();
}
export async function getErrorLog(): Promise<string> {
return (await readFile(ErrorLogPath)).toString();
}
}

View file

@ -0,0 +1,13 @@
import { readdirSync } from "fs";
import { readdir } from "fs/promises";
import { join } from "path";
export async function requireDirectory<T>(directory: string): Promise<T[]> {
const requireFiles = (await readdir(directory)).filter(file => file.endsWith(".js"));
return requireFiles.map(file => require(join(directory, file)).default as T);
}
export function requireDirectorySync<T>(directory: string): T[] {
const requireFiles = readdirSync(directory).filter(file => file.endsWith(".js"));
return requireFiles.map(file => require(join(directory, file)).default as T).filter(x=>x);
}

2305
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load diff

14
prisma.config.ts Normal file
View file

@ -0,0 +1,14 @@
// This file was generated by Prisma, and assumes you have installed the following:
// npm install --save-dev prisma dotenv
import "dotenv/config";
import { defineConfig } from "prisma/config";
export default defineConfig({
schema: "prisma/schema.prisma",
migrations: {
path: "prisma/migrations",
},
datasource: {
url: process.env["DATABASE_URL"],
},
});

34
prisma/schema.prisma Normal file
View file

@ -0,0 +1,34 @@
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init
generator client {
provider = "prisma-client"
specifying = ["prismaSchemaFolder"]
output = "../packages/db/generated/prisma"
}
datasource db {
provider = "postgresql"
}
enum Voice {
TypeCast
Papago
}
model DiscordUserProfile {
id String @id @default(cuid())
userId String @unique
voice Voice @default(Papago)
nya Boolean @default(false)
canTypecast Boolean @default(false)
}
model DiscordGuildProfile {
id String @id @default(cuid())
guildId String @unique
readChannel String[] @default([])
}

14
tsconfig.json Normal file
View file

@ -0,0 +1,14 @@
{
"compilerOptions": {
"target": "ES6",
"module": "commonjs",
"lib": [ "ES2021", "DOM", "DOM.Iterable" ],
"moduleResolution": "node",
"outDir": "dist",
"strict": true,
"exactOptionalPropertyTypes": false,
"allowSyntheticDefaultImports": true,
"skipLibCheck": true
},
"include": ["./packages/**/*"]
}