Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: custom webhooks #1486

Open
wants to merge 12 commits into
base: development
Choose a base branch
from
4 changes: 4 additions & 0 deletions src/classes/MyHandler/MyHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ import filterAxiosError from '@tf2autobot/filter-axios-error';
import sendTf2SystemMessage from '../../lib/DiscordWebhook/sendTf2SystemMessage';
import sendTf2DisplayNotification from '../../lib/DiscordWebhook/sendTf2DisplayNotification';
import sendTf2ItemBroadcast from '../../lib/DiscordWebhook/sendTf2ItemBroadcast';
import WebhookHandler from '../../lib/Webhooks/Webhook';

const filterReasons = (reasons: string[]) => {
const filtered = new Set(reasons);
Expand All @@ -65,6 +66,8 @@ export default class MyHandler extends Handler {

readonly cartQueue: CartQueue;

readonly webhookHandler: WebhookHandler;

private groupsStore: string[];

private get opt(): Options {
Expand Down Expand Up @@ -203,6 +206,7 @@ export default class MyHandler extends Handler {
this.commands = new Commands(bot, priceSource);
this.cartQueue = new CartQueue(bot);
this.autokeys = new Autokeys(bot);
this.webhookHandler = new WebhookHandler(bot);

this.paths = genPaths(this.opt.steamAccountName);

Expand Down
9 changes: 9 additions & 0 deletions src/classes/MyHandler/offer/accepted/processAccepted.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,15 @@ export default function processAccepted(
offer.data('processOfferTime')) as number;
const timeTakenToCounterOffer = offer.data('processCounterTime') as number | undefined;

bot.handler.webhookHandler.tradeSummery(
offer,
accepted,
bot,
timeTakenToComplete,
timeTakenToProcessOrConstruct,
timeTakenToCounterOffer,
isOfferSent
);
if (isWebhookEnabled) {
void sendTradeSummary(
offer,
Expand Down
12 changes: 12 additions & 0 deletions src/classes/Options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2116,6 +2116,17 @@ interface StrangeParts {
'Robots Killed During Halloween'?: string;
}

interface Webhooks {
tradeSummary?: Omit<TradeSummaryDW, 'mentionOwner' | 'misc'>;
declinedTrade?: DeclinedTradeDW;
messages?: Omit<MessagesDW, 'isMention'>;
priceUpdate?: PriceUpdateDW;
sendAlert?: Omit<SendAlertStatsDW, 'isMention'>;
sendStats?: SendStatsDW;
sendTf2Events?: SendTf2Events;
webhookSecret?: string;
}

// ------------ JsonOptions ------------

export interface JsonOptions {
Expand All @@ -2139,6 +2150,7 @@ export interface JsonOptions {
customMessage?: CustomMessage;
commands?: Commands;
detailsExtra?: DetailsExtra;
webhooks?: Webhooks;
}

export default interface Options extends JsonOptions {
Expand Down
154 changes: 154 additions & 0 deletions src/lib/Webhooks/Webhook.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import filterAxiosError from '@tf2autobot/filter-axios-error';
import axios, { AxiosError } from 'axios';
import Bot from '../../classes/Bot';
import { TradeSummery } from './tradeSummery';
import * as tradeSummery from './tradeSummery';

// eslint-disable-next-line @typescript-eslint/no-empty-interface
interface WebhookHandler extends TradeSummery {}

export enum WebhookType {
priceUpdate = 'priceUpdate',
tradeSummary = 'tradeSummary',
declinedTrade = 'declinedTrade',
sendStats = 'sendStats',
tf2SystemMessage = 'tf2SystemMessage',
tf2ItemBroadcast = 'tf2ItemBroadcast',
tf2DisplayNotification = 'tf2DisplayNotification',
sendAlert = 'sendAlert',
messages = 'messages'
}

class WebhookHandler {
// Lets make a list of what is enabled
// and what is not

// WebhookHandler is a class that will handle
// all the webhooks that are enabled in the config
// and will send them to the correct place
private enablePriceListUpdate: boolean;

private enableTradeSummary: boolean;

private enableTradeDeclined: boolean;

private enableStats: boolean;

private enableTf2SystemMessage: boolean;

private enableTf2ItemBroadcast: boolean;

private enableTf2DisplayNotification: boolean;

private enableAlert: boolean;

private enableMessages: boolean;

private webhookSecret: string;

constructor(private readonly bot: Bot) {
this.init();
}

private init() {
// Lets get webhook settings from bot
const { webhooks } = this.bot.options;
this.enablePriceListUpdate = webhooks.priceUpdate.enable;
this.enableTradeSummary = webhooks.tradeSummary.enable;
this.enableTradeDeclined = webhooks.declinedTrade.enable;
this.enableStats = webhooks.sendStats.enable;
this.enableTf2SystemMessage = webhooks.sendTf2Events.systemMessage.enable;
this.enableTf2ItemBroadcast = webhooks.sendTf2Events.itemBroadcast.enable;
this.enableTf2DisplayNotification = webhooks.sendTf2Events.displayNotification.enable;
this.enableAlert = webhooks.sendAlert.enable;
this.enableMessages = webhooks.messages.enable;
this.webhookSecret = webhooks.webhookSecret;
}

private getUrl(type: WebhookType) {
switch (type) {
case WebhookType.priceUpdate:
return this.bot.options.webhooks.priceUpdate.url;
case WebhookType.tradeSummary:
return this.bot.options.webhooks.tradeSummary.url;
case WebhookType.declinedTrade:
return this.bot.options.webhooks.declinedTrade.url;
case WebhookType.sendStats:
return this.bot.options.webhooks.sendStats.url;
case WebhookType.tf2DisplayNotification:
return this.bot.options.webhooks.sendTf2Events.displayNotification.url;
case WebhookType.tf2ItemBroadcast:
return this.bot.options.webhooks.sendTf2Events.itemBroadcast.url;
case WebhookType.tf2SystemMessage:
return this.bot.options.webhooks.sendTf2Events.systemMessage.url;
case WebhookType.sendAlert:
return this.bot.options.webhooks.sendAlert.url.main;
case WebhookType.messages:
return this.bot.options.webhooks.messages.url;
default:
return null;
}
}

private isEnabled(type: WebhookType) {
switch (type) {
case WebhookType.priceUpdate:
return this.enablePriceListUpdate;
case WebhookType.tradeSummary:
return this.enableTradeSummary;
case WebhookType.declinedTrade:
return this.enableTradeDeclined;
case WebhookType.sendStats:
return this.enableStats;
case WebhookType.tf2DisplayNotification:
return this.enableTf2DisplayNotification;
case WebhookType.tf2ItemBroadcast:
return this.enableTf2ItemBroadcast;
case WebhookType.tf2SystemMessage:
return this.enableTf2SystemMessage;
case WebhookType.sendAlert:
return this.enableAlert;
case WebhookType.messages:
return this.enableMessages;
default:
return false;
}
}

async sendWebhook(type: WebhookType, data: Record<any, any>) {
return new Promise((resolve, reject) => {
const url = this.getUrl(type);

const urls = Array.isArray(url) ? url : [url];
// Lets check if the webhook is enabled
if (this.isEnabled(type)) {
// Lets send the webhook
for (const url of urls) {
void axios({
method: 'POST',
url,
headers: {
'Content-Type': 'application/json',
'x-webhook-secret': this.webhookSecret ?? ''
},
data: JSON.stringify(data)
})
.then(() => {
resolve();
})
.catch((err: AxiosError) => {
reject({ err: filterAxiosError(err) });
});
}
} else {
// Webhook is disabled
resolve();
}
});
}
}

// Assign tradeSummery to WebhookHandler
Object.assign(WebhookHandler.prototype, tradeSummery);

export default WebhookHandler;
1 change: 1 addition & 0 deletions src/lib/Webhooks/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export default './Webhooks';
74 changes: 74 additions & 0 deletions src/lib/Webhooks/tradeSummery.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
import { ItemsDict, TradeOffer } from '@tf2autobot/tradeoffer-manager';
import WebhookHandler, { WebhookType } from './Webhook';
import Bot from '../../classes/Bot';
import * as t from '../tools/export';
import { getPartnerDetails } from '../DiscordWebhook/utils';

export interface TradeSummery {
tradeSummery: (
offer: TradeOffer,
accepted: Accepted,
bot: Bot,
timeTakenToComplete: number,
timeTakenToProcessOrConstruct: number,
timeTakenToCounterOffer: number | undefined,
isOfferSent: boolean | undefined
) => void;
}

interface Accepted {
invalidItems: string[];
disabledItems: string[];
overstocked: string[];
understocked: string[];
highValue: string[];
isMention: boolean;
}

export async function tradeSummery(
this: WebhookHandler,
offer: TradeOffer,
accepted: Accepted,
bot: Bot,
timeTakenToComplete: number,
timeTakenToProcessOrConstruct: number,
timeTakenToCounterOffer: number | undefined,
isOfferSent: boolean | undefined
) {
const value = t.valueDiff(offer);

const dict = offer.data('dict') as ItemsDict;

const details = await getPartnerDetails(offer, bot);

const links = t.generateLinks(offer.partner.toString());

const slots = bot.tf2.backpackSlots;
const autokeys = bot.handler.autokeys;
const status = autokeys.getOverallStatus;

const message = t.replace.specialChar(offer.message);
const itemsToGiveCount = offer.itemsToGive.length;
const isAdmin = bot.isAdmin(offer.partner);
const isDonate = t.isDonate(offer) && itemsToGiveCount === 0 && !isAdmin;

const botInfo = bot.handler.getBotInfo;

const data = {
bot: botInfo,
value,
links,
slots,
status,
message,
isDonate,
details,
dict,
timeTakenToComplete,
timeTakenToProcessOrConstruct,
timeTakenToCounterOffer,
isOfferSent
};

void this.sendWebhook(WebhookType.tradeSummary, data);
}
4 changes: 3 additions & 1 deletion src/lib/tools/export.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import summarize, { summarizeToChat } from './summarizeOffer';
import profit from './profit';
import itemStats from './itemStats';
import testPriceKey from './testPriceKey';
import isDonate from './isDonate';

export {
getHighValueItems,
Expand All @@ -28,5 +29,6 @@ export {
convertTime,
profit,
itemStats,
testPriceKey
testPriceKey,
isDonate
};
38 changes: 38 additions & 0 deletions src/lib/tools/isDonate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { TradeOffer } from '@tf2autobot/tradeoffer-manager';

export default function isDonate(offer: TradeOffer) {
// Check if the message is a donation
const offerMessage = offer.message.toLowerCase();
const isGift = [
'gift',
'donat', // So that 'donate' or 'donation' will also be accepted
'tip', // All others are synonyms
'tribute',
'souvenir',
'favor',
'giveaway',
'bonus',
'grant',
'bounty',
'present',
'contribution',
'award',
'nice', // Up until here actually
'happy', // All below people might also use
'thank',
'goo', // For 'good', 'goodie' or anything else
'awesome',
'rep',
'joy',
'cute', // right?
'enjoy',
'prize',
'free',
'tnx',
'ty',
'love',
'<3'
].some(word => offerMessage.includes(word));

return isGift;
}
Loading