Inline Thumbnail

Expand to view thumbnails in the row.

当前为 2014-07-01 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Inline Thumbnail
  3. // @namespace inline_thumbnail
  4. // @description Expand to view thumbnails in the row.
  5. // @run-at document-end
  6. // @include http://hootsuite.com/*
  7. // @include https://hootsuite.com/*
  8. // @include http://twitter.com/*
  9. // @include https://twitter.com/*
  10. // @include https://mobile.twitter.com/*
  11. // @include http://www.crowy.net/*
  12. // @include http://twipple.jp/*
  13. // @include https://web.tweetdeck.com/*
  14. // @version 1.6.2
  15. // ==/UserScript==
  16.  
  17. (function() {
  18. function source() { // source code
  19.  
  20. var VERSION = '1.6.2';
  21. var USE_LOCAL_STORAGE_ENABLE = true;
  22. var MAX_URL_CACHE = 400;
  23.  
  24. function isSupportXhr2() { return 'withCredentials' in $.ajaxSettings.xhr(); }
  25. function ajax(arg) {
  26. var done = 0;
  27. var option = {
  28. url: arg.url, data: arg.data, dataType: arg.dataType, type: 'GET', cache: true,
  29. success: function(data, status, xhr) { done++ || arg.success(data, status, xhr); },
  30. error: function(xhr, status, errorThrown) { done++ || arg.error(xhr, status, errorThrown); },
  31. };
  32. if (arg.dataType == 'jsonp') {
  33. setTimeout(function() { option.error(); }, 15 * 1000);
  34. }
  35. $.ajax(option);
  36. }
  37. function loadJQuery() {
  38. var id = 'dy-load-jq';
  39. if (document.getElementById(id)) {
  40. return;
  41. }
  42. var script = document.createElement('script');
  43. script.id = id;
  44. script.type = 'text/javascript';
  45. script.src = '//ajax.googleapis.com/ajax/libs/jquery/2.0.0/jquery.min.js';
  46. document.body.appendChild(script);
  47. }
  48.  
  49. function main(EXT_SERV_HOST_URL) { // main logic
  50.  
  51. var STORE_PREFIX = 'inlineThumbnail.';
  52. var EXPANDED_URL_CACHE_KEY = STORE_PREFIX + 'expandedUrls';
  53. var THUMBNAIL_URL_CACHE_KEY = STORE_PREFIX + 'thumbnailUrls';
  54. var EXT_SERV_ENDS = (function(base) {
  55. var ret = {
  56. expandurl: 'expandurl',
  57. amazon: 'amazon',
  58. flickr: 'flickr',
  59. nicovideo: 'nicovideo',
  60. videosurf: 'videosurf',
  61. tinami: 'tinami',
  62. ustream: 'ustream',
  63. tumblr: 'tumblr',
  64. rakuten: 'rakuten',
  65. ogmedia: 'ogmedia',
  66. jcomi: 'jcomi',
  67. myspacevideo: 'myspacevideo',
  68. pictwitter: 'pictwitter',
  69. itunes: 'itunes',
  70. loctouch: 'loctouch',
  71. i500px: '500px',
  72. twil: 'proxy/twil'
  73. };
  74. $.each(ret, function(key, val) { ret[key] = base + '/' + val; });
  75. return ret;
  76. })(EXT_SERV_HOST_URL);
  77.  
  78. var HTTPS_SUPPORT_SITE_REGEX = (function() {
  79. var fragment = (
  80. 'pbs.twimg.com twitpic.com img.ly yfrog.com p.twipple.jp farm\\d.static.flickr.com miil.me' + ' ' +
  81. ''
  82. ).replace(/\./g,'\\.').replace(/ /g, '|');
  83. return new RegExp('^https?://(?:' + fragment + ')/');
  84. })();
  85. function modPrt(origUrl) {
  86. if (HTTPS_SUPPORT_SITE_REGEX.test(origUrl)) {
  87. return origUrl.replace(/^https?:/, '');
  88. }
  89. return origUrl;
  90. }
  91.  
  92. var ACCESS_CONTROL_SUPPORT_DOMAIN_REGEX = new RegExp('^' + EXT_SERV_HOST_URL + '/');
  93. var SUPPORT_XHR2_CROSS_DOMAIN = isSupportXhr2();
  94.  
  95. // Utils
  96. function $E(tagName, attributes) {
  97. var e = $(document.createElement(tagName));
  98. attributes && e.attr(attributes);
  99. return e;
  100. }
  101.  
  102. function callWebApi(endpoint, sendData, complete) {
  103. var dataType = SUPPORT_XHR2_CROSS_DOMAIN && ACCESS_CONTROL_SUPPORT_DOMAIN_REGEX.test(endpoint) ? 'json' : 'jsonp';
  104. ajax({
  105. url: endpoint, data: sendData, dataType: dataType,
  106. success: function(data, status, xhr) {
  107. complete(data, xhr && xhr.status);
  108. },
  109. error: function(xhr, status, errorThrown) {
  110. complete(null, xhr && xhr.status);
  111. }
  112. });
  113. }
  114.  
  115. function baseDecode(letters, snipcode) {
  116. var ret = 0;
  117. for (var i = snipcode.length, m = 1; i; i--, m *= letters.length) {
  118. ret += letters.indexOf(snipcode.charAt(i-1)) * m;
  119. }
  120. return ret;
  121. }
  122. function base58decode(snipcode) {
  123. return baseDecode('123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ', snipcode);
  124. }
  125. function base36decode(snipcode) {
  126. return baseDecode('0123456789abcdefghijklmnopqrstuvwxyz', snipcode);
  127. }
  128.  
  129. function unique(array, prop) {
  130. if (array == null) {
  131. return null;
  132. }
  133. var uniqueSet = {};
  134. return $.grep(array, function(value) {
  135. var key = value[prop];
  136. var result = !(key in uniqueSet);
  137. if (result) {
  138. uniqueSet[key] = true;
  139. }
  140. return result;
  141. });
  142. }
  143. function objectToArray(obj) {
  144. var arry = [];
  145. for (var k in obj) {
  146. arry.push(obj[k]);
  147. }
  148. return arry;
  149. }
  150. // /Utils
  151.  
  152. // Cache
  153. var Cache = (function() {
  154. var storage = null;
  155. if (USE_LOCAL_STORAGE_ENABLE && window.localStorage) {
  156. storage = localStorage;
  157. } else if (window.sessionStorage) {
  158. storage = sessionStorage;
  159. }
  160.  
  161. function constructor(cacheSizeMax, storeKey) {
  162. this.cacheSizeMax = cacheSizeMax;
  163. this.storeKey = storeKey;
  164. this.interval = 20 * 1000;
  165. this.init();
  166. }
  167.  
  168. function genCache() {
  169. return { version: VERSION, size: 0 };
  170. }
  171.  
  172. constructor.prototype = {
  173. init: function() {
  174. this.modified = false;
  175. this.caches = [];
  176. if (storage && this.storeKey) {
  177. var json = storage[this.storeKey];
  178. if (json) {
  179. var cache = JSON.parse(json);
  180. if (cache.version == VERSION) {
  181. this.caches.push(cache);
  182. }
  183. }
  184. var self = this;
  185. setInterval(function() {
  186. if (self.modified) {
  187. storage[self.storeKey] = JSON.stringify(self.cache());
  188. self.modified = false;
  189. }
  190. }, this.interval);
  191. }
  192. if (this.caches.length == 0) {
  193. this.caches.push(genCache());
  194. }
  195. },
  196. get: function(key) {
  197. for(var i = 0; i < this.caches.length; i++) {
  198. var val = this.caches[i][key];
  199. if (val) {
  200. return val;
  201. }
  202. }
  203. return null;
  204. },
  205. put: function(key, value) {
  206. this.shift();
  207. var cache = this.cache();
  208. if (!(key in cache)) {
  209. cache.size += 1;
  210. }
  211. cache[key] = value;
  212. this.modified = true;
  213. },
  214. shift: function() {
  215. if (this.cache().size > this.cacheSizeMax) {
  216. if (this.caches.unshift(genCache()) > 3) {
  217. this.caches.pop();
  218. }
  219. this.modified = true;
  220. }
  221. },
  222. cache: function() { return this.caches[0]; }
  223. };
  224. return constructor;
  225. })();
  226.  
  227. var ExpandedUrlCache = new Cache(MAX_URL_CACHE, EXPANDED_URL_CACHE_KEY);
  228. ExpandedUrlCache.put_orig = ExpandedUrlCache.put;
  229. ExpandedUrlCache.put = function(key, value) {
  230. if (key == value) {
  231. return;
  232. }
  233. this.put_orig(key, value);
  234. };
  235. var ThumbnailUrlCache = new Cache(MAX_URL_CACHE, THUMBNAIL_URL_CACHE_KEY);
  236. // /Cache
  237.  
  238. // WebService
  239. function expandShortUrl(shortUrl, complete) {
  240. var cached = ExpandedUrlCache.get(shortUrl);
  241. if (cached) {
  242. complete(cached);
  243. return;
  244. }
  245. callWebApi(EXT_SERV_ENDS.expandurl, { url: shortUrl }, function(data, statusCode) {
  246. var longUrl = null;
  247. if (data) {
  248. longUrl = data['long-url'];
  249. ExpandedUrlCache.put(shortUrl, longUrl);
  250. }
  251. complete(longUrl);
  252. });
  253. }
  254.  
  255. var twitcasting = {
  256. fetchUserImage: function(userId, complete) {
  257. callWebApi('http://api.twitcasting.tv/api/userstatus', { user: userId }, function(data) {
  258. complete(data && data.image, true);
  259. });
  260. },
  261. fetchMovieThumbnail: function(movieId, userId, complete) {
  262. var self = this;
  263. callWebApi('http://api.twitcasting.tv/api/moviestatus', { movieid: movieId }, function(movieData) {
  264. if (movieData) {
  265. if (movieData.thumbnailsmall) {
  266. complete(movieData.thumbnailsmall, true);
  267. } else {
  268. self.fetchUserImage(userId, complete);
  269. }
  270. } else {
  271. complete();
  272. }
  273. });
  274. },
  275. fetchLiveThumbnail: function(userId, complete) {
  276. var completeNoCache = function(thumbUrl) { complete(thumbUrl); }; // ignore cache
  277. var self = this;
  278. callWebApi('http://api.twitcasting.tv/api/livestatus', { user: userId }, function(liveData) {
  279. if (liveData) {
  280. if (liveData.islive) {
  281. completeNoCache('http://twitcasting.tv/' + userId + '/thumbstream/liveshot-1');
  282. } else if (liveData.movieid) {
  283. self.fetchMovieThumbnail(liveData.movieid, userId, completeNoCache);
  284. } else {
  285. self.fetchUserImage(userId, completeNoCache);
  286. }
  287. } else {
  288. completeNoCache();
  289. }
  290. });
  291. }
  292. };
  293.  
  294. function fetchUstreamThumbnail(subject, uid, complete) {
  295. callWebApi('http://api.ustream.tv/json/' + subject + '/' + uid + '/getValueOf/imageUrl', {}, function(data) {
  296. complete(data && data.small, true);
  297. });
  298. }
  299.  
  300. function resolvOgmediaDefault(matched, complete) {
  301. callWebApi(EXT_SERV_ENDS.ogmedia, { url: matched[0] }, function(data) {
  302. complete(data && data.url, true);
  303. });
  304. }
  305. // /WebService
  306.  
  307. // ShortUrlRegexs
  308. var SHORT_URL_REGEX = (function() {
  309. var fragment = (
  310. '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' + ' ' +
  311. 'cot.ag p.tl amzn.to dlvr.it goo.gl moi.st dailybooth.com/u tinyurl.com sgp.cm pbckt.com' + ' ' +
  312. 'fon.gs mysp.ac itun.es is.gd v.gd r.sm3.jp tou.ch po.st post.ly pocket.co'
  313. ).replace(/\./g,'\\.').replace(/ /g, '|');
  314. return new RegExp('^http://(?:' + fragment + ')/[\\w\\-:\\.]+');
  315. })();
  316. // /ShortUrlRegexs
  317.  
  318. // ThumbnailResolvers
  319. var THUMB_RESOLVERS = {
  320. allimg: {
  321. priority: -1,
  322. regex: /^https?:\/\/[^?]+?\.(?:jpe?g|png|gif|bmp|JPE?G|PNG|GIF|BMP)(?=\?|$)/,
  323. resolv: function(matched, complete) {
  324. complete(matched[0]);
  325. }
  326. },
  327. twitpic: {
  328. priority: 2,
  329. regex: /^http:\/\/twitpic\.com\/(\w+)/,
  330. regexThumb: /^http:\/\/twitpic\.com\/show\/thumb\/\w+/,
  331. resolv: function(matched, complete) {
  332. complete('http://twitpic.com/show/thumb/' + matched[1]);
  333. }
  334. },
  335. imgly: {
  336. regex: /^http:\/\/img\.ly\/(\w+)/,
  337. regexThumb: /^http:\/\/img\.ly\/show\/thumb\/\w+/,
  338. resolv: function(matched, complete) {
  339. complete('http://img.ly/show/thumb/' + matched[1]);
  340. }
  341. },
  342. twitgoo: {
  343. regex: /^http:\/\/twitgoo\.com\/(\w+)/,
  344. regexThumb: /^http:\/\/twitgoo\.com\/show\/thumb\/\w+/,
  345. resolv: function(matched, complete) {
  346. complete('http://twitgoo.com/show/thumb/' + matched[1]);
  347. }
  348. },
  349. picim: {
  350. regex: /^http:\/\/pic\.im\/(\w+)/,
  351. regexThumb: /^http:\/\/pic\.im\/website\/thumbnail\/\w+/,
  352. resolv: function(matched, complete) {
  353. complete('http://pic.im/website/thumbnail/' + matched[1]);
  354. }
  355. },
  356. youtube: {
  357. regex: /^(?:http:\/\/www\.youtube\.com\/watch\/?\?v=([\w\-]+))|(?:http:\/\/youtu\.be\/([\w\-]+))/,
  358. resolv: function(matched, complete) {
  359. var id = matched[2] || matched[1];
  360. complete('http://i.ytimg.com/vi/' + id + '/default.jpg');
  361. }
  362. },
  363. imgur: {
  364. regex: /^http:\/\/imgur\.com\/(?:r\/pics\/)?(\w+)(?!\/)/,
  365. resolv: function(matched, complete) {
  366. complete('http://i.imgur.com/' + matched[1] + 't.jpg');
  367. }
  368. },
  369. owly: {
  370. regex: /^http:\/\/ow\.ly\/i\/(\w+)/,
  371. resolv: function(matched, complete) {
  372. complete('http://static.ow.ly/photos/thumb/' + matched[1] + '.jpg');
  373. }
  374. },
  375. movapic: {
  376. regex: /^http:\/\/movapic\.com\/pic\/(\w+)/,
  377. resolv: function(matched, complete) {
  378. complete('http://image.movapic.com/pic/s_' + matched[1] + '.jpeg');
  379. }
  380. },
  381. hatena: {
  382. regex: /^http:\/\/f\.hatena\.ne\.jp\/([\w\-]+)\/(\d+)/,
  383. resolv: function(matched, complete) {
  384. 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');
  385. }
  386. },
  387. moby: {
  388. regex: /^http:\/\/moby\.to\/(\w+)/,
  389. resolv: function(matched, complete) {
  390. complete(matched[0] + ':square');
  391. }
  392. },
  393. yfrog: {
  394. regex: /^http:\/\/yfrog\.com\/\w+/,
  395. resolv: function(matched, complete) {
  396. complete(matched[0] + ':small');
  397. }
  398. },
  399. plixi: {
  400. regex: /^http:\/\/plixi\.com\/p\/(\w+)/,
  401. regexThumb: /^http:\/\/api\.plixi\.com\/api\/TPAPI\.svc\/imagefromurl\?size=thumbnail&url=http:\/\/plixi\.com\/p\/\w+/,
  402. resolv: function(matched, complete) {
  403. complete('http://api.plixi.com/api/TPAPI.svc/imagefromurl?size=thumbnail&url=' + matched[0]);
  404. }
  405. },
  406. nicovideo: {
  407. regex: /^(?:http:\/\/www\.nicovideo\.jp\/watch\/(?:sm|nm)(\d+))|(?:http:\/\/nico\.ms\/(?:sm|nm)(\d+))/,
  408. regexThumb: /^http:\/\/tn-skr\d?\.smilevideo\.jp\/smile\?i=\d+/,
  409. resolv: function(matched, complete) {
  410. var id = matched[2] || matched[1];
  411. complete('http://tn-skr.smilevideo.jp/smile?i=' + id);
  412. }
  413. },
  414. nicovideoapi: {
  415. regex: /^(?:http:\/\/(?:www|live)\.nicovideo\.jp\/(?:watch|gate)\/((?:lv)?\d+))|(?:http:\/\/nico\.ms\/((?:lv)?\d+))/,
  416. resolv: function(matched, complete) {
  417. var id = matched[2] || matched[1];
  418. callWebApi(EXT_SERV_ENDS.nicovideo, { id: id }, function(data) {
  419. complete(data && data.url, true);
  420. });
  421. }
  422. },
  423. nicoseiga: {
  424. regex: /^(?:http:\/\/seiga\.nicovideo\.jp\/seiga\/im(\d+))|(?:http:\/\/nico\.ms\/im(\d+))/,
  425. regexThumb: /^http:\/\/lohas\.nicoseiga\.jp\/thumb\/\d+[qi]/,
  426. resolv: function(matched, complete) {
  427. var id = matched[2] || matched[1];
  428. complete('http://lohas.nicoseiga.jp/thumb/' + id + 'q');
  429. }
  430. },
  431. nicocomu: {
  432. regex: /^http:\/\/com\.nicovideo\.jp\/community\/co(\d+)/,
  433. resolv: function(matched, complete) {
  434. complete('http://icon.nimg.jp/community/co' + matched[1] + '.jpg');
  435. }
  436. },
  437. twipple: {
  438. regex: /^http:\/\/p\.twipple\.jp\/(\w+)$/,
  439. regexThumb: /^http:\/\/p\.twpl\.jp\/show\/thumb\/\w+/,
  440. resolv: function(matched, complete) {
  441. complete('http://p.twpl.jp/show/thumb/' + matched[1]);
  442. }
  443. },
  444. photozou: {
  445. regex: /^http:\/\/photozou\.jp\/photo\/show\/\d+\/(\d+)/,
  446. regexThumb: /^http:\/\/photozou\.jp\/p\/thumb\/\d+/,
  447. resolv: function(matched, complete) {
  448. complete('http://photozou.jp/p/thumb/' + matched[1]);
  449. }
  450. },
  451. ustream: {
  452. regex: /^http:\/\/(?:www\.)?ustream\.tv\/(channel|recorded)\/(?:id\/)?([\w\-%]+)/,
  453. subjects: { 'channel': 'channel', 'recorded': 'video' },
  454. resolv: function(matched, complete) {
  455. fetchUstreamThumbnail(this.subjects[matched[1]], matched[2], complete);
  456. }
  457. },
  458. lockerz: {
  459. regex: /^http:\/\/lockerz\.com\/s\/\d+/,
  460. regexThumb: /^http:\/\/api\.plixi\.com\/api\/tpapi\.svc\/imagefromurl\?size=thumbnail&url=http:\/\/lockerz\.com\/s\/\d+/,
  461. resolv: function(matched, complete) {
  462. complete('http://api.plixi.com/api/tpapi.svc/imagefromurl?size=thumbnail&url=' + matched[0]);
  463. }
  464. },
  465. jcomi: {
  466. regex: /^http:\/\/(?:www\.|vw\.)?j-comi\.jp\/(?:book|murasame)\/(?:comic|detail|view)\/(\d+)/,
  467. resolv: function(matched, complete) {
  468. var id = matched[1];
  469. if (id == '1' || id == '21') {
  470. id += '01';
  471. }
  472. callWebApi(EXT_SERV_ENDS.jcomi, { id: id }, function(data) {
  473. complete(data && data.url, true);
  474. });
  475. }
  476. },
  477. picplz: {
  478. regex: /^http:\/\/picplz\.com\/(?:user\/\w+\/pic\/(\w+)|(\w+))/,
  479. resolv: function(matched, complete) {
  480. var param = {};
  481. if (matched[1]) {
  482. param.longurl_id = matched[1];
  483. } else {
  484. param.shorturl_id = matched[2];
  485. }
  486. callWebApi('http://api.picplz.com/api/v2/pic.json', param, function(data) {
  487. var imgUrl = null;
  488. if (data && data.result == 'ok') {
  489. imgUrl = data.value.pics[0].pic_files['100sh'].img_url;
  490. };
  491. complete(imgUrl, true);
  492. });
  493. }
  494. },
  495. kabegami: {
  496. regex: /^http:\/\/www\.kabegami\.com\/[\w\-]+\/\w+\/show\/id\/(PHOT(\d{10})(\w\w)(\w\w)\w\w)/,
  497. resolv: function(matched, complete) {
  498. complete(['http://www.kabegami.com/content_image/phot', matched[2], matched[3], matched[4], matched[1]].join('/') + '_s90.jpg');
  499. }
  500. },
  501. instagram: {
  502. regex: /^http:\/\/(?:instagr\.am|instagram\.com)\/p\/[\w\-]+/,
  503. resolv: function(matched, complete) {
  504. complete(matched[0] + '/media/?size=t');
  505. }
  506. },
  507. mypix: {
  508. regex: /^http:\/\/www\.mypix\.jp\/app\.php\/picture\/\d+/,
  509. resolv: function(matched, complete) {
  510. complete(matched[0] + '/thumbx.jpg');
  511. }
  512. },
  513. fotolog: {
  514. regex: /^http:\/\/fotolog\.cc\/(\w+)/,
  515. regexThumb: /^http:\/\/fotolog\.cc\/image\/\w+\/mini/,
  516. resolv: function(matched, complete) {
  517. complete('http://fotolog.cc/image/' + matched[1] + '/mini');
  518. }
  519. },
  520. vimeo: {
  521. regex: /^http:\/\/(?:www\.)?vimeo\.com\/(\d+)/,
  522. resolv: function(matched, complete) {
  523. callWebApi('http://vimeo.com/api/v2/video/' + matched[1] + '.json', {}, function(data) {
  524. complete(data && data[0].thumbnail_small, true);
  525. });
  526. }
  527. },
  528. dailybooth: {
  529. regex: /^http:\/\/dailybooth\.com\/[\w\-]{2,}\/(\d+)/,
  530. resolv: function(matched, complete) {
  531. callWebApi('http://api.dailybooth.com/v2/pictures/' + matched[1] + '.json', {}, function(data) {
  532. complete(data && data.urls.small, true);
  533. });
  534. }
  535. },
  536. twitcasting: {
  537. regex: /^http:\/\/twitcasting\.tv\/([\w\-]+)\/movie\/(\d+)/,
  538. resolv: function(matched, complete) {
  539. var userId = matched[1];
  540. var movieId = matched[2];
  541. twitcasting.fetchMovieThumbnail(movieId, userId, complete);
  542. }
  543. },
  544. twitcastinglive: {
  545. regex: /^http:\/\/twitcasting\.tv\/([\w\-]+)\/?$/,
  546. resolv: function(matched, complete) {
  547. var userId = matched[1];
  548. twitcasting.fetchLiveThumbnail(userId, complete);
  549. }
  550. },
  551. metacafe: {
  552. regex: /^http:\/\/www\.metacafe\.com\/(?:w|watch)\/([\w\-]+)/,
  553. resolv: function(matched, complete) {
  554. complete('http://www.metacafe.com/thumb/' + matched[1] + '.jpg');
  555. }
  556. },
  557. dailymotion: {
  558. regex: /^http:\/\/(?:www|touch)\.dailymotion\.com\/(?:#\/)?video\/([\w\-]+)/,
  559. resolv: function(matched, complete) {
  560. callWebApi('https://api.dailymotion.com/video/' + matched[1], { fields: 'thumbnail_medium_url' }, function(data) {
  561. complete(data && data.thumbnail_medium_url, true);
  562. });
  563. }
  564. },
  565. stickamjpprof: {
  566. regex: /^http:\/\/(?:www\.stickam\.jp\/profile|stick\.am\/p)\/(\w+)/,
  567. resolv: function(matched, complete) {
  568. callWebApi('http://api.stickam.jp/api/user/' + matched[1] + '/profile', { mime: 'json' }, function(data) {
  569. complete(data && data.profile_image, true);
  570. });
  571. }
  572. },
  573. stickamjpmedia: {
  574. regex: /^http:\/\/www\.stickam\.jp\/video\/(\d+)/,
  575. resolv: function(matched, complete) {
  576. callWebApi('http://api.stickam.jp/api/media/' + matched[1], { mime: 'json' }, function(data) {
  577. complete(data && data.media && data.media.thumb, true);
  578. });
  579. }
  580. },
  581. photobucket: {
  582. regex: /^http:\/\/[is]\d+\.photobucket\.com\/albums\/.+/,
  583. resolv: function(matched, complete) {
  584. callWebApi('http://photobucket.com/oembed', { url: matched[0] }, function(data) {
  585. complete(data && data.thumbnail_url, true);
  586. });
  587. }
  588. },
  589. pixiv: {
  590. regex: /^http:\/\/(?:www\.pixiv\.net\/member_illust\.php\/?\?(?:mode=medium&)?illust_id=|p\.tl\/i\/)(\d+)/,
  591. resolv: function (matched, complete) {
  592. callWebApi(EXT_SERV_ENDS.ogmedia, { url: 'http://www.pixiv.net/member_illust.php?mode=medium&illust_id=' + matched[1] }, function(data) {
  593. complete(data && data.url, true);
  594. });
  595. }
  596. },
  597. flickr: {
  598. regex: /^http:\/\/(?:www\.flickr\.com\/photos\/[\w@\-]+\/(\d+))|(?:flic\.kr\/p\/(\w+))/,
  599. resolv: function(matched, complete) {
  600. var id = matched[2] ? base58decode(matched[2]) : matched[1];
  601. callWebApi(EXT_SERV_ENDS.flickr, { photo_id: id }, function(data) {
  602. complete(data && data.url, true);
  603. });
  604. }
  605. },
  606. videosurf: {
  607. regex: /^http:\/\/www\.videosurf\.com\/video\/-?(?:[a-zA-Z0-9%]+-)*(\d+)\b/,
  608. resolv: function(matched, complete) {
  609. callWebApi(EXT_SERV_ENDS.videosurf, { id: matched[1] }, function(data) {
  610. complete(data && data.url, true);
  611. });
  612. }
  613. },
  614. tinami: {
  615. regex: /^http:\/\/(?:www\.tinami\.com\/view\/(\w+)|tinami\.jp\/(\w+))/,
  616. resolv: function(matched, complete) {
  617. var id = matched[2] ? base36decode(matched[2]) : matched[1];
  618. callWebApi(EXT_SERV_ENDS.tinami, { id: id }, function(data) {
  619. complete(data && data.url, true);
  620. });
  621. }
  622. },
  623. akibablog: {
  624. regex: /^http:\/\/blog\.livedoor\.jp\/geek\/archives\/\d+\.html/,
  625. resolv: resolvOgmediaDefault
  626. },
  627. photobucketmedia: {
  628. regex: /^http:\/\/media\.photobucket\.com\/.+/,
  629. resolv: resolvOgmediaDefault
  630. },
  631. ustreamsp: {
  632. regex: /^http:\/\/(?:www\.)?ustream\.tv\/[\w\-%]+(?=\/?$)/,
  633. resolv: function(matched, complete) {
  634. callWebApi(EXT_SERV_ENDS.ustream, { url: matched[0] }, function(data) {
  635. if (data) {
  636. fetchUstreamThumbnail('channel', data.channelId, complete);
  637. } else {
  638. complete();
  639. }
  640. });
  641. }
  642. },
  643. tumblr: {
  644. regex: /^http:\/\/([\w\-]+\.tumblr\.com)\/post\/(\d+)/,
  645. resolv: function(matched, complete) {
  646. callWebApi(EXT_SERV_ENDS.tumblr, { base_hostname: matched[1], post_id: matched[2] }, function(data) {
  647. complete(data && data.url, true);
  648. });
  649. }
  650. },
  651. tumblrs: {
  652. regex: /^http:\/\/(?:tumblr\.com|tmblr\.co)\/[\w\-]+/,
  653. resolv: function(matched, complete) {
  654. expandShortUrl(matched[0], function(longUrl) {
  655. var regex = /^http:\/\/((?:\.?[\w\-]+)+)\/post\/(\d+)/;
  656. if (regex.test(longUrl)) {
  657. THUMB_RESOLVERS.tumblr.resolv(longUrl.match(regex), complete);
  658. } else {
  659. complete();
  660. }
  661. });
  662. }
  663. },
  664. rakutenbooks: {
  665. regex: /^http:\/\/books\.rakuten\.co\.jp\/rb\/[\w%\-]+\-(\d+)\//,
  666. resolv: function(matched, complete) {
  667. callWebApi(EXT_SERV_ENDS.rakuten, { type: 'book', id: matched[1] }, function(data) {
  668. complete(data && data.url, true);
  669. });
  670. }
  671. },
  672. rakutenitem: {
  673. regex: /^http:\/\/item\.rakuten\.co\.jp\/([\w\-]+\/[\w\-]+)/,
  674. resolv: function(matched, complete) {
  675. callWebApi(EXT_SERV_ENDS.rakuten, { type: 'item', id: matched[1] }, function(data) {
  676. complete(data && data.url, true);
  677. });
  678. }
  679. },
  680. twil: {
  681. regex: /^http:\/\/shlink\.st\/[\w\-]+/,
  682. resolv: function(matched, complete) {
  683. callWebApi(EXT_SERV_ENDS.twil, { url: matched[0] }, function(data) {
  684. complete(data && data.thumbnail_url, true);
  685. });
  686. }
  687. },
  688. twitrpix: {
  689. regex: /^http:\/\/twitrpix\.com\/(\w+)/,
  690. regexThumb: /^http:\/\/img\.twitrpix\.com\/thumb\/\w+/,
  691. resolv: function(matched, complete) {
  692. complete('http://img.twitrpix.com/thumb/' + matched[1]);
  693. }
  694. },
  695. pikchur: {
  696. regex: /^http:\/\/(?:pikchur\.com|pk\.gd)\/(\w+)/,
  697. resolv: function(matched, complete) {
  698. complete(' http://img.pikchur.com/pic_' + matched[1] + '_s.jpg');
  699. }
  700. },
  701. twitxr: {
  702. regex: /^http:\/\/twitxr\.com\/(\w+)\/updates\/(\d+)/,
  703. regexThumb: /^http:\/\/twitxr\.com\/image\/\d+\/th/,
  704. resolv: function(matched, complete) {
  705. complete('http://twitxr.com/image/' + matched[2] + '/th');
  706. }
  707. },
  708. myspacevideo: {
  709. regex: /^http:\/\/(?:www\.)?myspace\.com\/video\/(?:(vid|rid)|[\w-]+\/[\w-]+)\/(\d+)/,
  710. resolv: function(matched, complete) {
  711. var id = (matched[1] || 'vid') + '/' + matched[2];
  712. callWebApi(EXT_SERV_ENDS.myspacevideo, { id: id }, function(data) {
  713. complete(data && data.url, true);
  714. });
  715. }
  716. },
  717. myspacevideovids: {
  718. regex: /^http:\/\/(?:vids\.|www\.)?myspace\.com\/index\.cfm\?fuseaction=vids\.individual&videoid=(\d+)/,
  719. resolv: function(matched, complete) {
  720. var id = 'vid/' + matched[1];
  721. callWebApi(EXT_SERV_ENDS.myspacevideo, { id: id }, function(data) {
  722. complete(data && data.url, true);
  723. });
  724. }
  725. },
  726. twitvideo: {
  727. regex: /^http:\/\/twitvideo\.jp\/(\w+)/,
  728. regexThumb: /^http:\/\/twitvideo\.jp\/img\/thumb\/\w+/,
  729. resolv: function(matched, complete) {
  730. complete('http://twitvideo.jp/img/thumb/' + matched[1]);
  731. }
  732. },
  733. mycom: {
  734. regex: /^http:\/\/(?:if\.|s\.)?journal\.mycom\.co\.jp\/\w+\/\d+\/\d+\/\d+\/\w+\//,
  735. resolv: function(matched, complete) {
  736. complete(matched[0] + 'index.top.jpg');
  737. }
  738. },
  739. twitvid: {
  740. regex: /^http:\/\/(?:www\.)?twitvid\.com\/(?!videos)(\w+)/,
  741. resolv: function(matched, complete) {
  742. var id = matched[1];
  743. complete(['http://llphotos.twitvid.com/twitvidthumbnails', id.charAt(0), id.charAt(1), id.charAt(2), id].join('/') + '_med.jpg');
  744. }
  745. },
  746. pckles: {
  747. regex: /^http:\/\/(?:pckl\.es|pckles\.com)\/\w+\/\w+/,
  748. resolv: function(matched, complete) {
  749. complete(matched[0] + '.thumb.jpg');
  750. }
  751. },
  752. itunes: {
  753. regex: /^http:\/\/(?:c\.)?itunes\.apple\.com\/.*\/id\w+(?:\?i=\d+)?/,
  754. resolv: function(matched, complete) {
  755. callWebApi(EXT_SERV_ENDS.itunes, { url: matched[0] }, function(data) {
  756. complete(data && data.url, true);
  757. });
  758. }
  759. },
  760. loctouch: {
  761. regex: /^http:\/\/tou\.ch\/spot\/\d+\/(\w+)\/(\d+)/,
  762. resolv: function(matched, complete) {
  763. callWebApi(EXT_SERV_ENDS.loctouch, { type: matched[1], id: matched[2] }, function(data) {
  764. complete(data && data.url, true);
  765. });
  766. }
  767. },
  768. miil: {
  769. regex: /^http:\/\/miil\.me\/p\/(\w+)/,
  770. resolv: function(matched, complete) {
  771. complete(matched[0] + '.jpeg?size=150');
  772. }
  773. },
  774. i500px: {
  775. regex: /^https?:\/\/(?:www\.)?500px\.com\/photo\/(\d+)/,
  776. resolv: function(matched, complete) {
  777. callWebApi(EXT_SERV_ENDS.i500px, { id: matched[1] }, function(data) {
  778. complete(data && data.url, true);
  779. });
  780. }
  781. },
  782. hulu: {
  783. regex: /^http:\/\/(?:www\.hulu\.com\/watch|hulu\.com\/w)\/.*/,
  784. resolv: function(matched, complete) {
  785. callWebApi('http://www.hulu.com/api/oembed', { url: matched[0] }, function(data) {
  786. complete(data && data.thumbnail_url, true);
  787. });
  788. }
  789. },
  790. facebook: {
  791. regex: /^https?:\/\/www\.facebook\.com\/photo\.php\?(pid=.+)/,
  792. resolv: function(matched, complete) {
  793. expandShortUrl('https://www.facebook.com/twitter/photo/?' + matched[1], function(url) {
  794. var imgUrl = null;
  795. if (url) {
  796. imgUrl = url.replace(/_n\.jpg$/, '_a.jpg');
  797. }
  798. complete(imgUrl, true);
  799. });
  800. }
  801. },
  802. immio: {
  803. regex: /^http:\/\/imm\.io\/\w+/,
  804. resolv: resolvOgmediaDefault
  805. },
  806. comicdash: {
  807. regex: /^http:\/\/ckworks\.jp\/comicdash\/series\/\d+/,
  808. resolv: resolvOgmediaDefault
  809. },
  810. gyazo: {
  811. regex: /^http:\/\/gyazo\.com\/\w+/,
  812. resolv: function(matched, complete) {
  813. complete(matched[0] + '.png');
  814. }
  815. },
  816. viame: {
  817. regex: /^http:\/\/via\.me\/-\w+/,
  818. resolv: resolvOgmediaDefault
  819. },
  820. posterous: {
  821. regex: /^http:\/\/[\w\-]+\.posterous\.com\/[\w\-]+/,
  822. resolv: resolvOgmediaDefault
  823. },
  824. bookmetercmt: {
  825. regex: /^http:\/\/book\.akahoshitakuya\.com\/cmt\/\w+/,
  826. resolv: resolvOgmediaDefault
  827. },
  828. booklogp: {
  829. regex: /^http:\/\/p\.booklog\.jp\/book\/\d+/,
  830. resolv: function(matched, complete) {
  831. callWebApi(EXT_SERV_ENDS.ogmedia, { url: matched[0] }, function(data) {
  832. var imgUrl = null;
  833. if (data && data.url) {
  834. imgUrl = data.url.replace(/_s\.jpg$/, '_m.jpg');
  835. }
  836. complete(imgUrl, true);
  837. });
  838. }
  839. },
  840. booklogitem: {
  841. regex: /^http:\/\/booklog\.jp\/item\/3\/\d+/,
  842. resolv: resolvOgmediaDefault
  843. },
  844. mlkshk: {
  845. regex: /^http:\/\/mlkshk\.com\/\w\/(\w+)/,
  846. resolv: function(matched, complete) {
  847. complete('http://mlkshk.com/r/' + matched[1]);
  848. }
  849. },
  850. twitdraw: {
  851. regex: /^http:\/\/twitdraw\.com\/(\w+)/,
  852. resolv: function(matched, complete) {
  853. complete('http://td-images.s3.amazonaws.com/' + matched[1] + '.png');
  854. }
  855. },
  856. tegaki: {
  857. regex: /^http:\/\/tegaki\.pipa\.jp\/\d+\/(\d+)\.html/,
  858. resolv: function(matched, complete) {
  859. complete('http://tegaki.pipa.jp/CGetBlogImgS.jsp?TD=' + matched[1]);
  860. }
  861. },
  862. gizmodo: {
  863. regex: /^http:\/\/(?:www|us)\.gizmodo\.(?:jp|co\.uk|de|com)\/\d+\/(?:\d+\/)?[\w\.\-]+/,
  864. resolv: resolvOgmediaDefault
  865. },
  866. piapro: {
  867. regex: /^http:\/\/piapro\.jp\/t\/[\w\-]+/,
  868. resolv: resolvOgmediaDefault
  869. },
  870. tweetvite: {
  871. regex: /^http:\/\/(?:tweetvite\.com\/event|twvt\.us)\/([\w\-]+)/,
  872. resolv: function resolvOgmediaDefault(matched, complete) {
  873. var url = 'http://tweetvite.com/event/' + matched[1];
  874. callWebApi(EXT_SERV_ENDS.ogmedia, { url: url }, function(data) {
  875. complete(data && data.url, true);
  876. });
  877. }
  878. },
  879. twitter: {
  880. priority: 1,
  881. regex: /^https?:\/\/twitter\.com\/(?:#!\/)?[\w\-]+\/status\/(\d+)\/photo\/1/,
  882. resolv: function(matched, complete) {
  883. callWebApi(EXT_SERV_ENDS.pictwitter, { id: matched[1] }, function(data) {
  884. complete(data && data.url, true);
  885. });
  886. }
  887. },
  888. eyeem: {
  889. regex: /^http:\/\/www\.eyeem\.com\/p\/\d+/,
  890. resolv: resolvOgmediaDefault
  891. }
  892. };
  893. // amazon
  894. (function(resolvers) {
  895. var fetchAmazonThumbnail = function(sendData, complete) {
  896. callWebApi(EXT_SERV_ENDS.amazon, sendData, function(data) {
  897. complete(data && data.url, true);
  898. });
  899. };
  900. var resolvAmazonThumbnailJpDefault = function(matched, complete) {
  901. fetchAmazonThumbnail({ tld: 'jp', asin: matched[1] }, complete);
  902. };
  903.  
  904. resolvers.amazon = {
  905. 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+))/,
  906. resolv: function(matched, complete) {
  907. fetchAmazonThumbnail({ tld: matched[1], asin: matched[3] || matched[2] }, complete);
  908. }
  909. };
  910. resolvers.bookmeter = {
  911. regex: /^http:\/\/book\.akahoshitakuya\.com\/b\/(\w+)/,
  912. resolv: resolvAmazonThumbnailJpDefault
  913. };
  914. resolvers.mediamarker = {
  915. regex: /^http:\/\/mediamarker\.net\/\w\/[\w\-]+\/\?asin=(\w+)/,
  916. resolv: resolvAmazonThumbnailJpDefault
  917. };
  918. resolvers.booklog = {
  919. regex: /^http:\/\/booklog\.jp\/(?:users\/[\w\-]+\/archives|asin|item)\/1\/(\w+)/,
  920. resolv: resolvAmazonThumbnailJpDefault
  921. };
  922. resolvers.sociallibrary = {
  923. regex: /^http:\/\/www\.sociallibrary\.jp\/entry\/(\w+)/,
  924. resolv: resolvAmazonThumbnailJpDefault
  925. };
  926. })(THUMB_RESOLVERS);
  927. // /amazon
  928.  
  929. // /ThumbnailResolvers
  930.  
  931. // Process
  932. var Process = function(args) {
  933. this.waitCount = 0;
  934. this.results = [];
  935. this.split = args.split;
  936. this.execute = args.execute;
  937. this.complete = args.complete;
  938. this.queue = args.queue;
  939. };
  940. Process.prototype = {
  941. run: function(input) {
  942. var subInputs = this.split(input);
  943. var inputLength = subInputs ? subInputs.length : 0;
  944. this.waitCount = inputLength;
  945. this.internalComplete();
  946. for (var i = 0; i < inputLength; i++) {
  947. this.execute(subInputs[i]);
  948. }
  949. },
  950. emitComplete: function(result) {
  951. this.waitCount--;
  952. if (result) {
  953. this.results.push(result);
  954. }
  955. this.internalComplete();
  956. },
  957. internalComplete: function() {
  958. if (this.waitCount == 0) {
  959. if (this.complete) {
  960. this.complete(this.results);
  961. }
  962. if (this.queue.length != 0) {
  963. this.queue.shift()();
  964. }
  965. }
  966. }
  967. };
  968. // /Process
  969.  
  970. // ExpandThumbnailMainProcess
  971. var thumbResolverArry = objectToArray(THUMB_RESOLVERS);
  972. thumbResolverArry.sort(function(a, b) {
  973. var a_p = 'priority' in a ? a.priority : 0;
  974. var b_p = 'priority' in b ? b.priority : 0;
  975. if (a_p < b_p) return 1;
  976. if (a_p > b_p) return -1;
  977. return 0;
  978. });
  979. function resolvThumbnailUrl(contentUrl, complete) {
  980. var cached = ThumbnailUrlCache.get(contentUrl);
  981. if (cached) {
  982. complete(cached);
  983. return true;
  984. }
  985. var match = false;
  986. $.each(thumbResolverArry, function(index, resolver) {
  987. if (resolver.regex.test(contentUrl)) {
  988. resolver.resolv(contentUrl.match(resolver.regex), function(thumbnailUrl, cache) {
  989. if (thumbnailUrl && cache) {
  990. ThumbnailUrlCache.put(contentUrl, thumbnailUrl);
  991. }
  992. complete(thumbnailUrl);
  993. });
  994. match = true;
  995. return false;
  996. } else if (resolver.regexThumb && resolver.regexThumb.test(contentUrl)) {
  997. complete(contentUrl.match(resolver.regexThumb)[0]);
  998. match = true;
  999. return false;
  1000. }
  1001. });
  1002. return match;
  1003. }
  1004.  
  1005. function createThumbnailElement(urlEntries) {
  1006. var ul = $E('ul', { 'class': 'ithumb-ul' });
  1007. for (var i = 0; i < urlEntries.length; i++) {
  1008. var urlEntry = urlEntries[i];
  1009. ul.append(
  1010. $E('li', { 'class':'ithumb-li' }).append(
  1011. $E('a', { 'class':'ithumb-a', 'target':'_blank', 'href':urlEntry.url, 'rel':'url' }).append(
  1012. $E('img', { 'src': modPrt(urlEntry.thumbUrl), 'class':'ithumb-img' })
  1013. .error(function() {
  1014. var img = $(this);
  1015. var tryload = img.data('tryload') || 0;
  1016. if (tryload < 2) {
  1017. setTimeout(function() {
  1018. img.data('tryload', tryload + 1).attr('src', img.attr('src'));
  1019. }, 7000);
  1020. } else {
  1021. img.hide();
  1022. }
  1023. })
  1024. )
  1025. )
  1026. );
  1027. }
  1028. var thumb = $E('div', { 'class':'ithumb-container' }).append(ul);
  1029. if (domainEnv.customizeThumbnailElement) {
  1030. domainEnv.customizeThumbnailElement(thumb);
  1031. }
  1032. return thumb;
  1033. }
  1034.  
  1035. function expandThumbnail(contentElement) {
  1036. var urlStack = [];
  1037. var processQueue = [];
  1038.  
  1039. /* <thumbnailExpandProcess> */
  1040. var urlExtractProcess = new Process({
  1041. queue: processQueue,
  1042. split: function(element) {
  1043. return element.find('a').map(function() {
  1044. var anchor = $(this);
  1045. var url = anchor.attr('href');
  1046. return (url && (/^https?:\/\/(?!twitter\.com\/#!\/)/).test(url)) ? anchor : null;
  1047. });
  1048. },
  1049. execute: function(anchor) {
  1050. var urlEntry = { thumbUrl: null, expandCount: 0 };
  1051. var expandedUrl = null;
  1052. if (domainEnv.getExpandedUrl) {
  1053. expandedUrl = domainEnv.getExpandedUrl(anchor);
  1054. }
  1055. urlEntry.url = expandedUrl || anchor.attr('href');
  1056. this.emitComplete(urlEntry);
  1057. },
  1058. complete: function(urlEntries) {
  1059. urlStack = unique(urlEntries, 'url');
  1060. }
  1061. });
  1062.  
  1063. var thumbnailUrlResolveProcess = new Process({
  1064. queue: processQueue,
  1065. split: function(urlEntries) {
  1066. return urlEntries;
  1067. },
  1068. execute: function (urlEntry) {
  1069. var self = this;
  1070. var match = resolvThumbnailUrl(urlEntry.url, function(thumbUrl) {
  1071. urlEntry.thumbUrl = thumbUrl;
  1072. self.emitComplete();
  1073. });
  1074. if (!match) {
  1075. if (urlEntry.expandCount < 5 && SHORT_URL_REGEX.test(urlEntry.url)) {
  1076. expandShortUrl(urlEntry.url, function(longUrl) {
  1077. if (urlEntry.url != longUrl) {
  1078. urlEntry.expandCount += 1;
  1079. urlEntry.url = longUrl;
  1080. self.execute(urlEntry);
  1081. } else {
  1082. self.emitComplete();
  1083. }
  1084. });
  1085. } else {
  1086. self.emitComplete();
  1087. }
  1088. }
  1089. },
  1090. });
  1091. /* </thumbnailExpandProcess> */
  1092.  
  1093. processQueue.push(function() { thumbnailUrlResolveProcess.run(urlStack); });
  1094. processQueue.push(function() {
  1095. var urlEntries = unique(
  1096. $.grep(urlStack, function(val) { return val.thumbUrl; }),
  1097. 'thumbUrl');
  1098. if (urlEntries.length != 0) {
  1099. domainEnv.appendThumbnail(contentElement, createThumbnailElement(urlEntries));
  1100. }
  1101. });
  1102.  
  1103. urlExtractProcess.run(contentElement);
  1104. }
  1105. // /ExpandThumbnailMainProcess
  1106.  
  1107. var CSS_URL_DEFAULT = EXT_SERV_HOST_URL + '/stylesheets/inlinethumbnail/1.1.0/default.css';
  1108. var APPEND_THUMBNAIL_DEFAULT = function(contentElement, thumbnailElement) { contentElement.append(thumbnailElement); };
  1109. var DOMAIN_ENVS = {
  1110. hootsuite: {
  1111. hostname: 'hootsuite.com',
  1112. select: function(context) {
  1113. return $('._baseTweetText:not([expanded-img])', context);
  1114. },
  1115. appendThumbnail: APPEND_THUMBNAIL_DEFAULT,
  1116. cssUrl: CSS_URL_DEFAULT
  1117. },
  1118. twitter: {
  1119. hostname: 'twitter.com',
  1120. select: function(context) {
  1121. return $('.tweet:not(.simple-tweet) > .content > p:not([expanded-img])', context);
  1122. },
  1123. appendThumbnail: APPEND_THUMBNAIL_DEFAULT,
  1124. getExpandedUrl: function(anchor) { return anchor.data('expanded-url'); },
  1125. cssUrl: EXT_SERV_HOST_URL + '/stylesheets/inlinethumbnail/1.6.2/twittercom.css'
  1126. },
  1127. twitter_mobile: {
  1128. hostname: 'mobile.twitter.com',
  1129. select: function(context) {
  1130. return $('.tweet-text:not([expanded-img])', context);
  1131. },
  1132. appendThumbnail: APPEND_THUMBNAIL_DEFAULT,
  1133. getExpandedUrl: function(anchor) { return anchor.data('url'); },
  1134. cssUrl: CSS_URL_DEFAULT
  1135. },
  1136. crowy: {
  1137. hostname: 'www.crowy.net',
  1138. select: function(context) {
  1139. $('.images', context).remove();
  1140. return $('.message-text:not([expanded-img])', context);
  1141. },
  1142. appendThumbnail: APPEND_THUMBNAIL_DEFAULT,
  1143. cssUrl: CSS_URL_DEFAULT
  1144. },
  1145. twipple: {
  1146. hostname: 'twipple.jp',
  1147. select: function(context) {
  1148. return $('.tweetBox:not([expanded-img])', context.parentNode);
  1149. },
  1150. appendThumbnail: APPEND_THUMBNAIL_DEFAULT,
  1151. cssUrl: CSS_URL_DEFAULT
  1152. },
  1153. tweetdeck: {
  1154. hostname: 'web.tweetdeck.com',
  1155. select: function(context) {
  1156. return $('.tweet-body:not([expanded-img])', context.parentNode);
  1157. },
  1158. appendThumbnail: function(contentElement, thumbnailElement) { contentElement.after(thumbnailElement); },
  1159. getExpandedUrl: function(anchor) { return anchor.data('full-url'); },
  1160. cssUrl: CSS_URL_DEFAULT
  1161. }
  1162. };
  1163.  
  1164. var domainEnv = null;
  1165. $.each(DOMAIN_ENVS, function() {
  1166. if (this.hostname == location.hostname) {
  1167. if (this.match) {
  1168. if (this.match()) {
  1169. domainEnv = this;
  1170. return false;
  1171. }
  1172. } else {
  1173. domainEnv = this;
  1174. return false;
  1175. }
  1176. }
  1177. });
  1178.  
  1179. function applyElements(context) {
  1180. domainEnv.select(context).each(function() {
  1181. expandThumbnail($(this).attr('expanded-img', 'expanded-img'));
  1182. });
  1183. if (domainEnv.fix) {
  1184. domainEnv.fix(context);
  1185. }
  1186. }
  1187.  
  1188. $(document.getElementsByTagName('head')[0]).append($E('link', { 'rel':'stylesheet', 'type':'text/css', 'media':'screen', 'href':domainEnv.cssUrl }));
  1189.  
  1190. var ApplyQueue = {
  1191. timeoutId: null,
  1192. queue: [],
  1193. apply: function() {
  1194. var targets = this.queue.splice(0, this.queue.length);
  1195. applyElements(targets);
  1196. },
  1197. push: function(elem) {
  1198. if (this.timeoutId) {
  1199. clearTimeout(this.timeoutId);
  1200. this.timeoutId = null;
  1201. }
  1202. this.queue.push(elem);
  1203.  
  1204. var self = this;
  1205. this.timeoutId = setTimeout(function() {
  1206. self.apply();
  1207. }, 1000);
  1208. }
  1209. };
  1210.  
  1211. // initial apply
  1212. ApplyQueue.push(document);
  1213.  
  1214. $(document).bind('DOMNodeInserted', function(e) {
  1215. ApplyQueue.push(e.target);
  1216. });
  1217.  
  1218. } // /main logic
  1219.  
  1220. // load
  1221. (function mainloader(tryCount, loaded) {
  1222. if (tryCount < 30 && !(window.jQuery)) {
  1223. setTimeout(function() { mainloader(tryCount + 1, loaded); }, 60);
  1224. return;
  1225. }
  1226. if (!loaded && !(window.jQuery)) {
  1227. loadJQuery();
  1228. setTimeout(function() { mainloader(0, true); }, 60);
  1229. return;
  1230. }
  1231. var hostname = 'thumbnailurlconv.appspot.com';
  1232. var endpoint = '//' + hostname + '/endpoint';
  1233. var dataType = isSupportXhr2() ? 'json' : 'jsonp';
  1234. ajax({ url: endpoint, dataType: dataType,
  1235. success: function(data) { main('//' + (data ? data.hostname : hostname)); },
  1236. error: function() { main('//' + hostname); }
  1237. });
  1238. })(0);
  1239.  
  1240. } // /source code
  1241.  
  1242. var script = document.createElement('script');
  1243. script.type = 'text/javascript';
  1244. script.innerHTML = '(' + source.toString() + ')();';
  1245. document.body.appendChild(script);
  1246.  
  1247. })();