Skip to content

Commit

Permalink
feat(premium): Improve premium handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Marco Crespi committed Nov 25, 2019
1 parent e009c1a commit 23f54d6
Show file tree
Hide file tree
Showing 10 changed files with 154 additions and 104 deletions.
2 changes: 1 addition & 1 deletion i18n/bot/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -765,7 +765,7 @@
"premium": {
"activate": "Use {{{ cmd }}} to activate it on this server",
"deactivate": "Use {{{ cmd }}} to deactivate it on this server",
"text": "Your premium subscription is valid for another {{ date }}.\n\nYou have activated premium on {{{ limit }}} servers.\n{{{ guildList }}}\n\n[What can I do with premium?]({{{ link }}})",
"text": "Your premium subscription is valid for {{ date }}.\n\nYou have activated premium on {{{ limit }}} servers.\n{{{ guildList }}}\n\n[What can I do with premium?]({{{ link }}})",
"title": "InviteManager Premium"
},
"self": {
Expand Down
15 changes: 5 additions & 10 deletions scripts/db/setup_db0.sql
Original file line number Diff line number Diff line change
Expand Up @@ -62,12 +62,10 @@ CREATE TABLE `musicNodes` (
--

CREATE TABLE `premiumSubscriptionGuilds` (
`id` int(11) NOT NULL,
`memberId` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`guildId` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`subscriptionId` int(11) DEFAULT NULL,
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deletedAt` datetime DEFAULT NULL
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- --------------------------------------------------------
Expand All @@ -81,11 +79,10 @@ CREATE TABLE `premiumSubscriptions` (
`amount` decimal(10,2) DEFAULT NULL,
`maxGuilds` int(11) NOT NULL DEFAULT '5',
`isFreeTier` tinyint(1) NOT NULL DEFAULT '0',
`isPatreon` tinyint(1) NOT NULL DEFAULT '0',
`validUntil` datetime DEFAULT NULL,
`createdAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
`updatedAt` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`deletedAt` datetime DEFAULT NULL,
`guildId` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`memberId` varchar(32) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
`reason` text COLLATE utf8mb4_unicode_ci
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
Expand Down Expand Up @@ -116,16 +113,14 @@ ALTER TABLE `musicNodes`
-- Indexes for table `premiumSubscriptionGuilds`
--
ALTER TABLE `premiumSubscriptionGuilds`
ADD PRIMARY KEY (`id`),
ADD KEY `guildId` (`guildId`),
ADD KEY `premiumSubscriptionId` (`subscriptionId`);
ADD PRIMARY KEY (`memberId`, `guildId`),
ADD KEY `guildId` (`guildId`);

--
-- Indexes for table `premiumSubscriptions`
--
ALTER TABLE `premiumSubscriptions`
ADD PRIMARY KEY (`id`),
ADD KEY `guildId` (`guildId`),
ADD KEY `memberId` (`memberId`);

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
Expand Down
57 changes: 42 additions & 15 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { LogAction } from './framework/models/Log';
import { CommandsService } from './framework/services/Commands';
import { DatabaseService } from './framework/services/DatabaseService';
import { MessagingService } from './framework/services/Messaging';
import { PremiumService } from './framework/services/PremiumService';
import { RabbitMqService } from './framework/services/RabbitMq';
import { SchedulerService } from './framework/services/Scheduler';
import { InviteCodeSettingsCache } from './invites/cache/InviteCodeSettingsCache';
Expand Down Expand Up @@ -94,6 +95,7 @@ export class IMClient extends Client {
public invs: InvitesService;
public music: MusicService;
public tracking: TrackingService;
public premium: PremiumService;

public startedAt: Moment;
public gatewayConnected: boolean;
Expand Down Expand Up @@ -167,6 +169,7 @@ export class IMClient extends Client {
this.invs = new InvitesService(this);
this.tracking = new TrackingService(this);
this.music = new MusicService(this);
this.premium = new PremiumService(this);

// Services
this.cmds.init();
Expand Down Expand Up @@ -248,22 +251,39 @@ export class IMClient extends Client {

case BotType.pro:
// If this is the pro bot then leave any guilds that aren't pro
const premium = await this.cache.premium._get(guild.id);
let premium = await this.cache.premium._get(guild.id);

if (!premium) {
const dmChannel = await this.getDMChannel(guild.ownerID);
await dmChannel
.createMessage(
'Hi!' +
`Thanks for inviting me to your server \`${guild.name}\`!\n\n` +
'I am the pro version of InviteManager, and only available to people ' +
'that support me on Patreon with the pro tier.\n\n' +
'To purchase the pro tier visit https://www.patreon.com/invitemanager\n\n' +
'If you purchased premium run `!premium check` and then `!premium activate` in the server\n\n' +
'I will be leaving your server now, thanks for having me!'
)
.catch(() => undefined);
await guild.leave();
// Let's try and see if this guild had pro before, and if maybe
// the member renewed it, but it didn't update.
const oldPremium = await this.db.getPremiumSubscriptionGuildForGuild(guild.id, false);
if (oldPremium) {
await this.premium.checkPatreon(oldPremium.memberId);
premium = await this.cache.premium._get(guild.id);
}

if (!premium) {
const dmChannel = await this.getDMChannel(guild.ownerID);
await dmChannel
.createMessage(
'Hi!' +
`Thanks for inviting me to your server \`${guild.name}\`!\n\n` +
'I am the pro version of InviteManager, and only available to people ' +
'that support me on Patreon with the pro tier.\n\n' +
'To purchase the pro tier visit https://www.patreon.com/invitemanager\n\n' +
'If you purchased premium run `!premium check` and then `!premium activate` in the server\n\n' +
'I will be leaving your server now, thanks for having me!'
)
.catch(() => undefined);
const onTimeout = async () => {
if (await this.cache.premium._get(guild.id)) {
return;
}

await guild.leave();
};
setTimeout(onTimeout, 2 * 60 * 1000);
}
}
break;

Expand Down Expand Up @@ -340,7 +360,14 @@ export class IMClient extends Client {
'I will be leaving your server soon, thanks for having me!'
)
.catch(() => undefined);
setTimeout(() => guild.leave().catch(() => undefined), 5 * 60 * 1000);
const onTimeout = async () => {
if (await this.cache.premium._get(guild.id)) {
return;
}

await guild.leave();
};
setTimeout(onTimeout, 2 * 60 * 1000);
return;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/framework/cache/PremiumCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ export class PremiumCache extends Cache<boolean> {
return true;
}

const sub = await this.client.db.getActivePremiumSubscriptionGuildForGuild(guildId);
const sub = await this.client.db.getPremiumSubscriptionGuildForGuild(guildId);
return !!sub;
}
}
62 changes: 19 additions & 43 deletions src/framework/commands/premium/premium.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import axios from 'axios';
import { Message } from 'eris';
import moment from 'moment';

Expand Down Expand Up @@ -40,14 +39,15 @@ export default class extends Command {
// TODO: Create list of premium features (also useful for FAQ)
const lang = settings.lang;
const guildId = guild ? guild.id : undefined;
const memberId = message.author.id;

const embed = this.createEmbed();

const sub = await this.client.db.getActivePremiumSubscriptionForMember(message.author.id);
const guildSubs = sub ? await this.client.db.getPremiumSubscriptionGuildsForSubscription(sub.id) : [];
const subs = await this.client.db.getPremiumSubscriptionsForMember(memberId, true);
const guildSubs = subs ? await this.client.db.getPremiumSubscriptionGuildsForMember(memberId) : [];

if (!action) {
if (!sub) {
if (!subs || subs.length === 0) {
embed.title = t('cmd.premium.noPremium.title');
embed.description = t('cmd.premium.noPremium.text');

Expand Down Expand Up @@ -77,7 +77,9 @@ export default class extends Command {
} else {
embed.title = t('cmd.premium.premium.title');

const date = moment(sub.validUntil)
const maxDate = subs.reduce((acc, sub) => Math.max(acc, moment(sub.validUntil).unix()), 0);
const date = moment
.unix(maxDate)
.locale(lang)
.fromNow(true);

Expand All @@ -102,7 +104,7 @@ export default class extends Command {
}
}

const limit = `**${guildSubs.length}/${sub.maxGuilds}**`;
const limit = `**${guildSubs.length}/${subs.reduce((acc, sub) => Math.max(acc, sub.maxGuilds), 0)}**`;

embed.description =
t('cmd.premium.premium.text', {
Expand All @@ -122,16 +124,17 @@ export default class extends Command {
embed.description = t('cmd.premium.activate.noGuild');
} else if (isPremium) {
embed.description = t('cmd.premium.activate.currentlyActive');
} else if (!sub) {
} else if (!subs) {
embed.description = t('cmd.premium.activate.noSubscription', {
cmd: '`' + settings.prefix + 'premium`'
});
} else {
if (guildSubs.length >= sub.maxGuilds) {
const maxGuilds = subs.reduce((acc, sub) => Math.max(acc, sub.maxGuilds), 0);
if (guildSubs.length >= maxGuilds) {
embed.description = t('cmd.premium.activate.maxGuilds');
} else {
await this.client.db.savePremiumSubscriptionGuild({
subscriptionId: sub.id,
memberId,
guildId
});

Expand All @@ -152,7 +155,7 @@ export default class extends Command {
} else if (!isPremium) {
embed.description = t('cmd.premium.deactivate.noSubscription');
} else {
await this.client.db.removePremiumSubscriptionGuild(guildId, sub.id);
await this.client.db.removePremiumSubscriptionGuild(memberId, guildId);

this.client.cache.premium.flush(guildId);

Expand All @@ -161,44 +164,17 @@ export default class extends Command {
} else if (action === Action.Check) {
embed.title = t('cmd.premium.check.title');

const userId = message.author.id;
const res = await axios
.get(`https://api.invman.gg/patreon/check/?userId=${userId}`, {
auth: this.client.config.bot.apiAuth
})
.catch(() => undefined);
const res = await this.client.premium.checkPatreon(memberId);

if (!res) {
if (res === 'not_found') {
embed.description = t('cmd.premium.check.notFound');
} else if (res.data.last_charge_status !== 'Paid') {
} else if (res === 'declined') {
embed.description = t('cmd.premium.check.declined');
} else if (res.data.patron_status !== 'active_patron') {
embed.description = t('cmd.premium.check.paused', {
since: res.data.last_charge_date
});
} else if (res === 'paused') {
embed.description = t('cmd.premium.check.paused');
} else {
const day = moment(res.data.last_charge_date).date();
const validUntil = moment()
.add(1, 'month')
.date(day)
.add(1, 'day');

if (sub) {
sub.validUntil = validUntil.toDate();
await this.client.db.savePremiumSubscription(sub);
} else {
await this.client.db.savePremiumSubscription({
memberId: userId,
validUntil: validUntil.toDate(),
amount: res.data.currently_entitled_amount_cents / 100,
maxGuilds: 5,
isFreeTier: false,
reason: '!premium check'
});
}

embed.description = t('cmd.premium.check.done', {
valid: validUntil.locale(lang).calendar(),
valid: res.locale(lang).calendar(),
cmd: '`' + settings.prefix + 'premium`'
});
}
Expand Down
16 changes: 8 additions & 8 deletions src/framework/commands/premium/tryPremium.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ export default class extends Command {
embed.title = t('cmd.tryPremium.title');
if (isPremium) {
embed.description = t('cmd.tryPremium.currentlyActive');
} else if (await this.guildHadTrial(guild.id)) {
} else if (await this.memberHadTrial(message.author.id)) {
embed.description = t('cmd.tryPremium.alreadyUsed', {
prefix
});
Expand All @@ -53,17 +53,17 @@ export default class extends Command {
return this.sendReply(message, t('prompt.canceled'));
}

const subId = await this.client.db.savePremiumSubscription({
await this.client.db.savePremiumSubscription({
amount: 0,
maxGuilds: 1,
isFreeTier: true,
validUntil: validUntil.toDate(),
memberId: message.author.id,
reason: '!try-premium'
reason: ''
});
await this.client.db.savePremiumSubscriptionGuild({
guildId: guild.id,
subscriptionId: subId
memberId: message.author.id,
guildId: guild.id
});

this.client.cache.premium.flush(guild.id);
Expand All @@ -76,8 +76,8 @@ export default class extends Command {
return this.sendReply(message, embed);
}

private async guildHadTrial(guildId: string): Promise<boolean> {
const sub = await this.client.db.getFreePremiumSubscriptionGuildForGuild(guildId);
return !!sub;
private async memberHadTrial(memberId: string): Promise<boolean> {
const sub = await this.client.db.getPremiumSubscriptionsForMember(memberId, false, true);
return sub && sub.length > 0;
}
}
2 changes: 1 addition & 1 deletion src/framework/models/PremiumSubscription.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
export class PremiumSubscription {
public id: number;
public createdAt?: Date;
public updatedAt?: Date;
public amount: number;
public maxGuilds: number;
public isFreeTier: boolean;
public isPatreon: boolean;
public validUntil: Date;
public memberId: string;
public reason: string;
Expand Down
3 changes: 1 addition & 2 deletions src/framework/models/PremiumSubscriptionGuild.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
export class PremiumSubscriptionGuild {
public id: number;
public createdAt?: Date;
public updatedAt?: Date;
public guildId: string;
public subscriptionId: number;
public memberId: string;
}
Loading

0 comments on commit 23f54d6

Please sign in to comment.