Compare commits

..

10 commits

Author SHA1 Message Date
7a1081833f [chore] vscode rule
All checks were successful
Lint and Format Check / lint-and-format (pull_request) Successful in 10s
2024-09-04 12:46:51 +02:00
aded2add59
fix: add pr types for action (#155)
All checks were successful
Publish latest version / build (push) Successful in 1m45s
Reviewed-on: #155
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-04 10:05:20 +02:00
0c617d6855
add action for pull requests (#152)
All checks were successful
Publish latest version / build (push) Successful in 1m26s
Closes #151

Reviewed-on: #152
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-04 09:57:53 +02:00
fd60b91ffe
chore: upgrade to discord.js 14.16 along with other dependencies (#149)
All checks were successful
Publish latest version / build (push) Successful in 1m47s
see #146

Reviewed-on: #149
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-09-04 09:20:09 +02:00
8f9cdbe753
fix lyrics (#144)
All checks were successful
Publish latest version / build (push) Successful in 1m31s
dont crash when there is not lyrics

Reviewed-on: #144
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-08-12 23:15:04 +02:00
fe38841bf6
fix: synced lyrics (#143)
All checks were successful
Publish latest version / build (push) Successful in 1m36s
Reviewed-on: #143
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-08-12 22:34:29 +02:00
3e43eddc8e
feat: synced lyrics (#142)
All checks were successful
Publish latest version / build (push) Successful in 2m10s
Close #134

Implement synced lyrics

Reviewed-on: #142
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-08-12 17:54:36 +02:00
9d4da0db49
Update dependencies (#141)
All checks were successful
Publish latest version / build (push) Successful in 1m41s
This PR fix music playback

Reviewed-on: #141
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-08-12 17:04:21 +02:00
bfc11f7f91
Use forgejo actions (#140)
All checks were successful
Publish latest version / build (push) Successful in 1m35s
Closes #139

- Replace Woodpecker CI by Forgejo actions

Reviewed-on: #140
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-08-11 16:42:21 +02:00
3e626b8b0e
update dependencies and use new youtube extractor (#138)
All checks were successful
ci/woodpecker/push/publish Pipeline was successful
Reviewed-on: #138
Co-authored-by: Mylloon <kennel.anri@tutanota.com>
Co-committed-by: Mylloon <kennel.anri@tutanota.com>
2024-07-31 13:47:47 +02:00
20 changed files with 495 additions and 485 deletions

View file

@ -0,0 +1,23 @@
name: Lint and Format Check
on:
pull_request:
types: [opened, synchronize, reopened]
branches:
- main
- dev
jobs:
lint-and-format:
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Install dependencies
run: npm install
- name: Run lint
run: npm run lint
- name: Run format check
run: npm run format-check

View file

@ -1,15 +1,36 @@
steps: name: Publish latest version
publish:
image: woodpeckerci/plugin-docker-buildx:2 on:
settings: push:
labels: branches: [main]
platform: linux/amd64
repo: git.mylloon.fr/${CI_REPO,,} jobs:
auto_tag: true build:
registry: git.mylloon.fr container:
username: ${CI_REPO_OWNER} image: ghcr.io/catthehacker/ubuntu:act-latest
password: steps:
from_secret: cb_token - name: Checkout
when: uses: actions/checkout@v4
event: push
branch: main - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Sanitize metadata
id: meta
uses: docker/metadata-action@v5
with:
tags: latest
images: git.mylloon.fr/${{ github.repository }}
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ${{ github.server_url }}
username: ${{ github.actor }}
password: ${{ secrets.TOKEN }}
- name: Build and push
uses: docker/build-push-action@v6
with:
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}

View file

@ -1,4 +1,4 @@
# 🌱 Botanique [![status-badge](https://ci.mylloon.fr/api/badges/ConfrerieDuKassoulait/Botanique/status.svg)](https://ci.mylloon.fr/ConfrerieDuKassoulait/Botanique) # 🌱 Botanique ![status-badge](https://git.mylloon.fr/ConfrerieDuKassoulait/Botanique/badges/workflows/publish.yml/badge.svg)
[**Ajoute le bot à ton serveur**](https://discord.com/api/oauth2/authorize?client_id=965598852407230494&permissions=8&scope=bot%20applications.commands) [**Ajoute le bot à ton serveur**](https://discord.com/api/oauth2/authorize?client_id=965598852407230494&permissions=8&scope=bot%20applications.commands)

717
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,21 +17,21 @@
"author": "La confrérie du Kassoulait", "author": "La confrérie du Kassoulait",
"license": "AGPL-3.0-only", "license": "AGPL-3.0-only",
"dependencies": { "dependencies": {
"@discord-player/extractor": "^4.4.7", "@discord-player/extractor": "^4.5.1",
"@discordjs/rest": "^2.3.0", "@discordjs/rest": "^2.4.0",
"@distube/ytdl-core": "^4.13.4",
"@types/sqlite3": "^3.1.11", "@types/sqlite3": "^3.1.11",
"@types/uuid": "^9.0.8", "@types/uuid": "^10.0.0",
"discord-player": "^6.6.10", "discord-player": "^6.7.1",
"discord.js": "^14.15.3", "discord-player-youtubei": "^1.3.1",
"discord.js": "^14.16.1",
"mediaplex": "^0.0.9", "mediaplex": "^0.0.9",
"sqlite3": "^5.1.7", "sqlite3": "^5.1.7",
"typescript": "^5.4.5", "typescript": "^5.5.4",
"uuid": "^10.0.0" "uuid": "^10.0.0"
}, },
"devDependencies": { "devDependencies": {
"@typescript-eslint/eslint-plugin": "~7.13.0", "@typescript-eslint/eslint-plugin": "~8.4.0",
"@typescript-eslint/parser": "~7.13.0", "@typescript-eslint/parser": "~8.4.0",
"dotenv": "~16.4.5", "dotenv": "~16.4.5",
"prettier-eslint": "~16.3.0", "prettier-eslint": "~16.3.0",
"ts-node-dev": "~2.0.0" "ts-node-dev": "~2.0.0"

View file

@ -1,6 +1,11 @@
import { readdir } from "fs/promises"; import { readdir } from "fs/promises";
import { removeExtension } from "../utils/misc"; import { removeExtension } from "../utils/misc";
import { ChatInputCommandInteraction, Client, MessageComponentInteraction } from "discord.js"; import {
ChatInputCommandInteraction,
Client,
Message,
MessageComponentInteraction,
} from "discord.js";
import { getLocale } from "../utils/locales"; import { getLocale } from "../utils/locales";
export default async (client: Client) => { export default async (client: Client) => {
@ -37,12 +42,14 @@ export default async (client: Client) => {
* @param client Client * @param client Client
* @param interaction Chat interaction * @param interaction Chat interaction
* @param id Button ID * @param id Button ID
* @param message Message holding the buttons
* @param deferUpdate defer update in case update take time * @param deferUpdate defer update in case update take time
*/ */
export const collect = ( export const collect = (
client: Client, client: Client,
interaction: ChatInputCommandInteraction | MessageComponentInteraction, interaction: ChatInputCommandInteraction | MessageComponentInteraction,
id: string, id: string,
message: Message,
deferUpdate = false, deferUpdate = false,
) => { ) => {
const loc = getLocale(client, interaction.locale); const loc = getLocale(client, interaction.locale);
@ -56,7 +63,7 @@ export const collect = (
} }
const filter = (i: MessageComponentInteraction) => i.customId === id; const filter = (i: MessageComponentInteraction) => i.customId === id;
const collector = interaction.channel?.createMessageComponentCollector({ const collector = message.createMessageComponentCollector({
filter, filter,
max: 1, max: 1,
}); });

View file

@ -59,8 +59,8 @@ export default {
); );
// Buttons interactions // Buttons interactions
collect(client, interaction, idPrec); collect(client, interaction, idPrec, interaction.message);
collect(client, interaction, idNext); collect(client, interaction, idNext, interaction.message);
return { return {
embeds: [list], embeds: [list],

View file

@ -59,8 +59,8 @@ export default {
); );
// Buttons interactions // Buttons interactions
collect(client, interaction, idPrec); collect(client, interaction, idPrec, interaction.message);
collect(client, interaction, idNext); collect(client, interaction, idNext, interaction.message);
return { return {
embeds: [list], embeds: [list],

View file

@ -59,8 +59,8 @@ export default {
); );
// Buttons interactions // Buttons interactions
collect(client, interaction, idPrec); collect(client, interaction, idPrec, interaction.message);
collect(client, interaction, idNext); collect(client, interaction, idNext, interaction.message);
} else { } else {
// In case queue doesn't exists // In case queue doesn't exists
embed.setDescription(loc.get("c_queue2")); embed.setDescription(loc.get("c_queue2"));

View file

@ -59,8 +59,8 @@ export default {
); );
// Buttons interactions // Buttons interactions
collect(client, interaction, idPrec); collect(client, interaction, idPrec, interaction.message);
collect(client, interaction, idNext); collect(client, interaction, idNext, interaction.message);
} else { } else {
// In case queue doesn't exists // In case queue doesn't exists
embed.setDescription(loc.get("c_queue2")); embed.setDescription(loc.get("c_queue2"));

View file

@ -230,15 +230,18 @@ export default {
.setStyle(ButtonStyle.Primary), .setStyle(ButtonStyle.Primary),
); );
// Buttons interactions const message = await interaction.reply({
collect(client, interaction, idPrec);
collect(client, interaction, idNext);
return await interaction.reply({
ephemeral: true, ephemeral: true,
embeds: [list], embeds: [list],
components: [row], components: [row],
fetchReply: true,
}); });
// Buttons interactions
collect(client, interaction, idPrec, message);
collect(client, interaction, idNext, message);
return;
} }
// Delete a reminder // Delete a reminder
case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "": { case loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "": {

View file

@ -1,6 +1,6 @@
import { SlashCommandBuilder } from "@discordjs/builders"; import { SlashCommandBuilder } from "@discordjs/builders";
import { Player, useMainPlayer, useQueue } from "discord-player"; import { Player, useMainPlayer, useQueue } from "discord-player";
import { ChatInputCommandInteraction, Client, EmbedBuilder } from "discord.js"; import { ChannelType, ChatInputCommandInteraction, Client, EmbedBuilder } from "discord.js";
import { getLocale, getLocalizations } from "../../utils/locales"; import { getLocale, getLocalizations } from "../../utils/locales";
import { getFilename } from "../../utils/misc"; import { getFilename } from "../../utils/misc";
@ -56,6 +56,15 @@ export default {
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`)), .setDescriptionLocalizations(getLocalizations(client, `c_${filename}_opt1_desc`)),
), ),
) )
// Synced
.addSubcommand((subcommand) =>
subcommand
.setName(loc_default.get(`c_${filename}_sub3_name`)?.toLowerCase() ?? "")
.setDescription(loc_default.get(`c_${filename}_sub3_desc`) ?? "")
.setNameLocalizations(getLocalizations(client, `c_${filename}_sub3_name`, true))
.setDescriptionLocalizations(getLocalizations(client, `c_${filename}_sub3_desc`)),
)
); );
}, },
@ -73,6 +82,7 @@ export default {
await interaction.deferReply(); await interaction.deferReply();
const player = useMainPlayer() as Player; const player = useMainPlayer() as Player;
const queue = useQueue(interaction.guildId ?? "");
if (request) { if (request) {
if ( if (
interaction.options.getSubcommand() == interaction.options.getSubcommand() ==
@ -88,23 +98,60 @@ export default {
} catch { } catch {
return await interaction.followUp(`❌ | ${loc.get("c_lyrics2")} \`${request}\``); return await interaction.followUp(`❌ | ${loc.get("c_lyrics2")} \`${request}\``);
} }
} else { } else if (queue) {
const queue = useQueue(interaction.guildId ?? ""); const track = queue.history.currentTrack;
if (queue) { if (track) {
const track = queue.history.currentTrack; try {
if (track) { data = await player.lyrics.search({
try { q: track.cleanTitle + " " + queue.history.currentTrack?.author,
data = await player.lyrics.search({ });
q: track.cleanTitle + " " + queue.history.currentTrack?.author, } catch {
}); return await interaction.followUp(`❌ | ${loc.get("c_lyrics2")} \`${track.title}\``);
} catch {
return await interaction.followUp(`❌ | ${loc.get("c_lyrics2")} \`${track.title}\``);
}
} }
} }
} }
if (data && data.length > 0) { if (
interaction.options.getSubcommand() ==
loc_default?.get(`c_${filename}_sub3_name`)?.toLowerCase() ??
""
) {
if (queue === null) {
return await interaction.followUp(`❌ | ${loc.get("c_lyrics1")}`);
}
if (data === null || !data[0] || !data[0].syncedLyrics) {
return await interaction.followUp(
`❌ | ${loc.get("c_lyrics3")} \`${queue.currentTrack?.cleanTitle}\``,
);
}
// Load lyrics
const syncedLyrics = queue.syncedLyrics(data[0]);
syncedLyrics?.onChange(async (lyrics) => {
const content = `[${data[0].trackName}]: ${lyrics}`;
if (interaction.channel?.type === ChannelType.GroupDM) {
await interaction.followUp({
content,
});
} else {
await interaction.channel?.send({
content,
});
}
});
// Live update
syncedLyrics.subscribe();
return await interaction.followUp({
content: `🎤 | ${loc.get("c_lyrics4")}`,
ephemeral: true,
});
}
if (data && data.length > 0 && data[0].plainLyrics !== null) {
const title = data[0]; const title = data[0];
const limit_desc = 4096; const limit_desc = 4096;
const nb_embed = Math.ceil(title.plainLyrics.length / limit_desc); const nb_embed = Math.ceil(title.plainLyrics.length / limit_desc);

View file

@ -181,6 +181,7 @@ export default {
delay, delay,
player.search(query, { player.search(query, {
requestedBy: interaction.user, requestedBy: interaction.user,
searchEngine: "spotifySearch",
}), }),
]) ])
.then((res) => { .then((res) => {

View file

@ -96,7 +96,6 @@ export default {
const queue = useQueue(interaction.guildId ?? ""); const queue = useQueue(interaction.guildId ?? "");
const embed = new EmbedBuilder(); const embed = new EmbedBuilder();
const rows = [];
if (queue) { if (queue) {
const subcommand = interaction.options.getSubcommand(); const subcommand = interaction.options.getSubcommand();
@ -110,6 +109,7 @@ export default {
embedListQueue(client, embed, queue, page, interaction.locale); embedListQueue(client, embed, queue, page, interaction.locale);
const rows = [];
const idPrec = "queueList-prec_" + uuidv4(); const idPrec = "queueList-prec_" + uuidv4();
const idNext = "queueList-next_" + uuidv4(); const idNext = "queueList-next_" + uuidv4();
rows.push( rows.push(
@ -128,11 +128,17 @@ export default {
), ),
); );
// Buttons interactions const message = await interaction.reply({
collect(client, interaction, idPrec); embeds: [embed],
collect(client, interaction, idNext); components: rows,
fetchReply: true,
});
break; // Buttons interactions
collect(client, interaction, idPrec, message);
collect(client, interaction, idNext, message);
return;
} }
// Shuffle Queue // Shuffle Queue
@ -166,6 +172,6 @@ export default {
embed.setDescription(loc.get("c_queue2")); embed.setDescription(loc.get("c_queue2"));
} }
return await interaction.reply({ embeds: [embed], components: rows }); return await interaction.reply({ embeds: [embed] });
}, },
}; };

View file

@ -1,4 +1,11 @@
import { Client, EmbedBuilder, GuildMember, Message, TextBasedChannel } from "discord.js"; import {
ChannelType,
Client,
EmbedBuilder,
GuildMember,
Message,
TextBasedChannel,
} from "discord.js";
import { getLocale } from "../../utils/locales"; import { getLocale } from "../../utils/locales";
import { isImage, userWithNickname } from "../../utils/misc"; import { isImage, userWithNickname } from "../../utils/misc";
import { showDate } from "../../utils/time"; import { showDate } from "../../utils/time";
@ -164,7 +171,8 @@ export default async (message: Message, client: Client) => {
if ( if (
!message.content.replace(new RegExp(regex, "g"), "").trim() && !message.content.replace(new RegExp(regex, "g"), "").trim() &&
messages.length === urls.length && messages.length === urls.length &&
!message.mentions.repliedUser !message.mentions.repliedUser &&
message.channel.type !== ChannelType.GroupDM
) { ) {
message.delete(); message.delete();
return message.channel.send({ embeds: [embed] }); return message.channel.send({ embeds: [embed] });

View file

@ -1,6 +1,6 @@
import { EmbedBuilder } from "@discordjs/builders"; import { EmbedBuilder } from "@discordjs/builders";
import { GuildQueue, Track } from "discord-player"; import { GuildQueue, Track } from "discord-player";
import { Client } from "discord.js"; import { ChannelType, Client } from "discord.js";
import { Metadata } from "../../utils/metadata"; import { Metadata } from "../../utils/metadata";
import { emojiPng } from "../../utils/misc"; import { emojiPng } from "../../utils/misc";
@ -19,5 +19,8 @@ export default (queue: GuildQueue<Metadata>, track: Track, client: Client) => {
} via ${track.source.capitalize()}`, } via ${track.source.capitalize()}`,
iconURL: emojiPng("🎶"), iconURL: emojiPng("🎶"),
}); });
queue.metadata?.channel?.send({ embeds: [embed] });
if (queue.metadata.channel?.type !== ChannelType.GroupDM) {
queue.metadata?.channel?.send({ embeds: [embed] });
}
}; };

View file

@ -134,10 +134,14 @@
"c_lyrics_name": "lyrics", "c_lyrics_name": "lyrics",
"c_lyrics_desc": "Displays the lyrics of a song", "c_lyrics_desc": "Displays the lyrics of a song",
"c_lyrics_sub3_name": "synced",
"c_lyrics_sub3_desc": "Synchronized lyrics search (updates in live)",
"c_lyrics_opt1_name": "song", "c_lyrics_opt1_name": "song",
"c_lyrics_opt1_desc": "Wanted song", "c_lyrics_opt1_desc": "Wanted song",
"c_lyrics1": "The bot is not playing anything at the moment and no songs are specified.", "c_lyrics1": "The bot is not playing anything at the moment and no songs are specified.",
"c_lyrics2": "Unable to find the lyrics for", "c_lyrics2": "Unable to find the lyrics for",
"c_lyrics3": "Impossible to find synchronized lyrics for",
"c_lyrics4": "It's karaoke time!",
"c_repeat_name": "repeat", "c_repeat_name": "repeat",
"c_repeat_desc": "Command for the type of music repetition", "c_repeat_desc": "Command for the type of music repetition",

View file

@ -141,10 +141,14 @@
"c_lyrics_sub1_desc": "Recherche de paroles", "c_lyrics_sub1_desc": "Recherche de paroles",
"c_lyrics_sub2_name": "romanized", "c_lyrics_sub2_name": "romanized",
"c_lyrics_sub2_desc": "Recherche de paroles romanisée (ex: hangul -> latin)", "c_lyrics_sub2_desc": "Recherche de paroles romanisée (ex: hangul -> latin)",
"c_lyrics_sub3_name": "synced",
"c_lyrics_sub3_desc": "Recherche de paroles synchronisée (se met à jour avec la chanson en direct)",
"c_lyrics_opt1_name": "chanson", "c_lyrics_opt1_name": "chanson",
"c_lyrics_opt1_desc": "Chanson recherchée", "c_lyrics_opt1_desc": "Chanson recherchée",
"c_lyrics1": "Le bot ne joue rien en ce moment et aucune chanson n'est renseignée.", "c_lyrics1": "Le bot ne joue rien en ce moment et aucune chanson n'est renseignée.",
"c_lyrics2": "Impossible de trouver les paroles pour", "c_lyrics2": "Impossible de trouver les paroles pour",
"c_lyrics3": "Impossible de trouver les paroles synchronisées pour",
"c_lyrics4": "C'est parti !",
"c_repeat_name": "repeat", "c_repeat_name": "repeat",
"c_repeat_desc": "Commande relative à la répétition des musiques", "c_repeat_desc": "Commande relative à la répétition des musiques",

View file

@ -4,6 +4,7 @@ import { readFileSync } from "fs";
import { Database } from "sqlite3"; import { Database } from "sqlite3";
import "../modules/client"; import "../modules/client";
import { loadLocales } from "./locales"; import { loadLocales } from "./locales";
import { YoutubeiExtractor } from "discord-player-youtubei";
/** Creation of the client and definition of its properties. */ /** Creation of the client and definition of its properties. */
export default async () => { export default async () => {
@ -44,7 +45,8 @@ export default async () => {
quality: "highestaudio", quality: "highestaudio",
}, },
}); });
await player.extractors.loadDefault(); await player.extractors.loadDefault((ext) => ext !== "YouTubeExtractor");
await player.extractors.register(YoutubeiExtractor, {});
console.log("Translations progression :"); console.log("Translations progression :");
client.locales = await loadLocales(client.config.default_lang); client.locales = await loadLocales(client.config.default_lang);

View file

@ -1,4 +1,4 @@
import { Client, Colors, EmbedBuilder, User } from "discord.js"; import { ChannelType, Client, Colors, EmbedBuilder, User } from "discord.js";
import { getLocale } from "./locales"; import { getLocale } from "./locales";
import { cleanCodeBlock } from "./misc"; import { cleanCodeBlock } from "./misc";
import { showDate, strToSeconds, timeDeltaToString } from "./time"; import { showDate, strToSeconds, timeDeltaToString } from "./time";
@ -169,7 +169,7 @@ export const sendReminder = (client: Client, info: infoReminder, option: OptionR
} else { } else {
// Channel // Channel
client.channels.fetch(info.channelId ?? "").then((channel) => { client.channels.fetch(info.channelId ?? "").then((channel) => {
if (channel?.isTextBased()) { if (channel?.isTextBased() && channel.type !== ChannelType.GroupDM) {
let content = `<@${info.userId}>`; let content = `<@${info.userId}>`;
embed.setFooter({ embed.setFooter({
text: `${loc.get("c_reminder17")} ${timeDeltaToString(info.createdAt)}`, text: `${loc.get("c_reminder17")} ${timeDeltaToString(info.createdAt)}`,