// ==UserScript== // @name Inline Thumbnail // @namespace inline_thumbnail // @description Expand to view thumbnails in the row. // @run-at document-end // @include http://hootsuite.com/* // @include https://hootsuite.com/* // @include http://twitter.com/* // @include https://twitter.com/* // @include https://mobile.twitter.com/* // @include http://www.crowy.net/* // @include http://twipple.jp/* // @include https://tweetdeck.twitter.com/* // @version 1.7.3 // @downloadURL https://update.greasyfork.icu/scripts/2923/Inline%20Thumbnail.user.js // @updateURL https://update.greasyfork.icu/scripts/2923/Inline%20Thumbnail.meta.js // ==/UserScript== (function() { function source() { // source code var VERSION = '1.7.3'; var USE_LOCAL_STORAGE_ENABLE = true; var MAX_URL_CACHE = 400; function isSupportXhr2() { return 'withCredentials' in $.ajaxSettings.xhr(); } function ajax(arg) { var done = 0; var option = { url: arg.url, data: arg.data, dataType: arg.dataType, type: 'GET', cache: true, success: function(data, status, xhr) { done++ || arg.success(data, status, xhr); }, error: function(xhr, status, errorThrown) { done++ || arg.error(xhr, status, errorThrown); }, }; if (arg.dataType == 'jsonp') { setTimeout(function() { option.error(); }, 15 * 1000); } $.ajax(option); } function loadJQuery() { var id = 'dy-load-jq'; if (document.getElementById(id)) { return; } var script = document.createElement('script'); script.id = id; script.type = 'text/javascript'; script.src = '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js'; document.body.appendChild(script); } function main(EXT_SERV_HOST_URL) { // main logic var STORE_PREFIX = 'inlineThumbnail.'; var EXPANDED_URL_CACHE_KEY = STORE_PREFIX + 'expandedUrls'; var THUMBNAIL_URL_CACHE_KEY = STORE_PREFIX + 'thumbnailUrls'; var EXT_SERV_ENDS = (function(base) { var ret = { expandurl: 'expandurl', amazon: 'amazon', flickr: 'flickr', nicovideo: 'nicovideo', videosurf: 'videosurf', tinami: 'tinami', ustream: 'ustream', tumblr: 'tumblr', rakuten: 'rakuten', ogmedia: 'ogmedia', myspacevideo: 'myspacevideo', pictwitter: 'pictwitter', itunes: 'itunes', loctouch: 'loctouch', i500px: '500px', facebook: 'facebook', twil: 'proxy/twil' }; $.each(ret, function(key, val) { ret[key] = base + '/' + val; }); return ret; })(EXT_SERV_HOST_URL); var HTTPS_SUPPORT_SITE_REGEX = (function() { var fragment = ( 'pbs.twimg.com twitpic.com img.ly yfrog.com p.twipple.jp farm\\d.static.flickr.com miil.me' + ' ' + '' ).replace(/\./g,'\\.').replace(/ /g, '|'); return new RegExp('^https?://(?:' + fragment + ')/'); })(); function modPrt(origUrl) { if (HTTPS_SUPPORT_SITE_REGEX.test(origUrl)) { return origUrl.replace(/^https?:/, ''); } return origUrl; } var ACCESS_CONTROL_SUPPORT_DOMAIN_REGEX = new RegExp('^' + EXT_SERV_HOST_URL + '/'); var SUPPORT_XHR2_CROSS_DOMAIN = isSupportXhr2(); // Utils function $E(tagName, attributes) { var e = $(document.createElement(tagName)); attributes && e.attr(attributes); return e; } function callWebApi(endpoint, sendData, complete) { var dataType = SUPPORT_XHR2_CROSS_DOMAIN && ACCESS_CONTROL_SUPPORT_DOMAIN_REGEX.test(endpoint) ? 'json' : 'jsonp'; ajax({ url: endpoint, data: sendData, dataType: dataType, success: function(data, status, xhr) { complete(data, xhr && xhr.status); }, error: function(xhr, status, errorThrown) { complete(null, xhr && xhr.status); } }); } function baseDecode(letters, snipcode) { var ret = 0; for (var i = snipcode.length, m = 1; i; i--, m *= letters.length) { ret += letters.indexOf(snipcode.charAt(i-1)) * m; } return ret; } function base58decode(snipcode) { return baseDecode('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', snipcode); } function base36decode(snipcode) { return baseDecode('0123456789abcdefghijklmnopqrstuvwxyz', snipcode); } function unique(array, prop) { if (array == null) { return null; } var uniqueSet = {}; return $.grep(array, function(value) { var key = value[prop]; var result = !(key in uniqueSet); if (result) { uniqueSet[key] = true; } return result; }); } function objectToArrayWithKey(obj) { var arry = []; for (var k in obj) { arry.push([k, obj[k]]); } return arry; } // /Utils // Cache var Cache = (function() { var storage = null; if (USE_LOCAL_STORAGE_ENABLE && window.localStorage) { storage = localStorage; } else if (window.sessionStorage) { storage = sessionStorage; } function constructor(cacheSizeMax, storeKey) { this.cacheSizeMax = cacheSizeMax; this.storeKey = storeKey; this.interval = 20 * 1000; this.init(); } function genCache() { return { version: VERSION, size: 0 }; } constructor.prototype = { init: function() { this.modified = false; this.caches = []; if (storage && this.storeKey) { var json = storage[this.storeKey]; if (json) { var cache = JSON.parse(json); if (cache.version == VERSION) { this.caches.push(cache); } } var self = this; setInterval(function() { if (self.modified) { storage[self.storeKey] = JSON.stringify(self.cache()); self.modified = false; } }, this.interval); } if (this.caches.length == 0) { this.caches.push(genCache()); } }, get: function(key) { for(var i = 0; i < this.caches.length; i++) { var val = this.caches[i][key]; if (val) { return val; } } return null; }, put: function(key, value) { this.shift(); var cache = this.cache(); if (!(key in cache)) { cache.size += 1; } cache[key] = value; this.modified = true; }, shift: function() { if (this.cache().size > this.cacheSizeMax) { if (this.caches.unshift(genCache()) > 3) { this.caches.pop(); } this.modified = true; } }, cache: function() { return this.caches[0]; } }; return constructor; })(); var ExpandedUrlCache = new Cache(MAX_URL_CACHE, EXPANDED_URL_CACHE_KEY); ExpandedUrlCache.put_orig = ExpandedUrlCache.put; ExpandedUrlCache.put = function(key, value) { if (key == value) { return; } this.put_orig(key, value); }; var ThumbnailUrlCache = new Cache(MAX_URL_CACHE, THUMBNAIL_URL_CACHE_KEY); // /Cache // WebService function expandShortUrl(shortUrl, complete) { var cached = ExpandedUrlCache.get(shortUrl); if (cached) { complete(cached); return; } callWebApi(EXT_SERV_ENDS.expandurl, { url: shortUrl }, function(data, statusCode) { var longUrl = null; if (data) { longUrl = data['long-url']; ExpandedUrlCache.put(shortUrl, longUrl); } complete(longUrl); }); } var twitcasting = { fetchUserImage: function(userId, complete) { callWebApi('http://api.twitcasting.tv/api/userstatus', { user: userId }, function(data) { complete(data && data.image, true); }); }, fetchMovieThumbnail: function(movieId, userId, complete) { var self = this; callWebApi('http://api.twitcasting.tv/api/moviestatus', { movieid: movieId }, function(movieData) { if (movieData) { if (movieData.thumbnailsmall) { complete(movieData.thumbnailsmall, true); } else { self.fetchUserImage(userId, complete); } } else { complete(); } }); }, fetchLiveThumbnail: function(userId, complete) { var completeNoCache = function(thumbUrl) { complete(thumbUrl); }; // ignore cache var self = this; callWebApi('http://api.twitcasting.tv/api/livestatus', { user: userId }, function(liveData) { if (liveData) { if (liveData.islive) { completeNoCache('http://twitcasting.tv/' + userId + '/thumbstream/liveshot-1'); } else if (liveData.movieid) { self.fetchMovieThumbnail(liveData.movieid, userId, completeNoCache); } else { self.fetchUserImage(userId, completeNoCache); } } else { completeNoCache(); } }); } }; function fetchUstreamThumbnail(subject, uid, complete) { callWebApi('http://api.ustream.tv/json/' + subject + '/' + uid + '/getValueOf/imageUrl', {}, function(data) { complete(data && data.small, true); }); } function resolvOgmediaDefault(matched, complete) { callWebApi(EXT_SERV_ENDS.ogmedia, { url: matched[0] }, function(data) { complete(data && data.url, true); }); } // /WebService // ShortUrlRegexs var SHORT_URL_REGEX = (function() { var fragment = ( 'bit.ly j.mp t.co htn.to ux.nu ustre.am am6.jp ow.ly ht.ly fb.me lnk.ms dai.ly a.r10.to' + ' ' + 'cot.ag p.tl amzn.to dlvr.it goo.gl moi.st dailybooth.com/u tinyurl.com sgp.cm pbckt.com' + ' ' + 'fon.gs mysp.ac itun.es is.gd v.gd r.sm3.jp tou.ch po.st post.ly pocket.co' ).replace(/\./g,'\\.').replace(/ /g, '|'); return new RegExp('^http://(?:' + fragment + ')/[\\w\\-:\\.]+'); })(); // /ShortUrlRegexs // ThumbnailResolvers var THUMB_RESOLVERS = { allimg: { priority: -1, regex: /^https?:\/\/[^?]+?\.(?:jpe?g|png|gif|bmp|JPE?G|PNG|GIF|BMP)(?=\?|$)/, resolv: function(matched, complete) { complete(matched[0]); } }, twitpic: { priority: 2, regex: /^http:\/\/twitpic\.com\/(\w+)/, regexThumb: /^http:\/\/twitpic\.com\/show\/thumb\/\w+/, resolv: function(matched, complete) { complete('http://twitpic.com/show/thumb/' + matched[1]); } }, imgly: { regex: /^http:\/\/img\.ly\/(\w+)/, regexThumb: /^http:\/\/img\.ly\/show\/thumb\/\w+/, resolv: function(matched, complete) { complete('http://img.ly/show/thumb/' + matched[1]); } }, twitgoo: { regex: /^http:\/\/twitgoo\.com\/(\w+)/, regexThumb: /^http:\/\/twitgoo\.com\/show\/thumb\/\w+/, resolv: function(matched, complete) { complete('http://twitgoo.com/show/thumb/' + matched[1]); } }, picim: { regex: /^http:\/\/pic\.im\/(\w+)/, regexThumb: /^http:\/\/pic\.im\/website\/thumbnail\/\w+/, resolv: function(matched, complete) { complete('http://pic.im/website/thumbnail/' + matched[1]); } }, youtube: { regex: /^(?:http:\/\/www\.youtube\.com\/watch\/?\?v=([\w\-]+))|(?:http:\/\/youtu\.be\/([\w\-]+))/, resolv: function(matched, complete) { var id = matched[2] || matched[1]; complete('http://i.ytimg.com/vi/' + id + '/default.jpg'); } }, imgur: { regex: /^http:\/\/imgur\.com\/(?:r\/pics\/)?(\w+)(?!\/)/, resolv: function(matched, complete) { complete('http://i.imgur.com/' + matched[1] + 't.jpg'); } }, owly: { regex: /^http:\/\/ow\.ly\/i\/(\w+)/, resolv: function(matched, complete) { complete('http://static.ow.ly/photos/thumb/' + matched[1] + '.jpg'); } }, movapic: { regex: /^http:\/\/movapic\.com\/pic\/(\w+)/, resolv: function(matched, complete) { complete('http://image.movapic.com/pic/s_' + matched[1] + '.jpeg'); } }, hatena: { regex: /^http:\/\/f\.hatena\.ne\.jp\/([\w\-]+)\/(\d+)/, resolv: function(matched, complete) { complete(['http://cdn-ak.f.st-hatena.com/images/fotolife', matched[1].charAt(0), matched[1], matched[2].substr(0, 8), matched[2]].join('/') + '_120.jpg'); } }, moby: { regex: /^http:\/\/moby\.to\/(\w+)/, resolv: function(matched, complete) { complete(matched[0] + ':square'); } }, yfrog: { regex: /^http:\/\/yfrog\.com\/\w+/, resolv: function(matched, complete) { complete(matched[0] + ':small'); } }, plixi: { regex: /^http:\/\/plixi\.com\/p\/(\w+)/, regexThumb: /^http:\/\/api\.plixi\.com\/api\/TPAPI\.svc\/imagefromurl\?size=thumbnail&url=http:\/\/plixi\.com\/p\/\w+/, resolv: function(matched, complete) { complete('http://api.plixi.com/api/TPAPI.svc/imagefromurl?size=thumbnail&url=' + matched[0]); } }, nicovideo: { regex: /^(?:http:\/\/www\.nicovideo\.jp\/watch\/(?:sm|nm)(\d+))|(?:http:\/\/nico\.ms\/(?:sm|nm)(\d+))/, regexThumb: /^http:\/\/tn-skr\d?\.smilevideo\.jp\/smile\?i=\d+/, resolv: function(matched, complete) { var id = matched[2] || matched[1]; complete('http://tn-skr.smilevideo.jp/smile?i=' + id); } }, nicovideoapi: { regex: /^(?:http:\/\/(?:www|live)\.nicovideo\.jp\/(?:watch|gate)\/((?:lv)?\d+))|(?:http:\/\/nico\.ms\/((?:lv)?\d+))/, resolv: function(matched, complete) { var id = matched[2] || matched[1]; callWebApi(EXT_SERV_ENDS.nicovideo, { id: id }, function(data) { complete(data && data.url, true); }); } }, nicoseiga: { regex: /^(?:http:\/\/seiga\.nicovideo\.jp\/seiga\/im(\d+))|(?:http:\/\/nico\.ms\/im(\d+))/, regexThumb: /^http:\/\/lohas\.nicoseiga\.jp\/thumb\/\d+[qi]/, resolv: function(matched, complete) { var id = matched[2] || matched[1]; complete('http://lohas.nicoseiga.jp/thumb/' + id + 'q'); } }, nicocomu: { regex: /^http:\/\/com\.nicovideo\.jp\/community\/co(\d+)/, resolv: function(matched, complete) { complete('http://icon.nimg.jp/community/co' + matched[1] + '.jpg'); } }, twipple: { regex: /^http:\/\/p\.twipple\.jp\/(\w+)$/, regexThumb: /^http:\/\/p\.twpl\.jp\/show\/thumb\/\w+/, resolv: function(matched, complete) { complete('http://p.twpl.jp/show/thumb/' + matched[1]); } }, photozou: { regex: /^http:\/\/photozou\.jp\/photo\/show\/\d+\/(\d+)/, regexThumb: /^http:\/\/photozou\.jp\/p\/thumb\/\d+/, resolv: function(matched, complete) { complete('http://photozou.jp/p/thumb/' + matched[1]); } }, ustream: { regex: /^http:\/\/(?:www\.)?ustream\.tv\/(channel|recorded)\/(?:id\/)?([\w\-%]+)/, subjects: { 'channel': 'channel', 'recorded': 'video' }, resolv: function(matched, complete) { fetchUstreamThumbnail(this.subjects[matched[1]], matched[2], complete); } }, lockerz: { regex: /^http:\/\/lockerz\.com\/s\/\d+/, regexThumb: /^http:\/\/api\.plixi\.com\/api\/tpapi\.svc\/imagefromurl\?size=thumbnail&url=http:\/\/lockerz\.com\/s\/\d+/, resolv: function(matched, complete) { complete('http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=' + matched[0]); } }, jcomi: { regex: /^http:\/\/(?:www\.|vw\.)?j-comi\.jp\/(?:book|murasame)\/(?:comic|detail|view)\/\d+/, resolv: resolvOgmediaDefault }, picplz: { regex: /^http:\/\/picplz\.com\/(?:user\/\w+\/pic\/(\w+)|(\w+))/, resolv: function(matched, complete) { var param = {}; if (matched[1]) { param.longurl_id = matched[1]; } else { param.shorturl_id = matched[2]; } callWebApi('http://api.picplz.com/api/v2/pic.json', param, function(data) { var imgUrl = null; if (data && data.result == 'ok') { imgUrl = data.value.pics[0].pic_files['100sh'].img_url; }; complete(imgUrl, true); }); } }, kabegami: { regex: /^http:\/\/www\.kabegami\.com\/[\w\-]+\/\w+\/show\/id\/(PHOT(\d{10})(\w\w)(\w\w)\w\w)/, resolv: function(matched, complete) { complete(['http://www.kabegami.com/content_image/phot', matched[2], matched[3], matched[4], matched[1]].join('/') + '_s90.jpg'); } }, instagram: { regex: /^http:\/\/(?:instagr\.am|instagram\.com)\/p\/[\w\-]+/, resolv: function(matched, complete) { complete(matched[0] + '/media/?size=t'); } }, mypix: { regex: /^http:\/\/www\.mypix\.jp\/app\.php\/picture\/\d+/, resolv: function(matched, complete) { complete(matched[0] + '/thumbx.jpg'); } }, fotolog: { regex: /^http:\/\/fotolog\.cc\/(\w+)/, regexThumb: /^http:\/\/fotolog\.cc\/image\/\w+\/mini/, resolv: function(matched, complete) { complete('http://fotolog.cc/image/' + matched[1] + '/mini'); } }, vimeo: { regex: /^http:\/\/(?:www\.)?vimeo\.com\/(\d+)/, resolv: function(matched, complete) { callWebApi('http://vimeo.com/api/v2/video/' + matched[1] + '.json', {}, function(data) { complete(data && data[0].thumbnail_small, true); }); } }, dailybooth: { regex: /^http:\/\/dailybooth\.com\/[\w\-]{2,}\/(\d+)/, resolv: function(matched, complete) { callWebApi('http://api.dailybooth.com/v2/pictures/' + matched[1] + '.json', {}, function(data) { complete(data && data.urls.small, true); }); } }, twitcasting: { regex: /^http:\/\/twitcasting\.tv\/([\w\-]+)\/movie\/(\d+)/, resolv: function(matched, complete) { var userId = matched[1]; var movieId = matched[2]; twitcasting.fetchMovieThumbnail(movieId, userId, complete); } }, twitcastinglive: { regex: /^http:\/\/twitcasting\.tv\/([\w\-]+)\/?$/, resolv: function(matched, complete) { var userId = matched[1]; twitcasting.fetchLiveThumbnail(userId, complete); } }, metacafe: { regex: /^http:\/\/www\.metacafe\.com\/(?:w|watch)\/([\w\-]+)/, resolv: function(matched, complete) { complete('http://www.metacafe.com/thumb/' + matched[1] + '.jpg'); } }, dailymotion: { regex: /^http:\/\/(?:www|touch)\.dailymotion\.com\/(?:#\/)?video\/([\w\-]+)/, resolv: function(matched, complete) { callWebApi('https://api.dailymotion.com/video/' + matched[1], { fields: 'thumbnail_medium_url' }, function(data) { complete(data && data.thumbnail_medium_url, true); }); } }, stickamjpprof: { regex: /^http:\/\/(?:www\.stickam\.jp\/profile|stick\.am\/p)\/(\w+)/, resolv: function(matched, complete) { callWebApi('http://api.stickam.jp/api/user/' + matched[1] + '/profile', { mime: 'json' }, function(data) { complete(data && data.profile_image, true); }); } }, stickamjpmedia: { regex: /^http:\/\/www\.stickam\.jp\/video\/(\d+)/, resolv: function(matched, complete) { callWebApi('http://api.stickam.jp/api/media/' + matched[1], { mime: 'json' }, function(data) { complete(data && data.media && data.media.thumb, true); }); } }, photobucket: { regex: /^http:\/\/[is]\d+\.photobucket\.com\/albums\/.+/, resolv: function(matched, complete) { callWebApi('http://photobucket.com/oembed', { url: matched[0] }, function(data) { complete(data && data.thumbnail_url, true); }); } }, pixiv: { regex: /^http:\/\/(?:www\.pixiv\.net\/member_illust\.php\/?\?(?:mode=medium&)?illust_id=|p\.tl\/i\/)(\d+)/, resolv: function (matched, complete) { callWebApi(EXT_SERV_ENDS.ogmedia, { url: 'http://www.pixiv.net/member_illust.php?mode=medium&illust_id=' + matched[1] }, function(data) { complete(data && data.url, true); }); } }, flickr: { regex: /^http:\/\/(?:www\.flickr\.com\/photos\/[\w@\-]+\/(\d+))|(?:flic\.kr\/p\/(\w+))/, resolv: function(matched, complete) { var id = matched[2] ? base58decode(matched[2]) : matched[1]; callWebApi(EXT_SERV_ENDS.flickr, { photo_id: id }, function(data) { complete(data && data.url, true); }); } }, videosurf: { regex: /^http:\/\/www\.videosurf\.com\/video\/-?(?:[a-zA-Z0-9%]+-)*(\d+)\b/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.videosurf, { id: matched[1] }, function(data) { complete(data && data.url, true); }); } }, tinami: { regex: /^http:\/\/(?:www\.tinami\.com\/view\/(\w+)|tinami\.jp\/(\w+))/, resolv: function(matched, complete) { var id = matched[2] ? base36decode(matched[2]) : matched[1]; callWebApi(EXT_SERV_ENDS.tinami, { id: id }, function(data) { complete(data && data.url, true); }); } }, akibablog: { regex: /^http:\/\/blog\.livedoor\.jp\/geek\/archives\/\d+\.html/, resolv: resolvOgmediaDefault }, photobucketmedia: { regex: /^http:\/\/media\.photobucket\.com\/.+/, resolv: resolvOgmediaDefault }, ustreamsp: { regex: /^http:\/\/(?:www\.)?ustream\.tv\/[\w\-%]+(?=\/?$)/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.ustream, { url: matched[0] }, function(data) { if (data) { fetchUstreamThumbnail('channel', data.channelId, complete); } else { complete(); } }); } }, tumblr: { regex: /^http:\/\/([\w\-]+\.tumblr\.com)\/post\/(\d+)/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.tumblr, { base_hostname: matched[1], post_id: matched[2] }, function(data) { complete(data && data.url, true); }); } }, tumblrs: { regex: /^http:\/\/(?:tumblr\.com|tmblr\.co)\/[\w\-]+/, resolv: function(matched, complete) { expandShortUrl(matched[0], function(longUrl) { var regex = /^http:\/\/((?:\.?[\w\-]+)+)\/post\/(\d+)/; if (regex.test(longUrl)) { THUMB_RESOLVERS.tumblr.resolv(longUrl.match(regex), complete); } else { complete(); } }); } }, rakutenbooks: { regex: /^http:\/\/books\.rakuten\.co\.jp\/rb\/[\w%\-]+\-(\d+)\//, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.rakuten, { type: 'book', id: matched[1] }, function(data) { complete(data && data.url, true); }); } }, rakutenitem: { regex: /^http:\/\/item\.rakuten\.co\.jp\/([\w\-]+\/[\w\-]+)/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.rakuten, { type: 'item', id: matched[1] }, function(data) { complete(data && data.url, true); }); } }, twil: { regex: /^http:\/\/shlink\.st\/[\w\-]+/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.twil, { url: matched[0] }, function(data) { complete(data && data.thumbnail_url, true); }); } }, twitrpix: { regex: /^http:\/\/twitrpix\.com\/(\w+)/, regexThumb: /^http:\/\/img\.twitrpix\.com\/thumb\/\w+/, resolv: function(matched, complete) { complete('http://img.twitrpix.com/thumb/' + matched[1]); } }, pikchur: { regex: /^http:\/\/(?:pikchur\.com|pk\.gd)\/(\w+)/, resolv: function(matched, complete) { complete(' http://img.pikchur.com/pic_' + matched[1] + '_s.jpg'); } }, twitxr: { regex: /^http:\/\/twitxr\.com\/(\w+)\/updates\/(\d+)/, regexThumb: /^http:\/\/twitxr\.com\/image\/\d+\/th/, resolv: function(matched, complete) { complete('http://twitxr.com/image/' + matched[2] + '/th'); } }, myspacevideo: { regex: /^http:\/\/(?:www\.)?myspace\.com\/video\/(?:(vid|rid)|[\w-]+\/[\w-]+)\/(\d+)/, resolv: function(matched, complete) { var id = (matched[1] || 'vid') + '/' + matched[2]; callWebApi(EXT_SERV_ENDS.myspacevideo, { id: id }, function(data) { complete(data && data.url, true); }); } }, myspacevideovids: { regex: /^http:\/\/(?:vids\.|www\.)?myspace\.com\/index\.cfm\?fuseaction=vids\.individual&videoid=(\d+)/, resolv: function(matched, complete) { var id = 'vid/' + matched[1]; callWebApi(EXT_SERV_ENDS.myspacevideo, { id: id }, function(data) { complete(data && data.url, true); }); } }, twitvideo: { regex: /^http:\/\/twitvideo\.jp\/(\w+)/, regexThumb: /^http:\/\/twitvideo\.jp\/img\/thumb\/\w+/, resolv: function(matched, complete) { complete('http://twitvideo.jp/img/thumb/' + matched[1]); } }, mycom: { regex: /^http:\/\/(?:if\.|s\.)?journal\.mycom\.co\.jp\/\w+\/\d+\/\d+\/\d+\/\w+\//, resolv: function(matched, complete) { complete(matched[0] + 'index.top.jpg'); } }, twitvid: { regex: /^http:\/\/(?:www\.)?twitvid\.com\/(?!videos)(\w+)/, resolv: function(matched, complete) { var id = matched[1]; complete(['http://llphotos.twitvid.com/twitvidthumbnails', id.charAt(0), id.charAt(1), id.charAt(2), id].join('/') + '_med.jpg'); } }, pckles: { regex: /^http:\/\/(?:pckl\.es|pckles\.com)\/\w+\/\w+/, resolv: function(matched, complete) { complete(matched[0] + '.thumb.jpg'); } }, itunes: { regex: /^http:\/\/(?:c\.)?itunes\.apple\.com\/.*\/id\w+(?:\?i=\d+)?/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.itunes, { url: matched[0] }, function(data) { complete(data && data.url, true); }); } }, loctouch: { regex: /^http:\/\/tou\.ch\/spot\/\d+\/(\w+)\/(\d+)/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.loctouch, { type: matched[1], id: matched[2] }, function(data) { complete(data && data.url, true); }); } }, miil: { regex: /^http:\/\/miil\.me\/p\/(\w+)/, resolv: function(matched, complete) { complete(matched[0] + '.jpeg?size=150'); } }, i500px: { regex: /^https?:\/\/(?:www\.)?500px\.com\/photo\/(\d+)/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.i500px, { id: matched[1] }, function(data) { complete(data && data.url, true); }); } }, hulu: { regex: /^http:\/\/(?:www\.hulu\.com\/watch|hulu\.com\/w)\/.*/, resolv: function(matched, complete) { callWebApi('http://www.hulu.com/api/oembed', { url: matched[0] }, function(data) { complete(data && data.thumbnail_url, true); }); } }, facebook: { regex: /^https?:\/\/www\.facebook\.com\/photo\.php\?fbid=(\w+)/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.facebook, { id: matched[1] }, function(data) { complete(data && data.url, true); }); } }, immio: { regex: /^http:\/\/imm\.io\/\w+/, resolv: resolvOgmediaDefault }, comicdash: { regex: /^http:\/\/ckworks\.jp\/comicdash\/series\/\d+/, resolv: resolvOgmediaDefault }, gyazo: { regex: /^http:\/\/gyazo\.com\/\w+/, resolv: function(matched, complete) { complete(matched[0] + '.png'); } }, viame: { regex: /^http:\/\/via\.me\/-\w+/, resolv: resolvOgmediaDefault }, posterous: { regex: /^http:\/\/[\w\-]+\.posterous\.com\/[\w\-]+/, resolv: resolvOgmediaDefault }, bookmetercmt: { regex: /^http:\/\/book\.akahoshitakuya\.com\/cmt\/\w+/, resolv: resolvOgmediaDefault }, booklogp: { regex: /^http:\/\/p\.booklog\.jp\/book\/\d+/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.ogmedia, { url: matched[0] }, function(data) { var imgUrl = null; if (data && data.url) { imgUrl = data.url.replace(/_s\.jpg$/, '_m.jpg'); } complete(imgUrl, true); }); } }, booklogitem: { regex: /^http:\/\/booklog\.jp\/item\/3\/\d+/, resolv: resolvOgmediaDefault }, mlkshk: { regex: /^http:\/\/mlkshk\.com\/\w\/(\w+)/, resolv: function(matched, complete) { complete('http://mlkshk.com/r/' + matched[1]); } }, twitdraw: { regex: /^http:\/\/twitdraw\.com\/(\w+)/, resolv: function(matched, complete) { complete('http://td-images.s3.amazonaws.com/' + matched[1] + '.png'); } }, tegaki: { regex: /^http:\/\/tegaki\.pipa\.jp\/\d+\/(\d+)\.html/, resolv: function(matched, complete) { complete('http://tegaki.pipa.jp/CGetBlogImgS.jsp?TD=' + matched[1]); } }, gizmodo: { regex: /^http:\/\/(?:www|us)\.gizmodo\.(?:jp|co\.uk|de|com)\/\d+\/(?:\d+\/)?[\w\.\-]+/, resolv: resolvOgmediaDefault }, piapro: { regex: /^http:\/\/piapro\.jp\/t\/[\w\-]+/, resolv: resolvOgmediaDefault }, tweetvite: { regex: /^http:\/\/(?:tweetvite\.com\/event|twvt\.us)\/([\w\-]+)/, resolv: function resolvOgmediaDefault(matched, complete) { var url = 'http://tweetvite.com/event/' + matched[1]; callWebApi(EXT_SERV_ENDS.ogmedia, { url: url }, function(data) { complete(data && data.url, true); }); } }, twitter: { priority: 1, regex: /^https?:\/\/twitter\.com\/(?:#!\/)?[\w\-]+\/status\/(\d+)\/photo\/1/, resolv: function(matched, complete) { callWebApi(EXT_SERV_ENDS.pictwitter, { id: matched[1] }, function(data) { complete(data && data.url, true); }); } }, eyeem: { regex: /^http:\/\/www\.eyeem\.com\/p\/\d+/, resolv: resolvOgmediaDefault }, vine: { regex: /^https?:\/\/vine\.co\/v\/\w+/, resolv: resolvOgmediaDefault } }; // amazon (function(resolvers) { var fetchAmazonThumbnail = function(sendData, complete) { callWebApi(EXT_SERV_ENDS.amazon, sendData, function(data) { complete(data && data.url, true); }); }; var resolvAmazonThumbnailJpDefault = function(matched, complete) { fetchAmazonThumbnail({ tld: 'jp', asin: matched[1] }, complete); }; resolvers.amazon = { regex: /^http:\/\/(?:www\.)?amazon(?:\.co)?\.(jp|com|ca|cn|de|fr|it|uk)\/(?:(?:(?:gp|dp)\/product(?:-\w+)?|o\/ASIN|exec\/obidos\/ASIN)\/(\w+)|(?:[^\/]+\/)?dp\/(\w+))/, resolv: function(matched, complete) { fetchAmazonThumbnail({ tld: matched[1], asin: matched[3] || matched[2] }, complete); } }; resolvers.bookmeter = { regex: /^http:\/\/book\.akahoshitakuya\.com\/b\/(\w+)/, resolv: resolvAmazonThumbnailJpDefault }; resolvers.mediamarker = { regex: /^http:\/\/mediamarker\.net\/\w\/[\w\-]+\/\?asin=(\w+)/, resolv: resolvAmazonThumbnailJpDefault }; resolvers.booklog = { regex: /^http:\/\/booklog\.jp\/(?:users\/[\w\-]+\/archives|asin|item)\/1\/(\w+)/, resolv: resolvAmazonThumbnailJpDefault }; resolvers.sociallibrary = { regex: /^http:\/\/www\.sociallibrary\.jp\/entry\/(\w+)/, resolv: resolvAmazonThumbnailJpDefault }; })(THUMB_RESOLVERS); // /amazon // /ThumbnailResolvers // Process var Process = function(args) { this.waitCount = 0; this.results = []; this.split = args.split; this.execute = args.execute; this.complete = args.complete; this.queue = args.queue; }; Process.prototype = { run: function(input) { var subInputs = this.split(input); var inputLength = subInputs ? subInputs.length : 0; this.waitCount = inputLength; this.internalComplete(); for (var i = 0; i < inputLength; i++) { this.execute(subInputs[i]); } }, emitComplete: function(result) { this.waitCount--; if (result) { this.results.push(result); } this.internalComplete(); }, internalComplete: function() { if (this.waitCount == 0) { if (this.complete) { this.complete(this.results); } if (this.queue.length != 0) { this.queue.shift()(); } } } }; // /Process // ExpandThumbnailMainProcess var thumbResolverWithKeyArry = objectToArrayWithKey(THUMB_RESOLVERS); thumbResolverWithKeyArry.sort(function(a, b) { var a_p = 'priority' in a[1] ? a[1].priority : 0; var b_p = 'priority' in b[1] ? b[1].priority : 0; if (a_p < b_p) return 1; if (a_p > b_p) return -1; return 0; }); function resolvThumbnailUrl(existsOfficalThumbnails, contentUrl, complete) { var cached = ThumbnailUrlCache.get(contentUrl); if (cached) { complete(cached); return true; } var match = false; $.each(thumbResolverWithKeyArry, function(index, resolverWithKey) { var resolver = resolverWithKey[1]; if (resolver.regex.test(contentUrl)) { if (existsOfficalThumbnails && domainEnv.supportedThumbnails && resolverWithKey[0] in domainEnv.supportedThumbnails) { return false; } resolver.resolv(contentUrl.match(resolver.regex), function(thumbnailUrl, cache) { if (thumbnailUrl && cache) { ThumbnailUrlCache.put(contentUrl, thumbnailUrl); } complete(thumbnailUrl); }); match = true; return false; } else if (resolver.regexThumb && resolver.regexThumb.test(contentUrl)) { complete(contentUrl.match(resolver.regexThumb)[0]); match = true; return false; } }); return match; } function createThumbnailElement(urlEntries) { var ul = $E('ul', { 'class': 'ithumb-ul' }); for (var i = 0; i < urlEntries.length; i++) { var urlEntry = urlEntries[i]; ul.append( $E('li', { 'class':'ithumb-li' }).append( $E('a', { 'class':'ithumb-a', 'target':'_blank', 'href':urlEntry.url, 'rel':'url' }).append( $E('img', { 'src': modPrt(urlEntry.thumbUrl), 'class':'ithumb-img' }) .error(function() { var img = $(this); var tryload = img.data('tryload') || 0; if (tryload < 2) { setTimeout(function() { img.data('tryload', tryload + 1).attr('src', img.attr('src')); }, 7000); } else { img.hide(); } }) ) ) ); } var thumb = $E('div', { 'class':'ithumb-container' }).append(ul); if (domainEnv.customizeThumbnailElement) { domainEnv.customizeThumbnailElement(thumb); } return thumb; } function expandThumbnail(contentElement, existsOfficalThumbnails) { var urlStack = []; var processQueue = []; /* */ var urlExtractProcess = new Process({ queue: processQueue, split: function(element) { return element.find('a').map(function() { var anchor = $(this); var url = anchor.attr('href'); return (url && (/^https?:\/\/(?!twitter\.com\/#!\/)/).test(url)) ? anchor : null; }); }, execute: function(anchor) { var urlEntry = { thumbUrl: null, expandCount: 0 }; var expandedUrl = null; if (domainEnv.getExpandedUrl) { expandedUrl = domainEnv.getExpandedUrl(anchor); } urlEntry.url = expandedUrl || anchor.attr('href'); this.emitComplete(urlEntry); }, complete: function(urlEntries) { urlStack = unique(urlEntries, 'url'); } }); var thumbnailUrlResolveProcess = new Process({ queue: processQueue, split: function(urlEntries) { return urlEntries; }, execute: function (urlEntry) { var self = this; var match = resolvThumbnailUrl(existsOfficalThumbnails, urlEntry.url, function(thumbUrl) { urlEntry.thumbUrl = thumbUrl; self.emitComplete(); }); if (!match) { if (urlEntry.expandCount < 5 && SHORT_URL_REGEX.test(urlEntry.url)) { expandShortUrl(urlEntry.url, function(longUrl) { if (urlEntry.url != longUrl) { urlEntry.expandCount += 1; urlEntry.url = longUrl; self.execute(urlEntry); } else { self.emitComplete(); } }); } else { self.emitComplete(); } } }, }); /* */ processQueue.push(function() { thumbnailUrlResolveProcess.run(urlStack); }); processQueue.push(function() { var urlEntries = unique( $.grep(urlStack, function(val) { return val.thumbUrl; }), 'thumbUrl'); if (urlEntries.length != 0) { domainEnv.appendThumbnail(contentElement, createThumbnailElement(urlEntries)); } }); urlExtractProcess.run(contentElement); } // /ExpandThumbnailMainProcess var CSS_URL_DEFAULT = EXT_SERV_HOST_URL + '/stylesheets/inlinethumbnail/1.1.0/default.css'; var APPEND_THUMBNAIL_DEFAULT = function(contentElement, thumbnailElement) { contentElement.append(thumbnailElement); }; var DOMAIN_ENVS = { hootsuite: { hostname: 'hootsuite.com', select: function(context) { return $('._baseTweetText:not([expanded-img])', context); }, appendThumbnail: APPEND_THUMBNAIL_DEFAULT, cssUrl: CSS_URL_DEFAULT }, twitter: { hostname: 'twitter.com', select: function(context) { return $('.tweet:not(.simple-tweet) > .content > p:not([expanded-img])', context); }, appendThumbnail: APPEND_THUMBNAIL_DEFAULT, getExpandedUrl: function(anchor) { return anchor.data('expanded-url'); }, cssUrl: EXT_SERV_HOST_URL + '/stylesheets/inlinethumbnail/1.6.2/twittercom.css', existsOfficalThumbnails: function(contentElement) { return contentElement.nextAll('.cards-media-container').length > 0; }, supportedThumbnails: { 'twitter' : true } }, twitter_mobile: { hostname: 'mobile.twitter.com', select: function(context) { return $('.tweet-text:not([expanded-img])', context); }, appendThumbnail: APPEND_THUMBNAIL_DEFAULT, getExpandedUrl: function(anchor) { return anchor.data('url'); }, cssUrl: CSS_URL_DEFAULT }, crowy: { hostname: 'www.crowy.net', select: function(context) { return $('.message-text:not([expanded-img])', context); }, appendThumbnail: APPEND_THUMBNAIL_DEFAULT, cssUrl: CSS_URL_DEFAULT, existsOfficalThumbnails: function(contentElement) { return contentElement.nextAll('.images').length > 0; }, supportedThumbnails: { 'twitter' : true, 'twitpic' : true, 'youtube' : true, 'amazon' : true } }, twipple: { hostname: 'twipple.jp', select: function(context) { return $('.tweetBox:not([expanded-img])', context.parentNode); }, appendThumbnail: APPEND_THUMBNAIL_DEFAULT, cssUrl: CSS_URL_DEFAULT, existsOfficalThumbnails: function(contentElement) { return contentElement.find('.thumbBoxImage').length > 0; }, supportedThumbnails: { 'twitter' : true, 'twitpic' : true, 'youtube' : true } }, tweetdeck: { hostname: 'tweetdeck.twitter.com', select: function(context) { return $('.tweet-body:not([expanded-img])', context.parentNode); }, appendThumbnail: function(contentElement, thumbnailElement) { contentElement.after(thumbnailElement); }, getExpandedUrl: function(anchor) { return anchor.data('full-url'); }, cssUrl: CSS_URL_DEFAULT, existsOfficalThumbnails: function(contentElement) { return contentElement.children('.media-preview').length > 0; }, supportedThumbnails: { 'twitter' : true, 'twitpic' : true, 'youtube' : true } } }; var domainEnv = null; $.each(DOMAIN_ENVS, function() { if (this.hostname == location.hostname) { if (this.match) { if (this.match()) { domainEnv = this; return false; } } else { domainEnv = this; return false; } } }); function applyElements(context) { domainEnv.select(context).each(function() { var contentElement = $(this); expandThumbnail( contentElement.attr('expanded-img', 'expanded-img'), domainEnv.existsOfficalThumbnails && domainEnv.existsOfficalThumbnails(contentElement)); }); } $(document.getElementsByTagName('head')[0]).append($E('link', { 'rel':'stylesheet', 'type':'text/css', 'media':'screen', 'href':domainEnv.cssUrl })); var ApplyQueue = { timeoutId: null, queue: [], apply: function() { var targets = this.queue.splice(0, this.queue.length); applyElements(targets); }, push: function(elem) { if (this.timeoutId) { clearTimeout(this.timeoutId); this.timeoutId = null; } this.queue.push(elem); var self = this; this.timeoutId = setTimeout(function() { self.apply(); }, 1000); } }; // initial apply ApplyQueue.push(document); $(document).bind('DOMNodeInserted', function(e) { ApplyQueue.push(e.target); }); } // /main logic // load (function mainloader(tryCount, loaded) { if (tryCount < 30 && !(window.jQuery)) { setTimeout(function() { mainloader(tryCount + 1, loaded); }, 60); return; } if (!loaded && !(window.jQuery)) { loadJQuery(); setTimeout(function() { mainloader(0, true); }, 60); return; } var hostname = 'thumbnailurlconv.appspot.com'; var endpoint = '//' + hostname + '/endpoint'; var dataType = isSupportXhr2() ? 'json' : 'jsonp'; ajax({ url: endpoint, dataType: dataType, success: function(data) { main('//' + (data ? data.hostname : hostname)); }, error: function() { main('//' + hostname); } }); })(0); } // /source code var script = document.createElement('script'); script.type = 'text/javascript'; script.innerHTML = '(' + source.toString() + ')();'; document.body.appendChild(script); })();