Skip to content

Commit

Permalink
[app] updated money service, user auth, args functionality
Browse files Browse the repository at this point in the history
  • Loading branch information
arranfletcher committed Feb 18, 2020
1 parent 42705d5 commit 9f9f207
Show file tree
Hide file tree
Showing 10 changed files with 209 additions and 106 deletions.
3 changes: 2 additions & 1 deletion src/app.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export class AppController {
)),
map(a => a.data),
map(account => {
// Set user currency
// Set user currency and id
user.wallet_id = account.id;
user.currency = account.defaultCurrency;
console.log('Account', user);
console.log('JSON:', JSON.stringify(user));
Expand Down
2 changes: 2 additions & 0 deletions src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { DiscordModule } from './discord/discord.module';
import config from 'config';
import { ProfileService } from './shared/services/profile/profile.service';
import { TransactionService } from './shared/services/transaction/transaction.service';
import { MoneyService } from './discord/services/money/money.service';

@Module({
imports: [
Expand All @@ -14,6 +15,7 @@ import { TransactionService } from './shared/services/transaction/transaction.se
],
controllers: [AppController],
providers: [
MoneyService,
ProfileService,
TransactionService
],
Expand Down
15 changes: 9 additions & 6 deletions src/discord/core/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@ export class Args extends Array<string> {
}

// Find first number in args
findFirstNumber(offset = 0, excludes?: string[], notEqual?: number[]): number | boolean {
findFirstNumber(offset = 0, excludes: string[] = [], notEqual: number[] = []): number | boolean {
// Find first amount in args
const amount = this.find((c, i) => i >= offset && !excludes.includes(c) && this.isNumber(c))
let amount = this.find((c, i) => i >= offset && !excludes.includes(c) && this.isNumber(c.replace(',','')))

// Return valid number or false
if (amount && this.isNumber(amount) && !notEqual.includes(parseFloat(amount)) && parseFloat(amount) > 0) {
return parseFloat(amount)
if (amount) {
amount = amount.replace(',','')
if (this.isNumber(amount) && !notEqual.includes(parseFloat(amount)) && parseFloat(amount) > 0) {
return parseFloat(amount)
}
}
return false
}
Expand All @@ -40,15 +43,15 @@ export class Args extends Array<string> {
}

// Combine args into string
combineArgs(excludes: string[], offset = 0): string {
combineArgs(excludes: string[] = [], offset = 0): string {
return this.reduce(
(t, c, i) => t += (i < offset || excludes.includes(c)) ? '' : `${c} `,
'').trim().toLowerCase()
}

// Number type checking
private isNumber(value: string | number): boolean {
return (
return (
(value != null) &&
(value !== '') &&
!isNaN(Number(value.toString()))
Expand Down
4 changes: 2 additions & 2 deletions src/discord/core/open-exchange-rates.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ export interface OpenExchangeRates {
timestamp: number;
base: string;
rates: {
[key: string]: number
}
[key: string]: number;
};
}
133 changes: 73 additions & 60 deletions src/discord/services/discord/discord.service.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Client, RichEmbed, Message, Attachment, User, DMChannel } from 'discord.js'
import { SHA256, enc } from 'crypto-js'
import accounting from 'accounting'
import config from 'config'
import { Colours } from '../../core/colours.enum'
import { Profile } from '../../../shared/core/profile'
Expand All @@ -11,6 +10,7 @@ import { TransactionService } from '../../../shared/services/transaction/transac
import { Args } from '../../core/args';
import { BalanceAmount } from 'src/discord/core/balance';
import { MoneyService } from '../money/money.service';
import { tap } from 'rxjs/operators';

interface Keywords {
currency: string[];
Expand All @@ -23,8 +23,9 @@ export class DiscordService {
private static readonly colours = Colours
private static readonly keywords: Keywords = {
currency: [
'usd',
'sgd'
'aud',
'sgd',
'eur'
], action: [
'split',
'each'
Expand Down Expand Up @@ -54,10 +55,11 @@ export class DiscordService {
console.log('Discord monitor running')
}

private async onMessage(message: Message): Promise<Message | Message[]> {
let channel: DMChannel
let user: Profile
private async onMessage(message: Message): Promise<void> {
const user: Profile = await this._profile.get(message.author.id)
const args = new Args(message.content)
let channel: DMChannel

console.log('Received', args)

switch (args[0]) {
Expand All @@ -73,6 +75,7 @@ export class DiscordService {
'`$source [source]` View and set available payment sources (`$source` to view `$source [source]` to set)\n\n' +
'`$sauce` Same as `$source`\n\n' +
'`$wallet` Get a link to your NR Wallet\n\n' +
'*To detect the correct amount, money needs to have a `.` decimal seperator*\n\n' +
'*Ex: $send @nygmarose @knightofhonour 13.37 SGD each*'
)
break
Expand All @@ -83,11 +86,10 @@ export class DiscordService {
break

case '$link':
user = await this._profile.get(message.author.id)

// If user is linked reply with status
if (user.linked) {
return message.reply(`It looks like you're already linked up`)
message.reply(`It looks like you're already linked up`)
return
}

// Create DM channel
Expand Down Expand Up @@ -120,10 +122,10 @@ export class DiscordService {
break

case '$balance':
user = await this._profile.get(message.author.id)

// Check if user is linked
if (!this.linkCheck(user, message)) return
if (!this.linkCheck(user, message)) {
return
}

// Get user balance
this._profile.balance(user).subscribe((b: BalanceAmount) => {
Expand All @@ -133,10 +135,10 @@ export class DiscordService {
break

case '$wallet':
user = await this._profile.get(message.author.id)

// Check if user is linked
if (!this.linkCheck(user, message)) return
if (!this.linkCheck(user, message)) {
return
}

message.reply(
new RichEmbed()
Expand All @@ -149,14 +151,15 @@ export class DiscordService {
break

case '$send':
user = await this._profile.get(message.author.id)

// Check if user is linked
if (!this.linkCheck(user, message)) return
if (!this.linkCheck(user, message)) {
return
}

// Check if DM or group
if (!message.guild) {
return message.reply(`Hey ${message.author.toString()}, I'll need you to do that in a group to send to someone`)
message.reply(`Hey ${message.author.toString()}, I'll need you to do that in a group to send to someone`)
return
}

// Create transaction
Expand All @@ -165,25 +168,26 @@ export class DiscordService {
// Get currency mentioned
transaction.currency = args.findKeyword(this.getKeywords('currency'), 1)
if (!transaction.currency) {
return message.reply(`Awesome. Now, can you send that again with a currency?`)
message.reply(`Awesome. Now, can you send that again with a currency?`)
return
}

// Get amount mentioned
transaction.amount = args.findFirstNumber(1)
if (!transaction.amount) {
return message.reply(`Uhhh I couldn't see any amount there`)
message.reply(`Uhhh I couldn't see any amount there`)
return
}

// Format amount to decimal number
transaction.amount = DiscordService.formatCurrency(transaction.amount as number, transaction.currency as string)

// TODO: Convert currencies if needed (transaction.amount -> user.source.default)
transaction.amount = this._money.unformat(transaction.amount as number, transaction.currency as string)

// Get users mentioned
transaction.users = message.mentions.users.array().filter(u => u.id !== message.author.id && !u.bot && !!message.guild.member(u)).map(u => u.id)

if (!transaction.users.length) {
return message.reply(`That would be awesome but I couldn't see anyone to send to, make sure you mention them *and* they're in this group`)
message.reply(`That would be awesome but I couldn't see anyone to send to, make sure you mention them *and* they're in this group`)
return
}

// Check if users are all linked
Expand All @@ -192,79 +196,93 @@ export class DiscordService {
)
const unlinked: Profile[] = profiles.filter((p: Profile) => !p.linked)
if (unlinked.length) {
return message.reply(`${unlinked.length > 1 ? 'These users' : 'This user'} hasn't linked an account: ${unlinked.map((u: Profile) => `<@${u.id}>`).join(', ')}`)
message.reply(`${unlinked.length > 1 ? 'These users' : 'This user'} hasn't linked an account: ${unlinked.map((u: Profile) => `<@${u.id}>`).join(', ')}`)
return
}

// If multiple users check for action to perform
if (transaction.users.length > 1) {
transaction.action = args.findKeyword(this.getKeywords('action'), 1)
if (!transaction.action) {
return message.reply(`Can you copy paste that message but let me know if I should send then ${transaction.amount} ${transaction.currency} \`each\` or should I \`split\` between them`)
message.reply(`Can you copy paste that message but let me know if I should send then ${transaction.amount} ${transaction.currency} \`each\` or should I \`split\` between them`)
return
}
}

// Save transaction
this._transaction.save(transaction)

// Return confirmation message
return message.reply(transaction.toString())
message.reply(this._transaction.toString(transaction))
break

case '$confirm':
// Check not a DM
if (!message.guild) {
return message.reply(`Well you can't pay me so I don't have a transaction for this DM`)
message.reply(`Well you can't pay me so... I don't have any transaction to do for this DM`)
return
}

user = await this._profile.get(message.author.id)

// Check if user is linked
if (!this.linkCheck(user, message)) return
if (!this.linkCheck(user, message)) {
return
}

// Get pending transaction
const confirmation = await this._transaction.get(message)

// Return an error if no pending transaction
if (!confirmation) {
return message.reply('Sure thing! I couldn\'t find anything to confirm though.')
message.reply('Sure thing! I couldn\'t find anything to confirm though.')
return
}

// TODO: Send out the money
// transaction.execute()

// DM the recipients
confirmation.users.forEach((u: string | User) => {
u = this.client.users.find('id', u)
u.createDM().then((c: DMChannel) => {
c.send(`Hey ${u.toString()}! ${this.client.users.find('id', confirmation.sender).username} sent a full ${confirmation.amount} ${confirmation.currency} to your NR Wallet!`)
// Get transaction recipe
const execute = await this._transaction.execute(confirmation, message)

// Send update message
message.reply('Roger, processing now')

// Execute and respond with status
execute.pipe(
tap(console.log),
).subscribe(() => {
// DM the recipients
confirmation.users.forEach((u: string | User) => {
u = this.client.users.find('id', u)
u.createDM().then((c: DMChannel) => {
c.send(`Hey ${u.toString()}! ${this.client.users.find('id', confirmation.sender).username} sent a full ${this._money.format(confirmation.amount as number, confirmation.currency as string)} to your NR Wallet!`)
})
})
})

// Confirm with the sender
message.reply('Will do. Everything\'s been sent out and everyone DM\'d')
// Confirm with the sender
message.reply('Done! Everything\'s been sent out and everyone DM\'d')

// Clear tx
this._transaction.clear(confirmation)
}, err => message.reply(`Unfortunately, an error happened in sending ${JSON.stringify(err)}`))
break

case '$source':
case '$sauce':
user = await this._profile.get(message.author.id)

// Check if user is linked
if (!this.linkCheck(user, message)) return
if (!this.linkCheck(user, message)) {
return
}

// Send warning if guild
if (message.guild) {
message.reply(`Shhh... we shouldn't talk about that here, I'll DM you`)
}

// TODO: Get sources and send to DM

// Create DM channel
channel = await message.author.createDM()

// Send link to channel
if (channel) {
this._profile.sources(user).subscribe(s =>
channel.send(`Hey ${message.author.toString()}, your current sources:\n${
s.reduce((str, source, i, arr) => str += `${source.card.brand[0].toUpperCase() + source.card.brand.slice(1)} ${source.type[0].toUpperCase() + source.type.slice(1)} - ${source.card.last4} (${source.card.exp_month}/${source.card.exp_year})${i !== arr.length-1 ? '\n':''}`, '')
s.reduce((str, source, i, arr) => str += `${source.card.brand[0].toUpperCase() + source.card.brand.slice(1)} ${source.type[0].toUpperCase() + source.type.slice(1)} ${source.card.last4}\t(${source.card.exp_month}/${source.card.exp_year})${i !== arr.length-1 ? '\n':''}`, '')
}`)
)
return
Expand All @@ -273,6 +291,10 @@ export class DiscordService {
// Send error if no channel
message.reply(`Hey uhm, I couldn't DM you, can you make sure I'm not blocked or anything?`)
break

case '$cena':
this.unauthorised(message)
break
}
}

Expand All @@ -299,15 +321,6 @@ export class DiscordService {

// Reply with an unauthorised message
private unauthorised(message: Message): Promise<Message | Message[]> {
return message.reply(new Attachment(`I'm sorry ${message.author.toString()}, I can't do that`, 'https://media1.tenor.com/images/86937766f3f44884362c716e8f1d0e19/tenor.gif'))
}

// Format currency input
private static formatCurrency(value: number, currency: string): number {
if (['EUR'].includes(currency)) {
return accounting.unformat(value.toString(), ',')
} else {
return accounting.unformat(value.toString())
}
return message.channel.send(`I'm sorry ${message.author.toString()}, you can't see this`, new Attachment('https://media1.tenor.com/images/86937766f3f44884362c716e8f1d0e19/tenor.gif'))
}
}
1 change: 1 addition & 0 deletions src/discord/services/money/money.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ export class MoneyService {
updateRates(): Observable<OpenExchangeRates> {
return this.http.get<OpenExchangeRates>(
`${config.get('fx.url')}`, {
// eslint-disable-next-line @typescript-eslint/camelcase
params: { app_id: config.get('fx.client_id') }
}
).pipe(
Expand Down
5 changes: 2 additions & 3 deletions src/shared/core/profile.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
import { HttpService } from "@nestjs/common";

export class Profile {
public token: string;
public currency: string;
public id: string;
public wallet_id: number;

constructor (data: {id?: string; token?: string; currency?: string}, private readonly http?: HttpService) {
constructor (data: {id?: string; token?: string; currency?: string}) {
Object.assign(this, data)
}

Expand Down
Loading

0 comments on commit 9f9f207

Please sign in to comment.