Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions experiments/get-friend-requests.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const { VK } = require('vk-io');
const { getToken } = require('../utils');
const { getToken, withRetry } = require('../utils');
const token = getToken();
const vk = new VK({ token });

(async () => {
const maxFriendRequestsCount = 23;
const requests = await vk.api.friends.getRequests({ count: maxFriendRequestsCount, sort: 1 }); // , extended: true
const requests = await withRetry(() =>
vk.api.friends.getRequests({ count: maxFriendRequestsCount, sort: 1 })
); // , extended: true
console.log(JSON.stringify(requests, null, 2));
})();
6 changes: 4 additions & 2 deletions friends.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { VK } = require('vk-io');
const { sleep, getToken, second, ms } = require('./utils');
const { sleep, getToken, second, ms, withRetry } = require('./utils');
const token = getToken();
const vk = new VK({ token });

Expand All @@ -15,7 +15,9 @@ const requestsLimit = 10000; // Maximum number of requests you expect
const requestsSegmentSize = 1000; // Number of requests fetched per segment

async function fetchRequests(segment, offset) {
const req = await vk.api.friends.getRequests({ out: 1, count: segment, offset: offset });
const req = await withRetry(() =>
vk.api.friends.getRequests({ out: 1, count: segment, offset: offset })
);
return req || [];
}

Expand Down
6 changes: 4 additions & 2 deletions reject-deactivated-friend-requests.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const { VK } = require('vk-io');
const { sleep, getToken, second, ms } = require('./utils');
const { sleep, getToken, second, ms, withRetry } = require('./utils');
const token = getToken();
const vk = new VK({ token });

const rejectDeactivatedFriendRequests = async () => {
try {
const maxFriendRequestsCount = 1000;
const requests = await vk.api.friends.getRequests({ count: maxFriendRequestsCount, sort: 1 });
const requests = await withRetry(() =>
vk.api.friends.getRequests({ count: maxFriendRequestsCount, sort: 1 })
);

for (let userId of requests.items) {
try {
Expand Down
6 changes: 4 additions & 2 deletions requests.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
const { VK } = require('vk-io');
const { getToken } = require('./utils');
const { getToken, withRetry } = require('./utils');
const token = getToken();
const vk = new VK({ token });

const requestsLimit = 10000; // Maximum number of requests you expect
const requestsSegmentSize = 1000; // Number of requests fetched per segment

async function fetchRequests(segment, offset) {
const req = await vk.api.friends.getRequests({ out: 1, count: segment, offset: offset });
const req = await withRetry(() =>
vk.api.friends.getRequests({ out: 1, count: segment, offset: offset })
);
return req || [];
}

Expand Down
8 changes: 5 additions & 3 deletions triggers/accept-friend-requests.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { getAllFriends, loadAllFriends } = require('../friends-cache');
const { sleep, priorityFriendIds, second, minute, ms } = require('../utils');
const { sleep, priorityFriendIds, second, minute, ms, withRetry } = require('../utils');

const sortByMutuals = { sort: 1 };
const maxFriends = 10000;
Expand Down Expand Up @@ -27,7 +27,7 @@ async function acceptFriendRequests({ vk }) {
console.log(`Friend request is sent to priority friend with id ${friendId}.`);
} catch (error) {
if (error.code === 177) { // APIError: Code β„–177 - Cannot add this user to friends as user not found
console.log(`Could not send friend request to priority friend with id ${friendId}, because this friend is not found.`);
console.log(`Could not accept ${friendId} friend request, because this friend is not found.`);
} else if (error.code === 242) { // APIError: Code β„–242 - Too many friends: friends count exceeded
console.log(`Could not send friend request to priority friend with id ${friendId}, because friends count (10000) exceeded.`);
break;
Expand All @@ -44,7 +44,9 @@ async function acceptFriendRequests({ vk }) {
}

const maxFriendRequestsCount = 23;
const requests = await vk.api.friends.getRequests({ count: maxFriendRequestsCount, ...sortByMutuals });
const requests = await withRetry(() =>
vk.api.friends.getRequests({ count: maxFriendRequestsCount, ...sortByMutuals })
);
await sleep((2 * second) / ms);
if (requests?.items?.length <= 0) {
console.log('No incoming friend requests to be accepted.');
Expand Down
6 changes: 4 additions & 2 deletions triggers/delete-outgoing-requests.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
const { sleep, priorityFriendIds, second, ms } = require('../utils');
const { sleep, priorityFriendIds, second, ms, withRetry } = require('../utils');

async function deleteOutgoingFriendRequests(context) {
try {
const count = context?.options?.maxRequests;
if (count <= 0) {
return;
}
const requests = await context.vk.api.friends.getRequests({ count, out: 1, need_viewed: 1 });
const requests = await withRetry(() =>
context.vk.api.friends.getRequests({ count, out: 1, need_viewed: 1 })
);
if (requests.items.length <= 0) {
console.log('No outgoing friend requests to be deleted');
return requests;
Expand Down
6 changes: 4 additions & 2 deletions triggers/react-to-cancelled-friendships.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const { DateTime } = require('luxon');
const { getRandomElement, sleep, second, ms } = require('../utils');
const { getRandomElement, sleep, second, ms, withRetry } = require('../utils');
const { trigger: greetingTrigger } = require('./greeting');
const { enqueueMessage } = require('../outgoing-messages');
const { setConversation } = require('../friends-conversations-cache');
Expand All @@ -14,7 +14,9 @@ async function reactToCancelledFriendships(context) {
if (count <= 0) {
return;
}
const requests = await context.vk.api.friends.getRequests({ count, out: 1, need_viewed: 1 });
const requests = await withRetry(() =>
context.vk.api.friends.getRequests({ count, out: 1, need_viewed: 1 })
);
await sleep((3 * second) / ms);
if (requests.items.length <= 0) {
console.log('No cancelled friendships to react to.');
Expand Down
54 changes: 54 additions & 0 deletions utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,59 @@ async function executeTrigger(trigger, context) {
}
}

/**
* Executes an async function with retry logic and exponential backoff
* @param {Function} fn - The async function to execute
* @param {Object} options - Retry options
* @param {number} options.maxRetries - Maximum number of retry attempts (default: 3)
* @param {number} options.initialDelay - Initial delay in milliseconds (default: 1000)
* @param {number} options.maxDelay - Maximum delay in milliseconds (default: 30000)
* @param {number} options.backoffMultiplier - Multiplier for exponential backoff (default: 2)
* @param {Array<string>} options.retryableErrors - List of error codes to retry (default: ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND'])
* @returns {Promise} The result of the function execution
*/
async function withRetry(fn, options = {}) {
const {
maxRetries = 3,
initialDelay = 1000,
maxDelay = 30000,
backoffMultiplier = 2,
retryableErrors = ['ECONNRESET', 'ETIMEDOUT', 'ECONNREFUSED', 'ENOTFOUND']
} = options;

let lastError;
let delay = initialDelay;

for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await fn();
} catch (error) {
lastError = error;

// Check if error is retryable
const isRetryable = retryableErrors.includes(error.code) ||
retryableErrors.includes(error.errno) ||
(error.type === 'system' && retryableErrors.includes(error.code));

if (!isRetryable || attempt === maxRetries) {
throw error;
}

// Log retry attempt
console.warn(`Attempt ${attempt + 1}/${maxRetries + 1} failed with ${error.code || error.errno}: ${error.message}`);
console.log(`Retrying in ${delay}ms...`);

// Wait before retrying
await new Promise(resolve => setTimeout(resolve, delay));

// Exponential backoff with max delay cap
delay = Math.min(delay * backoffMultiplier, maxDelay);
}
}

throw lastError;
}

module.exports = {
getToken,
getRandomElement,
Expand All @@ -190,6 +243,7 @@ module.exports = {
readJsonSync,
saveTextSync,
saveJsonSync,
withRetry,
app,
...timeUnits,
priorityFriendIds,
Expand Down