Skip to content

Commit 57c7cbb

Browse files
committed
Finished enqueue feature
1 parent dce8106 commit 57c7cbb

File tree

8 files changed

+129
-65
lines changed

8 files changed

+129
-65
lines changed

bot/src/controller/commandHandler.ts

Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,34 @@ import { SpotifyAPI } from "../model/spotifyApi";
77
import { getTrackURIFromLink, isSpotifyURL} from "../model/spotifyLinkUtil"
88

99
import * as Logging from '../logging';
10+
import { url } from "inspector";
1011

1112
export class CommandHandler {
1213
readonly commandPrefix = "$";
14+
readonly ignorePrefix = "//"
1315
repository : Repository;
1416
spotifyApi : SpotifyAPI;
15-
logger = Logging.buildLogger("httpsController");
17+
logger = Logging.buildLogger("commandHandler");
1618

1719
constructor(repository : Repository, spotifyApi : SpotifyAPI) {
1820
this.repository = repository;
1921
this.spotifyApi = spotifyApi;
2022
}
2123

2224
handleCommand = async (message : Message) => {
25+
if (message.content.startsWith(this.ignorePrefix)) {
26+
return;
27+
}
28+
29+
this.logger.verbose("Read message in channel " + message.channel.id);
2330
try {
31+
if(await this.repository.isChannelPartyChannel(message.channel.id)){
32+
this.logger.verbose("Handling party channel message: " + message.content);
33+
this.handlePartyChannelCommand(message);
34+
}
2435
if (this.isMessageBotCommand(message)) {
2536
this.handleBotCommand(message);
26-
if(await this.repository.isChannelPartyChannel(message.channel.id)){
27-
this.handlePartyChannelCommand(message);
28-
}
37+
2938
}
3039
} catch(err) {
3140
this.logger.error(err);
@@ -37,15 +46,25 @@ export class CommandHandler {
3746
}
3847

3948
async handlePartyChannelCommand(message: Message) {
40-
let owner = await this.repository.getPartyChannelOwner(message.channel.id);
41-
let commandParts = this.seperateCommandParts(message.content);
42-
let urls = commandParts.parameters.filter((value) => isSpotifyURL(value));
43-
if (urls.length == 0)
44-
return;
49+
try {
50+
let owner = await this.repository.getPartyChannelOwner(message.channel.id);
51+
52+
let urls = message.content.split(" ").filter((value) => isSpotifyURL(value));
53+
54+
if (urls.length == 0) {
55+
this.logger.verbose("No URLs found in party channel message");
56+
return
57+
}
4558

46-
getTrackURIFromLink(urls[0]).then(trackURI => {
47-
this.spotifyApi.addToQueue(owner.tokenPair, trackURI);
48-
});
59+
let trackURI = await getTrackURIFromLink(urls[0]);
60+
this.logger.debug(`trackURI ${trackURI} extracted`);
61+
this.logger.debug("Attempting to use token with expiration time " + owner.tokenPair.expirationTime)
62+
await this.spotifyApi.addToQueue(owner.tokenPair, trackURI);
63+
} catch(err) {
64+
this.logger.error(err)
65+
message.channel.send("// Failed to add song. " + err.message);
66+
throw err;
67+
}
4968
}
5069

5170
handleBotCommand(message : Message) {
@@ -67,7 +86,8 @@ export class CommandHandler {
6786
break;
6887
}
6988
} catch (err) {
70-
console.error(err);
89+
this.logger.error(err)
90+
throw err;
7191
}
7292
}
7393

bot/src/logging.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ const myFormat = printf(( {level, message, label, timestamp} ) => {
88
})
99

1010
export function buildLogger(displayLabel : string) : Logger{
11+
let loglevel = process.env.LOGLEVEL ? process.env.LOGLEVEL : 'info'
1112
return createLogger({
12-
level: 'debug',
13+
level: loglevel,
1314
format: combine(
1415
label({ label: displayLabel }),
1516
timestamp(),

bot/src/model/commands/createPartyCommand.ts

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import { Channel, Guild, GuildCreateChannelOptions, Message, MessageMentions, OverwriteData, User } from "discord.js";
22
import { Repository } from "../data/repository";
33

4+
import * as Logging from "../../logging"
5+
let logger = Logging.buildLogger("createPartyCommand");
46

57
export class CreatePartyCommand {
68

7-
guild : Guild;
8-
creator : User;
9+
message : Message;
910
mentions : User[];
1011
partyChannel : Channel|undefined = undefined;
1112
repository : Repository;
1213

1314
constructor(message : Message, repository : Repository) {
14-
if (!message.guild)
15-
throw new Error();
16-
this.guild = message.guild;
17-
this.creator = message.author;
15+
this.message = message;
1816
this.mentions = this.extractUsers(message.mentions);
1917
this.repository = repository;
2018
}
@@ -25,19 +23,27 @@ export class CreatePartyCommand {
2523

2624
private async setupPartyChannel(){
2725
let channelName = this.createChannelName();
26+
logger.debug("Attempting to setup party channel " + channelName)
2827
let options = this.createOptionsObject();
29-
this.partyChannel = await this.guild.channels.create(channelName, options);
30-
this.repository.addPartyChannel(this.partyChannel, this.creator);
28+
if (this.message.guild == null) {
29+
return;
30+
}
31+
try {
32+
this.partyChannel = await this.message.guild.channels.create(channelName, options);
33+
this.repository.addPartyChannel(this.partyChannel, this.message.author);
34+
} catch(err) {
35+
this.printErrorMessage(err);
36+
}
3137
}
3238

3339
private extractUsers(mentions : MessageMentions) : User[] {
3440
let users : User[] = Array.from(mentions.users.values());
35-
users.push(this.creator);
41+
users.push(this.message.author);
3642
return users;
3743
}
3844

3945
private createChannelName() {
40-
let authorName = this.creator.tag.split("#")[0].toLowerCase();
46+
let authorName = this.message.author.tag.split("#")[0].toLowerCase();
4147
let date = new Date();
4248
let channelName = `${authorName}-${date.getDate()}-${date.getMonth() + 1}`;
4349
return channelName;
@@ -75,4 +81,8 @@ export class CreatePartyCommand {
7581
return overwrite;
7682
}
7783

84+
private printErrorMessage(err : Error) {
85+
this.message.channel.send("Failed to create party channel.\n" + err.message);
86+
}
87+
7888
}

bot/src/model/commands/registerMeCommand.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { Message } from "discord.js";
22
import { Repository } from "../data/repository";
33
import { SpotifyAPI } from "../spotifyApi";
44
import { Command } from "./command";
5-
5+
import * as crypto from "crypto";
66

77
export class RegisterMeCommand implements Command {
88
message : Message;
@@ -27,8 +27,7 @@ export class RegisterMeCommand implements Command {
2727
}
2828

2929
private produceState() : string {
30-
let now = new Date();
31-
let state = this.message.author.tag + "#" + now.getTime();
30+
let state = crypto.randomBytes(8).toString('hex');
3231
return state;
3332
}
3433
}

bot/src/model/data/repositoryImpl.ts

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ export class RepositoryImpl implements Repository {
2020
user: process.env.MYSQL_USER,
2121
password: process.env.MYSQL_PASSWORD,
2222
connectionLimit: 5,
23-
database: "playback_enq"
23+
database: "playback_enq",
24+
bigNumberStrings : true
2425
})
2526
return this.pool;
2627
} else {
@@ -53,7 +54,12 @@ export class RepositoryImpl implements Repository {
5354
let conn;
5455
try {
5556
conn = await this.getPool().getConnection();
56-
await conn.query("INSERT INTO party_channels VALUES (?, ?, ?)", [channel.id, owner.id, autoDelete]);
57+
58+
if (autoDelete) {
59+
await conn.query("INSERT INTO party_channels VALUES (?, ?, ?)", [channel.id, owner.id, autoDelete]);
60+
} else {
61+
await conn.query("INSERT INTO party_channels VALUES (?, ?, NULL)", [channel.id, owner.id]);
62+
}
5763
logger.debug(`Successfully added party channel ${channel.id} by ${owner.tag}`)
5864
} catch(err) {
5965
logger.error(err)
@@ -83,7 +89,7 @@ export class RepositoryImpl implements Repository {
8389
let conn;
8490
try {
8591
conn = await this.getPool().getConnection();
86-
let rows : any[] = await conn.query("SELECT * FROM discord_users WHERE id IN (SELECT id FROM party_channels WHERE party_channels.id = ?)", [channelId]);
92+
let rows : any[] = await conn.query("SELECT * FROM discord_users WHERE id IN (SELECT owner_id FROM party_channels WHERE id = ?)", [channelId]);
8793
if (rows.length === 0) {
8894
throw new Error("Channel not found");
8995
}
@@ -171,7 +177,7 @@ export class RepositoryImpl implements Repository {
171177
let conn;
172178
try {
173179
conn = await this.getPool().getConnection();
174-
let result = await conn.query("UPDATE discord_users" +
180+
let result = await conn.query("UPDATE discord_users " +
175181
"SET access_token = ?, refresh_token = ?, token_expiration = ? " +
176182
"WHERE access_token=? AND refresh_token=?",
177183
[oldToken.accessToken, oldToken.refreshToken, oldToken.expirationTime, newToken.accessToken, newToken.refreshToken]);
@@ -330,11 +336,18 @@ export class RepositoryImpl implements Repository {
330336
try {
331337
conn = await this.getPool().getConnection();
332338
await conn.beginTransaction();
333-
let userId = await conn.query("SELECT discord_user FROM code_requests WHERE request_id = ?", [requestId])
339+
let idResult = await conn.query("SELECT discord_user FROM code_requests WHERE request_id = ?", [requestId]);
340+
let userId = idResult[0].discord_user;
334341
if (userId.length == 0) {
335342
throw new Error("Not Found");
336343
}
337-
await conn.query("UPDATE discord_users SET access_token = ?, refresh_token = ?, token_expiration = ? WHERE id = ?", [newToken.accessToken, newToken.refreshToken, newToken.expirationTime, userId]);
344+
logger.debug(`Code request with id ${requestId} belongs to user with user id ${userId}`);
345+
346+
let res = await conn.query("UPDATE discord_users SET access_token = ?, refresh_token = ?, token_expiration = ? WHERE id = ?", [newToken.accessToken, newToken.refreshToken, newToken.expirationTime, userId]);
347+
if (res.affectedRows === 0) {
348+
logger.error("Update of token pair affected 0 rows!");
349+
}
350+
338351
await conn.query("DELETE FROM code_requests WHERE request_id = ?", [requestId]);
339352
logger.debug(`Successfully finished code request with id ${requestId} and deleted it.`)
340353
await conn.commit();

bot/src/model/spotifyApi.ts

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,31 +28,35 @@ export class SpotifyAPI {
2828
}
2929

3030
async addToQueue(tokenPair : TokenPair, trackURI : string, refreshTokenOnFailure : boolean = true) {
31-
logger.verbose("Request to add to Queue started.")
32-
this.updateTokenIfExpiringSoon(tokenPair).then(token => {
31+
try {
32+
logger.verbose("Request to add to Queue started.")
33+
let token = await this.updateTokenIfExpiringSoon(tokenPair);
3334
let options = this.createPostSongOptions(trackURI, token.accessToken);
35+
3436
request(options, (res) => {
3537
res.on("end", () => {
3638
if (res.statusCode !== 304) {
3739
logger.debug("Add to queue request failed with code " + res.statusCode);
3840
throw new Error("Status Code not 304");
41+
} else {
42+
logger.debug("Add to queue request finished with code " + res.statusCode);
3943
}
4044
})
4145
}).end();
42-
}).catch(err => {
46+
} catch(err) {
4347
if (refreshTokenOnFailure) {
4448
logger.verbose("Attempting to refresh token after addToQueue api failure.")
45-
this.updateToken(tokenPair).then(newToken => {
46-
this.addToQueue(newToken, trackURI, false)
47-
}).catch(err => {throw err});
49+
let newToken = await this.updateToken(tokenPair)
50+
await this.addToQueue(newToken, trackURI, false)
4851
}
4952
throw err;
50-
})
53+
}
5154
}
5255

5356
async updateTokenIfExpiringSoon(tokenPair : TokenPair) : Promise<TokenPair> {
5457
let now = new Date();
5558
if ((tokenPair.expirationTime.getTime() - now.getTime()) / 1000 < this.minimumTokenTimeRemaining) {
59+
logger.verbose("Updated token as it was expiring soon or was already expired.");
5660
return this.updateToken(tokenPair);
5761
}
5862
return tokenPair;
@@ -70,6 +74,7 @@ export class SpotifyAPI {
7074
try {
7175
let code = await this.repository.getRequestCodeById(requestId);
7276
let tokenPair = await this.fetchTokenFromCode(code);
77+
logger.debug("Fetched token pair with expiration time " + tokenPair.expirationTime)
7378
this.repository.finishCodeRequest(requestId, tokenPair);
7479
} catch (err) {
7580
logger.error("Error while attempting to fetch token pair from code (requestId: " + requestId + ")");
@@ -80,28 +85,28 @@ export class SpotifyAPI {
8085
private fetchTokenFromRefresh(tokenPair : TokenPair) : Promise<TokenPair> {
8186
logger.verbose("Attempting to fetch token pair from refresh token")
8287
let options = this.createRefreshTokenOptions();
83-
let body = `{'grant_type' : 'refresh_token', 'refresh_token':'${tokenPair.refreshToken}', 'redirect_uri':'${encodeURIComponent(this.redirectUri)}', 'client_id:${this.clientId}, 'client_secret:${this.clientSecret}'}`;
84-
return this.fetchTokenPair(options, body);
88+
let body = `grant_type=refresh_token&refresh_token=${tokenPair.refreshToken}&redirect_uri=${encodeURIComponent(this.redirectUri)}&client_id=${this.clientId}&client_secret=${this.clientSecret}`;
89+
return this.fetchTokenPair(options, body, tokenPair);
8590
}
8691

8792
private fetchTokenFromCode(code : string) : Promise<TokenPair> {
8893
let options = this.createFetchFromCodeOptions();
89-
let body = `{'grant_type' : 'authorization_code', 'code' : '${code}', 'redirect_uri':' ${ encodeURIComponent(this.redirectUri)}, 'client_id:${this.clientId}, 'client_secret:${this.clientSecret}'}`
94+
let body = `grant_type=authorization_code&code=${code}&redirect_uri=${encodeURIComponent(this.redirectUri)}&client_id=${this.clientId}&client_secret=${this.clientSecret}`
9095
return this.fetchTokenPair(options, body);
9196
}
9297

93-
private fetchTokenPair(requestOptions : RequestOptions, body : string) : Promise<TokenPair>{
98+
private fetchTokenPair(requestOptions : RequestOptions, body : string, oldToken? : TokenPair) : Promise<TokenPair>{
9499
return new Promise((resolve, reject) => {
95100
let data = ""
96-
logger.debug("Making spotify api request:\nBody: " + body + "\nOptions: " + JSON.stringify(requestOptions))
101+
logger.debug("Making spotify api token fetch request");
97102
request(requestOptions, (res) => {
98103
res.on('data', (chunk) => {
99104
data += chunk;
100105
});
101106

102107
res.on('end', () => {
103108
if (res.statusCode === 200) {
104-
let tokenPair = this.createTokenPairFromResponse(data);
109+
let tokenPair = this.createTokenPairFromResponse(data, oldToken);
105110
resolve(tokenPair);
106111
} else {
107112
reject(new Error(data));
@@ -124,6 +129,7 @@ export class SpotifyAPI {
124129
method : "POST",
125130
headers : {
126131
"Accept" : "Application/json",
132+
"Content-Type" : "application/x-www-form-urlencoded"
127133
}
128134
}
129135
}
@@ -137,6 +143,7 @@ export class SpotifyAPI {
137143
method : "POST",
138144
headers : {
139145
"Accept" : "Application/json",
146+
"Content-Type" : "application/x-www-form-urlencoded"
140147
}
141148
}
142149
}
@@ -148,20 +155,22 @@ export class SpotifyAPI {
148155
path : `/v1/me/player/queue?uri=${trackURI}`,
149156
method : "POST",
150157
headers : {
151-
"Authorization" : `Bearer ${access_token}`,
158+
"Authorization" : `Bearer ${access_token}`
152159
}
153160
}
154161
}
155162

156-
private createTokenPairFromResponse(response : string) : TokenPair {
163+
private createTokenPairFromResponse(response : string, oldTokenPair? : TokenPair) : TokenPair {
157164
let responseObject = JSON.parse(response);
158165

159-
if (!responseObject.access_token || !responseObject.token_type || !responseObject.scope || !responseObject.expires_in || !responseObject.refresh_token)
166+
if (!responseObject.access_token || !responseObject.token_type || !responseObject.scope || !responseObject.expires_in)
160167
throw new Error("Not all information received");
161168

162169
let now = new Date();
163170
let expiration = new Date((new Date()).setSeconds(now.getSeconds() + parseInt(responseObject.expires_in)));
164171

165-
return new TokenPair(responseObject.access_token, responseObject.refresh_token, expiration);
172+
let refreshToken = responseObject.refresh_token ? responseObject.refresh_token : oldTokenPair?.refreshToken;
173+
174+
return new TokenPair(responseObject.access_token, refreshToken, expiration);
166175
}
167176
}

0 commit comments

Comments
 (0)