Skip to content

Instantly share code, notes, and snippets.

@satyr
Last active February 4, 2016 13:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save satyr/103277 to your computer and use it in GitHub Desktop.
Save satyr/103277 to your computer and use it in GitHub Desktop.
twitter tools
Cu.import("resource://ubiquity/modules/oauth.js")
const self = this,
H = Utils.escapeHtml,
Twitter = 'http://twitter.com/',
TWS = 'http://search.twitter.com/',
TWAPI = 'https://api.twitter.com/1.1/',
Yats = 'http://yats-data.com/yats/',
twicli = 'http://twicli.neocat.jp/',
Icon = 'chrome://ubiquity/skin/icons/twitter.ico',
Logo = '<a href='+ Twitter +' id=twlogo accesskey="/">'
+ '<img border=0 width=60 style="margin:30px"'
+ ' src=https://g.twimg.com/Twitter_logo_blue.png>'
+ '</a>'
NoLogin = 'Error (need to <a href='+ Twitter +'login accesskey=l>'
+ '<u>l</u>ogin</a>, maybe?)',
CSS = '\
a img {border:none; max-width:99%; height:auto; vertical-align:top}\
.error {font-style:oblique; line-height:1.8}\
.logo {clear:both}\
.logo img {vertical-align:middle}\
.loading {opacity:0.9}\
.loading + .logo {opacity:0.4}\
.error + .logo {opacity:0.7}\
',
CSS_TW = '\
.entry-content {margin:0 0.2em; white-space:pre-wrap}\
.entry-content img {display:block}\
.actions, .actions-hover .del, .actions-hover span:not(.reply), .geo-pin\
{display:none}\
.entry-meta, .actions-hover {font-size:92%}\
.actions-hover {list-style:none; padding-left:0}\
.listable, .actions-hover, .actions-hover > li {display:inline}\
.entry-date + span + a {margin-right:0.3em}\
.reply::before {content:"["}\
.reply::after {content:"]"}\
.big-retweet-icon + *::before {content:"\267a "}\
.status-body + .status-body {font-size:92%}\
p.clearleft {margin:0.5ex 0}\
#twlogo {display:inline-block}\
',
XQ = [],
XHosts = {
'am6.jp': 'bit.ly', 'digs.by': 'bit.ly', 'ht.ly': 'ow.ly',
'tumblr.com': 'www.tumblr.com',
},
XCache = {__proto__: null},
B58Seq = '123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ'
function xpand(target){
var $t = $(target)
for (let a of $t.find('a').add($t.filter('a')).get())
~XQ.indexOf(a) || XQ.push(a)
if(XQ.length) Utils.setTimeout(xproc)
return target
}
function xproc(){
XQ.forEach(xpit);
XQ.length = 0;
}
function xpit(a){
if(/^(?!\s*http:\/\/\S)/.test(a.textContent)) return;
var h = a.hostname;
if(h in XHosts) a.hostname = h = XHosts[h];
else if(h == 'bit.ly') a.href = a.href.replace(/\+$/, '');
var {href} = a;
if(href in XCache) return void(a.innerHTML = XCache[href]);
xsrc(a) || xget(a, xpandurl,
'http://api.longurl.org/v2/expand?format=json&title=1&url='+
encodeURIComponent(href), 'json')
}
function xtrim(u){
u = u.replace(/^http:\/+(?:www\.)?([^/]+)(?:\/$)?/, '$1')
.replace(/[?&#]utm_\w+=\w+/g, '')
.replace(/\/#!/, '')
try { while(u !== (u = decodeURIComponent(u))); } catch([]){}
return Utils.escapeHtml(u);
}
var xset = (a, h) => a.innerHTML = XCache[a.href] = h;
var ximg = (a, s) => s && xset(a, '<img src="'+ H(s) +'" align=top>')
function xget(a, f, u, dt, hs){
a.style.opacity = .6;
function x_ok(r){
f(a, r);
a.style.opacity = 'inherit';
}
function x_ng(x){
Utils.reportInfo(
'failed to expand ('+ x.status +' '+ x.statusText +')\n'+ a);
a.style.opacity = .9;
}
o = {url: u, dataType: dt || 'text', success: x_ok, error: x_ng};
if(hs) o.beforeSend = function(x){
for(var k in hs) x.setRequestHeader(k, hs[k]);
};
return $.ajax(o);
}
function xsrc(a){
if(a.querySelector('img')) return true;
var {href, hostname, pathname} = a, it;
if(pathname == '/') return false;
hostname = hostname.replace(/^www\./, '');
if(it = (
'grab.by' == hostname && /^\/[^/]+$/.test(pathname)
? [tinygrab, href] :
'photozou.jp' == hostname && (it = /\d+$/.exec(pathname))
? [photozou,
'http://api.photozou.jp/rest/photo_info?photo_id='+ it, 'xml'] :
'twitpaint.com' == hostname && /\/\w+$/.test(pathname)
? [twitpaint, href] :
~['p.tl', 'pixiv.net'].indexOf(hostname) && /[id][\/=]\d+/.test(href)
? [pixiv, href] :
(it = (
/^http:\/\/www\.flickr\.com\/photos\/[\w-_@]+\/(\d+)/.test(href)
? RegExp.$1 :
/^http:\/\/flic\.kr\/p\/(\w+)/.test(href)
? Array.reduce(RegExp.$1, (r, c) => r * 58 + B58Seq.indexOf(c), 0)
: ''))
? [flickr,
'https://api.flickr.com/services/rest/'+ Utils.paramsToString({
method: 'flickr.photos.getInfo',
photo_id: it, format: 'json', nojsoncallback: 1,
api_key: '88dd5c2e369b0c2d5e7cff6f6258f215'})] :
/^https?:\/\/pic\.twitter\.com\/\w+/.test(href) ||
/^https?:\/\/twitter\.com\/\w+\/status\/\d+\/photo\//.test(href)
? [twimg, href] :
/^http:\/\/movapic\.com\/\w+\/pic\/\d+/.test(href)
? [movapic, href] :
/^(http:\/\/[\w-]+\.tumblr\.com\/)post\/(\d+)/.test(href)
? [tumblr, RegExp.$1 +'api/read/json?id='+ RegExp.$2] :
/^http:\/+(twit(?:draw|paint))\.com\/\w+$/.test(href)
? [self[RegExp.$1], href] :
/^http:\/+instagr\.?am(?:\.com)?\/p\/\w/.test(href)
? [instagram, href] :
/^http:\/+picplz\.com\/\w{4,}/.test(href)
? [picplz, href] :
/^http:\/+cl\.ly\/\w+/.test(href)
? [clly, href] :
/^http:\/\/lockerz\.com\/s\/\d/.test(href)
? [lockerz, href] :
0)) return xget.apply(this, [a].concat(it));
return ximg(
a,
/\.(?:png|gif|jpe?g)$/i.test(pathname) || /^data:image\/\w/.test(href)
? href :
'gyazo.com' == hostname && /^\/\w+$/.test(pathname)
? href + '.png' :
~['twitpic.com', 'img.ly'].indexOf(hostname) &&
/^(?!\/show\/)/.test(pathname)
? 'http://'+ hostname +'/show/thumb'+ pathname :
~['plixi.com', 'tweetphoto.com', 'pic.gd'].indexOf(hostname)
? ('http://api.plixi.com/api/tpapi.svc/imagefromurl?'+
'size=medium&url='+ encodeURIComponent(href)) :
'pic.im' == hostname && /^(?!\/website\/)/.test(pathname)
? 'http://pic.im/website/thumbnail'+ pathname :
'twitgoo.com' == hostname
? href + (pathname.lastIndexOf('/') > 0 ? '' : '/img') :
'p.twipple.jp' == hostname
? 'http://p.twpl.jp/show/orig'+ pathname :
'movapic.com' == hostname && /\/pic\/(\w+)$/.test(pathname)
? 'http://image.movapic.com/pic/m_'+ RegExp.$1 +'.jpeg' :
'moby.to' == hostname
? 'http://mobypicture.com/?'+ pathname.slice(1) +':medium' :
'pikchur.com' == hostname
? 'http://img.pikchur.com/pic_'+ pathname.slice(1) +'_m.jpg' :
'bcphotoshare.com' == hostname && (it = /\d+$/.exec(pathname)) ||
'bctiny.com' == hostname && (it = parseInt(pathname.slice(2), 36))
? 'http://images.bcphotoshare.com/storages/'+ it +'/thumb180.jpg' :
/^http:\/+yfrog\.(?:com|us)\/\w+$/.test(href)
? href +'.th.jpg' :
/^http:\/+ow\.ly\/i\/(\w+)/.test(href)
? 'http://static.ow.ly/photos/normal/'+ RegExp.$1 +'.jpg' :
'youtu.be' == hostname && (it = pathname.slice(1)) ||
'youtube.com' == hostname && (it = (/\bv=([\w-]+)/.exec(a.search) || 0)[1])
? 'http://i.ytimg.com/vi/'+ it +'/hqdefault.jpg' :
~['nico.ms', 'nicovideo.jp'].indexOf(hostname) &&
(it = (/^\/(?:watch\/)?(?!lv)..(\d+)/.exec(pathname) || 0)[1])
? 'http://tn-skr.smilevideo.jp/smile?i='+ it :
'twitvid.com' == hostname
? 'http://images.twitvid.com'+ pathname +'.jpg' :
'');
}
function twimg(a, htm){
var m = /https:\/\/pbs\.twimg\.com\/media\/[\w-]+\.\w+:/.exec(htm)
if(m) return ximg(a, m +'small')
}
var clly = (a, htm) =>
ximg(a, (/"embed" href="(.+?)"/.exec(htm) || 0)[1])
var picplz = (a, htm) =>
ximg(a, /http:\/+[^/]+.picplzthumbs.com\/[^\"\'\s]+?\.jpg/.exec(htm))
var instagram = (a, htm) =>
ximg(a, /http:\/+distilleryimage\d+\.ak\.instagram\.com\/[^\"' ]+/.exec(htm))
var twitdraw = (a, htm) =>
ximg(a, /<link rel="image_src"[^>]*(http:.+\.png)/.test(htm) && RegExp.$1)
var twitpaint = (a, htm) =>
ximg(a, /http:\/+twitpaint\.com\/public\/graffiti\/\w+\.png/.exec(htm))
function xpandurl(a, res){
const GSORRY = 'http://sorry.google.com/sorry/?continue='
var lu = res['long-url']
if(~lu.lastIndexOf(GSORRY, 0))
lu = decodeURIComponent(lu.slice(GSORRY.length))
if(a.hash && !~lu.indexOf('#')) lu += a.hash
var {href} = a
if(lu == href) return a.innerHTML = XCache[href] = xtrim(href);
xset(a, xtrim(lu));
a.title = (a.title && a.title +' ') + res.title +' '+ href
a.href = lu
return xsrc(a)
}
var pixiv = (a, htm) => (
RegExp('<img[^>]+\\D'+ /\d+/.exec(a.href) +'_[^>]+>').test(htm) &&
xset(a, RegExp.lastMatch.replace(/_\w\.j/, '_m.j')));
var photozou = (a, xml) => (
ximg(a, xml.querySelector('original_image_url').textContent));
function flickr(a, json){
try{ var {farm, server, id, secret} = JSON.parse(json).photo }
catch(e){
e.message += '\n'+ a.href +'\n'+ json;
Cu.reportError(e);
return '';
}
return ximg(a, (
'http://farm'+ farm +'.static.flickr.com/'+ server +'/'+
id +'_'+ secret +'.jpg'));
}
var tinygrab = (a, htm) =>
/<img[^>]*\ssrc="http:\/\/grab\.by\/grabs\/.+?"/.test(htm) &&
xset(a, RegExp.lastMatch)
var lockerz = (a, htm) =>
/<img id="photo"[^>]+>/.test(htm) &&
xset(a, RegExp.lastMatch)
var movapic = (a, htm) => (
/<img class="image"[^>]*>/.test(htm) && xset(a, RegExp.lastMatch));
var tumblr = (a, js) => (
ximg(a, JSON.parse(js.slice(22, -2)).posts[0]['photo-url-500']));
function ok(c, it){
if(!it) return ng(ok.caller.name);
if(it.jquery) $(c).empty().append(it);
else c.innerHTML = it;
c.className = '';
c.ownerDocument.defaultView.scrollTo(0, 0);
return c;
}
function ng(c){
c.innerHTML = Array.slice(arguments, 1).join(' ');
c.className = 'error';
return c;
}
var ngx = (c) => function(x){ if(x.status) ng(c, x.status, x.statusText) }
function jax(cn, url, cb, oauth){
cn.className = 'loading'
var o = {url: url, success: cb, error: ngx(cn)}
if(oauth) o = ocomp(o)
return CmdUtils.previewAjax(cn.ownerDocument.body, o)
}
var atlink = (h) => h.replace(/[@\uff20](\w+)/g, _atlink);
var _atlink = (_, i) => _.link(Twitter + i);
var qsi = (l, s) => new Iterator(Array.slice(l.querySelectorAll(s)));
function noop(){}
function execurl(args){
Utils.openUrlInBrowser((this._url || noop)(args) || this._du || this._hp);
}
function content(pb, cb){
for(var cn, set;; set = pb.innerHTML = this._base)
if((cn = pb.ownerDocument.getElementById(this._name))){
set && cb && cb.call(this, cn);
return cn;
}
}
function ocomp(o, name){
if(!name){
let [login] = noun_type_twitter_user.logins()
if(login) name = login.text
}
let pair = CmdUtils.loadPassword('TwitterOAuth', name)
if(!pair) return o
let [key, secret] = pair.split(' '), token = {
token: key, tokenSecret: secret,
consumerKey : 'C6h2HUUjmOcqXTtPRYqAVg',
consumerSecret : 'AYNHPfkpm5lL3uPKXRCuzGFYItA8EOWlrkajyEBOd6s',
}
return OAuth.completeAjaxSettings(o, token)
}
function TwitterCommand(o, notw){
if('_css' in o || '_logo' in o){
let [name] = o.names || [o.name];
o._name = name = name.replace(/\W+/g, '_');
o._base = '<style>'+ CSS + (notw ? '' : CSS_TW) + [o._css] +'</style>'
+ '<div class='+ name +'>'
+ '<div id='+ name +'>'+ H(o._htm || ' ') +'</div>'
+ '<div class=logo>'+ (o._logo || Logo) +'</div>'
+ '</div>'
o._content = content;
o._htm = o._css = o._logo = null;
}
'execute' in o || (o.execute = execurl);
'icon' in o || (o.icon = Icon);
return CmdUtils.CreateCommand(o);
}
const noun_twitter_friend = {
name: 'twitter friend',
label: 'screen name',
suggest: function ntf_suggest(txt, htm, cb, sx){
var id = (/^\s*[@\uff20]?(\w+)\s*$/.exec(txt) || 0)[1];
if(!id) return [];
var ss = this._friends(id, cb) || [];
ss.some(s => s.score === 1) ||
ss.push(CmdUtils.makeSugg(id, htm, null, .1, sx && [0, id.length]));
return ss;
},
_friends: function ntf_friends(input, cb){
var ls = this._list;
if(ls.length) return CmdUtils.grepSuggs(input, ls);
var ms = Date.now();
if(ms - this._last < 6e4) return;
this._last = ms;
var dic = {__proto__: null};
var fun = (ss) =>
(ss = CmdUtils.grepSuggs(input, ss)).length && cb(ss);
for(let {text} of noun_type_twitter_user.logins()){
dic[text] = 1;
this._request(fun, text, ls, dic);
}
},
_request: function ntf_request(back, name, list, dict){
$.ajax(ocomp({
url: TWAPI +'friends/ids.json',
dataType: 'json',
data: {screen_name: name, stringify_ids: 1},
success: function ntf_ids_success({ids}){
list.push({text: name, summary: name})
while(ids.length) lookup(ids.splice(0, 100))
},
error: function ntf_ids_error(x){
Utils.reportInfo([
'error retrieving friends of @'+ name,
'status: '+ x.status +' '+ x.statusText].join('\n'))
Utils.setTimeout(function(){ list.length = 0 }, 6e4)
},
}, name))
function lookup(ids){
$.ajax(ocomp({
url: TWAPI +'users/lookup.json', dataType: 'json',
data: {user_id: ''+ids},
success: function(users){
var ss = []
for(let {screen_name: sn} of users) if(!(sn in dict))
ss.push({text: dict[sn] = sn, summary: sn})
list.push.apply(list, ss)
back(ss)
},
}, name))
}
},
_list: [],
_last: 0,
}
TwitterCommand({
names: ['peek', 'view tweets'],
description:
'Peeks '+ 'tweets'.link(Twitter) +' of the specified user.',
authors: [{name: 'powchin', homepage: 'http://friendfeed.com/powchin'},
'satyr'],
arguments: noun_twitter_friend,
execute: function peek_execute({object: {text: sn}}){
var u = sn && Twitter + sn
if(!u){
let {list} = noun_twitter_friend
if(list) u = Twitter + list[list.length * Math.random() | 0].text
}
u && Utils.openUrlInBrowser(u)
},
preview: function peek_preview(pb, {object: {text: sn}}){
var cn = this._content(pb), me = this
if(!sn) return
jax(cn, TWAPI +'statuses/user_timeline.json?count=100&screen_name='+ sn
, function peek_success(rs){
var htm = '<a class=user href='+ Twitter + sn +' accesskey="@">'
+ '<span class=name>'+ sn +'</span></a>'
rs.length
? htm = '<img class=image src="'
+ H(rs[0].user.profile_image_url)
+ '">' + htm
: htm += '<br><em>No recent tweets.'
for(let r of rs) htm +=
'<pre class=text>'+ H(r.text) +
' <a class="created" href='+ Twitter + sn +'/status/'+ r.id_str +'>'+
H(new Date(r.created_at).toLocaleString()) +'</a></pre>'
ok(cn, htm)
}, true)
},
_css: '\
.user {magin-left:1em}\
.image {vertical-align:middle; max-height:5ex}\
.name {font-weight:bolder; font-size:128%; margin:0 0.5em}\
.text {margin:0 0.5em 0.5ex}\
.created {display:block; text-decoration:none; font-size:88%}\
',
})
TwitterCommand({
names: ['glance', 'search tweets'],
arguments: {
object: noun_arb_text,
format: CmdUtils.NounType('language', {
'*': 'all',
'Arabic (\u0627\u0644\u0639\u0631\u0628\u064A\u0629)': 'ar',
'Danish (dansk)': 'da',
'Dutch (Nederlands)': 'nl',
'English': 'en',
'Farsi/Persian (\u0641\u0627\u0631\u0633\u06CC)': 'fa',
'Finnish (suomen kieli)': 'fi',
'French (fran\xE7ais)': 'fr',
'German (Deutsch)': 'de',
'Hungarian (Magyar)': 'hu',
'Icelandic (\xCDslenska)': 'is',
'Italian (Italiano)': 'it',
'Japanese (\u65E5\u672C\u8A9E)': 'ja',
'Norwegian (Norsk)': 'no',
'Polish (polski)': 'pl',
'Portuguese (Portugu\xEAs)': 'pt',
'Russian (\u0440\u0443\u0441\u0441\u043A\u0438\u0439 \u044F\u0437\u044B\u043A)': 'ru',
'Spanish (espa\xF1ol)': 'es',
'Swedish (Svenska)': 'sv',
'Thai (\u0E44\u0E17\u0E22)': 'th',
}, '^\\*'),
},
description: 'Twitter Search'.link(TWS),
preview: function tws_preview(pb, {object: {text: q}, format: {data: l}}){
var cn = this._content(pb), q = q.trim()
if(!q) return
jax(cn
, TWAPI +'search/tweets.json?count=100&q='+
encodeURIComponent(q) +'&lang='+ l
, function tws_success({statuses: ss}){
var htm = ''
for(var s of ss){
let sn = s.user.screen_name
htm += '<div class=entry>'
+ '<a class=author href="'+ Twitter + sn +'">'
+ sn +'</a>'
+ '<a class=updated href="'
+ Twitter + sn +'/status/'+ s.id_str +'">'
+ H(new Date(s.created_at).toLocaleString()) +'</a>'
+ '<p class=tweet>'+ H(s.text)
let {urls} = s.entities
if(urls.length){
htm += '<ul class=urls>'
for(let u of urls){
let hu = H(u.url)
htm += '<li>'+ hu.link(hu)
}
htm += '</ul>'
}
htm += '</p></div>'
}
ok(cn, htm || '<p><em>No results.')
}, true)
},
_hp: TWS,
_du: 'http://support.twitter.com/groups/31-twitter-basics/topics/'+
'110-search/articles/71577-how-to-use-advanced-twitter-search',
_url: function tws_url({object: {text: q}, format: {data: l}}){
return (q = q.trim())
&& Twitter +'search?q='+ encodeURIComponent(q) + (l ? '&lang='+ l : '')
},
_css: '\
a {text-decoration:none; margin-left:0.3em}\
.author {font-weight:bold}\
.updated {margin-left:1ex}\
.tweet {margin:0 0.5em 0.5ex}\
.urls {margin:0}\
',
})
let logo = '<a href="http://longurl.org">'
+ '<img src="http://assets.longurl.org/static/images/longurl-logo.png"'
+ ' border=0></a>'
TwitterCommand({
names: ['expand URLs'],
description:
'Expands all shortened and/or image links in <b>the focused page</b>.',
execute: function lu_execute(){
!function recur(win){
xpand(win.document.body)
Array.forEach(win, recur)
}(context.focusedWindow.top)
},
preview: function lu_preview(pb){
var cn = this._content(pb, this._form)
$(cn).find('b').text(context.focusedWindow.top.location.href)
},
_form: function lu_form(cn){
cn.innerHTML = this.previewDefault()
},
_css: '',
_logo: logo,
}) & TwitterCommand({
names: ['expand a URL'],
description: 'Expands a single URL.',
argument: {
default: function xau_default(){
var [a] = CmdUtils.getSelectedNodes('a')
if(a) return CmdUtils.makeSugg(a.href)
},
suggest: function xau_suggest(text){
var url = text.trim()
if(/^(?:\w+:)?\/+\S+$/.test(url)) return CmdUtils.makeSugg(url)
},
},
execute: function xau_execute(args){
var a = this._a(args)
if(a) CmdUtils.setSelection(a)
for (let a of CmdUtils.getSelectedNodes('a')) xpand(a)
},
preview: function xau_preview(pb, args){
var cn = this._content(pb)
;(cn.innerHTML = this._a(args))
? xpand(cn)
: this.previewDefault(cn)
},
_a: function xau_a({object: {text: u}}){
if(!u) return
var hu = H(u)
return '<a href="'+ hu +'" class=url>'+ hu +'</a>'
},
_css: '.url {display:inline-block; padding:1ex 1em}',
_logo: logo,
})
/*
CmdUtils.onPageLoad(function autoxpand(doc){
var win = doc.defaultView
if(win.parent != win) return
var target = doc.querySelector('#tw')
if(!target) return
xpand(target)
new win.MutationObserver(function(ms){
for(let m of ms) if(m.type == 'childList'){
for(let n of Array.slice(m.addedNodes)) xpand(n)
break
}
}).observe(target, {childList: true})
}, [twicli +'twicli.html'])
*/
$.extend(feed, {
author: {name: 'satyr', homepage: 'http://ubigist.appspot.com/?o=satyr'},
license: 'X',
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment