Press n or j to go to the next uncovered block, b, p or k for the previous block.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 | 33x 33x 9x 9x 9x 2x 2x 7x 7x 7x 7x 7x 7x 1x 1x 6x 5x 4x 1x 3x 2x 2x 3x 3x 1x 1x 16x 8x 8x 8x 1x 1x 1x 27x 7x 134x 7x 4x 3x 6x 6x 2x 4x 6x 6x 1x 1x 5x 4x 4x 4x 1x 1x 3x 4x 4x 4x 4x 4x 4x 4x 1x 10x | /** * WithdrawalProcessor - Handles proposal withdrawals and resolution removal * * Manages the complex process of finding and removing passed resolutions through * democratic withdrawal proposals. Enables the community to reverse previous decisions * when circumstances change or errors are discovered. * * Design rationale: * - Democratic reversibility: Communities can correct mistakes through proper process * - Resolution matching: Intelligent search to find target resolutions across channels * - Audit trail: Maintains record of what was withdrawn and when * - Safe removal: Validates withdrawal targets to prevent accidental deletions * - Multi-format support: Handles various ways users might reference resolutions */ class WithdrawalProcessor { constructor(bot, proposalConfig) { this.bot = bot; this.proposalConfig = proposalConfig; } // Parse withdrawal proposal to find the target resolution to remove // Searches resolution channels to locate the specific resolution being withdrawn async parseWithdrawalTarget(content, proposalType, config) { try { // Extract resolution reference from withdrawal proposal text // Expected format: **Withdraw**: [Resolution description/link] const withdrawMatch = content.match(/\*\*Withdraw\*\*:\s*(.+)/i); if (!withdrawMatch) { console.log('No withdrawal content found'); return null; } const withdrawalContent = withdrawMatch[1].trim(); console.log(`Looking for resolution to withdraw: "${withdrawalContent}"`); // Access the resolutions channel to search for target resolution const guild = this.bot.client.guilds.cache.get(this.bot.getGuildId()); const resolutionsChannelId = config.resolutionsChannelId; const resolutionsChannel = guild.channels.cache.get(resolutionsChannelId); if (!resolutionsChannel) { console.error(`Resolutions channel ${resolutionsChannelId} not found`); return null; } // Fetch recent messages to find the target resolution const messages = await resolutionsChannel.messages.fetch({ limit: 100 }); for (const [messageId, message] of messages) { // Skip if not a resolution message if (!message.content.includes('PASSED') || !message.content.includes('RESOLUTION')) { continue; } // Check if this resolution matches the withdrawal request if (this.isMatchingResolution(message.content, withdrawalContent)) { console.log(`Found matching resolution: ${messageId}`); return { messageId: messageId, channelId: resolutionsChannelId, content: message.content, originalContent: this.extractOriginalResolution(message.content) }; } } console.log('No matching resolution found'); return null; } catch (error) { console.error('Error parsing withdrawal target:', error); return null; } } // Determine if a resolution matches the withdrawal target using multiple strategies // Uses fuzzy matching to handle variations in wording and partial references isMatchingResolution(resolutionContent, withdrawalTarget) { // Strategy 1: Direct substring matching for exact references if (resolutionContent.toLowerCase().includes(withdrawalTarget.toLowerCase())) { return true; } // Strategy 2: Extract and compare policy text specifically // Focuses on the actual policy content rather than metadata const policyMatch = resolutionContent.match(/\*\*(?:Policy|Governance|Resolution)\*\*:\s*(.+?)(?:\n|$)/i); if (policyMatch) { const policyText = policyMatch[1].trim(); Eif (policyText.toLowerCase().includes(withdrawalTarget.toLowerCase()) || withdrawalTarget.toLowerCase().includes(policyText.toLowerCase())) { return true; } } // Strategy 3: Keyword overlap analysis for partial matches // Handles cases where withdrawal references don't exactly match resolution text const withdrawalWords = withdrawalTarget.toLowerCase().split(/\s+/).filter(w => w.length > 3); const resolutionWords = resolutionContent.toLowerCase().split(/\s+/); const matchCount = withdrawalWords.filter(word => resolutionWords.some(rw => rw.includes(word))).length; // Require 60% keyword overlap to minimize false matches if (withdrawalWords.length > 0 && matchCount / withdrawalWords.length >= 0.6) { return true; } return false; } extractOriginalResolution(resolutionContent) { // Extract the original proposal text from a resolution message const resolutionMatch = resolutionContent.match(/\*\*Resolution:\*\*\s*(.+?)(?:\n\*|$)/s); if (resolutionMatch) { return resolutionMatch[1].trim(); } // Fallback: return the whole resolution content return resolutionContent; } // Execute the withdrawal by removing the target resolution and posting notification // Creates permanent record of the withdrawal for transparency and accountability async processWithdrawal(proposal, guild) { try { if (!proposal.targetResolution) { console.error('No target resolution found for withdrawal'); return; } // Remove the original resolution message from the resolutions channel // This effectively revokes the policy from active status const resolutionsChannel = guild.channels.cache.get(proposal.targetResolution.channelId); Eif (resolutionsChannel) { try { const targetMessage = await resolutionsChannel.messages.fetch(proposal.targetResolution.messageId); await targetMessage.delete(); console.log(`Deleted resolution message ${proposal.targetResolution.messageId}`); } catch (error) { console.error('Could not delete target resolution:', error); } } // Post withdrawal notification in resolutions channel const proposalTypeConfig = this.proposalConfig[proposal.proposalType]; const resolutionsChannelId = proposalTypeConfig.resolutionsChannelId; const resolutionsChannelForNotification = guild.channels.cache.get(resolutionsChannelId); Eif (resolutionsChannelForNotification) { const withdrawalContent = `🗑️ **WITHDRAWN ${proposal.proposalType.toUpperCase()} RESOLUTION** **Withdrawn by:** <@${proposal.authorId}> **Withdrawn on:** <t:${Math.floor(Date.parse(proposal.completedAt) / 1000)}:F> **Final Vote:** ✅ ${proposal.finalYes} - ❌ ${proposal.finalNo} **Original Resolution (now withdrawn):** ${proposal.targetResolution.originalContent} **Withdrawal Proposal:** ${proposal.content} *This resolution has been officially withdrawn and is no longer active policy.*`; await resolutionsChannelForNotification.send(withdrawalContent); console.log(`Withdrawal notification posted to ${resolutionsChannelId}`); } } catch (error) { console.error('Error processing withdrawal:', error); } } } module.exports = WithdrawalProcessor; |