From 1c833364568aa71d65be05ed6009e61652cddf57 Mon Sep 17 00:00:00 2001 From: John Ivison Date: Thu, 28 Nov 2024 03:26:11 -0800 Subject: [PATCH 1/3] Switch rymimport to use Lilac --- .../NowPlaying/NowPlayingBaseCommand.ts | 1 + .../{Import.ts => ImportRatings.ts} | 85 ++++--------------- .../RateYourMusicParentCommand.ts | 2 +- .../Lastfm/RateYourMusic/connectors.ts | 24 ------ src/errors/lilac.ts | 4 +- src/lib/command/Command.ts | 2 +- src/lib/indexing/MirrorballCommands.ts | 10 +-- src/lib/ui/embeds/SuccessEmbed.ts | 11 ++- src/lib/ui/views/RatingsImportProgressView.ts | 67 +++++++++++++++ src/services/ConcurrencyService.ts | 56 ------------ src/services/ServicesRegistry.ts | 4 +- ...ingService.ts => ErrorReportingService.ts} | 0 src/services/lilac/LilacAPIService.types.ts | 12 +++ src/services/lilac/LilacRatingsService.ts | 39 +++++++++ 14 files changed, 155 insertions(+), 162 deletions(-) rename src/commands/Lastfm/RateYourMusic/{Import.ts => ImportRatings.ts} (57%) delete mode 100644 src/commands/Lastfm/RateYourMusic/connectors.ts create mode 100644 src/lib/ui/views/RatingsImportProgressView.ts delete mode 100644 src/services/ConcurrencyService.ts rename src/services/analytics/{ReportingService.ts => ErrorReportingService.ts} (100%) diff --git a/src/commands/Lastfm/NowPlaying/NowPlayingBaseCommand.ts b/src/commands/Lastfm/NowPlaying/NowPlayingBaseCommand.ts index 3d893ebb..78d620ed 100644 --- a/src/commands/Lastfm/NowPlaying/NowPlayingBaseCommand.ts +++ b/src/commands/Lastfm/NowPlaying/NowPlayingBaseCommand.ts @@ -68,6 +68,7 @@ export abstract class NowPlayingBaseCommand< dbUser, username ); + const renderedComponents = await this.nowPlayingService.renderComponents( this.ctx, await Promise.resolve(this.getConfig(senderUser!)), diff --git a/src/commands/Lastfm/RateYourMusic/Import.ts b/src/commands/Lastfm/RateYourMusic/ImportRatings.ts similarity index 57% rename from src/commands/Lastfm/RateYourMusic/Import.ts rename to src/commands/Lastfm/RateYourMusic/ImportRatings.ts index 1eec57e3..c1e52530 100644 --- a/src/commands/Lastfm/RateYourMusic/Import.ts +++ b/src/commands/Lastfm/RateYourMusic/ImportRatings.ts @@ -3,35 +3,20 @@ import streamToString from "stream-to-string"; import { NoRatingsFileAttatchedError, TooManyAttachmentsError, - UnknownRatingsImportError, WrongFileFormatAttachedError, } from "../../../errors/commands/library"; import { CannotBeUsedAsASlashCommand } from "../../../errors/errors"; -import { AlreadyImportingRatingsError } from "../../../errors/external/rateYourMusic"; import { StringArgument } from "../../../lib/context/arguments/argumentTypes/StringArgument"; import { ArgumentsMap } from "../../../lib/context/arguments/types"; -import { Emoji } from "../../../lib/emoji/Emoji"; -import { SuccessEmbed } from "../../../lib/ui/embeds/SuccessEmbed"; import { WarningEmbed } from "../../../lib/ui/embeds/WarningEmbed"; -import { ConcurrentAction } from "../../../services/ConcurrencyService"; -import { RateYourMusicIndexingChildCommand } from "./RateYourMusicChildCommand"; -import { - ImportRatingsConnector, - ImportRatingsParams, - ImportRatingsResponse, -} from "./connectors"; +import { RatingsImportProgressView } from "../../../lib/ui/views/RatingsImportProgressView"; +import { RateYourMusicChildCommand } from "./RateYourMusicChildCommand"; const args = { input: new StringArgument({ index: { start: 0 }, slashCommandOption: false }), } satisfies ArgumentsMap; -export class ImportRatings extends RateYourMusicIndexingChildCommand< - ImportRatingsResponse, - ImportRatingsParams, - typeof args -> { - connector = new ImportRatingsConnector(); - +export class ImportRatings extends RateYourMusicChildCommand { idSeed = "sonamoo high d"; aliases = ["rymimport", "rymsimport"]; description = @@ -41,22 +26,11 @@ export class ImportRatings extends RateYourMusicIndexingChildCommand< slashCommand = true; - async beforeRun() { - if ( - await this.concurrencyService.isUserDoingAction( - this.author.id, - ConcurrentAction.RYMImport - ) - ) { - throw new AlreadyImportingRatingsError(); - } - } - async run() { if (this.payload.isInteraction()) { await this.reply( new WarningEmbed().setDescription( - "As of right now, you cannot import with slash commands.\n\nPlease go to https://gowon.bot/import-ratings to import, or use message commands" + `As of right now, you cannot import with slash commands.\n\nPlease use the message command \`${this.prefix}rymimport\`` ) ); @@ -70,40 +44,25 @@ export class ImportRatings extends RateYourMusicIndexingChildCommand< const ratings = await this.getRatings(); - if (this.payload.isMessage()) { - this.payload.source.react(Emoji.loading); - } - - this.concurrencyService.registerUser( - this.ctx, - ConcurrentAction.RYMImport, - this.author.id - ); - - let response: ImportRatingsResponse; - - try { - response = await this.query({ - csv: ratings, - user: { discordID: this.author.id }, - }); - this.unregisterConcurrency(); - } catch (e) { - this.unregisterConcurrency(); - throw e; - } + const ratingsProgress = this.lilacRatingsService.importProgress(this.ctx, { + discordID: this.author.id, + }); - const errors = this.parseErrors(response); + const embed = this.minimalEmbed().setDescription(`Preparing import...`); - if (errors) { - throw new UnknownRatingsImportError(); - } + await this.reply(embed); - const embed = new SuccessEmbed().setDescription( - `RateYourMusic ratings imported succesfully! ${Emoji.gowonRated}` + const syncProgressView = new RatingsImportProgressView( + this.ctx, + embed, + ratingsProgress ); - await this.reply(embed); + syncProgressView.subscribeToObservable(); + + await this.lilacRatingsService.import(this.ctx, ratings, { + discordID: this.author.id, + }); } private async getRatings(): Promise { @@ -164,12 +123,4 @@ export class ImportRatings extends RateYourMusicIndexingChildCommand< return ""; } - - private unregisterConcurrency() { - this.concurrencyService.unregisterUser( - this.ctx, - ConcurrentAction.RYMImport, - this.author.id - ); - } } diff --git a/src/commands/Lastfm/RateYourMusic/RateYourMusicParentCommand.ts b/src/commands/Lastfm/RateYourMusic/RateYourMusicParentCommand.ts index ed2f052e..648b7734 100644 --- a/src/commands/Lastfm/RateYourMusic/RateYourMusicParentCommand.ts +++ b/src/commands/Lastfm/RateYourMusic/RateYourMusicParentCommand.ts @@ -2,7 +2,7 @@ import { CommandGroup } from "../../../lib/command/CommandGroup"; import { ParentCommand } from "../../../lib/command/ParentCommand"; import { ArtistRatings } from "./ArtistRatings"; import { Help } from "./Help"; -import { ImportRatings } from "./Import"; +import { ImportRatings } from "./ImportRatings"; import { Link } from "./Link"; import { Rating } from "./Rating"; import { Ratings } from "./Ratings"; diff --git a/src/commands/Lastfm/RateYourMusic/connectors.ts b/src/commands/Lastfm/RateYourMusic/connectors.ts deleted file mode 100644 index ad0a856e..00000000 --- a/src/commands/Lastfm/RateYourMusic/connectors.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { gql } from "apollo-server-express"; -import { BaseConnector } from "../../../lib/indexing/BaseConnector"; -import { UserInput } from "../../../services/mirrorball/MirrorballTypes"; - -// ImportRatings -export interface ImportRatingsResponse { - importRatings: {}; -} - -export interface ImportRatingsParams { - user: UserInput; - csv: string; -} - -export class ImportRatingsConnector extends BaseConnector< - ImportRatingsResponse, - ImportRatingsParams -> { - query = gql` - mutation importRatings($user: UserInput!, $csv: String!) { - importRatings(user: $user, csv: $csv) - } - `; -} diff --git a/src/errors/lilac.ts b/src/errors/lilac.ts index 1fd25898..7051f4dc 100644 --- a/src/errors/lilac.ts +++ b/src/errors/lilac.ts @@ -1,5 +1,5 @@ import { ApolloError, ServerError } from "@apollo/client"; -import { ErrorWithSupernovaID } from "../services/analytics/ReportingService"; +import { ErrorWithSupernovaID } from "../services/analytics/ErrorReportingService"; import { ClientError } from "./errors"; export function parseLilacError(error: Error): Error { @@ -9,7 +9,7 @@ export function parseLilacError(error: Error): Error { error instanceof ApolloError && isServerError(error.networkError) ) { - if (error.networkError.result.supernova_id) { + if (error?.networkError?.result?.supernova_id) { return new LilacError( error.message, error.networkError.result.supernova_id diff --git a/src/lib/command/Command.ts b/src/lib/command/Command.ts index a706cf1c..d956aeb1 100644 --- a/src/lib/command/Command.ts +++ b/src/lib/command/Command.ts @@ -15,7 +15,7 @@ import { GowonService } from "../../services/GowonService"; import { NowPlayingEmbedParsingService } from "../../services/NowPlayingEmbedParsingService"; import { ServiceRegistry } from "../../services/ServicesRegistry"; import { TrackingService } from "../../services/TrackingService"; -import { ErrorReportingService } from "../../services/analytics/ReportingService"; +import { ErrorReportingService } from "../../services/analytics/ErrorReportingService"; import { ArgumentParsingService } from "../../services/arguments/ArgumentsParsingService"; import { MentionsService } from "../../services/arguments/mentions/MentionsService"; import { diff --git a/src/lib/indexing/MirrorballCommands.ts b/src/lib/indexing/MirrorballCommands.ts index a3338762..3b2675bc 100644 --- a/src/lib/indexing/MirrorballCommands.ts +++ b/src/lib/indexing/MirrorballCommands.ts @@ -1,10 +1,9 @@ -import { Command } from "../command/Command"; -import { Connector } from "./BaseConnector"; -import { ArgumentsMap } from "../context/arguments/types"; -import { LastFMService } from "../../services/LastFM/LastFMService"; -import { ConcurrencyService } from "../../services/ConcurrencyService"; import { LastFMArguments } from "../../services/LastFM/LastFMArguments"; +import { LastFMService } from "../../services/LastFM/LastFMService"; import { ServiceRegistry } from "../../services/ServicesRegistry"; +import { Command } from "../command/Command"; +import { ArgumentsMap } from "../context/arguments/types"; +import { Connector } from "./BaseConnector"; export interface ErrorResponse { errors: { message: string }[]; @@ -26,7 +25,6 @@ export abstract class MirrorballBaseCommand< abstract connector: Connector; lastFMService = ServiceRegistry.get(LastFMService); lastFMArguments = ServiceRegistry.get(LastFMArguments); - concurrencyService = ServiceRegistry.get(ConcurrencyService); protected readonly progressBarWidth = 15; diff --git a/src/lib/ui/embeds/SuccessEmbed.ts b/src/lib/ui/embeds/SuccessEmbed.ts index e01339a8..21168c8d 100644 --- a/src/lib/ui/embeds/SuccessEmbed.ts +++ b/src/lib/ui/embeds/SuccessEmbed.ts @@ -4,10 +4,17 @@ import { EmbedView } from "../views/EmbedView"; export const successColour = "#02BCA1"; export class SuccessEmbed extends EmbedView { - asDiscordSendable(): EmbedView { + private successEmoji: string = Emoji.checkmark; + + public asDiscordSendable(): EmbedView { return super .asDiscordSendable() .setColour(successColour) - .setDescription(`${Emoji.checkmark} ${this.getDescription()}`); + .setDescription(`${this.successEmoji} ${this.getDescription()}`); + } + + public setSuccessEmoji(emoji: string): this { + this.successEmoji = emoji; + return this; } } diff --git a/src/lib/ui/views/RatingsImportProgressView.ts b/src/lib/ui/views/RatingsImportProgressView.ts new file mode 100644 index 00000000..12671205 --- /dev/null +++ b/src/lib/ui/views/RatingsImportProgressView.ts @@ -0,0 +1,67 @@ +import { Observable, ObservableSubscription } from "@apollo/client"; +import { italic } from "../../../helpers/discord"; +import { + LilacRatingsImportStage, + RatingsImportProgress, +} from "../../../services/lilac/LilacAPIService.types"; +import { GowonContext } from "../../context/Context"; +import { Emoji } from "../../emoji/Emoji"; +import { displayNumber } from "../displays"; +import { ErrorEmbed } from "../embeds/ErrorEmbed"; +import { SuccessEmbed } from "../embeds/SuccessEmbed"; +import { EmbedView } from "./EmbedView"; +import { UnsendableView } from "./View"; + +export class RatingsImportProgressView extends UnsendableView { + private subscription: ObservableSubscription | undefined; + + constructor( + private ctx: GowonContext, + private embed: EmbedView, + private observable: Observable + ) { + super(); + } + + public subscribeToObservable(): this { + this.subscription = this.observable.subscribe(async (progress) => { + await this.handleProgress(progress); + }); + + return this; + } + + private async handleProgress(progress: RatingsImportProgress) { + if (progress.stage === LilacRatingsImportStage.Started) { + this.embed + .setDescription( + `${Emoji.loading} Starting import of ${displayNumber( + progress.count, + "rating" + )}...` + ) + .editMessage(this.ctx); + } else if (progress.stage === LilacRatingsImportStage.Finished) { + this.embed + .convert(SuccessEmbed) + .setSuccessEmoji(Emoji.gowonRated) + .setDescription( + `Successfully imported ${displayNumber(progress.count, "rating")}!` + ) + .editMessage(this.ctx); + + this.subscription?.unsubscribe(); + } else if (progress.stage === LilacRatingsImportStage.Errored) { + this.embed + .convert(ErrorEmbed) + .setDescription( + `Something went wrong importing your ratings:\n\n${italic( + progress.error + )}` + ) + .editMessage(this.ctx); + + this.subscription?.unsubscribe(); + } + } +} diff --git a/src/services/ConcurrencyService.ts b/src/services/ConcurrencyService.ts deleted file mode 100644 index d579d25e..00000000 --- a/src/services/ConcurrencyService.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { GowonContext } from "../lib/context/Context"; -import { BaseService } from "./BaseService"; - -export enum ConcurrentAction { - Indexing = "Indexing", - Updating = "Updating", - RYMImport = "RYMImport", - Minting = "Minting", -} - -interface ConcurrencyCache { - [action: string]: Set; -} - -export class ConcurrencyService extends BaseService { - readonly defaultTimeout = 10 * 60 * 60; - - cache: ConcurrencyCache = {}; - - constructor() { - super(); - for (const action of Object.values(ConcurrentAction)) { - this.cache[action] = new Set(); - } - } - - registerUser(ctx: GowonContext, action: ConcurrentAction, discordID: string) { - this.log(ctx, `Registering user ${discordID} as doing ${action}`); - this.cache[action].add(discordID); - this.makeEphemeral(action, discordID); - } - - unregisterUser( - ctx: GowonContext, - action: ConcurrentAction, - discordID: string - ) { - this.log(ctx, `Unregistering user ${discordID} as doing ${action}`); - this.cache[action].delete(discordID); - } - - async isUserDoingAction( - discordID: string, - ...actions: ConcurrentAction[] - ): Promise { - return actions.some((action) => { - return this.cache[action].has(discordID); - }); - } - - private makeEphemeral(action: ConcurrentAction, discordID: string) { - setTimeout(() => { - this.cache[action].delete(discordID); - }, this.defaultTimeout); - } -} diff --git a/src/services/ServicesRegistry.ts b/src/services/ServicesRegistry.ts index d76053a1..32bebf3b 100644 --- a/src/services/ServicesRegistry.ts +++ b/src/services/ServicesRegistry.ts @@ -4,7 +4,6 @@ import { PermissionsCacheService } from "../lib/permissions/PermissionsCacheServ import { PermissionsService } from "../lib/permissions/PermissionsService"; import { SettingsService } from "../lib/settings/SettingsService"; import { BaseService } from "./BaseService"; -import { ConcurrencyService } from "./ConcurrencyService"; import { DiscordService } from "./Discord/DiscordService"; import { EmojiService } from "./Discord/EmojiService"; import { GuildEventService } from "./Discord/GuildEventService"; @@ -23,7 +22,7 @@ import { SpotifyService } from "./Spotify/SpotifyService"; import { TimeAndDateService } from "./TimeAndDateService"; import { TrackingService } from "./TrackingService"; import { WordBlacklistService } from "./WordBlacklistService"; -import { ErrorReportingService } from "./analytics/ReportingService"; +import { ErrorReportingService } from "./analytics/ErrorReportingService"; import { ArgumentParsingService } from "./arguments/ArgumentsParsingService"; import { MentionsService } from "./arguments/mentions/MentionsService"; import { BotStatsService } from "./dbservices/BotStatsService"; @@ -70,7 +69,6 @@ const services: Service[] = [ CrownsHistoryService, CrownsUserService, ComboService, - ConcurrencyService, DatasourceService, DiscordService, EmojiService, diff --git a/src/services/analytics/ReportingService.ts b/src/services/analytics/ErrorReportingService.ts similarity index 100% rename from src/services/analytics/ReportingService.ts rename to src/services/analytics/ErrorReportingService.ts diff --git a/src/services/lilac/LilacAPIService.types.ts b/src/services/lilac/LilacAPIService.types.ts index e9c99b9c..a1c7bb76 100644 --- a/src/services/lilac/LilacAPIService.types.ts +++ b/src/services/lilac/LilacAPIService.types.ts @@ -140,6 +140,18 @@ export interface SyncProgress< total: number; } +export enum LilacRatingsImportStage { + Started = "started", + Finished = "finished", + Errored = "errored", +} + +export interface RatingsImportProgress { + stage: LilacRatingsImportStage; + count?: number; + error?: string; +} + export interface LilacPagination { currentPage: number; perPage: number; diff --git a/src/services/lilac/LilacRatingsService.ts b/src/services/lilac/LilacRatingsService.ts index 7ed42162..8dd22cd8 100644 --- a/src/services/lilac/LilacRatingsService.ts +++ b/src/services/lilac/LilacRatingsService.ts @@ -1,3 +1,4 @@ +import { Observable } from "@apollo/client"; import { gql } from "apollo-server-express"; import { GowonContext } from "../../lib/context/Context"; import { LilacAPIService } from "./LilacAPIService"; @@ -6,7 +7,9 @@ import { LilacRatingsFilters, LilacRatingsPage, LilacUserInput, + RatingsImportProgress, } from "./LilacAPIService.types"; +import { userToUserInput } from "./helpers"; export class LilacRatingsService extends LilacAPIService { async ratings( @@ -114,4 +117,40 @@ export class LilacRatingsService extends LilacAPIService { return response; } + + public async import( + ctx: GowonContext, + csv: string, + user: LilacUserInput + ): Promise { + const mutation = gql` + mutation importRatings($ratingsCsv: String!, $user: UserInput!) { + importRatings(ratingsCsv: $ratingsCsv, user: $user) + } + `; + + await this.mutate(ctx, mutation, { ratingsCsv: csv, user }); + } + + public importProgress( + ctx: GowonContext, + user: LilacUserInput + ): Observable { + const subscription = gql` + subscription RatingsImportProgress($user: UserInput!) { + ratingsImport(user: $user) { + stage + count + error + } + } + `; + + return this.subscribe< + { ratingsImport: RatingsImportProgress }, + { user: LilacUserInput } + >(ctx, subscription, { user: userToUserInput(user) }).map( + (data) => data.ratingsImport + ); + } } From 37c83a6017b9da656430abea184affd14c1ecfaf Mon Sep 17 00:00:00 2001 From: John Ivison Date: Thu, 28 Nov 2024 03:44:13 -0800 Subject: [PATCH 2/3] Support lilac ratings import subscription --- .../Lastfm/RateYourMusic/ImportRatings.ts | 81 +++---------------- src/errors/commands/library.ts | 22 ----- .../argumentTypes/SlashCommandTypes.ts | 20 ++--- .../discord/AttachmentArgument.ts | 41 ++++++++++ src/lib/context/arguments/types.ts | 4 +- 5 files changed, 68 insertions(+), 100 deletions(-) create mode 100644 src/lib/context/arguments/argumentTypes/discord/AttachmentArgument.ts diff --git a/src/commands/Lastfm/RateYourMusic/ImportRatings.ts b/src/commands/Lastfm/RateYourMusic/ImportRatings.ts index c1e52530..6f21298e 100644 --- a/src/commands/Lastfm/RateYourMusic/ImportRatings.ts +++ b/src/commands/Lastfm/RateYourMusic/ImportRatings.ts @@ -1,42 +1,30 @@ import fetch from "node-fetch"; import streamToString from "stream-to-string"; -import { - NoRatingsFileAttatchedError, - TooManyAttachmentsError, - WrongFileFormatAttachedError, -} from "../../../errors/commands/library"; -import { CannotBeUsedAsASlashCommand } from "../../../errors/errors"; -import { StringArgument } from "../../../lib/context/arguments/argumentTypes/StringArgument"; +import { WrongFileFormatAttachedError } from "../../../errors/commands/library"; +import { AttachmentArgument } from "../../../lib/context/arguments/argumentTypes/discord/AttachmentArgument"; import { ArgumentsMap } from "../../../lib/context/arguments/types"; -import { WarningEmbed } from "../../../lib/ui/embeds/WarningEmbed"; import { RatingsImportProgressView } from "../../../lib/ui/views/RatingsImportProgressView"; import { RateYourMusicChildCommand } from "./RateYourMusicChildCommand"; const args = { - input: new StringArgument({ index: { start: 0 }, slashCommandOption: false }), + ratings: new AttachmentArgument({ + index: 0, + description: "The file containing your RateYourMusic ratings", + required: true, + }), } satisfies ArgumentsMap; export class ImportRatings extends RateYourMusicChildCommand { idSeed = "sonamoo high d"; aliases = ["rymimport", "rymsimport"]; description = - "Import your rateyourmusic ratings. See ryms help for more info on how to import"; + "Import your RateYourMusic ratings. See rym help for more info on how to import"; arguments = args; slashCommand = true; async run() { - if (this.payload.isInteraction()) { - await this.reply( - new WarningEmbed().setDescription( - `As of right now, you cannot import with slash commands.\n\nPlease use the message command \`${this.prefix}rymimport\`` - ) - ); - - return; - } - await this.getMentions({ senderRequired: true, syncedRequired: true, @@ -66,15 +54,13 @@ export class ImportRatings extends RateYourMusicChildCommand { } private async getRatings(): Promise { - let ratings: string; + const ratingsAttachment = this.parsedArguments.ratings; - if (!this.parsedArguments.input) { - ratings = await this.getRatingsFromAttached(); - } else { - ratings = this.getRatingsFromContent(); - } + const file = await fetch(ratingsAttachment.url); - ratings = ratings.trim(); + const fileContent = await streamToString(file.body); + + const ratings = fileContent.trim(); if (!ratings.startsWith("RYM Album,")) { throw new WrongFileFormatAttachedError(); @@ -82,45 +68,4 @@ export class ImportRatings extends RateYourMusicChildCommand { return ratings; } - - private async getRatingsFromAttached(): Promise { - if (this.payload.isInteraction()) { - throw new CannotBeUsedAsASlashCommand(); - } else if (this.payload.isMessage()) { - const attachments = this.payload.source.attachments; - - if (attachments.size > 1) { - throw new TooManyAttachmentsError(); - } - - const attachment = attachments.first(); - - if (!attachment) { - throw new NoRatingsFileAttatchedError(this.prefix); - } - - const file = await fetch(attachment.url); - - const fileContent = await streamToString(file.body); - - return fileContent; - } - - return ""; - } - - private getRatingsFromContent(): string { - if (this.payload.isInteraction()) { - throw new CannotBeUsedAsASlashCommand(); - } else if (this.payload.isMessage()) { - const rawMessageContent = this.gowonService.removeCommandName( - this.ctx, - this.payload.source.content - ); - - return rawMessageContent; - } - - return ""; - } } diff --git a/src/errors/commands/library.ts b/src/errors/commands/library.ts index 547bbf17..b340d454 100644 --- a/src/errors/commands/library.ts +++ b/src/errors/commands/library.ts @@ -70,34 +70,12 @@ export class TooManySearchResultsError extends ClientError { } } -export class UnknownRatingsImportError extends ClientError { - constructor() { - super("Something went wrong when importing your ratings"); - } -} - export class WrongFileFormatAttachedError extends ClientError { constructor() { super("Please attach a file with the correct format"); } } -export class TooManyAttachmentsError extends ClientError { - constructor() { - super( - "Too many attachments! Please attach only one file with your ratings" - ); - } -} - -export class NoRatingsFileAttatchedError extends ClientError { - constructor(prefix: string) { - super( - `Please attach your ratings! (See \`${prefix}rym help\` for more info)` - ); - } -} - export class CouldNotFindRatingError extends ClientError { constructor() { super("Couldn't find that album in your ratings!"); diff --git a/src/lib/context/arguments/argumentTypes/SlashCommandTypes.ts b/src/lib/context/arguments/argumentTypes/SlashCommandTypes.ts index 1f4a4816..9d59f8d0 100644 --- a/src/lib/context/arguments/argumentTypes/SlashCommandTypes.ts +++ b/src/lib/context/arguments/argumentTypes/SlashCommandTypes.ts @@ -1,27 +1,28 @@ import { SlashCommandBuilder as _SlashCommandBuilder, - SlashCommandRoleOption, - SlashCommandUserOption, - SlashCommandNumberOption, - SlashCommandStringOption, + SlashCommandAttachmentOption, SlashCommandBooleanOption, SlashCommandChannelOption, SlashCommandIntegerOption, SlashCommandMentionableOption, + SlashCommandNumberOption, + SlashCommandRoleOption, + SlashCommandStringOption, + SlashCommandUserOption, } from "@discordjs/builders"; // Discord.js doesn't export any interfaces to help deal with building slash commands // so I created them myself export { - SlashCommandRoleOption, - SlashCommandUserOption, - SlashCommandNumberOption, - SlashCommandStringOption, SlashCommandBooleanOption, SlashCommandChannelOption, SlashCommandIntegerOption, SlashCommandMentionableOption, + SlashCommandNumberOption, + SlashCommandRoleOption, + SlashCommandStringOption, + SlashCommandUserOption, }; // Any type of slash command option @@ -33,7 +34,8 @@ export type SlashCommandOption = | SlashCommandBooleanOption | SlashCommandChannelOption | SlashCommandIntegerOption - | SlashCommandMentionableOption; + | SlashCommandMentionableOption + | SlashCommandAttachmentOption; export type SlashCommandBuilder = _SlashCommandBuilder; export type SlashCommandBuilderReturn = diff --git a/src/lib/context/arguments/argumentTypes/discord/AttachmentArgument.ts b/src/lib/context/arguments/argumentTypes/discord/AttachmentArgument.ts new file mode 100644 index 00000000..dbb9daa1 --- /dev/null +++ b/src/lib/context/arguments/argumentTypes/discord/AttachmentArgument.ts @@ -0,0 +1,41 @@ +import { CommandInteraction, Message, MessageAttachment } from "discord.js"; +import { GowonContext } from "../../../Context"; +import { + BaseArgument, + BaseArgumentOptions, + IndexableArgumentOptions, + defaultIndexableOptions, +} from "../BaseArgument"; +import { SlashCommandBuilder } from "../SlashCommandTypes"; + +export interface AttachmentArgumentOptions + extends BaseArgumentOptions, + IndexableArgumentOptions {} + +export class AttachmentArgument< + OptionsT extends Partial +> extends BaseArgument { + constructor(options?: OptionsT) { + super({ ...defaultIndexableOptions, ...(options ?? {}) } as OptionsT); + } + + parseFromMessage(message: Message, _: string): MessageAttachment | undefined { + const attachments = Array.from(message.attachments.values()); + + return this.getElementFromIndex(attachments, this.options.index); + } + + parseFromInteraction( + interaction: CommandInteraction, + _: GowonContext, + argumentName: string + ): MessageAttachment | undefined { + return interaction.options.getAttachment(argumentName) ?? undefined; + } + + addAsOption(slashCommand: SlashCommandBuilder, argumentName: string) { + return slashCommand.addAttachmentOption((option) => + this.baseOption(option, argumentName) + ); + } +} diff --git a/src/lib/context/arguments/types.ts b/src/lib/context/arguments/types.ts index bb410915..7f80d562 100644 --- a/src/lib/context/arguments/types.ts +++ b/src/lib/context/arguments/types.ts @@ -4,6 +4,7 @@ import { Flag } from "./argumentTypes/Flag"; import { NumberArgument } from "./argumentTypes/NumberArgument"; import { StringArgument } from "./argumentTypes/StringArgument"; import { StringArrayArgument } from "./argumentTypes/StringArrayArgument"; +import { AttachmentArgument } from "./argumentTypes/discord/AttachmentArgument"; import { ChannelArgument } from "./argumentTypes/discord/ChannelArgument"; import { DiscordRoleArgument } from "./argumentTypes/discord/DiscordRoleArgument"; import { EmojisArgument } from "./argumentTypes/discord/EmojisArgument"; @@ -27,7 +28,8 @@ export type ImplementedOptions> = | EmojisArgument | DateArgument | ChannelArgument - | DiscordRoleArgument; + | DiscordRoleArgument + | AttachmentArgument; export type UnwrapProvidedOptions> = T extends ImplementedOptions ? U : {}; From 997c2ccb6f0ece7e731579ae945fc31615377b35 Mon Sep 17 00:00:00 2001 From: John Ivison Date: Thu, 28 Nov 2024 04:00:31 -0800 Subject: [PATCH 3/3] Fix rymimport ratings required error message --- src/commands/Lastfm/RateYourMusic/ImportRatings.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/commands/Lastfm/RateYourMusic/ImportRatings.ts b/src/commands/Lastfm/RateYourMusic/ImportRatings.ts index 6f21298e..957fd7f0 100644 --- a/src/commands/Lastfm/RateYourMusic/ImportRatings.ts +++ b/src/commands/Lastfm/RateYourMusic/ImportRatings.ts @@ -10,7 +10,9 @@ const args = { ratings: new AttachmentArgument({ index: 0, description: "The file containing your RateYourMusic ratings", - required: true, + required: { + customMessage: `Please attach your ratings! (See \`rym help\` for more info)`, + }, }), } satisfies ArgumentsMap;