summary refs log tree commit diff
diff options
context:
space:
mode:
authorreverite <samantha@chalker.io>2018-04-08 21:37:25 -0700
committerreverite <samantha@chalker.io>2018-04-08 21:37:25 -0700
commite369e5a8e45ebe660a468baced573a79ddb9debf (patch)
treeb02310293b48b2d44a6bebb24f0a56c14b6bec1d
parent9f5b181e825f9e5ecf25d74e419f72c6931a5ca0 (diff)
Ambassador edits (credit to @rtucker)
-rw-r--r--index.js154
1 files 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);