Skip to content

Commit

Permalink
Merge pull request #5 from dergigi/feature/1-reply-with-source
Browse files Browse the repository at this point in the history
Reply with source if users ask for it
  • Loading branch information
dergigi authored Jan 30, 2019
2 parents e44b4da + ed988cc commit aae4052
Show file tree
Hide file tree
Showing 7 changed files with 311 additions and 10 deletions.
4 changes: 3 additions & 1 deletion index.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
'use strict';

require('./src/bot');
var bot = require('./src/bot');

bot.postRandomQuote();
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
"scripts": {
"start": "node index.js",
"test": "./node_modules/.bin/mocha --reporter spec",
"lint": "node_modules/.bin/goodparts *.js src/**"
"lint": "node_modules/.bin/goodparts *.js src/**",
"reply": "node reply.js"
},
"keywords": [
"twitter",
Expand Down
4 changes: 4 additions & 0 deletions reply.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
'use strict';

var bot = require('./src/bot');
bot.replyAllWithSource();
139 changes: 131 additions & 8 deletions src/bot.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,25 @@ const quotes = require('./quotes.json')

const bot = new Twit(config)

// Pick a random quote
var quote = quotes[Math.floor(Math.random()*quotes.length)]
const WHITEPAPER_URL = 'https://bitcoin.org/bitcoin.pdf'
const BITCOINTALK_URL = 'https://satoshi.nakamotoinstitute.org/posts/bitcointalk/'
const EMAIL_URL = 'https://satoshi.nakamotoinstitute.org/emails/cryptography/'
const P2PFOUNDATION_URL = 'https://satoshi.nakamotoinstitute.org/posts/p2pfoundation/'

// Sanitze quote (remove double spaces)
var sanitizedQuote = sanitizeQuote(quote)

// Reduce length of quote to fit twitter
var tweetableQuote = shortenQuote(sanitizedQuote)
function postRandomQuote() {
// Pick a random quote
var quote = quotes[Math.floor(Math.random()*quotes.length)]

// Post quote to twitter
postQuote(tweetableQuote)
// Sanitze quote (remove double spaces)
var sanitizedQuote = sanitizeQuote(quote)

// Reduce length of quote to fit twitter
var tweetableQuote = shortenQuote(sanitizedQuote)

// Post quote to twitter
postQuote(tweetableQuote)
}

/**
* Get rid of Satoshi's double spaces since they use up valuable
Expand Down Expand Up @@ -73,5 +81,120 @@ function postQuote(quote) {
}
}

function getRepliesAskingForSource(callback) {
bot.get('search/tweets', { q: 'to:@QuotableSatoshi source', count: 100 }, callback)
}

function getRepliesByBot(tweet, callback) {
var since_id = tweet.id_str
bot.get('search/tweets', { q: 'from:@QuotableSatoshi to:' + tweet.user.screen_name, since_id: since_id, count: 10 }, callback)
}

function replyAllWithSource() {
getRepliesAskingForSource(function(err, data, response) {
if (err) {
console.log(err)
} else {
data.statuses.forEach(s => {
getRepliesByBot(s, function(err, data, response) {
if (data.statuses.length == 0) {
replyWithSource(s);
}
});
})
}
})
}

function replyWithSource(tweet) {
getParentTweet(tweet, function(err, data, response) {
console.log("--")
console.log(tweet.text)
var metadata = getQuoteMetadata(data.text.substring(0, 70)) // twitter API might return truncated text
var reply = '@'
reply += tweet.user.screen_name
reply += ' Satoshi wrote this on '
reply += metadata.date
reply += ', '
reply += mediumPhrase(metadata.medium)
reply += '. You can find the full quote, context, and more information here: '
reply += metadata.source

console.log(reply)

if (config.post_to_twitter) {
bot.post('statuses/update', { status: reply, in_reply_to_status_id: tweet.id_str }, function(err, data, response) {
if (err) {
console.log(err)
} else {
console.log(data)
}
})
} else {
console.log("(Not replying to user. ENV var POST_TO_TWITTER has to be set to true.)")
}
})
}

function mediumPhrase(medium) {
switch(medium) {
case "whitepaper":
return "in the whitepaper"
case "email":
return "in an email to the cryptography mailing list"
case "bitcointalk":
return "in a BitcoinTalk thread"
break;
case "p2pfoundation":
return "in an email to the P2P Foundation mailing list"
default:
source = null;
break;
}
}

function getQuoteMetadata(quote, callback) {
var PATTERN = new RegExp(quote);
var matched_quotes = quotes.filter(function (q) { return PATTERN.test(q.text); });
if (!matched_quotes || matched_quotes.length > 1) {
return null;
}

var quote_entry = matched_quotes[0]
var source = null
switch(quote_entry.medium) {
case "whitepaper":
source = WHITEPAPER_URL;
break;
case "email":
source = EMAIL_URL + quote_entry.email_id;
break;
case "bitcointalk":
source = BITCOINTALK_URL + quote_entry.post_id;
break;
case "p2pfoundation":
source = P2PFOUNDATION_URL + quote_entry.post_id;
break;
default:
source = null;
break;
}

quote_entry['source'] = source;

return quote_entry
}

function getParentTweet(tweet, callback) {
bot.get('statuses/show/:id', { id: tweet.in_reply_to_status_id_str }, callback)
}

module.exports.shortenQuote = shortenQuote;
module.exports.quotes = quotes;
module.exports.getRepliesAskingForSource = getRepliesAskingForSource;
module.exports.replyWithSource = replyWithSource;
module.exports.replyAllWithSource = replyAllWithSource;
module.exports.getParentTweet = getParentTweet;
module.exports.getRepliesByBot = getRepliesByBot;
module.exports.getQuoteMetadata = getQuoteMetadata;
module.exports.postRandomQuote = postRandomQuote;
101 changes: 101 additions & 0 deletions test/assets/tweet_asking_for_source.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
{
"created_at": "Fri Jan 25 23:59:44 +0000 2019",
"id": 1088949574264414200,
"id_str": "1088949574264414213",
"text": "@QuotableSatoshi Source?",
"truncated": false,
"entities": {
"hashtags": [],
"symbols": [],
"user_mentions": [
{
"screen_name": "QuotableSatoshi",
"name": "Quotable Satoshi",
"id": 1081377398514425900,
"id_str": "1081377398514425856",
"indices": [
0,
16
]
}
],
"urls": []
},
"source": "<a href=\"http://twitter.com/download/android\" rel=\"nofollow\">Twitter for Android</a>",
"in_reply_to_status_id": 1085371334950092800,
"in_reply_to_status_id_str": "1085371334950092800",
"in_reply_to_user_id": 1081377398514425900,
"in_reply_to_user_id_str": "1081377398514425856",
"in_reply_to_screen_name": "QuotableSatoshi",
"user": {
"id": 18213426,
"id_str": "18213426",
"name": "Gigi",
"screen_name": "dergigi",
"location": "The Internet, Planet Earth",
"description": "Bitcoin twitter is real life.",
"url": "https://t.co/vrNoQgEBxT",
"entities": {
"url": {
"urls": [
{
"url": "https://t.co/vrNoQgEBxT",
"expanded_url": "https://medium.com/@dergigi",
"display_url": "medium.com/@dergigi",
"indices": [
0,
23
]
}
]
},
"description": {
"urls": []
}
},
"protected": false,
"followers_count": 580,
"friends_count": 162,
"listed_count": 46,
"created_at": "Thu Dec 18 11:49:14 +0000 2008",
"favourites_count": 12596,
"utc_offset": null,
"time_zone": null,
"geo_enabled": false,
"verified": false,
"statuses_count": 4665,
"lang": "en",
"contributors_enabled": false,
"is_translator": false,
"is_translation_enabled": false,
"profile_background_color": "000000",
"profile_background_image_url": "http://abs.twimg.com/images/themes/theme1/bg.png",
"profile_background_image_url_https": "https://abs.twimg.com/images/themes/theme1/bg.png",
"profile_background_tile": false,
"profile_image_url": "http://pbs.twimg.com/profile_images/975173422119612417/h2VyxVmR_normal.jpg",
"profile_image_url_https": "https://pbs.twimg.com/profile_images/975173422119612417/h2VyxVmR_normal.jpg",
"profile_banner_url": "https://pbs.twimg.com/profile_banners/18213426/1529070904",
"profile_link_color": "ABB8C2",
"profile_sidebar_border_color": "000000",
"profile_sidebar_fill_color": "000000",
"profile_text_color": "000000",
"profile_use_background_image": false,
"has_extended_profile": false,
"default_profile": false,
"default_profile_image": false,
"following": true,
"follow_request_sent": false,
"notifications": false,
"translator_type": "none"
},
"geo": null,
"coordinates": null,
"place": null,
"contributors": null,
"is_quote_status": false,
"retweet_count": 0,
"favorite_count": 0,
"favorited": false,
"retweeted": false,
"lang": "en"
}
File renamed without changes.
70 changes: 70 additions & 0 deletions test/replytest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
var assert = require('assert');
var bot = require('../src/bot');

const REPLY_TWEET_ID = '1088949574264414213'
const EXPECTED_ROOT_TWEET_ID = '1085371334950092800'
const TWEET_ASKING_FOR_SOURCE = require('./assets/tweet_asking_for_source.json')

const QUOTE_BITCOINTALK = 'At equilibrium size, many nodes will be server farms with one or two network nodes that feed the rest of the farm over a LAN.'
const QUOTE_BITCOINTALK_SOURCE = 'https://satoshi.nakamotoinstitute.org/posts/bitcointalk/188'

const QUOTE_WHITEPAPER = 'A purely peer-to-peer version of electronic cash would allow online payments to be sent directly from one party to another without going through a financial institution.'
const QUOTE_WHITEPAPER_SOURCE = 'https://bitcoin.org/bitcoin.pdf'

const QUOTE_EMAIL = 'It might make sense just to get some in case it catches on. If enough people think the same way, that becomes a self fulfilling prophecy.'
const QUOTE_EMAIL_SOURCE = 'https://satoshi.nakamotoinstitute.org/emails/cryptography/17'

const QUOTE_P2PFOUNDATION = 'To Sepp\'s question, indeed there is nobody to act as central bank or federal reserve to adjust the money supply as the population of users grows.'
const QUOTE_P2PFOUNDATION_SOURCE = 'https://satoshi.nakamotoinstitute.org/posts/p2pfoundation/3'

describe('quotableSatoshi', function() {
describe('#getParentTweet()', function() {
it('should get the correct root tweet id of a reply', function() {
bot.getParentTweet(TWEET_ASKING_FOR_SOURCE, function(err, data, response) {
assert.equal(data.id_str, EXPECTED_ROOT_TWEET_ID, 'tweet id is the expected root tweet id')
assert.equal(data.user.screen_name, 'dergigi')
})
});
});
describe('#getRepliesByBot()', function() {
it('should detect if tweet asking for source was replied to', function() {
bot.getRepliesByBot(TWEET_ASKING_FOR_SOURCE, function(err, data, response) {
assert.equal(data.statuses.length, 0, 'statuses should be empty')
});
});
});
describe('#getQuoteMetadata()', function() {
it('should look up the source of a bitcointalk quote correctly', function() {
var metadata = bot.getQuoteMetadata(QUOTE_BITCOINTALK);
assert.equal(metadata.source, QUOTE_BITCOINTALK_SOURCE);
assert.equal(metadata.date, "July 14, 2010");
});
it('should look up the source of a whitepaper quote correctly', function() {
var metadata = bot.getQuoteMetadata(QUOTE_WHITEPAPER);
assert.equal(metadata.source, QUOTE_WHITEPAPER_SOURCE);
assert.equal(metadata.date, "October 31, 2008");
});
it('should look up the source of a email quote correctly', function() {
var metadata = bot.getQuoteMetadata(QUOTE_EMAIL);
assert.equal(metadata.source, QUOTE_EMAIL_SOURCE);
assert.equal(metadata.date, "January 17, 2009");
});
it('should look up the source of a p2pfoundation quote correctly', function() {
var metadata = bot.getQuoteMetadata(QUOTE_P2PFOUNDATION);
assert.equal(metadata.source, QUOTE_P2PFOUNDATION_SOURCE);
assert.equal(metadata.date, "February 18, 2009");
});
});
});

function onSearchComplete(err, data, response) {
if (err) {
fail("Search should not throw an error")
}
if (data) {
console.log(data)
assert.equal(data.count > 0, true)
} else {
fail("No data received")
}
}

0 comments on commit aae4052

Please sign in to comment.