Skip to content

Instantly share code, notes, and snippets.

@syoichi
Last active December 22, 2015 17:28
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save syoichi/6506075 to your computer and use it in GitHub Desktop.
Save syoichi/6506075 to your computer and use it in GitHub Desktop.
Tombfixの新しいTwitter Modelの実装のWIP Patch。 https://github.com/tombfix/core/issues/60
Models.register(update({
name : 'Twitter Mod',
ICON : 'https://twitter.com/favicon.ico',
ORIGIN : 'https://twitter.com',
ACCOUT_URL : 'https://twitter.com/settings/account',
TWEET_API_URL : 'https://twitter.com/i/tweet',
UPLOAD_API_URL : 'https://upload.twitter.com/i/tweet/create_with_media.iframe',
STATUS_MAX_LENGTH : 140,
SHORT_URL_LENGTH : 22,
check : function (ps) {
return /^(?:regular|photo|quote|link|conversation|video)$/.test(ps.type);
},
post : function (ps) {
var status = this.createStatus(ps);
return this.getToken().addCallback(token => {
if (ps.type === 'photo') {
return this.upload(ps, token, status);
}
return this.update(token, status);
});
},
getToken : function () {
return request(this.ACCOUT_URL, {
responseType : 'document'
}).addCallback(({response : doc}) => {
if (doc.body.classList.contains('logged-out')) {
throw new Error(getMessage('error.notLoggedin'));
}
return {
authenticity_token : doc.querySelector('.authenticity_token').value
};
});
},
createStatus : function (ps) {
var contents, maxLen, status, statusLen, over;
contents = {
desc : (ps.description || '').trim(),
quote : ps.type !== 'video' && ps.body ? ps.body.trim().wrap('"') : '',
title : ps.item.trim(),
url : ps.itemUrl || '',
tags : (ps.tags || []).map(tag => '#' + tag)
};
maxLen = this.STATUS_MAX_LENGTH;
if (ps.type === 'photo') {
contents.url = ps.pageUrl;
maxLen -= this.SHORT_URL_LENGTH + 1;
}
status = this.joinContents(contents);
if (contents.usage && !contents.usage.url) {
contents.url = '';
}
if (contents.url) {
let padding = /^https:\/\//.test(contents.url) ? 2 : 1,
tmp = update({}, contents);
tmp.url = '';
statusLen = this.joinContents(tmp).charLength + (this.SHORT_URL_LENGTH + padding);
} else {
statusLen = status.charLength;
}
over = statusLen - maxLen;
if (over > 0/* && getPref('model.twitter.truncateStatus')*/) {
return this.truncateStatus(contents, over);
}
return status;
},
joinContents : function ({desc, quote, title, url, tags}) {
var prefix = desc ? '' : '見てる:'; // getPref('model.twitter.template.prefix') 例: 見てる:
// getPref('model.twitter.template')
if (true) {
return this.extractTemplate(prefix, arguments[0]);
}
return joinText([prefix, desc, quote, title, url, ...tags], ' ');
},
extractTemplate : function (prefix, contents) {
var template = '%desc% %quote% %title% %url%%br%%tags% [via Test]'; // getPref('model.twitter.template') 例: %desc% %quote% %title% %url%%br%%tags% [via Test]
contents.usage = {};
template = template.replace(/%(desc|quote|title|url|tags|br)%/g, (match, name) => {
if (name === 'br') {
return '\n';
}
contents.usage[name] = true;
if (contents[name].length) {
return match;
}
return '';
}).replace(/^ +| +$/mg, '').replace(/ +/g, ' ');
return joinText([prefix, ...(template.split(' '))].map(content => {
return content.replace(/%(desc|quote|title|url|tags)%/g, (match, name) => {
if (name === 'tags') {
return joinText([...tags], ' ');
}
return contents[name];
});
}), ' ');
},
truncateStatus : function (contents, over) {
var truncator = {
tags : tags => {
contents.tags = tags = tags.reverse().filter(tag => {
if (over <= 0) {
return true;
}
over -= tag.charLength + 1;
}).reverse();
if (tags.length || over <= 0) {
return true;
}
},
title : title => {
var len = contents.title.charLength;
contents.title = title = title.slice(0, -(over + 1));
if (title.charLength) {
contents.title += '…';
} else {
over -= len + 1;
if (over > 0) {
return false;
}
}
return true;
},
quote : quote => {
quote = quote.slice(1, -(over + 2));
if (quote.charLength) {
contents.quote = (quote + '…').wrap('"');
} else {
over -= contents.quote.charLength + 1;
contents.quote = quote;
if (over > 0) {
return false;
}
}
return true;
},
desc : desc => {
contents.desc = desc.slice(0, -(over + 1)) + '…';
}
};
for (let name of Object.keys(truncator)) {
if (contents.usage && !contents.usage[name]) {
contents[name] = name === 'tags' ? [] : '';
}
let content = contents[name];
if (content.length && truncator[name](content)) {
break;
}
}
return this.joinContents(contents);
},
update : function (token, status) {
token.status = status;
return request(this.TWEET_API_URL + '/create', {
sendContent : token,
}).addErrback(err => {
throw new Error(JSON.parse(err.message.responseText).message.trimTag());
});
},
upload : function (ps, token, status) {
return (ps.file ? succeed(ps.file) : download(ps.itemUrl, getTempDir())).addCallback(file => {
var bis = new BinaryInputStream(new FileInputStream(file, -1, 0, false));
return request(this.UPLOAD_API_URL, {
sendContent : {
status : status,
'media_data[]' : btoa(bis.readBytes(bis.available())),
iframe_callback : 'window.top.swift_tweetbox_tombfix',
post_authenticity_token : token.authenticity_token
}
}).addErrback(({message : msg}) => {
if (msg.responseText.length) {
let json = msg.responseText.extract(
/window.top.swift_tweetbox_tombfix\((\{.+\})\);/
);
throw new Error(JSON.parse(json).error);
}
// image is over 3MB
throw new Error(msg.message || 'The file you selected is greater than the 3MB limit.'); // getMessage('message.model.twitter.upload') 選択した画像は 3MB を超えています。
});
});
},
favor : function (ps) {
return this.getToken().addCallback(token => {
token.id = ps.favorite.id;
return request(this.TWEET_API_URL + '/favorite', {
sendContent : token
});
});
},
login : function (user, password) {
notify(this.name, getMessage('message.changeAccount.logout'), this.ICON);
return (this.getCurrentUser() ? this.logout() : succeed()).addCallback(() => {
return request(this.ORIGIN, {
responseType : 'document'
}).addCallback(({response : doc}) => {
var form = doc.querySelector('form.signin');
notify(this.name, getMessage('message.changeAccount.login'), this.ICON);
return request(this.ORIGIN + '/sessions', {
sendContent : update(formContents(form), {
'session[username_or_email]' : user,
'session[password]' : password
})
});
});
}).addCallback(() => {
this.updateSession();
this.user = user;
notify(this.name, getMessage('message.changeAccount.done'), this.ICON);
});
},
logout : function () {
return request(this.ACCOUT_URL, {
responseType : 'document'
}).addCallback(({response : doc}) => {
return request(this.ORIGIN + '/logout', {
sendContent : formContents(doc.getElementById('signout-form'))
});
});
},
getAuthCookie : function () {
return getCookieString('twitter.com', 'auth_token');
},
getCurrentUser : function () {
return request(this.ACCOUT_URL, {
responseType : 'document'
}).addCallback(({response : doc}) => {
var user = doc.getElementsByName('user[screen_name]')[0].value;
if (!/[^\w]/.test(user)) {
this.user = user;
}
return user;
});
},
getPasswords : function () {
return getPasswords(this.ORIGIN);
}
}, AbstractSessionService));
Object.defineProperties(String.prototype, {
'charLength' : {
set : function () {
return this.charLength;
},
get : function () {
return this.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '1').length;
},
enumerable : false,
configurable : true
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment