Skip to content

Commit

Permalink
bot updates for discord
Browse files Browse the repository at this point in the history
  • Loading branch information
chopperdaddy committed Apr 15, 2023
1 parent 9278241 commit 0a42dcf
Show file tree
Hide file tree
Showing 9 changed files with 1,583 additions and 947 deletions.
7 changes: 5 additions & 2 deletions twitter-bot/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"scripts": {
"prebuild": "rimraf dist",
"build": "nest build && cp -R src/static dist",
"start": "nest start",
"start:dev": "nest start --watch",
"start": "cp -R src/static dist && nest start",
"start:dev": "cp -R src/static dist && nest start --watch",
"start:debug": "nest start --debug --watch",
"start:prod": "node dist/main",
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
Expand All @@ -20,11 +20,14 @@
"test:e2e": "jest --config ./test/jest-e2e.json"
},
"dependencies": {
"@nestjs/axios": "^2.0.0",
"@nestjs/common": "^8.0.0",
"@nestjs/core": "^8.0.0",
"@nestjs/platform-express": "^8.0.0",
"@supabase/supabase-js": "^2.20.0",
"canvas": "^2.9.3",
"date-fns": "^2.29.1",
"discord.js": "13.3.0",
"dotenv": "^10.0.0",
"ethers": "^5.6.9",
"reflect-metadata": "^0.1.13",
Expand Down
9 changes: 7 additions & 2 deletions twitter-bot/src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { Module } from '@nestjs/common';
import { HttpModule } from '@nestjs/axios';

import { AppService } from './app.service';
import { ImageService } from './services/image.service';
import { TweetService } from './services/tweet.service';
import { Web3Service } from './services/web3.service';
import { DiscordService } from './services/discord.service';
import { SupabaseService } from './services/supabase.service';

@Module({
imports: [],
imports: [HttpModule],
controllers: [],
providers: [
AppService,
TweetService,
Web3Service,
ImageService
ImageService,
DiscordService,
SupabaseService
],
})
export class AppModule {}
40 changes: 24 additions & 16 deletions twitter-bot/src/app.service.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import { Injectable } from '@nestjs/common';
import { Injectable, Logger } from '@nestjs/common';

import { Web3Service } from './services/web3.service';
import { TweetService } from './services/tweet.service';
import { ImageService } from './services/image.service';

// import { writeFile } from 'fs/promises';
import { DiscordService } from './services/discord.service';

import { format, fromUnixTime } from 'date-fns'
import { BigNumber, Event } from 'ethers';

import { Message } from './interfaces/message.interface';

import dotenv from 'dotenv';
dotenv.config();

const auctionURL = process.env.NODE_ENV === 'prod' ? 'https://phunks.auction' : 'https://testnet.phunks.auction';

interface Time {
days: string,
hours: string,
Expand All @@ -31,7 +30,8 @@ export class AppService {
constructor(
private readonly web3Svc: Web3Service,
private readonly imgSvc: ImageService,
private readonly twSvc: TweetService
private readonly twSvc: TweetService,
private readonly discordSvc: DiscordService
) {

this.web3Svc.auctionHouseContract.on('AuctionCreated', (
Expand All @@ -57,12 +57,16 @@ export class AppService {
// console.log({ phunkId, id: Number(auctionId), sender, value, extended, event });
});


this.web3Svc.auctionHouseContract['auction']().then(({ endTime }) => {
this.setTimers(endTime);
});
}

sendNotification(data: Message): void {
// this.twSvc.tweet({ text: data.text, image: data.image });
this.discordSvc.postMessage(data);
}

async onAuctionCreated(
phunkId: BigNumber,
auctionId: BigNumber,
Expand All @@ -77,12 +81,14 @@ export class AppService {
const timeLeft = this.convertTimeLeft(endTime);

const image = await this.imgSvc.createImage(this.pad(phunkId.toString()));

const receipt = await event.getTransactionReceipt();
const ens = await this.web3Svc.provider.lookupAddress(receipt?.from);

const text = `📢 Phunk #${phunkId.toString()} has been put up for auction\n\nStarted by: ${ens ?? this.shortenAddress(receipt?.from)}\nAuction Ends: ${format(date, 'PPpp')} GMT\n\nTime remaining:\n${timeLeft.days !== '00' ? timeLeft.days + ' days\n' : ''}${timeLeft.hours !== '00' ? timeLeft.hours + ' hours\n' : ''}${timeLeft.minutes !== '00' ? timeLeft.minutes + ' minutes\n' : ''}${timeLeft.seconds !== '00' ? timeLeft.seconds + ' seconds\n\n' : ''}${auctionURL}`;
const title = `📢 Phunk #${phunkId.toString()} has been put up for auction!`;
const text = `Started by: ${ens ?? this.shortenAddress(receipt?.from)}\nAuction Ends: ${format(date, 'PPpp')} GMT\n\nTime remaining:\n${timeLeft.days !== '00' ? timeLeft.days + ' days\n' : ''}${timeLeft.hours !== '00' ? timeLeft.hours + ' hours\n' : ''}${timeLeft.minutes !== '00' ? timeLeft.minutes + ' minutes\n' : ''}${timeLeft.seconds !== '00' ? timeLeft.seconds + ' seconds\n\n' : ''}`;

this.twSvc.tweet({ text, image });
this.sendNotification({ text, title, image, phunkId: phunkId.toString() });

// await writeFile(`./phunk${this.pad(phunkId.toString())}.png`, image, 'base64');
}
Expand All @@ -104,9 +110,10 @@ export class AppService {
const image = await this.imgSvc.createImage(this.pad(phunkId.toString()));
const ens = await this.web3Svc.provider.lookupAddress(sender);

const text = `📢 Phunk #${phunkId.toString()} has a new bid of Ξ${this.web3Svc.weiToEth(value)}\n\nFrom: ${ens ?? this.shortenAddress(sender)}\n\nTime remaining:\n${timeLeft.days !== '00' ? timeLeft.days + ' days\n' : ''}${timeLeft.hours !== '00' ? timeLeft.hours + ' hours\n' : ''}${timeLeft.minutes !== '00' ? timeLeft.minutes + ' minutes\n' : ''}${timeLeft.seconds !== '00' ? timeLeft.seconds + ' seconds\n\n' : ''}${auctionURL}`;
const title = `📢 Phunk #${phunkId.toString()} has a new bid of Ξ${this.web3Svc.weiToEth(value)}!`;
const text = `From: ${ens ?? this.shortenAddress(sender)}\n\nTime remaining:\n${timeLeft.days !== '00' ? timeLeft.days + ' days\n' : ''}${timeLeft.hours !== '00' ? timeLeft.hours + ' hours\n' : ''}${timeLeft.minutes !== '00' ? timeLeft.minutes + ' minutes\n' : ''}${timeLeft.seconds !== '00' ? timeLeft.seconds + ' seconds\n\n' : ''}`;

this.twSvc.tweet({ text, image });
this.sendNotification({ text, title, image, phunkId: phunkId.toString() });

// await writeFile(`./phunk${this.pad(phunkId.toString())}.png`, image, 'base64');
}
Expand All @@ -118,9 +125,10 @@ export class AppService {

const image = await this.imgSvc.createImage(this.pad(phunkId.toString()));

const text = `📢 The auction for Phunk #${phunkId.toString()} is ending soon!\n\nTime remaining:\n${timeLeft.days !== '00' ? timeLeft.days + ' days\n' : ''}${timeLeft.hours !== '00' ? timeLeft.hours + ' hours\n' : ''}${timeLeft.minutes !== '00' ? timeLeft.minutes + ' minutes\n' : ''}${timeLeft.seconds !== '00' ? timeLeft.seconds + ' seconds\n\n' : ''}${auctionURL}`;
const title = `📢 The auction for Phunk #${phunkId.toString()} is ending soon!`;
const text = `Time remaining:\n${timeLeft.days !== '00' ? timeLeft.days + ' days\n' : ''}${timeLeft.hours !== '00' ? timeLeft.hours + ' hours\n' : ''}${timeLeft.minutes !== '00' ? timeLeft.minutes + ' minutes\n' : ''}${timeLeft.seconds !== '00' ? timeLeft.seconds + ' seconds\n\n' : ''}`;

this.twSvc.tweet({ text, image });
this.sendNotification({ text, title, image, phunkId: phunkId.toString() });
}

setTimers(endTime: BigNumber) {
Expand All @@ -140,17 +148,17 @@ export class AppService {

if (diff > 0) {
if (diff > time24) {
console.log('Starting 24 hour timer');
Logger.debug('Starting 24 hour timer');
this.timer24 = setTimeout(() => this.onTimer(), diff - time24);
}

if (diff > time6) {
console.log('Starting 6 hour timer');
Logger.debug('Starting 6 hour timer');
this.timer6 = setTimeout(() => this.onTimer(), diff - time6);
}

if (diff > time1) {
console.log('Starting 1 hour timer');
Logger.debug('Starting 1 hour timer');
this.timer1 = setTimeout(() => this.onTimer(), diff - time1);
}
}
Expand Down
6 changes: 6 additions & 0 deletions twitter-bot/src/interfaces/message.interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface Message {
title: string,
text: string,
image: { base64: string, color: string },
phunkId: string
}
142 changes: 142 additions & 0 deletions twitter-bot/src/services/discord.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
import { Injectable } from '@nestjs/common';
import { HttpService } from '@nestjs/axios';

import { SupabaseService } from './supabase.service';
import { ImageService } from './image.service';

import { Client, CommandInteraction, Intents, MessageAttachment, MessageEmbed, Permissions } from 'discord.js';
import { firstValueFrom } from 'rxjs';

import { Message } from 'src/interfaces/message.interface';

import dotenv from 'dotenv';
dotenv.config();

@Injectable()
export class DiscordService {
private readonly client: Client;

constructor(
private readonly http: HttpService,
private readonly supaSvc: SupabaseService,
private imgSvc: ImageService
) {
this.client = new Client({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_MESSAGES] });
this.initializeBot();
this.registerSlashCommands();
}

// Initialize the bot
async initializeBot() {
this.client.on('ready', () => {
console.log(`Logged in as ${this.client.user.tag}!`);
});

this.client.login(process.env.DISCORD_BOT_TOKEN);

this.client.on('interactionCreate', async (interaction) => {
if (!interaction.isCommand()) return;

if (interaction.commandName === 'setchannel') {
await this.setDesignatedChannel(interaction);
}
});

// this.testMessage();
}

// Set the designated channel for the bot to post in
async setDesignatedChannel(interaction: CommandInteraction<any>) {

if (!interaction.member) return;

const member = interaction.member;
if (!('permissions' in member)) return;

const memberPermissions = member.permissions instanceof Permissions ? member.permissions : new Permissions();
if (!memberPermissions.has(Permissions.FLAGS.MANAGE_CHANNELS)) {
await interaction.reply({
content: 'You do not have the required permissions to set the designated channel.',
ephemeral: true,
});
return;
}

const channelId = interaction.channelId;
const serverId = interaction.guildId;

await this.supaSvc.setChannelForServer(serverId, channelId);

console.log({ channelId, serverId });

await interaction.reply({
content: 'Designated channel set successfully!',
ephemeral: true,
});
}

async postMessage(data: Message) {

const channels = await this.supaSvc.getAllChannels();

// Create the attachment
const imageBuffer = Buffer.from(data.image.base64, 'base64');
const attachment = new MessageAttachment(imageBuffer, `${data.phunkId}.png`);

// Create the embed
const embed = new MessageEmbed()
.setColor(`#${data.image.color}`)
.setImage(`attachment://${data.phunkId}.png`)
.addFields({
name: data.title,
value: `\`\`\`${data.text}\`\`\``,
inline: true
});

// Loop through all channels and send the message
for (const channelId of channels) {
try {
const channel = await this.client.channels.fetch(channelId);
if (channel?.isText()) {
await channel.send({ embeds: [embed], files: [attachment] });
} else {
console.error(`Channel with ID ${channelId} not found or not a text channel.`);
}
} catch (error) {
console.error(`Error sending message to channel with ID ${channelId}:`, error);
}
}
}

// This only needs to be run once to register the slash commands
async registerSlashCommands() {
const commands = [
{
name: 'setchannel',
description: 'Set the designated channel for the bot to post in',
},
];

try {
console.log('Started refreshing application (/) commands.');

const url = `https://discord.com/api/v9/applications/${process.env.DISCORD_CLIENT_ID}/commands`;
const headers = { Authorization: `Bot ${process.env.DISCORD_BOT_TOKEN}` };

await firstValueFrom(this.http.put(url, commands, { headers }));

console.log('Successfully reloaded application (/) commands.');
} catch (error) {
console.error(error);
}
}

// async testMessage() {
// const image = await this.imgSvc.createImage('1060');
// const title = `📢 Phunk #1060 has been put up for auction!`;
// const text = `Started by: chopperdad.eth\nAuction Ends: ${new Date().toUTCString()}\n\nTime remaining:\n0 days\n0 hours\n0 minutes\n0 seconds`;

// this.postMessage({ text, title, image, phunkId: '1060' });
// }

}
17 changes: 10 additions & 7 deletions twitter-bot/src/services/image.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ImageService {
private readonly web3Svc: Web3Service
) {}

async createImage(phunkId: string): Promise<string> {
async createImage(phunkId: string): Promise<{ base64: string, color: string }> {

registerFont(path.join(__dirname, '../static/retro-computer.ttf'), { family: 'RetroComputer' });

Expand Down Expand Up @@ -181,25 +181,28 @@ export class ImageService {

const buffer = canvas.toBuffer('image/png');

return buffer.toString('base64');
return { base64: buffer.toString('base64'), color: tinyColor(color).toHex() };
}

// Get the "brightest" color from the punk data
getColor(punkData: string): string {

const colorGroups: any = {};
const phunkArr = punkData?.replace('0x', '').match(/.{1,8}/g) as string[];

phunkArr.map((color: any) => {
if (!colorGroups[color]) colorGroups[color] = 1;
colorGroups[color]++;
});

const hslColors = Object.keys(colorGroups).map((key: any) => tinyColor(key).toHsl());
const brightest = hslColors.sort((a: any, b: any) => (`${b.s}`.localeCompare(`${a.s}`) || b.l - a.l))[2];
const brightest = hslColors.sort((a: any, b: any) => {
if (b.s !== a.s) return b.s - a.s;
return b.l - a.l;
})[0];

return tinyColor(brightest).setAlpha(.15).toRgbString();
}

// Create SVG from punk data
async createPhunkSvg(punkData: string, width: number, height: number): Promise<any> {

Expand Down
Loading

0 comments on commit 0a42dcf

Please sign in to comment.