Skip to content

Commit 70100e7

Browse files
committed
Rewrite ACMURL Command
1 parent 7132bca commit 70100e7

File tree

1 file changed

+210
-0
lines changed

1 file changed

+210
-0
lines changed

src/commands/ACMURL.ts

Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
import { SlashCommandBuilder } from '@discordjs/builders';
2+
import { CommandInteraction, MessageEmbed } from 'discord.js';
3+
import got from 'got';
4+
import { v4 as newUUID } from 'uuid';
5+
import Command from '../Command';
6+
import { BotClient, UUIDv4 } from '../types';
7+
import Logger from '../utils/Logger';
8+
import { validURL } from '../utils/validType';
9+
10+
/**
11+
* Command to shorten long URL's to an ACMURL.
12+
*
13+
* ACMURL's are of format `https://acmurl.com/some-text`.
14+
* This Command required API calls to YOURLS, among other various complicated checks.
15+
*/
16+
export default class ACMURL extends Command {
17+
constructor(client: BotClient) {
18+
const definition = new SlashCommandBuilder()
19+
.setName('acmurl')
20+
.addStringOption((option) => option.setName('shortlink').setDescription('The short ID of the url (eg. "discord")').setRequired(true))
21+
.addStringOption((option) => option.setName('longlink').setDescription('The URL to shorten.').setRequired(true))
22+
.addStringOption((option) => option.setName('description').setDescription('The description of the link in ACMURL\'s dashboard.'))
23+
.setDescription('Shortens the provided link into an ACMURL link.');
24+
25+
super(client, {
26+
name: 'acmurl',
27+
boardRequired: true,
28+
enabled: true,
29+
description: 'Shortens the provided link into an `ACMURL` link.',
30+
category: 'Utility',
31+
usage: client.settings.prefix.concat('acmurl <shortlink> <longlink> [description]'),
32+
requiredPermissions: ['SEND_MESSAGES'],
33+
}, definition);
34+
}
35+
36+
public async run(interaction: CommandInteraction): Promise<void> {
37+
// Let's defer the reply first
38+
await super.defer(interaction);
39+
// Alright, this is the biggest command by far.
40+
// This might be able to be split better, but who knows?
41+
//
42+
// Get command arguments. Make description tokens all together in an array.
43+
const shortlink = interaction.options.getString('shortlink', true);
44+
const longlink = interaction.options.getString('longlink', true);
45+
const description = interaction.options.getString('description');
46+
47+
// Set title of URL, or undefined if initial array did not exist.
48+
const linkTitle = description || `Discord Bot - ${shortlink}`; // optional argument or slashtag
49+
50+
// If we didn't get our required arguments...
51+
if (!shortlink || !longlink) {
52+
await super.edit(interaction, 'You must provide both the long link and the short link!');
53+
return;
54+
}
55+
56+
// If provided long link is not a valid URL...
57+
if (!validURL(longlink)) {
58+
await super.edit(interaction, 'The long link must be a valid HTTP/HTTPS URL!');
59+
return;
60+
}
61+
62+
try {
63+
// Add our ACMURL
64+
const shortURL = await this.addACMURL(shortlink, longlink, linkTitle);
65+
// Make an embed representing success.
66+
const shortenEmbed = new MessageEmbed()
67+
.setTitle('Set shortened link!')
68+
.setDescription(`Short link: ${shortURL}`)
69+
.setURL(shortURL)
70+
.setColor('BLUE');
71+
await super.edit(interaction, {
72+
embeds: [shortenEmbed],
73+
});
74+
return;
75+
} catch (e) {
76+
// We might error out if an ACMURL already exists with the provided shortlink.
77+
// We'll attempt to handle that by updating the ACMURL.
78+
const errorUUID: UUIDv4 = newUUID();
79+
80+
const error = e as any;
81+
82+
// If the error we get is specifically that a ACMURL already existed.
83+
if (error.message === 'error:keyword') {
84+
try {
85+
// Make a new one and return the old long link and new ACMURL
86+
const [previousURL, newURL] = await this.handleExistingACMURL(
87+
shortlink, longlink, linkTitle,
88+
);
89+
// Create an embed signalling an updated ACMURL.
90+
const shortenEmbed = new MessageEmbed()
91+
.setTitle('Updated shortened link!')
92+
.setDescription(
93+
`Short link: ${newURL}\nPreviously shortened link: ${previousURL}`,
94+
)
95+
.setURL(newURL)
96+
.setColor('BLUE');
97+
await super.edit(interaction, {
98+
embeds: [shortenEmbed],
99+
});
100+
} catch (e2) {
101+
const updateError = e2 as any;
102+
// If by any chance there's an error when updating the ACMURL, log it and return.
103+
Logger.error(`Error whilst updating short URL on YOURLS API: ${updateError.message}`, {
104+
eventType: 'interfaceError',
105+
interface: 'YOURLS',
106+
error: updateError,
107+
uuid: errorUUID,
108+
});
109+
await super.edit(interaction, `An error occurred when attempting to update the short URL. *(Error UUID: ${errorUUID})*`);
110+
}
111+
} else {
112+
// If the error we had initially when adding the ACMURL is any other error,
113+
// log it and return.
114+
Logger.error(`Error whilst creating short URL on YOURLS API: ${error.message}`, {
115+
eventType: 'interfaceError',
116+
interface: 'YOURLS',
117+
error,
118+
uuid: errorUUID,
119+
});
120+
await super.edit(interaction, `An error occurred when shortening the URL. *(Error UUID: ${errorUUID})*`);
121+
}
122+
}
123+
}
124+
125+
/**
126+
* Handle an existing ACMURL by updating it properly.
127+
* @param shortlink The short link to make an ACMURL for.
128+
* @param longlink The link to point it to.
129+
* @param title Title of ACMURL in YOURLS interface.
130+
* @private
131+
* @returns Tuple of old URL on YOURLS and new ACMURL.
132+
*/
133+
private async handleExistingACMURL(
134+
shortlink: string, longlink: string, title: string,
135+
): Promise<[string, string]> {
136+
// get the old URL
137+
const previousURL = await this.expandACMURL(shortlink);
138+
// Add the new one.
139+
await this.updateACMURL(shortlink, longlink, title);
140+
return [previousURL, `https://acmurl.com/${shortlink}`];
141+
}
142+
143+
/**
144+
* Add an ACMURL. Makes one HTTP call to YOURLS' API.
145+
*
146+
* @param shortlink The short link to make an ACMURL for.
147+
* @param longlink The link to point it to.
148+
* @param title Title of ACMURL in YOURLS interface.
149+
* @private
150+
* @returns The new shortened ACMURL.
151+
*/
152+
private async addACMURL(shortlink: string, longlink: string, title: string): Promise<string> {
153+
const acmurlAPIResponse = await got.post('https://acmurl.com/yourls-api.php', {
154+
form: {
155+
username: this.client.settings.acmurl.username,
156+
password: this.client.settings.acmurl.password,
157+
action: 'shorturl',
158+
keyword: shortlink,
159+
url: longlink,
160+
format: 'json',
161+
title,
162+
},
163+
}).json() as any;
164+
165+
if (acmurlAPIResponse.status === 'fail') {
166+
throw new Error(acmurlAPIResponse.code);
167+
}
168+
return acmurlAPIResponse.shorturl;
169+
}
170+
171+
/**
172+
* Get the link that is redirected from a given ACMURL. Makes one HTTP call to YOURLS' API.
173+
* @param shortlink The short link to check the ACMURL for.
174+
* @private
175+
* @returns the link that `acmurl.com/shortlink` points to.
176+
*/
177+
private async expandACMURL(shortlink: string): Promise<string> {
178+
const acmurlAPIResponse = await got.post('https://acmurl.com/yourls-api.php', {
179+
form: {
180+
username: this.client.settings.acmurl.username,
181+
password: this.client.settings.acmurl.password,
182+
action: 'expand',
183+
shorturl: shortlink,
184+
format: 'json',
185+
},
186+
}).json() as any;
187+
return acmurlAPIResponse !== undefined ? acmurlAPIResponse.longurl : undefined;
188+
}
189+
190+
/**
191+
* Overwrite the current ACMURL with a new one. Makes one HTTP call to YOURLS' API.
192+
* @param shortlink The short link to make an ACMURL for.
193+
* @param longlink The link to point it to.
194+
* @param title Title of ACMURL in YOURLS interface.
195+
* @private
196+
*/
197+
private async updateACMURL(shortlink: string, longlink: string, title: string): Promise<void> {
198+
await got.post('https://acmurl.com/yourls-api.php', {
199+
form: {
200+
username: this.client.settings.acmurl.username,
201+
password: this.client.settings.acmurl.password,
202+
action: 'update',
203+
shorturl: shortlink,
204+
url: longlink,
205+
format: 'json',
206+
title,
207+
},
208+
});
209+
}
210+
}

0 commit comments

Comments
 (0)