From e369e5a8e45ebe660a468baced573a79ddb9debf Mon Sep 17 00:00:00 2001 From: reverite Date: Sun, 8 Apr 2018 21:37:25 -0700 Subject: Ambassador edits (credit to @rtucker) --- index.js | 154 ++++++++++++++++++++++++++++++++++++++------------------------- 1 file changed, 92 insertions(+), 62 deletions(-) diff --git a/index.js b/index.js index 3e69036..8b949ec 100644 --- a/index.js +++ b/index.js @@ -1,23 +1,17 @@ var mastodon = require('mastodon'); var pg = require('pg'); -var query = `SELECT id, created_at -FROM public_toots -WHERE favourites_count > ( - SELECT avg(favourites_count) - FROM public_toots - WHERE - favourites_count > 1 - AND created_at > NOW() - INTERVAL '30 days' -) -AND created_at > NOW() - INTERVAL '5 days';` - var DB_USER = process.env.DB_USER || 'ambassador'; var DB_NAME = process.env.DB_NAME || 'mastodon_production'; var DB_PASSWORD = process.env.DB_PASSWORD || ''; var DB_HOST = process.env.DB_HOST || '/var/run/postgresql'; var AMBASSADOR_TOKEN = process.env.AMBASSADOR_TOKEN; var INSTANCE_HOST = process.env.INSTANCE_HOST; +var BOOSTS_PER_CYCLE = process.env.BOOSTS_PER_CYCLE || 2; +var THRESHOLD_INTERVAL_DAYS = process.env.THRESHOLD_INTERVAL_DAYS || 30; +var BOOST_MAX_DAYS = process.env.BOOST_MAX_DAYS || 5; +var THRESHOLD_CHECK_INTERVAL = process.env.THRESHOLD_CHECK_INTERVAL || 15; // cycles +var CYCLE_INTERVAL = process.env.CYCLE_INTERVAL || 15; // minutes var config = { user: process.env.DB_USER || 'ambassador', @@ -29,6 +23,27 @@ var config = { idleTimeoutMillis: 30000 // how long a client is allowed to remain idle before being closed }; +// Define our threshold (average faves over the past x days) +var thresh_query = `SELECT ceil(avg(favourites_count)) AS threshold + FROM public_toots + WHERE + favourites_count > 1 + AND updated_at > NOW() - INTERVAL '` + THRESHOLD_INTERVAL_DAYS + ` days'` + +// Find all toots we haven't boosted yet, but ought to +var query = `SELECT id, updated_at + FROM public_toots + WHERE + favourites_count >= $1 + AND NOT EXISTS ( + SELECT 1 + FROM public_toots AS pt2 + WHERE + pt2.reblog_of_id = public_toots.id + AND pt2.account_id = $2 + ) + AND updated_at > NOW() - INTERVAL '` + BOOST_MAX_DAYS + ` days' + LIMIT $3` console.dir('STARTING AMBASSADOR'); console.log('\tDB_USER:', DB_USER); @@ -37,31 +52,67 @@ console.log('\tDB_PASSWORD:', DB_PASSWORD.split('').map(function() { return "*" console.log('\tDB_HOST:', DB_HOST); console.log('\tAMBASSADOR_TOKEN:', AMBASSADOR_TOKEN); console.log('\tINSTANCE_HOST:', INSTANCE_HOST); +console.log('\tBOOSTS_PER_CYCLE:', BOOSTS_PER_CYCLE); +console.log('\tTHRESHOLD_INTERVAL_DAYS:', THRESHOLD_INTERVAL_DAYS); +console.log('\tBOOST_MAX_DAYS:', BOOST_MAX_DAYS); +console.log('\tTHRESHOLD_CHECK_INTERVAL:', THRESHOLD_CHECK_INTERVAL); +console.log('\tCYCLE_INTERVAL:', CYCLE_INTERVAL); + +var g_threshold_downcount = 0; +var g_threshold = 0; + +function getThreshold(client, f) { + if (g_threshold_downcount <= 0 || g_threshold <= 0) { + console.log('Threshold is stale, recalculating...'); + client.query(thresh_query, [], function (err, result) { + if (err) { + throw "error running threshold query: " + err; + } -var client = new pg.Client(config); + g_threshold = result.rows[0].threshold; + g_threshold_downcount = THRESHOLD_CHECK_INTERVAL; + return f(g_threshold); + }); + } else { + g_threshold_downcount--; + console.log('Cycles until next threshold update: ' + g_threshold_downcount); + return f(g_threshold); + } +} function cycle() { + console.log('Cycle beginning'); + var client = new pg.Client(config); + client.connect(function (err) { if (err) { console.error('error connecting to client'); return console.dir(err); } - client.query(query, [], function (err, result) { - if(err) { - console.error('error running query'); - return console.dir(err); - } - - client.end(function (err) { - if (err) { - console.error('error disconnecting from client'); - console.dir(err); + whoami(function (account_id) { + getThreshold(client, function (threshold) { + console.log('Current threshold: ' + threshold); + if (threshold < 1) { + throw "threshold too low: " + threshold; } - }); - boost(result.rows); - }); + client.query(query, [threshold, account_id, BOOSTS_PER_CYCLE], function (err, result) { + if (err) { + throw "error running toot query: " + err; + } + + client.end(function (err) { + if (err) { + throw "error disconnecting from client: " + err; + } + }); + + boost(result.rows); + console.log('Cycle complete'); + }); + }); + }) }); } @@ -70,56 +121,35 @@ var M = new mastodon({ api_url: INSTANCE_HOST + '/api/v1' }); -var boosted = (function() { - const bucketSpan = 3600; // 1 hour buckets => up to 121 buckets over 5 days - const buckets = new Map(); - - function prune() { - // Bucket id for 5 days ago - const threshold = (Date.now() - 5 * 24 * 3600 * 1000) / bucketSpan; - - for (var bucket of buckets.keys()) { - if (bucket < threshold) buckets.delete(bucket); +function whoami(f) { + M.get('/accounts/verify_credentials', function(err, result) { + if (err) { + console.error('error getting current user id'); + throw err; } - } - - function bucket(row) { - return row.created_at.getTime() / bucketSpan; - } - - function already(row) { - const b = bucket(row); - return buckets.has(b) && buckets.get(b).has(row.id); - } - - function set(row) { - const b = bucket(row); - if (!buckets.has(b)) buckets.set(b, new Set()); - buckets.get(b).add(row.id); - } - - return { already: already, prune: prune, set: set }; -})(); + if (result.id === undefined) { + console.error('verify_credentials result is undefined'); + throw "verify_credentials failed"; + } + console.log('Authenticated as ' + result.id + ' (' + result.display_name + ')'); + return f(result.id); + }) +} function boost(rows) { - rows.filter(function(x) { return !boosted.already(x); }) - .forEach(function(row) { + rows.forEach(function(row) { M.post('/statuses/' + row.id + '/reblog', function(err, result) { if (err) { if (err.message === 'Validation failed: Reblog of status already exists') { - boosted.set(row); - return console.log('Warning: tried to boost #' + row.id + ' but it had already been boosted by this account. Adding to cache.'); + return console.log('Warning: tried to boost #' + row.id + ' but it had already been boosted by this account.'); } return console.log(err); } - boosted.set(row); console.log('boosted status #' + row.id); }); }) } cycle(); -// Prune the set of boosted toots every hour -setInterval(boosted.prune, 1000 * 3600); -setInterval(cycle, 1000 * 60 * 15); +setInterval(cycle, 1000 * 60 * CYCLE_INTERVAL); -- cgit