Skip to content

Commit

Permalink
use sharp, ensure wordcloud drawing doesn't block main thread, remove…
Browse files Browse the repository at this point in the history
… size option (#30)

* use sharp, ensure wordcloud drawing doesn't block main thread

* lint

* add time interval
  • Loading branch information
AlecM33 authored Mar 21, 2024
1 parent 5d20680 commit 651e7e0
Show file tree
Hide file tree
Showing 5 changed files with 50 additions and 61 deletions.
7 changes: 0 additions & 7 deletions commands/wordcloud.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,6 @@ module.exports = {
data: new SlashCommandBuilder()
.setName('wordcloud')
.setDescription('Generate a colorful wordcloud from your server\'s quotes!')
.addIntegerOption(option =>
option.setName('size')
.setDescription('Size of the wordcloud - 1 (small), 2 (medium), or 3 (large)')
.setMinValue(1)
.setMaxValue(3)
.setRequired(false)
)
.addStringOption(option =>
option.setName('author')
.setDescription('Generate a wordcloud from a specific author')
Expand Down
8 changes: 2 additions & 6 deletions modules/constants.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
module.exports = {
MAX_QUOTE_LENGTH: 1500,
MAX_SEARCH_RESULTS: 100,
WORDCLOUD_SIZE_MAP: {
1: 'SIZE_SMALL',
2: 'SIZE_MEDIUM',
3: 'SIZE_LARGE'
},
MAX_WORDCLOUD_WORDS: 300,
WORDCLOUD_SIZE: 1000,
MAX_WORDCLOUD_WORDS: 100,
MAX_DISCORD_MESSAGE_LENGTH: 2000,
MAX_AUTHOR_LENGTH: 240,
MENTION_REGEX: /<@[&!0-9]+>|<#[0-9]+>/g,
Expand Down
85 changes: 43 additions & 42 deletions modules/interaction-handlers.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ const queries = require('../database/queries.js');
const { AttachmentBuilder } = require('discord.js');
const wordcloudConstructor = require('../modules/wordcloud-constructor.js');
const { JSDOM } = require('jsdom');
const canvas = require('canvas');
const sharp = require('sharp');
const constants = require('./constants.js');
const utilities = require('./utilities.js');

Expand Down Expand Up @@ -185,7 +185,8 @@ module.exports = {
wordcloudHandler: async (interaction) => {
console.info(`WORDCLOUD command invoked by guild: ${interaction.guildId}`);
await interaction.deferReply();
global.document = new JSDOM().window.document; // d3-cloud requires that document be defined in the global scope.
// TODO: d3-cloud requires that document be defined in the global scope. Long-term, this is a problem.
global.document = new JSDOM().window.document;
const author = interaction.options.getString('author')?.trim();
const quotesForCloud = author && author.length > 0
? await queries.getQuotesFromAuthor(author, interaction.guildId)
Expand All @@ -197,48 +198,48 @@ module.exports = {
});
return;
}
const wordsWithOccurrences = utilities.mapQuotesToFrequencies(quotesForCloud);
const constructor = await wordcloudConstructor;
const initializationResult = constructor.initialize(
wordsWithOccurrences
.sort((a, b) => a.frequency >= b.frequency ? -1 : 1)
.slice(0, constants.MAX_WORDCLOUD_WORDS),
constants.WORDCLOUD_SIZE_MAP[interaction.options.getInteger('size')] || 'SIZE_MEDIUM'
);
initializationResult.cloud.on('end', () => {
const d3 = constructor.draw(
initializationResult.cloud,
initializationResult.words,
global.document.body
try {
const wordsWithOccurrences = utilities.mapQuotesToFrequencies(quotesForCloud);
const constructor = await wordcloudConstructor;
const initializationResult = constructor.initialize(
wordsWithOccurrences
.sort((a, b) => a.frequency >= b.frequency ? -1 : 1)
.slice(0, constants.MAX_WORDCLOUD_WORDS),
constants.WORDCLOUD_SIZE
);
const img = new canvas.Image();
img.onload = async () => {
const myCanvas = canvas.createCanvas(
initializationResult.config[
constants.WORDCLOUD_SIZE_MAP[interaction.options.getInteger('size')] || 'SIZE_MEDIUM'
],
initializationResult.config[
constants.WORDCLOUD_SIZE_MAP[interaction.options.getInteger('size')] || 'SIZE_MEDIUM'
]
initializationResult.cloud.on('end', () => {
const d3 = constructor.draw(
initializationResult.cloud,
initializationResult.words,
global.document.body
);
const myContext = myCanvas.getContext('2d');
myContext.drawImage(img, 0, 0);
await interaction.followUp({
files: [new AttachmentBuilder(myCanvas.toBuffer('image/png'), { name: 'wordcloud.png' })],
content: author && author.length > 0
? 'Here\'s a wordcloud for quotes said by "' + author + '"!'
: 'Here\'s a wordcloud I generated from this server\'s quotes!'
});
global.document = null;
};
img.onerror = err => {
console.error(err);
global.document = null;
};
img.src = 'data:image/svg+xml;base64,' + btoa(
decodeURIComponent(encodeURIComponent(d3.select(global.document.body).node().innerHTML)));
});
initializationResult.cloud.start();
const buffer = Buffer.from(d3.select(global.document.body).node().innerHTML.toString());
sharp(buffer)
.resize(constants.WORDCLOUD_SIZE, constants.WORDCLOUD_SIZE)
.png()
.toBuffer()
.then(async (data) => {
await interaction.followUp({
files: [new AttachmentBuilder(data, { name: 'wordcloud.png' })],
content: author && author.length > 0
? 'Here\'s a wordcloud for quotes said by "' + author + '"!'
: 'Here\'s a wordcloud I generated from this server\'s quotes!'
});
})
.catch(async err => {
console.error(err);
await interaction.followUp({
content: responseMessages.GENERIC_INTERACTION_ERROR
});
});
});
initializationResult.cloud.start();
} catch (e) {
console.error(e);
await interaction.followUp({
content: responseMessages.GENERIC_INTERACTION_ERROR
});
}
},

authorsHandler: async (interaction, guildManager) => {
Expand Down
8 changes: 3 additions & 5 deletions modules/wordcloud-constructor.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@ const randomColor = require('randomcolor');
const CONFIG = {
FONT_SIZE_EXPONENT: 3,
MIN_FONT_SIZE: 25,
MAX_FONT_SIZE: 200,
SIZE_SMALL: 500,
SIZE_MEDIUM: 1000,
SIZE_LARGE: 1500,
MAX_FONT_SIZE: 100,
WORD_PADDING: 5,
WORD_ROTATION: 0,
COLORS: null
Expand All @@ -22,7 +19,7 @@ module.exports = import('d3').then((d3) => {
count: 3
});
wordcloud
.size([CONFIG[size], CONFIG[size]])
.size([size, size])
.words(wordsWithOccurrences = wordsWithOccurrences.map(function (d) {
return {
text: d.word,
Expand All @@ -32,6 +29,7 @@ module.exports = import('d3').then((d3) => {
}))
.padding(CONFIG.WORD_PADDING)
.rotate(CONFIG.WORD_ROTATION)
.timeInterval(10)
.fontSize(function (d) {
return d.size;
});
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,8 @@
"discord.js": "^14.14.1",
"jsdom": "^21.1.1",
"pg": "^8.5.1",
"randomcolor": "^0.6.2"
"randomcolor": "^0.6.2",
"sharp": "^0.33.2"
},
"devDependencies": {
"@babel/core": "^7.16.7",
Expand Down

0 comments on commit 651e7e0

Please sign in to comment.