From 04e17da24d40e6d30de8dd452dd38a37e3c7fbe1 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 22 Sep 2025 22:42:05 +0300 Subject: [PATCH 1/3] Initial commit with task details for issue #45 Adding CLAUDE.md with task information for AI processing. This file will be removed when the task is complete. Issue: https://github.com/konard/vk-bot/issues/45 --- CLAUDE.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..41e7349 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,5 @@ +Issue to solve: https://github.com/konard/vk-bot/issues/45 +Your prepared branch: issue-45-9d530666 +Your prepared working directory: /tmp/gh-issue-solver-1758570122246 + +Proceed. \ No newline at end of file From 8db808616f271f1380b2bb8ffb9babbddd686124 Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 22 Sep 2025 22:49:17 +0300 Subject: [PATCH 2/3] Add neural network page posting functionality MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements functionality to post content to VK communities focused on neural networks, AI, and language models. Features: - Configurable community list via neural-network-communities.json - AI/ML-focused post content with multiple message variants - Automatic duplicate detection and cleanup - Comprehensive error handling for VK API limitations - 15-minute posting interval (configurable) - Unit tests for functionality verification Files added: - triggers/neural-network-page-posts.js - Main posting trigger - neural-network-communities.json - Community configuration - NEURAL_NETWORK_POSTING.md - Feature documentation - __tests__/triggers/neural-network-page-posts.test.js - Unit tests πŸ€– Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- NEURAL_NETWORK_POSTING.md | 67 +++++++ .../neural-network-page-posts.test.js | 62 ++++++ index.js | 6 + neural-network-communities.json | 22 +++ triggers/neural-network-page-posts.js | 183 ++++++++++++++++++ 5 files changed, 340 insertions(+) create mode 100644 NEURAL_NETWORK_POSTING.md create mode 100644 __tests__/triggers/neural-network-page-posts.test.js create mode 100644 neural-network-communities.json create mode 100644 triggers/neural-network-page-posts.js diff --git a/NEURAL_NETWORK_POSTING.md b/NEURAL_NETWORK_POSTING.md new file mode 100644 index 0000000..c9e6eff --- /dev/null +++ b/NEURAL_NETWORK_POSTING.md @@ -0,0 +1,67 @@ +# Neural Network Page Posting Feature + +This feature allows the VK bot to automatically post content to VK communities focused on neural networks, AI, and language models. + +## Configuration + +To use this feature, edit the `neural-network-communities.json` file: + +```json +{ + "communities": [ + { + "id": 123456789, + "name": "AI Community", + "description": "Community about artificial intelligence", + "enabled": true + }, + { + "id": 987654321, + "name": "Neural Networks Group", + "description": "Group for neural network enthusiasts", + "enabled": true + } + ] +} +``` + +## How to find VK Community IDs + +1. Go to the VK community page +2. Look at the URL: `https://vk.com/club123456789` or `https://vk.com/public123456789` +3. The number after `club` or `public` is the community ID + +## Post Content + +The bot will randomly select from AI/ML-focused messages that include: +- Information about neural networks and language models +- Links to GPT bot services +- Invitations for collaboration in AI field +- Contact information for discussions + +## Posting Schedule + +- Posts are sent every 15 minutes (configurable in `index.js`) +- The bot checks for existing posts to avoid duplicates +- Previous posts are automatically cleaned up + +## Error Handling + +The bot handles various VK API errors: +- Access denied (community walls disabled) +- Rate limiting (automatic delays) +- Captcha requirements +- Advertisement posting limits + +Communities that encounter errors are temporarily disabled and retried after 24 hours. + +## Testing + +Run the tests with: +```bash +npm test __tests__/triggers/neural-network-page-posts.test.js +``` + +## Manual Configuration + +If you need to add communities manually, edit the `neuralNetworkCommunities` array in `triggers/neural-network-page-posts.js`. \ No newline at end of file diff --git a/__tests__/triggers/neural-network-page-posts.test.js b/__tests__/triggers/neural-network-page-posts.test.js new file mode 100644 index 0000000..31e410d --- /dev/null +++ b/__tests__/triggers/neural-network-page-posts.test.js @@ -0,0 +1,62 @@ +const { trigger, neuralNetworkPostMessages } = require('../../triggers/neural-network-page-posts'); + +describe('neural network page posts trigger', () => { + const triggerDescription = 'neural network page posts trigger'; + + it('should have correct trigger structure', () => { + expect(trigger).toHaveProperty('name'); + expect(trigger).toHaveProperty('action'); + expect(trigger.name).toBe('SendNeuralNetworkPagePosts'); + expect(typeof trigger.action).toBe('function'); + }); + + it('should have neural network focused post messages', () => { + expect(neuralNetworkPostMessages).toBeDefined(); + expect(Array.isArray(neuralNetworkPostMessages)).toBe(true); + expect(neuralNetworkPostMessages.length).toBeGreaterThan(0); + + // Check that messages contain AI/ML related keywords + neuralNetworkPostMessages.forEach(message => { + expect(typeof message).toBe('string'); + expect(message.length).toBeGreaterThan(0); + + // Should contain at least one AI/ML related term + const aiTerms = ['Π½Π΅ΠΉΡ€ΠΎΠ½Π½', 'AI', 'machine learning', 'GPT', 'искусствСнного ΠΈΠ½Ρ‚Π΅Π»Π»Π΅ΠΊΡ‚Π°', 'neural']; + const containsAiTerms = aiTerms.some(term => + message.toLowerCase().includes(term.toLowerCase()) + ); + expect(containsAiTerms).toBe(true); + }); + }); + + it('should not execute when no communities are configured', async () => { + const mockContext = { + vk: { + api: { + wall: { + get: jest.fn(), + search: jest.fn(), + post: jest.fn(), + delete: jest.fn() + } + } + } + }; + + // Mock console.log to verify the skip message + const consoleSpy = jest.spyOn(console, 'log').mockImplementation(); + + await trigger.action(mockContext); + + expect(consoleSpy).toHaveBeenCalledWith( + trigger.name, + 'No neural network communities configured. Skipping.' + ); + + // VK API should not be called + expect(mockContext.vk.api.wall.get).not.toHaveBeenCalled(); + expect(mockContext.vk.api.wall.post).not.toHaveBeenCalled(); + + consoleSpy.mockRestore(); + }); +}); \ No newline at end of file diff --git a/index.js b/index.js index 4226830..ef79111 100644 --- a/index.js +++ b/index.js @@ -130,3 +130,9 @@ const sendBirthDayCongratulationsIntervalAction = async () => { } const sendBirthDayCongratulationsInterval = setInterval(sendBirthDayCongratulationsIntervalAction, (23 * 60 * minute) / ms); // sendBirthDayCongratulationsIntervalAction(); + +const { trigger: sendNeuralNetworkPostsTrigger } = require('./triggers/neural-network-page-posts'); +const sendNeuralNetworkPostsIntervalAction = async () => { + await executeTrigger(sendNeuralNetworkPostsTrigger, { vk }); +}; +const sendNeuralNetworkPostsInterval = setInterval(sendNeuralNetworkPostsIntervalAction, (15 * minute) / ms); diff --git a/neural-network-communities.json b/neural-network-communities.json new file mode 100644 index 0000000..c3b7273 --- /dev/null +++ b/neural-network-communities.json @@ -0,0 +1,22 @@ +{ + "communities": [ + { + "id": null, + "name": "Example Neural Network Community", + "description": "Add your neural network/AI community IDs here. Find VK community ID from URL: vk.com/club[ID] or vk.com/public[ID]", + "enabled": false + } + ], + "instructions": { + "how_to_add": "To add a neural network community, find the VK community ID and add it to the 'communities' array", + "community_id_format": "VK community ID should be a positive number (e.g., 123456789)", + "finding_community_id": "Look at the VK community URL: vk.com/club123456789 or vk.com/public123456789", + "example": { + "id": 123456789, + "name": "AI Community", + "description": "Community about artificial intelligence", + "enabled": true + }, + "note": "The bot will only post to communities where it has posting permissions" + } +} \ No newline at end of file diff --git a/triggers/neural-network-page-posts.js b/triggers/neural-network-page-posts.js new file mode 100644 index 0000000..9e55a32 --- /dev/null +++ b/triggers/neural-network-page-posts.js @@ -0,0 +1,183 @@ +const { sleep, getRandomElement, second, minute, ms, day, app } = require('../utils'); +const fs = require('fs').promises; +const path = require('path'); + +// Load neural network communities from configuration +async function loadNeuralNetworkCommunities() { + try { + const configPath = path.join(__dirname, '..', 'neural-network-communities.json'); + const configData = await fs.readFile(configPath, 'utf8'); + const config = JSON.parse(configData); + + return config.communities + .filter(community => community.enabled && community.id) + .map(community => community.id); + } catch (error) { + console.warn('Failed to load neural network communities configuration:', error.message); + return []; + } +} + +let neuralNetworkCommunities = []; + +let disabledNeuralCommunities = []; + +const disabledCommunitiesCleanupInterval = setInterval(() => { + if (app.gracefullyFinished) { + clearInterval(disabledCommunitiesCleanupInterval); + return; + } + disabledNeuralCommunities = []; +}, (1 * day) / ms); + +// Neural network and AI-focused post content +const neuralNetworkPostMessages = [ + `🧠 ΠŸΡ€ΠΈΠ²Π΅Ρ‚! Π― программист, Ρ€Π°Π±ΠΎΡ‚Π°ΡŽΡ‰ΠΈΠΉ с Π½Π΅ΠΉΡ€ΠΎΠ½Π½Ρ‹ΠΌΠΈ сСтями ΠΈ языковыми модСлями. +ВсСгда Π³ΠΎΡ‚ΠΎΠ² ΠΎΠ±ΡΡƒΠ΄ΠΈΡ‚ΡŒ AI, machine learning, ΠΈ Π΄Π΅Π»ΠΈΡ‚ΡŒΡΡ ΠΎΠΏΡ‹Ρ‚ΠΎΠΌ Π² этой области. +ΠŸΡ€ΠΈΠ½ΠΈΠΌΠ°ΡŽ всС заявки Π² Π΄Ρ€ΡƒΠ·ΡŒΡ ΠΎΡ‚ Π΅Π΄ΠΈΠ½ΠΎΠΌΡ‹ΡˆΠ»Π΅Π½Π½ΠΈΠΊΠΎΠ²! + +πŸ€– Для доступа ΠΊ GPT: https://t.me/DeepGPTBot?start=1339837872 (наша Ρ€Π°Π·Ρ€Π°Π±ΠΎΡ‚ΠΊΠ°) +πŸ“± Telegram: https://t.me/link_konard - ΠΊΠ°Π½Π°Π», https://t.me/drakonard - Π»ΠΈΡ‡ΠΊΠ°`, + + `πŸš€ Π˜Π½Ρ‚Π΅Ρ€Π΅ΡΡƒΠ΅ΡˆΡŒΡΡ Π½Π΅ΠΉΡ€ΠΎΠ½Π½Ρ‹ΠΌΠΈ сСтями ΠΈ языковыми модСлями? +Π― программист, ΡΠΏΠ΅Ρ†ΠΈΠ°Π»ΠΈΠ·ΠΈΡ€ΡƒΡŽΡ‰ΠΈΠΉΡΡ Π½Π° AI/ML. Π”Π°Π²Π°ΠΉΡ‚Π΅ Π΄Ρ€ΡƒΠΆΠΈΡ‚ΡŒ ΠΈ ΠΎΠ±ΠΌΠ΅Π½ΠΈΠ²Π°Ρ‚ΡŒΡΡ знаниями! + +πŸ’¬ Пиши Π² Π»ΠΈΡ‡ΠΊΡƒ - обсудим Π»ΡŽΠ±Ρ‹Π΅ вопросы ΠΏΠΎ ΠΌΠ°ΡˆΠΈΠ½Π½ΠΎΠΌΡƒ ΠΎΠ±ΡƒΡ‡Π΅Π½ΠΈΡŽ +πŸ”— ΠŸΠΎΠ»Π΅Π·Π½Ρ‹Π΅ ссылки Π² ΠΌΠΎΠ΅ΠΌ ΠΏΡ€ΠΎΡ„ΠΈΠ»Π΅`, + + `🎯 Π˜Ρ‰Ρƒ Π΅Π΄ΠΈΠ½ΠΎΠΌΡ‹ΡˆΠ»Π΅Π½Π½ΠΈΠΊΠΎΠ² Π² области искусствСнного ΠΈΠ½Ρ‚Π΅Π»Π»Π΅ΠΊΡ‚Π°! +ΠŸΡ€ΠΎΠ³Ρ€Π°ΠΌΠΌΠΈΡΡ‚ с ΠΎΠΏΡ‹Ρ‚ΠΎΠΌ Π² neural networks. ΠŸΡ€ΠΈΠ½ΠΈΠΌΠ°ΡŽ заявки Π² Π΄Ρ€ΡƒΠ·ΡŒΡ. + +πŸ“š Π“ΠΎΡ‚ΠΎΠ² ΠΏΠΎΠ΄Π΅Π»ΠΈΡ‚ΡŒΡΡ ΠΎΠΏΡ‹Ρ‚ΠΎΠΌ ΠΈ ΡƒΠ·Π½Π°Ρ‚ΡŒ Ρ‡Ρ‚ΠΎ-Ρ‚ΠΎ Π½ΠΎΠ²ΠΎΠ΅ +πŸ’Ό ΠžΡ‚ΠΊΡ€Ρ‹Ρ‚ для интСрСсных ΠΏΡ€ΠΎΠ΅ΠΊΡ‚ΠΎΠ² ΠΈ ΠΊΠΎΠ»Π»Π°Π±ΠΎΡ€Π°Ρ†ΠΈΠΉ` +]; + +const postsSearchRequest = `Π½Π΅ΠΉΡ€ΠΎΠ½Π½`; // Part of "Π½Π΅ΠΉΡ€ΠΎΠ½Π½Ρ‹ΠΉ" to find existing posts + +const avatarImagePath = 'avatar.jpeg'; + +/** + * Uploads an avatar image for neural network communities + */ +async function uploadAvatarPicture(context, communityId, imagePath) { + // Using the same avatar approach as the main posting trigger + return 'photo3972090_457245285_5f56ac9e1f0de697db'; +} + +function disableNeuralCommunity(communityId) { + if (!disabledNeuralCommunities.includes(communityId)) { + disabledNeuralCommunities.push(communityId); + console.warn(trigger.name, `Neural network community ${communityId} is added to disabled communities list.`); + } +} + +async function sendNeuralNetworkPosts(context) { + try { + // Load communities from configuration each time to allow dynamic updates + neuralNetworkCommunities = await loadNeuralNetworkCommunities(); + + if (neuralNetworkCommunities.length === 0) { + console.log(trigger.name, 'No neural network communities configured. Skipping.'); + return; + } + + for (const communityId of neuralNetworkCommunities) { + if (disabledNeuralCommunities.includes(communityId)) { + console.log(trigger.name, `Neural network community ${communityId} is disabled. Skipping.`); + continue; + } + + try { + // For wall posts, VK expects a negative owner_id for communities. + const ownerId = '-' + communityId.toString(); + + const topPosts = await context.vk.api.wall.get({ + owner_id: ownerId, + count: 10 + }); + + const topPostsHaveNeuralPost = topPosts.items.some(post => + post.text.toLowerCase().includes(postsSearchRequest.toLowerCase()) + ); + + console.log(trigger.name, `Loaded ${topPosts.items.length} posts from neural network community ${communityId}. Our neural network post is ${topPostsHaveNeuralPost ? 'found' : 'not found'} in these posts.`); + + await sleep(trigger.name, (10 * second) / ms); + + if (topPostsHaveNeuralPost) { + continue; + } + + const previousPosts = await context.vk.api.wall.search({ + owner_id: ownerId, + query: postsSearchRequest, + count: 15 + }); + const postsToDelete = previousPosts.items.filter(post => + post.text.toLowerCase().includes(postsSearchRequest.toLowerCase()) && post.can_delete + ); + + console.log(trigger.name, `Found ${postsToDelete.length} previous neural network posts to be deleted.`); + await sleep(trigger.name, (5 * second) / ms); + console.log(trigger.name, `Sending neural network post to ${communityId} community.`); + + const message = getRandomElement(neuralNetworkPostMessages); + const avatarAttachment = await uploadAvatarPicture(context, communityId, avatarImagePath); + const attachments = [avatarAttachment]; + + await context.vk.api.wall.post({ owner_id: ownerId, message, attachments }); + console.log(trigger.name, 'Neural network post is sent to', communityId, 'community.'); + await sleep(trigger.name, (5 * second) / ms); + + for (const post of postsToDelete) { + try { + await context.vk.api.wall.delete({ owner_id: ownerId, post_id: post.id }); + console.log(trigger.name, `Neural network post ${post.id} is deleted.`); + await sleep(trigger.name, (5 * second) / ms); + } catch (e) { + if (e.code === 104 || e.code === 100) { + console.warn(trigger.name, `Neural network post ${post.id} is not found. It may already be deleted.`); + continue; + } + throw e; + } + } + } catch (err) { + if (err.code === 210) { + console.warn(trigger.name, `Warning: Access to wall's post denied for neural network community ${communityId}.`); + disableNeuralCommunity(communityId); + await sleep(trigger.name, (1 * minute) / ms); + } else if (err.code === 219) { + console.warn(trigger.name, `Warning: Advertisement post was recently added to neural network community ${communityId}.`); + disableNeuralCommunity(communityId); + await sleep(trigger.name, (1 * minute) / ms); + } else if (err.code === 15) { + console.warn(trigger.name, `Warning: Access denied to post to neural network community ${communityId} wall because it was disabled.`); + disableNeuralCommunity(communityId); + await sleep(trigger.name, (1 * minute) / ms); + } else if (err.code === 14) { + console.warn(trigger.name, `Warning: Captcha needed to post to neural network community ${communityId}.`); + await sleep(trigger.name, (1 * minute) / ms); + } else if (err.code === 10) { + console.warn(trigger.name, `Warning: Unknown error occurred while posting to neural network community ${communityId}.`); + await sleep(trigger.name, (1 * minute) / ms); + } else { + throw err; + } + } + } + } catch (error) { + console.error(trigger.name, error); + } +} + +const trigger = { + name: "SendNeuralNetworkPagePosts", + action: sendNeuralNetworkPosts +}; + +module.exports = { + trigger, + neuralNetworkCommunities, // Export for configuration + neuralNetworkPostMessages // Export for testing/customization +}; \ No newline at end of file From 16aec19e0340fdc78278f3d43a84b334eaf6625c Mon Sep 17 00:00:00 2001 From: konard Date: Mon, 22 Sep 2025 22:50:10 +0300 Subject: [PATCH 3/3] Remove CLAUDE.md - Claude command completed --- CLAUDE.md | 5 ----- 1 file changed, 5 deletions(-) delete mode 100644 CLAUDE.md diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 41e7349..0000000 --- a/CLAUDE.md +++ /dev/null @@ -1,5 +0,0 @@ -Issue to solve: https://github.com/konard/vk-bot/issues/45 -Your prepared branch: issue-45-9d530666 -Your prepared working directory: /tmp/gh-issue-solver-1758570122246 - -Proceed. \ No newline at end of file