Skip to content

Commit

Permalink
Add toggle for Japanese translation (#50)
Browse files Browse the repository at this point in the history
* Add toggle for Japanese translation
Fixes #49
- update package to 2.1.1
- add migration for translate Status, add entity
- add command for toggleTranslate, translateStatus

* Fix migration to be dynamic seeding of guild data
Add toggle for Japanese translation
Fixes #49
  • Loading branch information
im-calvin authored Oct 31, 2023
1 parent 402dc2f commit b09ef8e
Show file tree
Hide file tree
Showing 11 changed files with 185 additions and 22 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "mittens",
"version": "2.1.3",
"version": "2.2.0",
"description": "",
"main": "src/bot.ts",
"scripts": {
Expand Down
56 changes: 39 additions & 17 deletions src/bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,11 @@ import Sentry from "@sentry/node";
import { readEnv } from "./utils/env.js";
import { init, kuroshiro } from "./init.js";
import { scrape } from "./utils/schedule.js";
import { AppDataSource } from "./db/data-source.js";
import { GuildTranslate } from "./db/entity/GuildTranslate.js";
import { CMD_PREFIX } from "./constants.js";

await init();

const boot = Sentry.startTransaction({
op: "boot",
name: "First time launch of Mittens",
});

// need to first init the client because some migrations in init() depend on client being up
export const client = new MittensClient({
intents: [
GatewayIntentBits.Guilds,
Expand All @@ -22,6 +18,15 @@ export const client = new MittensClient({
],
});

await init();

const boot = Sentry.startTransaction({
op: "boot",
name: "First time launch of Mittens",
});

const guildTranslateRepo = AppDataSource.getRepository(GuildTranslate);

// on boot
client.once("ready", async () => {
await scrape();
Expand Down Expand Up @@ -82,10 +87,19 @@ client.on(Events.InteractionCreate, async (interaction) => {

// handle translating
client.on(Events.MessageCreate, async (message: Message) => {
const transaction = Sentry.startTransaction({
op: "msgCreate",
name: "Message creation interaction",
// if dm
if (!message.guildId) {
return;
}
// if the admins turned off translating in their server
// the database should always have a guild in it because the db gets updated every time mittens joins a guild
const guildTranslate = await guildTranslateRepo.findOneByOrFail({
discordGuildId: message.guildId,
});
if (!guildTranslate.status) {
return;
}

if (
message.author.id === client.user!.id ||
message.author.bot ||
Expand Down Expand Up @@ -129,7 +143,6 @@ client.on(Events.MessageCreate, async (message: Message) => {
const myMessage = await message.channel.send(translatedText);
client.messageCache.set(message.id, myMessage.id);
}
transaction.finish();
});

// handle translating edits
Expand All @@ -139,10 +152,6 @@ client.on(
oldMessage: Message<boolean> | PartialMessage,
newMessage: Message<boolean> | PartialMessage
) => {
const transaction = Sentry.startTransaction({
op: "msgUpdate",
name: "Message update interaction",
});

if (oldMessage.partial || newMessage.partial) {
return; // partials are not enabled
Expand All @@ -162,9 +171,22 @@ client.on(
// fetch the old message that I sent and edit it
(await oldMessage.channel.messages.fetch(myOldMessageId)).edit(translatedText);
}
transaction.finish();
}
);

client.login(readEnv("DISCORD_TOKEN"));
// handle joining a guild (for toggling translate)
// set default for translation to be false
client.on(Events.GuildCreate, (guild) => {
const transaction = Sentry.startTransaction({
op: "guildCreate",
name: "Mittens added to a new guild",
});
const guildTranslate = new GuildTranslate();
guildTranslate.status = false;
guildTranslate.discordGuildId = guild.id;

guildTranslateRepo.insert(guildTranslate);
transaction.finish();
});

boot.finish();
2 changes: 0 additions & 2 deletions src/commands/list.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
import { SlashCommandBuilder } from "discord.js";
import { CommandData } from "../utils/cmdLoader.js";
import { autoCompleteStreamers } from "../utils/cmdLoader.js";
import { DiscordUser } from "../db/entity/DiscordUser.js";
import { DiscordUserSubscription } from "../db/entity/DiscordUserSubscription.js";
import { AppDataSource } from "../db/data-source.js";
import { Streamer } from "../db/entity/Streamer.js";

const command = new SlashCommandBuilder()
.setName("list")
Expand Down
41 changes: 41 additions & 0 deletions src/commands/toggleTranslate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { SlashCommandBuilder, PermissionFlagsBits, bold } from "discord.js";
import { CommandData } from "../utils/cmdLoader.js";
import { AppDataSource } from "../db/data-source.js";
import { GuildTranslate } from "../db/entity/GuildTranslate.js";

const guildTranslateRepo = AppDataSource.getRepository(GuildTranslate);

const command = new SlashCommandBuilder()
.setName("toggle-translate")
.setDescription("Toggle auto-translation from Japanese to English for your server.")
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);

const toggleTranslate: CommandData = {
command,
autoComplete: () => {},
execute: async (interaction) => {
// check if the user is an admin in the server
if (!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)) {
await interaction.editReply(`You need to be an admin to perform this action!`);
return;
}

const guildId = interaction.guildId;
// not a dm
if (guildId) {
const guildTranslate = await guildTranslateRepo.findOneByOrFail({
discordGuildId: guildId,
});
guildTranslate.status = !guildTranslate.status;

await guildTranslateRepo.upsert(guildTranslate, ["discordGuildId"]);

await interaction.editReply(
// need to convert from integer (0, 1) to bool to string
`Set Japanese to English translation to ${bold(String(Boolean(guildTranslate.status)))}`
);
}
},
};

export default toggleTranslate;
39 changes: 39 additions & 0 deletions src/commands/translateStatus.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { SlashCommandBuilder, PermissionFlagsBits, bold } from "discord.js";
import { CommandData } from "../utils/cmdLoader.js";
import { AppDataSource } from "../db/data-source.js";
import { GuildTranslate } from "../db/entity/GuildTranslate.js";

const guildTranslateRepo = AppDataSource.getRepository(GuildTranslate);

const command = new SlashCommandBuilder()
.setName("translate-status")
.setDescription(
"Check to see if Mittens is actively translating from Japanese to English for your server."
)
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator);

const translateStatus: CommandData = {
command,
autoComplete: () => {},
execute: async (interaction) => {
// make sure that it's not a dm
if (interaction.guildId) {
// check if the user is an admin in the server
if (!interaction.memberPermissions?.has(PermissionFlagsBits.Administrator)) {
await interaction.editReply(`You need to be an admin to perform this action!`);
return;
}

const guildTranslate = await guildTranslateRepo.findOneByOrFail({
discordGuildId: interaction.guildId,
});

await interaction.editReply(
// need to convert from integer (0, 1) to bool to string
`Japanese to English translation is set to ${bold(String(Boolean(guildTranslate.status)))}`
);
}
},
};

export default translateStatus;
5 changes: 5 additions & 0 deletions src/db/data-source.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,12 @@ import { UniqueIndexSubs1684569492134 } from "./migration/1684569492134-UniqueIn
import { ClearNonUniqueSubs1684426659702 } from "./migration/1684426659702-ClearNonUniqueSubs.js";
import { StreamersLanguages1685251838704 } from "./migration/1685251838704-StreamersLanguages.js";
import { AddLivePinged1688194621864 } from "./migration/1688194621864-AddLivePinged.js";
import { AddTranslateStatuses1688860131193 } from "./migration/1688860131193-AddTranslateStatuses.js";
import { GuildTranslate } from "./entity/GuildTranslate.js";
import { AddHoloEnAdvent1690521022694 } from "./migration/1690521022694-AddHoloEnAdvent.js";
import { NormalizeLanguages1690528636089 } from "./migration/1690528636089-NormalizeLanguages.js";


export const AppDataSource = new DataSource({
type: "sqlite",
database: "data/database.sqlite",
Expand All @@ -29,6 +32,7 @@ export const AppDataSource = new DataSource({
DiscordUserSubscription,
VideoParticipant,
Language,
GuildTranslate,
],
migrations: [
InitMigration,
Expand All @@ -39,6 +43,7 @@ export const AppDataSource = new DataSource({
AddLivePinged1688194621864,
AddHoloEnAdvent1690521022694,
NormalizeLanguages1690528636089,
AddTranslateStatuses1688860131193,
],
subscribers: [],
migrationsTransactionMode: "all",
Expand Down
13 changes: 13 additions & 0 deletions src/db/entity/GuildTranslate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Entity, PrimaryGeneratedColumn, PrimaryColumn } from "typeorm";

@Entity({ name: "guild_translate_statuses" })
export class GuildTranslate {
constructor() {}

// the name of the group
@PrimaryColumn({ type: "text", name: "discord_guild_id" })
discordGuildId: string;

@PrimaryColumn({ type: "integer", name: "status" })
status: boolean;
}
34 changes: 34 additions & 0 deletions src/db/migration/1688860131193-AddTranslateStatuses.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MigrationInterface, QueryRunner, Table } from "typeorm";
import { client } from "../../bot.js";

export class AddTranslateStatuses1688860131193 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.createTable(
new Table({
name: "guild_translate_statuses",
columns: [
{
name: "discord_guild_id",
type: "text",
isPrimary: true,
},
{
name: "status",
type: "integer", // bool is integer (0, 1)
},
],
})
);

// insert curr servers
const guildsInsertStmt = `INSERT INTO guild_translate_statuses (discord_guild_id, status) VALUES (?, ?)`;
const guilds = await client.guilds.fetch();
guilds.forEach(async (server) => {
await queryRunner.query(guildsInsertStmt, [server.id, false]);
});
}

public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.dropTable("guild_translate_statuses");
}
}
2 changes: 2 additions & 0 deletions src/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,12 @@ import { readEnv } from "./utils/env.js";
import { Video } from "./db/entity/Video.js";
import { MoreThan } from "typeorm";
import { scheduleAnnounce } from "./utils/schedule.js";
import { client } from "./bot.js";

export const kuroshiro = new Kuroshiro();

export async function init(): Promise<void> {
client.login(readEnv("DISCORD_TOKEN"));
// inits Sentry
Sentry.init({
dsn: "https://c9c992d5a347411db99537a0ed2c0094@o4505106964742144.ingest.sentry.io/4505106967691264",
Expand Down
12 changes: 11 additions & 1 deletion src/utils/cmdLoader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ import schedule from "../commands/schedule.js";
import { Group } from "../db/entity/Group.js";
import { Streamer } from "../db/entity/Streamer.js";
import { Language } from "../db/entity/Language.js";
import toggleTranslate from "../commands/toggleTranslate.js";
import translateStatus from "../commands/translateStatus.js";
import history from "../commands/history.js";

export interface CommandData {
Expand All @@ -19,7 +21,15 @@ export interface CommandData {
execute: (interaction: ChatInputCommandInteraction) => void | Promise<void>;
}

export const commands: CommandData[] = [add, remove, list, schedule, history];
export const commands: CommandData[] = [
add,
remove,
list,
schedule,
toggleTranslate,
translateStatus,
history
];

export async function autoCompleteStreamers(interaction: AutocompleteInteraction): Promise<void> {
const focusedValue = interaction.options.getString("streamer", true).toLowerCase();
Expand Down
1 change: 0 additions & 1 deletion src/utils/schedule.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ const scheduler = new ToadScheduler();
const videoRepo = AppDataSource.getRepository(Video);
const subRepo = AppDataSource.getRepository(DiscordUserSubscription);
const streamerRepo = AppDataSource.getRepository(Streamer);
const participantRepo = AppDataSource.getRepository(VideoParticipant);

/**
* schedules a job to message users on discord about a particular video
Expand Down

0 comments on commit b09ef8e

Please sign in to comment.