summary refs log tree commit diff
path: root/index.js
blob: 8b949ecf0655bcfaca47545ecd78b64fa16b21ba (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
var mastodon = require('mastodon');
var pg = require('pg');

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',
  database: process.env.DB_NAME || 'mastodon_production',
  password: process.env.DB_PASSWORD || '',
  host: process.env.DB_HOST || '/var/run/postgresql',
  port: 5432, //env var: PGPORT
  max: 2, // max number of clients in the pool
  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);
console.log('\tDB_NAME:', DB_NAME);
console.log('\tDB_PASSWORD:', DB_PASSWORD.split('').map(function() { return "*" }).join(''));
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;
      }

      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);
    }

    whoami(function (account_id) {
      getThreshold(client, function (threshold) {
        console.log('Current threshold: ' + threshold);
        if (threshold < 1) {
          throw "threshold too low: " + threshold;
        }

        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');
        });
      });
    })
  });
}

var M = new mastodon({
  access_token: AMBASSADOR_TOKEN,
  api_url: INSTANCE_HOST + '/api/v1'
});

function whoami(f) {
  M.get('/accounts/verify_credentials', function(err, result) {
    if (err) {
      console.error('error getting current user id');
      throw err;
    }
    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.forEach(function(row) {
    M.post('/statuses/' + row.id + '/reblog', function(err, result) {
      if (err) {
        if (err.message === 'Validation failed: Reblog of status already exists') {
          return console.log('Warning: tried to boost #' + row.id + ' but it had already been boosted by this account.');
        }

        return console.log(err);
      }
      console.log('boosted status #' + row.id);
    });
  })
}

cycle();
setInterval(cycle, 1000 * 60 * CYCLE_INTERVAL);