Commit 6ac4a88c authored by Iván Sánchez Ortega's avatar Iván Sánchez Ortega

Add "reply" and "echo" commands. They seem to work but are sure buggy

parent 9159fc3d
......@@ -48,13 +48,28 @@ export default class Account {
return [];
}
// 🍂method listChannels(): Array
// Returns the timestamp of when the platform was last queried.
getLastUpdateTimestamp() {
// The clock runs updates real-time, so
return Date.now();
}
// 🍂method post(str)
// Given a string, posts it in the platform (as an tweet/toot/update/whatever)
post(str){}
// 🍂method reply(item, str)
// Posts the given string as a reply to the given item. Item shall be from
// the same platform and account.
reply(item, str){}
// 🍂method echoify(item): String
// Returns a string representing the text content of the item, suitable
// for echoing that item in other platforms. Item shall be from the same
// platform as the account.
// e.g. return "RT @username: foobar" for tweets, so it can be boosted in mastodon.
echoify(item){}
// 🍂method echo(item, str): String
// Echoes (RTs/boosts/reshares) an item. If the item cannot be directly used
// due to platform incompatibility, the given string will be posted instead,
// most probably using also the item's URL (the string is assumed to be the
// echoify()ed version of the item
echo(item, str){}
// 🍂section Internal methods
......
🍂namespace Item
An *item* is a JSON structure representing a toot, a tweet, a status update, or anything
similar to that.
🍂section
In soCLIal, an `Item` has the following fields:
🍂property accountNumber: Number
The numerical ID representing this account, as per the currently loaded soCLIal
configuration.
🍂property accountId: String
A human-readable representation of this account. Mainly for debugging, should
include some kind of combination of username/screenname and platform/server.
🍂property platform: String
A short string defining the platform. Should be similar to the class for the
platform, as it will be used to compare items to see if they belong to the
same platform, thus enabling extra features (e.g. platform-specific echoes)
🍂property raw
A blob (most probably a JSON structure) with the *raw* data provided by the
underlying platform.
🍂property sender: String
A string of text representing a short name / screen name / nickname of the
person/bot who created this item.
🍂property str: String
The main readable text of the item. Might contain URLs, emojis (or any other UTF8
characters), and newlines.
🍂property url: String
A full unequivocal URL where the item can be seen and interacted with using a
graphical web browser.
🍂property media: Array
An array of strings, each containing a URL for a media item (a photo, video or audio).
🍂property timestamp: Number
The timestamp this item was *created*, as milliseconds in UNIX epoch time.
🍂property echoed: Item|undefined
If the item is an echo (a retweet/boost/share) of other item, this contains the
echoed (retweeted/boosted/shared) item.
🍂property quoted: Item|undefined
If the item is a quote (a retweet with comments) of other item, this contains
the quoted (retweeted/commented) item.
......@@ -83,22 +83,11 @@ class MastodonAccount extends Account {
}
listen(channel) {
/*
this._timers[ channel ] = setTimeout(()=>{
}, delay - msSinceLastTick);
*/
/// FIXME!!!
}
unlisten(channel) {
// channel = Number(channel);
// if (channel <= 0) return;
//
// delete this._timers[ channel ];
//
// return this;
/// FIXME!!!
}
listChannels() {
......@@ -111,10 +100,59 @@ class MastodonAccount extends Account {
post(str) {
this._masto.post('statuses', {status: str});
this._post({status: str});
}
reply (item, str) {
let replyStatusId = item.raw.id;
let replyStatusScreenName = "@" + item.raw.account.acct;
if (!str.toLowerCase().indexOf( replyStatusScreenName.toLowerCase() )) {
/// FIXME!!! This will match partial usernames (e.g. searching for @john
/// will match @johnDoe). Should use some kind of regexp here.
str = replyStatusScreenName + ' ' + str;
}
return this._post({
status: str,
in_reply_to_id: replyStatusId
});
}
echoify(item) {
return "Boost @" + item.raw.account.acct + ': ' + item.str;
}
echo(item, str) {
if (item.platform === 'mastodon') {
// Native boost
return this._masto.post('statuses/' + item.raw.id + '/reblog');
// .then((error, toot, response)=>{
// if(error) throw error;
// // console.log(toot); // Toot body.
// // console.log(response); // Raw response object.
// });
} else {
let totalLength = str.length + 1 + item.url.length;
if (totalLength > 500) {
// Time to clip something, and replace rest with an ellipsis
str = str.substr(0, 495 - item.url.length) + '…';
}
str += ' ' + item.url;
return this.post(str);
}
}
// Common to post, reply, replyall, echo
_post(payload) {
this._masto.post('statuses', payload);
// .then((error, toot, response)=>{
// if(error) throw error;
// // console.log(toot); // Tootbody.
// // console.log(toot); // Toot body.
// // console.log(response); // Raw response object.
// });
}
......@@ -150,9 +188,10 @@ class MastodonAccount extends Account {
return({
accountNumber: this._accountNumber,
accountId: this._id,
platform: 'mastodon',
sender: msg.account.acct,
str: msg.reblog ? '' : this._cleanString(msg.content),
url: msg.url,
url: msg.url,
channel: channel,
// realtime: true,
media: msg.media_attachments.map(i=>i.text_url || i.remote_url),
......
......@@ -39,6 +39,8 @@ class TwitterAccount extends Account {
this._vorpal = vorpal;
this._accountNumber = accountNumber;
this._lenghtOfTCoLinks = 30; // Start with a conservative value
let initialTimeLock = new TimeLock();
// Lock the stream until we have some data from all the channels
......@@ -119,30 +121,34 @@ class TwitterAccount extends Account {
initialTimeLock.unlock();
// console.log('TW platform unlocked user init');
// Fetches some "constants" from the API.
// See https://dev.twitter.com/rest/reference/get/help/configuration
this._tw.get('help/configuration', {}, (err, json)=>{
if (err) { return vorpal.log(chalk.red(err)); }
this._lenghtOfTCoLinks = json.short_url_length_https;
return vorpal.log(
chalk.purple('>>> Length of t.co links in the twitter platform is currently: '),
chalk.cyan(this._lenghtOfTCoLinks)
);
});
});
}
listen(channel) {
/*
this._timers[ channel ] = setTimeout(()=>{
}, delay - msSinceLastTick);
*/
/// FIXME!!!
}
unlisten(channel) {
// channel = Number(channel);
// if (channel <= 0) return;
//
// delete this._timers[ channel ];
//
// return this;
/// FIXME!!!
}
listChannels() {
// return ['home', 'notifications', 'public'];
// return ['home', 'replies', 'notifications', 'public'];
}
getLastUpdateTimestamp() {
......@@ -150,15 +156,65 @@ class TwitterAccount extends Account {
}
post(str) {
this._tw.post('statuses/update', {status: str}, function(error, tweet, response) {
return this._post({ status: str });
}
reply (item, str) {
let replyStatusId = item.raw.id_str;
let replyStatusScreenName = "@" + item.raw.user.screen_name;
if (!str.toLowerCase().indexOf( replyStatusScreenName.toLowerCase() )) {
/// FIXME!!! This will match partial usernames (e.g. searching for @john
/// will match @johnDoe). Should use some kind of regexp here.
str = replyStatusScreenName + ' ' + str;
}
return this._post({
status: str,
in_reply_to_status_id: replyStatusId
});
}
echoify(item) {
return "RT @" + item.raw.user.screen_name + ': ' + item.str;
}
echo(item, str) {
if (item.platform === 'twitter') {
// Native RT
return this._tw.post('statuses/retweet', {
id: item.raw.id_str
}, function(error, tweet, response) {
if(error) {
this._vorpal.log(chalk.red('Could not echo into twitter: ' + error[0].message));
}
});
} else {
/// TODO: Take into account that twitter shortens ALL URLs into t.co links,
/// potentially shortening the length
let totalLength = str.length + 1 + this._lenghtOfTCoLinks;
if (totalLength > 140) {
// Time to clip something, and replace rest with an ellipsis
str = str.substr(0, 140 - this._lenghtOfTCoLinks - 2) + '…';
}
str += ' ' + item.url;
return this.post(str);
}
}
// Common to post, reply, replyall, echo
_post(payload) {
this._tw.post('statuses/update', payload, function(error, tweet, response) {
if(error) throw error;
// console.log(tweet); // Tweet body.
// console.log(response); // Raw response object.
});
}
_cleanString(str, entities, inReplyTo) {
_cleanString(str, entities, inReplyTo) {
if (entities.urls) {
entities.urls.forEach(el=>str = str.replace(el.url, el.expanded_url));
}
......@@ -291,6 +347,7 @@ class TwitterAccount extends Account {
Promise.resolve({
accountNumber: this._accountNumber,
accountId: this._id,
platform: 'twitter',
raw: channel && msg,
sender: msg.user.screen_name,
str: '',
......@@ -331,13 +388,12 @@ class TwitterAccount extends Account {
Promise.resolve({
accountNumber: this._accountNumber,
accountId: this._id,
platform: 'twitter',
sender: msg.user.screen_name,
str: str,
url: 'https://www.twitter.com/' + msg.user.screen_name + '/status/' + msg.id_str,
channel: channel,
raw: channel && msg,
// realtime: true,
// media: msg.media_attachments.map(i=>i.text_url || i.remote_url),
media: this._cleanMedia(msg),
timestamp: Date.parse(msg.created_at),
quoted: quoted
......@@ -352,13 +408,12 @@ class TwitterAccount extends Account {
itemPromise: Promise.resolve({
accountNumber: this._accountNumber,
accountId: this._id,
platform: 'twitter',
sender: msg.user.screen_name,
str: str,
url: 'https://www.twitter.com/' + msg.user.screen_name + '/status/' + msg.id_str,
channel: channel,
raw: channel && msg,
// realtime: true,
// media: msg.media_attachments.map(i=>i.text_url || i.remote_url),
media: this._cleanMedia(msg),
timestamp: Date.parse(msg.created_at),
})
......
......@@ -439,6 +439,48 @@ vorpal
});
// Command: reply to something
vorpal
.command('reply <id> [str...]')
.description('Post a reply to an item.')
.action((args, callback)=>{
if (args.id === undefined) {
this.log( chalk.red('>>> Specify item ID, e.g. "url ac7".'));
} else {
let item = circularBuffer.get(args.id);
let str = args.str.join(' ');
vorpal.log(chalk.green('Should reply with: ' + args.str.join(' ')));
// this.cancel();
activeAccounts[Number(item.accountNumber)].reply(item, str);
}
callback();
});
// Command: echo something
vorpal
.command('echo <id>')
.description('Echo (RT/boost/reshare) an item, in all platforms at once.')
.action((args, callback)=>{
if (args.id === undefined) {
this.log( chalk.red('>>> Specify item ID, e.g. "url ac7".'));
} else {
let item = circularBuffer.get(args.id);
let fallbackStr = activeAccounts[Number(item.accountNumber)].echoify(item);
vorpal.log(chalk.green('String for fallback echo is: ' + fallbackStr));
for (let i in activeAccounts) {
activeAccounts[i].echo(item, fallbackStr);
}
}
callback();
});
vorpal.log( chalk.white.bold('>>> Welcome to soCLIal !!!') );
vorpal.log( chalk.white.bold('>>> Type "help" to see available commands.') );
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment