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