Add server partnership management system with request and end commands
Implement partnership management commands including request, list, pending, accept, reject, and end subcommands. Add a helper function to notify partner servers of partnership status changes. Replit-Commit-Author: Agent Replit-Commit-Session-Id: aed2e46d-25bb-4b73-81a1-bb9e8437c261 Replit-Commit-Checkpoint-Type: full_checkpoint Replit-Commit-Event-Id: bf03030c-b05b-43d7-9556-dd29f9fb7953 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/3bdfff67-975a-46ad-9845-fbb6b4a4c4b5/aed2e46d-25bb-4b73-81a1-bb9e8437c261/INbSSam Replit-Helium-Checkpoint-Created: true
This commit is contained in:
parent
16d8286cf8
commit
0931891163
1 changed files with 339 additions and 0 deletions
|
|
@ -69,6 +69,23 @@ module.exports = {
|
||||||
.addSubcommand(sub => sub.setName('leaderboard').setDescription('View global reputation leaderboard'))
|
.addSubcommand(sub => sub.setName('leaderboard').setDescription('View global reputation leaderboard'))
|
||||||
.addSubcommand(sub => sub.setName('profile').setDescription('View cross-server profile')
|
.addSubcommand(sub => sub.setName('profile').setDescription('View cross-server profile')
|
||||||
.addUserOption(opt => opt.setName('user').setDescription('User to lookup')))
|
.addUserOption(opt => opt.setName('user').setDescription('User to lookup')))
|
||||||
|
)
|
||||||
|
.addSubcommandGroup(group =>
|
||||||
|
group
|
||||||
|
.setName('partners')
|
||||||
|
.setDescription('Server partnership management')
|
||||||
|
.addSubcommand(sub => sub.setName('request').setDescription('Send a partnership request to another server')
|
||||||
|
.addStringOption(opt => opt.setName('server_id').setDescription('Server ID to partner with').setRequired(true))
|
||||||
|
.addStringOption(opt => opt.setName('message').setDescription('Partnership proposal message').setRequired(true).setMaxLength(500)))
|
||||||
|
.addSubcommand(sub => sub.setName('list').setDescription('View your server partnerships'))
|
||||||
|
.addSubcommand(sub => sub.setName('pending').setDescription('View pending partnership requests'))
|
||||||
|
.addSubcommand(sub => sub.setName('accept').setDescription('Accept a partnership request')
|
||||||
|
.addStringOption(opt => opt.setName('request_id').setDescription('Request ID to accept').setRequired(true)))
|
||||||
|
.addSubcommand(sub => sub.setName('reject').setDescription('Reject a partnership request')
|
||||||
|
.addStringOption(opt => opt.setName('request_id').setDescription('Request ID to reject').setRequired(true))
|
||||||
|
.addStringOption(opt => opt.setName('reason').setDescription('Rejection reason')))
|
||||||
|
.addSubcommand(sub => sub.setName('end').setDescription('End an existing partnership')
|
||||||
|
.addStringOption(opt => opt.setName('partner_id').setDescription('Partner server ID').setRequired(true)))
|
||||||
),
|
),
|
||||||
|
|
||||||
async execute(interaction, supabase, client) {
|
async execute(interaction, supabase, client) {
|
||||||
|
|
@ -94,6 +111,8 @@ module.exports = {
|
||||||
await handleMembership(interaction, supabase, client, subcommand);
|
await handleMembership(interaction, supabase, client, subcommand);
|
||||||
} else if (group === 'scouts') {
|
} else if (group === 'scouts') {
|
||||||
await handleScouts(interaction, supabase, client, subcommand);
|
await handleScouts(interaction, supabase, client, subcommand);
|
||||||
|
} else if (group === 'partners') {
|
||||||
|
await handlePartners(interaction, supabase, client, subcommand);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
@ -694,3 +713,323 @@ async function createBanAlerts(supabase, client, userId, severity) {
|
||||||
console.error('Error creating ban alerts:', err);
|
console.error('Error creating ban alerts:', err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function notifyPartnerServer(client, supabase, targetGuildId, embed) {
|
||||||
|
try {
|
||||||
|
const targetGuild = client.guilds.cache.get(targetGuildId);
|
||||||
|
if (!targetGuild) return;
|
||||||
|
|
||||||
|
const { data: config } = await supabase
|
||||||
|
.from('server_config')
|
||||||
|
.select('mod_log_channel')
|
||||||
|
.eq('guild_id', targetGuildId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (config?.mod_log_channel) {
|
||||||
|
const channel = targetGuild.channels.cache.get(config.mod_log_channel);
|
||||||
|
if (channel) {
|
||||||
|
await channel.send({ embeds: [embed] });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const systemChannel = targetGuild.systemChannel;
|
||||||
|
if (systemChannel) {
|
||||||
|
await systemChannel.send({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to notify partner server:', err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handlePartners(interaction, supabase, client, subcommand) {
|
||||||
|
if (!supabase) {
|
||||||
|
return interaction.reply({ content: 'Database not available.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!interaction.member.permissions.has(PermissionFlagsBits.Administrator)) {
|
||||||
|
return interaction.reply({ content: 'Only administrators can manage partnerships.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'request') {
|
||||||
|
const targetServerId = interaction.options.getString('server_id');
|
||||||
|
const message = interaction.options.getString('message');
|
||||||
|
|
||||||
|
if (targetServerId === interaction.guildId) {
|
||||||
|
return interaction.reply({ content: 'You cannot partner with your own server.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: targetServer } = await supabase
|
||||||
|
.from('federation_servers')
|
||||||
|
.select('guild_id, guild_name')
|
||||||
|
.eq('guild_id', targetServerId)
|
||||||
|
.eq('status', 'approved')
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (!targetServer) {
|
||||||
|
return interaction.reply({ content: 'Target server is not in the federation or not found.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data: existing } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.select('id, status')
|
||||||
|
.or(`and(requester_guild_id.eq.${interaction.guildId},target_guild_id.eq.${targetServerId}),and(requester_guild_id.eq.${targetServerId},target_guild_id.eq.${interaction.guildId})`)
|
||||||
|
.in('status', ['pending', 'accepted'])
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (existing) {
|
||||||
|
return interaction.reply({
|
||||||
|
content: `A partnership ${existing.status === 'accepted' ? 'already exists' : 'request is pending'} with this server.`,
|
||||||
|
ephemeral: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await supabase.from('server_partnerships').insert({
|
||||||
|
requester_guild_id: interaction.guildId,
|
||||||
|
requester_guild_name: interaction.guild.name,
|
||||||
|
target_guild_id: targetServerId,
|
||||||
|
target_guild_name: targetServer.guild_name,
|
||||||
|
message,
|
||||||
|
status: 'pending',
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
console.error('Partnership request error:', error);
|
||||||
|
return interaction.reply({ content: 'Failed to send partnership request.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0x00ff00)
|
||||||
|
.setTitle('Partnership Request Sent')
|
||||||
|
.setDescription(`Your partnership request has been sent to **${targetServer.guild_name}**!`)
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Your Message', value: message },
|
||||||
|
{ name: 'Status', value: 'Pending', inline: true }
|
||||||
|
)
|
||||||
|
.setFooter({ text: 'They will be notified of your request.' })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'list') {
|
||||||
|
const { data: partnerships } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.select('*')
|
||||||
|
.eq('status', 'accepted')
|
||||||
|
.or(`requester_guild_id.eq.${interaction.guildId},target_guild_id.eq.${interaction.guildId}`)
|
||||||
|
.order('accepted_at', { ascending: false });
|
||||||
|
|
||||||
|
if (!partnerships || partnerships.length === 0) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0x7c3aed)
|
||||||
|
.setTitle('Server Partnerships')
|
||||||
|
.setDescription('No active partnerships.\nUse `/federation partners request` to request one!')
|
||||||
|
.setTimestamp();
|
||||||
|
return interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const partnerList = partnerships.map((p, i) => {
|
||||||
|
const partnerName = p.requester_guild_id === interaction.guildId
|
||||||
|
? p.target_guild_name
|
||||||
|
: p.requester_guild_name;
|
||||||
|
const partnerId = p.requester_guild_id === interaction.guildId
|
||||||
|
? p.target_guild_id
|
||||||
|
: p.requester_guild_id;
|
||||||
|
const since = p.accepted_at ? new Date(p.accepted_at).toLocaleDateString() : 'Unknown';
|
||||||
|
return `${i + 1}. **${partnerName}** (ID: \`${partnerId}\`)\n Since: ${since}`;
|
||||||
|
}).join('\n\n');
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0x7c3aed)
|
||||||
|
.setTitle('Server Partnerships')
|
||||||
|
.setDescription(partnerList)
|
||||||
|
.setFooter({ text: `${partnerships.length} active partnership(s)` })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'pending') {
|
||||||
|
const { data: pending } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.select('*')
|
||||||
|
.eq('target_guild_id', interaction.guildId)
|
||||||
|
.eq('status', 'pending')
|
||||||
|
.order('created_at', { ascending: false });
|
||||||
|
|
||||||
|
if (!pending || pending.length === 0) {
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0x7c3aed)
|
||||||
|
.setTitle('Pending Partnership Requests')
|
||||||
|
.setDescription('No pending partnership requests.')
|
||||||
|
.setTimestamp();
|
||||||
|
return interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingList = pending.map(p => {
|
||||||
|
const date = new Date(p.created_at).toLocaleDateString();
|
||||||
|
return `**Request #${p.id}** from **${p.requester_guild_name}**\n` +
|
||||||
|
`> ${p.message?.substring(0, 100) || 'No message'}${p.message?.length > 100 ? '...' : ''}\n` +
|
||||||
|
`Received: ${date}`;
|
||||||
|
}).join('\n\n');
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0xffff00)
|
||||||
|
.setTitle('Pending Partnership Requests')
|
||||||
|
.setDescription(pendingList)
|
||||||
|
.addFields({
|
||||||
|
name: 'Actions',
|
||||||
|
value: '`/federation partners accept <request_id>` - Accept a request\n' +
|
||||||
|
'`/federation partners reject <request_id>` - Reject a request'
|
||||||
|
})
|
||||||
|
.setFooter({ text: `${pending.length} pending request(s)` })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'accept') {
|
||||||
|
const requestId = interaction.options.getString('request_id');
|
||||||
|
|
||||||
|
const { data: request } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', requestId)
|
||||||
|
.eq('target_guild_id', interaction.guildId)
|
||||||
|
.eq('status', 'pending')
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return interaction.reply({ content: 'Partnership request not found or not pending.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.update({
|
||||||
|
status: 'accepted',
|
||||||
|
accepted_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', requestId);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return interaction.reply({ content: 'Failed to accept partnership.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0x00ff00)
|
||||||
|
.setTitle('Partnership Accepted!')
|
||||||
|
.setDescription(`You are now partners with **${request.requester_guild_name}**!`)
|
||||||
|
.addFields(
|
||||||
|
{ name: 'Partner Server ID', value: `\`${request.requester_guild_id}\``, inline: true }
|
||||||
|
)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
|
||||||
|
const notifyEmbed = new EmbedBuilder()
|
||||||
|
.setColor(0x00ff00)
|
||||||
|
.setTitle('Partnership Request Accepted!')
|
||||||
|
.setDescription(`**${interaction.guild.name}** has accepted your partnership request!`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await notifyPartnerServer(client, supabase, request.requester_guild_id, notifyEmbed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'reject') {
|
||||||
|
const requestId = interaction.options.getString('request_id');
|
||||||
|
const reason = interaction.options.getString('reason') || 'No reason provided';
|
||||||
|
|
||||||
|
const { data: request } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.select('*')
|
||||||
|
.eq('id', requestId)
|
||||||
|
.eq('target_guild_id', interaction.guildId)
|
||||||
|
.eq('status', 'pending')
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (!request) {
|
||||||
|
return interaction.reply({ content: 'Partnership request not found or not pending.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.update({
|
||||||
|
status: 'rejected',
|
||||||
|
rejection_reason: reason,
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', requestId);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return interaction.reply({ content: 'Failed to reject partnership.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0xff6600)
|
||||||
|
.setTitle('Partnership Rejected')
|
||||||
|
.setDescription(`Request from **${request.requester_guild_name}** has been rejected.`)
|
||||||
|
.addFields({ name: 'Reason', value: reason })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
|
||||||
|
const notifyEmbed = new EmbedBuilder()
|
||||||
|
.setColor(0xff6600)
|
||||||
|
.setTitle('Partnership Request Declined')
|
||||||
|
.setDescription(`**${interaction.guild.name}** has declined your partnership request.`)
|
||||||
|
.addFields({ name: 'Reason', value: reason })
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await notifyPartnerServer(client, supabase, request.requester_guild_id, notifyEmbed);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (subcommand === 'end') {
|
||||||
|
const partnerId = interaction.options.getString('partner_id');
|
||||||
|
|
||||||
|
const { data: partnership } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.select('*')
|
||||||
|
.eq('status', 'accepted')
|
||||||
|
.or(`and(requester_guild_id.eq.${interaction.guildId},target_guild_id.eq.${partnerId}),and(requester_guild_id.eq.${partnerId},target_guild_id.eq.${interaction.guildId})`)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (!partnership) {
|
||||||
|
return interaction.reply({ content: 'No active partnership found with this server.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const partnerName = partnership.requester_guild_id === interaction.guildId
|
||||||
|
? partnership.target_guild_name
|
||||||
|
: partnership.requester_guild_name;
|
||||||
|
|
||||||
|
const { error } = await supabase
|
||||||
|
.from('server_partnerships')
|
||||||
|
.update({
|
||||||
|
status: 'ended',
|
||||||
|
ended_at: new Date().toISOString(),
|
||||||
|
updated_at: new Date().toISOString()
|
||||||
|
})
|
||||||
|
.eq('id', partnership.id);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
return interaction.reply({ content: 'Failed to end partnership.', ephemeral: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
const embed = new EmbedBuilder()
|
||||||
|
.setColor(0xff0000)
|
||||||
|
.setTitle('Partnership Ended')
|
||||||
|
.setDescription(`Your partnership with **${partnerName}** has been ended.`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await interaction.reply({ embeds: [embed] });
|
||||||
|
|
||||||
|
const notifyEmbed = new EmbedBuilder()
|
||||||
|
.setColor(0xff0000)
|
||||||
|
.setTitle('Partnership Ended')
|
||||||
|
.setDescription(`**${interaction.guild.name}** has ended their partnership with your server.`)
|
||||||
|
.setTimestamp();
|
||||||
|
|
||||||
|
await notifyPartnerServer(client, supabase, partnerId, notifyEmbed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue