Compare commits

..

10 Commits

Author SHA1 Message Date
8d340db915 rotate avatars from a folder with fs watch 2026-02-14 02:45:39 -05:00
fe7b30908e more funny random emojis 2026-01-16 23:29:03 -05:00
e2e6fa8e8a add fixupx option 2026-01-12 18:56:03 -05:00
d862a7aa37 honestly its probably best i can find out if i need to 2025-12-20 07:35:15 -05:00
d620b20ecf turn !status into /status now that theres proper commands 2025-12-20 07:31:45 -05:00
42405dd268 eval was funny but the bit is stale, this commit can always be reverted though 2025-12-20 07:11:23 -05:00
da4cc21558 comments 2025-12-20 07:10:25 -05:00
c36807e2aa simplify and dedup url convert logic 2025-12-20 06:55:32 -05:00
fd349e20fc better funny odds 2025-12-16 00:51:19 -05:00
4e80f42510 grin 2025-12-16 00:46:08 -05:00
7 changed files with 148 additions and 67 deletions

View File

@@ -1,3 +1,4 @@
node_modules
.git
config.json
avatars

1
.gitignore vendored
View File

@@ -142,3 +142,4 @@ dist
config.json
state.json
avatars

18
commands/status.js Normal file
View File

@@ -0,0 +1,18 @@
const { SlashCommandBuilder, ActivityType } = require('discord.js');
module.exports = {
data: new SlashCommandBuilder()
.setName('status')
.setDescription('Change the bot\'s status message')
.addStringOption(option =>
option.setName('message')
.setDescription('The new status message')
.setRequired(true)
),
async execute(interaction) {
const message = interaction.options.getString('message');
console.log(`${interaction.user.tag} is changing the status to ${message}`);
interaction.client.user.setActivity(message, { type: ActivityType.Custom });
await interaction.reply({ content: `Status updated to: ${message}`, ephemeral: true });
},
};

View File

@@ -1,6 +1,7 @@
{
"token": "bot token go here",
"status": "looking for x.com links",
"avatarInterval": 15,
"parentsAndOrGuardians": [
"230659159450845195",
"297983197990354944"

View File

@@ -4,3 +4,4 @@ services:
build: .
volumes:
- ./config.json:/usr/src/bot/config.json
- ./avatars:/usr/src/bot/avatars

104
index.js
View File

@@ -17,6 +17,7 @@ const client = new Client({
]
});
// build the button below the message that swaps the URL
const { ActionRowBuilder, ButtonBuilder, ButtonStyle } = require('discord.js');
const swapRow = new ActionRowBuilder()
.addComponents(
@@ -26,35 +27,38 @@ const swapRow = new ActionRowBuilder()
.setStyle(ButtonStyle.Secondary),
);
// the main function that swaps the URL, takes
function convertURL(url, regex, domain) {
const match = url.match(regex);
if (match) {
console.log(`Converting ${url} to ${domain}`)
return `https://${domain}/${match[1]}/status/${match[2]}`;
}
}
// called by the swap button, swaps girlcock links to fxtwitter, and back again if needed
function swapify(url) {
const girlcockRegex = /https?:\/\/girlcockx\.com\/(.*?)\/status\/(\d+)/;
const fxtwitterRegex = /https?:\/\/fxtwitter\.com\/(.*?)\/status\/(\d+)/;
let link = '';
if (url.match(girlcockRegex)) {
const original = url.match(girlcockRegex)[0];
const profile = url.match(girlcockRegex)[1];
const stub = url.match(girlcockRegex)[2];
console.log(`Button: Asked to swap ${url}, fxtwittering it`);
link = `https://fxtwitter.com/${profile}/status/${stub}`;
} else if (url.match(fxtwitterRegex)) {
const original = url.match(fxtwitterRegex)[0];
const profile = url.match(fxtwitterRegex)[1];
const stub = url.match(fxtwitterRegex)[2];
console.log(`Button: Asked to swap ${url}, cocking it again`);
link = `https://girlcockx.com/${profile}/status/${stub}`;
}
return link;
const fixupxRegex = /https?:\/\/fixupx\.com\/(.*?)\/status\/(\d+)/;
if (url.match(girlcockRegex)) return convertURL(url, girlcockRegex, "fxtwitter.com");
if (url.match(fxtwitterRegex)) return convertURL(url, fxtwitterRegex, "fixupx.com");
if (url.match(fixupxRegex)) return convertURL(url, fixupxRegex, "girlcockx.com");
// if we got this far, somethings not right but we'll try twitter before giving up
const twitterRegex = /https?:\/\/x\.com\/(.*?)\/status\/(\d+)/;
if (url.match(twitterRegex)) return convertURL(url, twitterRegex, "girlcockx.com");
return url; // give up, we'll just return the original URL
}
// what actually listens for the button press and calls swapify
client.on(Events.InteractionCreate, async interaction => {
if (interaction.isButton()) {
if (interaction.customId === 'swap_twitter') {
try {
const regex = /https?:\/\/girlcockx\.com\/(.*?)\/status\/(\d+)/;
await interaction.update(swapify(interaction.message.content));
} catch (error) {
} catch (error) { // honestly the swapify function has its own error handling and it should be fine but whatever
console.error(error);
await interaction.reply({ content: 'I couldn\'t swap them for some reason', ephemeral: true });
await interaction.reply({ content: "Something went wrong trying to swap the URL", ephemeral: true });
}
}
return;
@@ -64,56 +68,36 @@ client.on(Events.InteractionCreate, async interaction => {
client.once(Events.ClientReady, readyClient => {
console.log(`Discord: Connected as ${readyClient.user.tag}`);
client.user.setActivity(config.status, { type: ActivityType.Custom });
const avatarRotation = require('./lib/avatarRotation');
avatarRotation.init(client);
});
client.login(config.token);
client.on(Events.MessageCreate, message => {
// if we smell a twitter link, girlcock it!
const regexProfile = /https?:\/\/x\.com\/(.*?)\/status\/(\d+)/;
if (message.content.match(regexProfile)) {
const original = message.content.match(regexProfile)[0]
const profile = message.content.match(regexProfile)[1]
const stub = message.content.match(regexProfile)[2]
console.log(`Chat: Detected ${original}, girlcocking it!`);
const cocklink = `https://girlcockx.com/${profile}/status/${stub}`
console.log(`Girlcock: Converted to ${cocklink}`)
const twitterRegex = /https?:\/\/x\.com\/(.*?)\/status\/(\d+)/;
const regexProfile = message.content.match(twitterRegex);
if (regexProfile) {
const cocklink = convertURL(regexProfile[0], twitterRegex, "girlcockx.com")
message.channel.send({ content: cocklink, flags: MessageFlags.SuppressNotifications, components: [swapRow] })
message.suppressEmbeds().catch(err =>
// this next bit just cuts down the error to the important part, which will usually end up being "no permissions"
console.error(err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0])
console.error("Removing original embed failed: " + err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0])
)
}
// hehe an eval :3
if (message.content.startsWith('!eval') && config.parentsAndOrGuardians.includes(message.author.id)) {
let code = message.content.substring('!eval'.length).trim();
// yeah a machine may have wrote this part
const codeBlockRegex = /```(?:js)?\n?([\s\S]+)```/;
const match = code.match(codeBlockRegex);
if (match) {
code = match[1];
}
try {
eval(code);
} catch (err) {
console.error(err);
}
}
// hehe we do a little if stacking
if (message.content.startsWith('!status') && config.parentsAndOrGuardians.includes(message.author.id)) {
let status = message.content.substring('!status'.length).trim();
client.user.setActivity(status, { type: ActivityType.Custom });
config.status = status;
}
// wouldnt it be funny to react to 1 in like 10000 messages with emoji from a list
if (Math.random() < 0.0001 && !message.author.bot) {
// wouldnt it be funny to react to 1 in like 1000 messages with emoji from a list
if (Math.random() < 0.001 && !message.author.bot) {
const customEmojis = [
'Shitten:1430413059574206555',
'BLOWSUP:1430413011918651503'
'BLOWSUP:1430413011918651503',
'grin_cat:1445254917991436449',
'R_:1461939667657298081',
'crumble:1461939666121920605',
'jumble:1461939664306045008',
'scrumble:1461939662930055278',
'Chundle:1461939661541867713',
'chimgen:1461939660212408351'
];
const randomEmoji = customEmojis[Math.floor(Math.random() * customEmojis.length)];
message.react(randomEmoji);
@@ -141,17 +125,15 @@ const mpregs = [
'mpreg17:1434030048586760303',
'mpreg18:1434030067419451402',
'mpreg19:1434030085794435092'
]
];
client.on(Events.MessageReactionAdd, (reaction, user) => {
if (reaction.emoji.name === '🫃' && !user.bot) {
reaction.message.react('🫃')
reaction.message.react('🫃');
for (const mpreg of mpregs) {
reaction.message.react(mpreg).catch(err => console.error(err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0]))
reaction.message.react(mpreg).catch(err => console.error(err.stack?.split('\n')[0] || err.message || String(err).split('\n')[0]));
}
}
})
});
// command handling for ./commands
const fs = require('fs');

77
lib/avatarRotation.js Normal file
View File

@@ -0,0 +1,77 @@
const fs = require('fs');
const path = require('path');
const config = require('../config.json');
const AVATAR_DIR = path.join(__dirname, '../avatars');
let avatarFiles = [];
let currentAvatarIndex = 0;
let timer = null;
function loadAvatars() {
try {
if (!fs.existsSync(AVATAR_DIR)) {
fs.mkdirSync(AVATAR_DIR);
console.log('Avatars: Created avatars directory');
return;
}
const files = fs.readdirSync(AVATAR_DIR);
avatarFiles = files.filter(file => /\.(jpg|jpeg|png|gif|webp)$/i.test(file));
// shuffle
for (let i = avatarFiles.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[avatarFiles[i], avatarFiles[j]] = [avatarFiles[j], avatarFiles[i]];
}
console.log(`Avatars: Loaded ${avatarFiles.length} avatars`);
currentAvatarIndex = 0; // Reset index on reload
} catch (err) {
console.error('Avatars: Error loading avatars:', err);
}
}
function rotateAvatar(client) {
if (avatarFiles.length === 0) return;
const avatarFile = avatarFiles[currentAvatarIndex];
const avatarPath = path.join(AVATAR_DIR, avatarFile);
try {
client.user.setAvatar(avatarPath);
console.log(`Avatars: Changed avatar to ${avatarFile}`);
} catch (err) {
console.error(`Avatars: Failed to set avatar to ${avatarFile}:`, err);
}
currentAvatarIndex = (currentAvatarIndex + 1) % avatarFiles.length;
}
function init(client) {
loadAvatars();
// Watch for changes in the avatars directory
let fsWait = null;
fs.watch(AVATAR_DIR, (eventType, filename) => {
if (filename) {
if (fsWait) clearTimeout(fsWait);
fsWait = setTimeout(() => {
console.log(`Avatars: Detected change in avatars directory`);
loadAvatars();
}, 1000);
}
});
const intervalMinutes = config.avatarInterval || 15;
const intervalMs = intervalMinutes * 60 * 1000;
console.log(`Avatars: Starting avatar rotation every ${intervalMinutes} minutes`);
// Initial rotation
rotateAvatar(client);
// make it wait every interval before rotating
timer = setInterval(() => rotateAvatar(client), intervalMs);
}
module.exports = { init };