diff --git a/commands/fun/tweet.js b/commands/fun/tweet.js new file mode 100644 index 0000000..c90ceec --- /dev/null +++ b/commands/fun/tweet.js @@ -0,0 +1,194 @@ +import { SlashCommandBuilder } from '@discordjs/builders'; +import { MessageEmbed } from 'discord.js'; +import Twit from 'twit'; +import fetch from 'node-fetch'; +import os from 'node:os'; +import fs from 'node:fs'; + +import db from '../../models/index.js'; +import wordToCensor from '../../json/censor.json' assert {type: 'json'};; +import dotenv from 'dotenv'; +dotenv.config(); +const { twiConsumer, twiConsumerSecret, twiToken, twiTokenSecret, twiChannel, twiLogChannel } = process.env; + +const Blacklists = db.Blacklists; + +export default { + data: new SlashCommandBuilder() + .setName('tweet') + .setDescription('Send tweet from Haha yes twitter account. Please do not use it for advertisement and keep it english') + .addStringOption(option => + option.setName('content') + .setDescription('The content of the tweet you want to send me.') + .setRequired(false)) + .addAttachmentOption(option => + option.setName('image') + .setDescription('Optional attachment (Image only.)') + .setRequired(false)), + async execute(interaction) { + await interaction.deferReply({ ephemeral: false }); + const client = interaction.client; + let tweet = interaction.options.getString('content'); + const attachment = interaction.options.getAttachment('image'); + const date = new Date(); + // If account is less than 6 months old don't accept the tweet ( alt prevention ) + if (interaction.user.createdAt > date.setMonth(date.getMonth() - 6)) { + await interaction.editReply({ content: 'Your account is too new to be able to use this command!' }); + return; + } + + // If account is less than 1 year old don't accept attachment + if (attachment && interaction.user.createdAt > date.setFullYear(date.getFullYear() - 1)) { + await interaction.editReply({ content: 'Your account need to be 1 year or older to be able to send attachment!' }); + return; + } + + // remove zero width space + if (tweet) { + tweet = tweet.replace('​', ''); + } + + if (tweet) { + // Detect banned word (Blacklist the user directly) + if (wordToCensor.includes(tweet) || wordToCensor.includes(tweet.substr(0, tweet.length - 1)) || wordToCensor.includes(tweet.substr(1, tweet.length))) { + const body = { type:'tweet', uid: interaction.user.id, reason: 'Automatic ban from banned word.' }; + Blacklists.create(body); + + await interaction.editReply({ content: 'Sike, you just posted cringe! Enjoy the blacklist :)' }); + return; + } + + // Very simple link detection + if (new RegExp('([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+@)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?').test(tweet) && !tweet.includes('twitter.com')) { + await interaction.editReply({ content: 'You may not tweet links outside of twitter.com' }); + return; + } + // Do not allow discord invites + if (tweet.includes('discord.gg') || tweet.includes('discord.com/invite/')) { + await interaction.editReply({ content: 'No discord invite allowed.' }); + return; + } + } + + const T = new Twit({ + consumer_key: twiConsumer, + consumer_secret: twiConsumerSecret, + access_token: twiToken, + access_token_secret: twiTokenSecret, + }); + + try { + // Make sure there is an attachment and if its an image + if (attachment) { + if (attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png') || attachment.name.toLowerCase().endsWith('.gif')) { + fetch(attachment.url) + .then(res => { + const dest = fs.createWriteStream(`${os.tmpdir()}/${attachment.name}`); + res.body.pipe(dest); + dest.on('finish', () => { + const file = fs.statSync(`${os.tmpdir()}/${attachment.name}`); + const fileSize = file.size / 1000000.0; + + if ((attachment.name.toLowerCase().endsWith('.jpg') || attachment.name.toLowerCase().endsWith('.png')) && fileSize > 5) { + return interaction.editReply({ content: 'Images can\'t be larger than 5 MB!' }); + } + else if (attachment.name.toLowerCase().endsWith('.gif') && fileSize > 15) { + return interaction.editReply({ content: 'Gifs can\'t be larger than 15 MB!' }); + } + + const b64Image = fs.readFileSync(`${os.tmpdir()}/${attachment.name}`, { encoding: 'base64' }); + T.post('media/upload', { media_data: b64Image }, function(err, data) { + if (err) { + console.log('OH NO AN ERROR!!!!!!!'); + console.error(err); + return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue! ' }); + } + else { + Tweet(data); + } + }); + }); + }); + } + else { + await interaction.editReply({ content: 'File type not supported, you can only send jpg/png/gif' }); + return; + } + } + else { + Tweet(); + } + } + catch (err) { + console.error(err); + await interaction.editReply({ content: 'Oh no, an error has occurred :(' }); + return; + } + + function Tweet(data) { + let options = { + status: tweet, + }; + + if (data && tweet) { + options = { + status: tweet, + media_ids: new Array(data.media_id_string), + }; + } + else if (data) { + options = { + media_ids: new Array(data.media_id_string), + }; + } + + T.post('statuses/update', options, function(err, response) { + if (err) { + // Rate limit exceeded + if (err.code == 88) return interaction.editReply({ content: err.interaction }); + // Tweet needs to be a bit shorter. + if (err.code == 186) return interaction.editReply({ content: `${err.interaction} Your interaction was ${tweet.length} characters, you need to remove ${tweet.length - 280} characters (This count may be inaccurate if your interaction contained link)` }); + // Status is a duplicate. + if (err.code == 187) return interaction.editReply({ content: err.interaction }); + // To protect our users from spam and other malicious activity, this account is temporarily locked. + if (err.code == 326) return interaction.editReply({ content: err.interaction }); + console.error('OH NO!!!!'); + console.error(err); + return interaction.editReply({ content: 'OH NO!!! AN ERROR HAS occurred!!! please hold on while i find what\'s causing this issue!' }); + } + + const tweetid = response.id_str; + const FunnyWords = ['oppaGangnamStyle', '69', '420', 'cum', 'funnyMan', 'GUCCISmartToilet', 'TwitterForClowns', 'fart', 'mcDotnamejeffDotxyz', 'ok', 'hi', 'howAreYou', 'WhatsNinePlusTen', '21']; + const TweetLink = `https://twitter.com/${FunnyWords[Math.floor((Math.random() * FunnyWords.length))]}/status/${tweetid}`; + + // Im too lazy for now to make an entry in config.json + let channel = interaction.client.channels.resolve(twiChannel); + channel.send(TweetLink); + + const Embed = new MessageEmbed() + .setAuthor({ name: interaction.user.username, iconURL: interaction.user.displayAvatarURL() }) + .setDescription(tweet) + .addField('Link', TweetLink, true) + .addField('Tweet ID', tweetid, true) + .addField('Channel ID', interaction.channel.id, true) + .addField('Messsage ID', interaction.id, true) + .addField('Author', `${interaction.user.username} (${interaction.user.id})`, true) + .setTimestamp(); + + if (interaction.guild) { + Embed.addField('Guild', `${interaction.guild.name} (${interaction.guild.id})`, true); + Embed.addField('message link', `https://discord.com/channels/${interaction.guild.id}/${interaction.channel.id}/${interaction.id}`); + } + else { + Embed.addField('message link', `https://discord.com/channels/@me/${interaction.channel.id}/${interaction.id}`); + } + + if (attachment) Embed.setImage(attachment.url); + + channel = interaction.client.channels.resolve(twiLogChannel); + channel.send({ embeds: [Embed] }); + return interaction.editReply({ content: `Go see ur epic tweet ${TweetLink}` }); + }); + } + }, +}; diff --git a/deploy-commands.cjs b/deploy-commands.cjs index dd38ee1..c916301 100644 --- a/deploy-commands.cjs +++ b/deploy-commands.cjs @@ -49,6 +49,18 @@ const commands = [ new SlashCommandBuilder() .setName('inspirobot') .setDescription('Get an image from inspirobot'), + + new SlashCommandBuilder() + .setName('tweet') + .setDescription('Send tweet from Haha yes twitter account. Please do not use it for advertisement and keep it english') + .addStringOption(option => + option.setName('content') + .setDescription('The content of the tweet you want to send me.') + .setRequired(false)) + .addAttachmentOption(option => + option.setName('image') + .setDescription('Optional attachment (Image only.)') + .setRequired(false)), ] .map(command => command.toJSON()); @@ -59,6 +71,11 @@ if (process.argv[2] === 'global') { .then(() => console.log('Successfully registered application commands globally.')) .catch(console.error); } +else if (process.argv[2] === 'delete') { + rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: [] }) + .then(() => console.log('Successfully deleted all guild commands.')) + .catch(console.error); +} rest.put(Routes.applicationGuildCommands(clientId, guildId), { body: commands }) .then(() => console.log(`Successfully registered application commands for the guild ${guildId}.`)) diff --git a/json/censor.json b/json/censor.json new file mode 100644 index 0000000..f7e5c08 --- /dev/null +++ b/json/censor.json @@ -0,0 +1 @@ +["1488","14/88","14 88","niggar", "nigger","nigar", "kys", "kill yourself", "faggot", "fag", "kill ur self","n\ni\ng\ng\ne\nr","n i g g e r","we must secure the existance of our people and a future for white children."] \ No newline at end of file