├── old ├── builds │ ├── packed │ │ ├── chrome.zip │ │ ├── firefox.xpi │ │ ├── icon.png │ │ └── icon64.png │ └── unpacked │ │ └── chrome │ │ ├── data │ │ ├── content_script │ │ │ ├── inject.css │ │ │ └── inject.js │ │ └── icons │ │ │ ├── 128.png │ │ │ ├── 16.png │ │ │ ├── 24.png │ │ │ ├── 256.png │ │ │ ├── 32.png │ │ │ ├── 48.png │ │ │ └── 64.png │ │ ├── lib │ │ ├── chrome │ │ │ ├── background.html │ │ │ ├── chrome.js │ │ │ └── toBlob.js │ │ ├── common.js │ │ └── config.js │ │ └── manifest.json ├── drawings │ ├── Screen Shot 2015-04-08 at 15.27.30.png │ ├── chrome-screenshot.png │ ├── large-tile.png │ ├── large-tile.xcf │ ├── marquee.png │ ├── marquee.xcf │ ├── promo.png │ ├── promo.xcf │ ├── screenshot.png │ ├── screenshot.xcf │ ├── small-tile.png │ └── small-tile.xcf ├── gulpfile.js └── src │ ├── Icon-64.png │ ├── Info.plist │ ├── data │ ├── content_script │ │ ├── inject.css │ │ └── inject.js │ ├── firefox │ │ └── chrome.js │ └── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 24.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 64.png │ ├── lib │ ├── chrome │ │ ├── background.html │ │ ├── chrome.js │ │ └── toBlob.js │ ├── common.js │ ├── config.js │ ├── firefox │ │ ├── firefox.js │ │ └── mm.js │ └── safari │ │ ├── background.html │ │ ├── q.js │ │ ├── safari.html │ │ ├── safari.js │ │ └── toBlob.js │ ├── manifest.json │ ├── package.json │ └── update.plist ├── v2 ├── background.js ├── data │ ├── icons │ │ ├── 128.png │ │ ├── 16.png │ │ ├── 24.png │ │ ├── 256.png │ │ ├── 32.png │ │ ├── 48.png │ │ └── 64.png │ └── inject │ │ ├── inject.css │ │ ├── inject.js │ │ ├── notify.css │ │ └── notify.js └── manifest.json └── v3 ├── _locales ├── bg │ └── messages.json ├── de │ └── messages.json ├── el │ └── messages.json ├── en │ └── messages.json ├── es │ └── messages.json ├── fr │ └── messages.json ├── it │ └── messages.json ├── ja │ └── messages.json ├── nl │ └── messages.json ├── pt_BR │ └── messages.json ├── ru │ └── messages.json └── zh_CN │ └── messages.json ├── data ├── icons │ ├── 128.png │ ├── 16.png │ ├── 24.png │ ├── 256.png │ ├── 32.png │ ├── 48.png │ └── 64.png └── inject │ ├── inject.css │ ├── inject.js │ ├── notify.css │ └── notify.js ├── manifest.json └── worker.js /old/builds/packed/chrome.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/packed/chrome.zip -------------------------------------------------------------------------------- /old/builds/packed/firefox.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/packed/firefox.xpi -------------------------------------------------------------------------------- /old/builds/packed/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/packed/icon.png -------------------------------------------------------------------------------- /old/builds/packed/icon64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/packed/icon64.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/content_script/inject.css: -------------------------------------------------------------------------------- 1 | .itrisearch-box { 2 | box-sizing: border-box; 3 | position: fixed; 4 | z-index: 2147483646; 5 | border: gray 1px dotted; 6 | box-shadow: 0 0 0 50000px rgba(0, 0, 0, 0.2); 7 | } 8 | .itrisearch-box:before { 9 | content: ''; 10 | display: block; 11 | width: calc(100% + 20px); 12 | height: calc(100% + 20px); 13 | margin-left: -10px; 14 | margin-top: -10px; 15 | cursor: crosshair; 16 | } 17 | 18 | .itrisearch-guide-1, 19 | .itrisearch-guide-2 { 20 | box-sizing: border-box; 21 | position: fixed; 22 | z-index: 2147483646; 23 | } 24 | .itrisearch-guide-1 { 25 | border-right: dashed 1px gray; 26 | top: 0; 27 | left: 0; 28 | height: 100%; 29 | } 30 | .itrisearch-guide-2 { 31 | border-bottom: dashed 1px gray; 32 | top: 0; 33 | width: 100%; 34 | } 35 | .itrisearch-guide-3 { 36 | z-index: 2147483645; 37 | position: fixed; 38 | top: 0; 39 | left: 0; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | 44 | .itrisearch-notification { 45 | position: fixed; 46 | z-index: 2147483646; 47 | right: 20px; 48 | bottom: 20px; 49 | width: 250px; 50 | height: 40px; 51 | line-height: 40px; 52 | background-color: #FFF; 53 | border: solid 2px gray; 54 | padding: 0 5px; 55 | box-shadow: 1px 1px 5px #aaa; 56 | color: #222; 57 | font-family: arial,sans-serif !important; 58 | font-size: 12px !important; 59 | } 60 | .itrisearch-notification button { 61 | position: absolute; 62 | top: 2px; 63 | right: 2px; 64 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAAilBMVEVEREBEREBEREBEREBEREBEREBEREAlJSMSEhE3NzQAAAAAAAAQEA8BAQEAAAAAAAAAAAAAAAAvLywBAQEAAAAAAAAcHBoAAAAAAAAAAAAAAAAAAAAyMi8AAAAAAAABAQELCwo4ODUAAAAAAAAWFhUsLCkKCgkDAwIAAAAAAAAtLSoCAgIHBwcAAACgX5xYAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAXEYAAFxGARSUQ0EAAAAHdElNRQffBAgGGDS4qporAAAAG0lEQVQI12NgZGRgAGJGMGBggJAMCA5YDsQEAAJ2AB0Q4n77AAAAAElFTkSuQmCC) center center no-repeat; 65 | width: 12px; 66 | height: 12px; 67 | margin: 0; 68 | padding: 0; 69 | border: 0; 70 | cursor: pointer; 71 | opacity: 0.5; 72 | } 73 | .itrisearch-notification button:hover { 74 | opacity: 1.0; 75 | } 76 | -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/content_script/inject.js: -------------------------------------------------------------------------------- 1 | /* global self, safari*/ 2 | 'use strict'; 3 | 4 | var background = {}, manifest = {}; 5 | /**** wrapper (start) ****/ 6 | if (typeof self !== 'undefined' && self.port) { //Firefox 7 | background.send = function (id, data) { 8 | self.port.emit(id, data); 9 | }; 10 | background.receive = function (id, callback) { 11 | self.port.on(id, callback); 12 | }; 13 | manifest.url = self.options.base; 14 | self.port.on('detach', function () { 15 | try { 16 | capture.remove(); 17 | guide.remove(); 18 | notification.remove(); 19 | } 20 | catch (e) {} 21 | }); 22 | } 23 | else if (typeof safari !== 'undefined') { // Safari 24 | background.send = function (id, obj) { 25 | safari.self.tab.dispatchMessage('message', { 26 | id: id, 27 | data: obj 28 | }); 29 | }; 30 | background.receive = (function () { 31 | var callbacks = {}; 32 | safari.self.addEventListener('message', function (e) { 33 | if (callbacks[e.name]) { 34 | callbacks[e.name](e.message); 35 | } 36 | }, false); 37 | 38 | return function (id, callback) { 39 | callbacks[id] = callback; 40 | }; 41 | })(); 42 | manifest.url = safari.extension.baseURI; 43 | 44 | document.addEventListener('contextmenu', function (e) { 45 | safari.self.tab.setContextMenuEventUserInfo(event, { 46 | nodeName: e.target.nodeName, 47 | src: e.target.src 48 | }); 49 | }, false); 50 | } 51 | else { // Chrome 52 | background.send = function (id, data) { 53 | chrome.runtime.sendMessage({method: id, data: data}); 54 | }; 55 | background.receive = function (id, callback) { 56 | chrome.runtime.onMessage.addListener(function (request) { 57 | if (request.method === id) { 58 | callback(request.data); 59 | } 60 | }); 61 | }; 62 | manifest.url = chrome.extension.getURL('./'); 63 | } 64 | /**** wrapper (end) ****/ 65 | 66 | var capture = (function () { 67 | var box, _left, _top, left, top, width, height; 68 | 69 | function update (e) { 70 | left = (e.clientX > _left ? _left : e.clientX - 1); 71 | top = (e.clientY > _top ? _top : e.clientY - 1); 72 | width = Math.abs(e.clientX - _left); 73 | height = Math.abs(e.clientY - _top); 74 | box.style.left = left + 'px'; 75 | box.style.top = top + 'px'; 76 | box.style.width = width + 'px'; 77 | box.style.height = height + 'px'; 78 | } 79 | function remove () { 80 | background.send('capture', { 81 | left: left + 1, 82 | top: top + 1, 83 | width: width - 2, 84 | height: height - 2, 85 | devicePixelRatio: window.devicePixelRatio 86 | }); 87 | guide.remove(); 88 | capture.remove(); 89 | monitor.remove(); 90 | } 91 | function mousedown(e) { 92 | // prevent content selection on Firefox 93 | e.stopPropagation(); 94 | e.preventDefault(); 95 | box = document.createElement('div'); 96 | box.setAttribute('class', 'itrisearch-box'); 97 | 98 | _left = e.clientX; 99 | _top = e.clientY; 100 | 101 | document.addEventListener('mousemove', update, false); 102 | document.addEventListener('mouseup', remove, false); 103 | document.body.appendChild(box); 104 | } 105 | 106 | return { 107 | install: function () { 108 | document.addEventListener('mousedown', mousedown, false); 109 | }, 110 | remove: function () { 111 | document.removeEventListener('mousedown', mousedown, false); 112 | document.removeEventListener('mousemove', update, false); 113 | document.removeEventListener('mouseup', remove, false); 114 | if (box && box.parentNode) { 115 | box.parentNode.removeChild(box); 116 | } 117 | } 118 | }; 119 | })(); 120 | 121 | var guide = (function () { 122 | var guide1, guide2, guide3; 123 | function position (left, top) { 124 | guide1.style.width = left + 'px'; 125 | guide2.style.height = top + 'px'; 126 | } 127 | function update (e) { 128 | position(e.clientX, e.clientY); 129 | } 130 | return { 131 | install: function () { 132 | guide1 = document.createElement('div'); 133 | guide2 = document.createElement('div'); 134 | guide3 = document.createElement('div'); 135 | guide1.setAttribute('class', 'itrisearch-guide-1'); 136 | guide2.setAttribute('class', 'itrisearch-guide-2'); 137 | guide3.setAttribute('class', 'itrisearch-guide-3'); 138 | document.body.appendChild(guide3); 139 | document.body.appendChild(guide1); 140 | document.body.appendChild(guide2); 141 | document.addEventListener('mousemove', update, false); 142 | }, 143 | remove: function () { 144 | document.removeEventListener('mousemove', update, false); 145 | if (guide1 && guide1.parentNode) { 146 | guide1.parentNode.removeChild(guide1); 147 | } 148 | if (guide2 && guide2.parentNode) { 149 | guide2.parentNode.removeChild(guide2); 150 | } 151 | if (guide3 && guide3.parentNode) { 152 | guide3.parentNode.removeChild(guide3); 153 | } 154 | capture.remove(); 155 | } 156 | }; 157 | })(); 158 | 159 | var monitor = (function () { 160 | function keydown (e) { 161 | if (e.keyCode === 27) { 162 | guide.remove(); 163 | capture.remove(); 164 | monitor.remove(); 165 | } 166 | } 167 | return { 168 | install: function () { 169 | window.addEventListener('keydown', keydown, false); 170 | }, 171 | remove: function () { 172 | window.removeEventListener('keydown', keydown, false); 173 | } 174 | }; 175 | })(); 176 | 177 | var notification = (function () { 178 | var box; 179 | return { 180 | install: function () { 181 | box = document.createElement('div'); 182 | box.setAttribute('class', 'itrisearch-notification'); 183 | var span = document.createElement('span'); 184 | var button = document.createElement('button'); 185 | button.setAttribute('type', 'close'); 186 | button.addEventListener('click', function () { 187 | notification.hide(); 188 | }); 189 | box.appendChild(span); 190 | box.appendChild(button); 191 | document.body.appendChild(box); 192 | }, 193 | display: function (msg) { 194 | if (!box) { 195 | this.install(); 196 | } 197 | box.style.display = 'block'; 198 | box.querySelector('span').textContent = msg; 199 | }, 200 | hide: function () { 201 | box.style.display = 'none'; 202 | }, 203 | remove: function () { 204 | if (box && box.parentNode) { 205 | box.parentNode.removeChild(box); 206 | } 207 | } 208 | }; 209 | })(); 210 | 211 | background.receive('notify', function (msg) { 212 | if (msg) { 213 | notification.display(msg); 214 | } 215 | else { 216 | notification.hide(); 217 | } 218 | }); 219 | 220 | background.receive('capture', function () { 221 | guide.install(); 222 | capture.install(); 223 | monitor.install(); 224 | }); 225 | -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/128.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/16.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/24.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/256.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/32.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/48.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/builds/unpacked/chrome/data/icons/64.png -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/lib/chrome/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/lib/chrome/chrome.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = {}; 4 | 5 | if (!Promise.defer) { 6 | Promise.defer = function () { 7 | var deferred = {}; 8 | var promise = new Promise(function (resolve, reject) { 9 | deferred.resolve = resolve; 10 | deferred.reject = reject; 11 | }); 12 | deferred.promise = promise; 13 | return deferred; 14 | }; 15 | } 16 | app.Promise = Promise; 17 | 18 | app.storage = { 19 | read: function (id) { 20 | return localStorage[id] || null; 21 | }, 22 | write: function (id, data) { 23 | localStorage[id] = data + ''; 24 | } 25 | }; 26 | 27 | app.inject = { 28 | send: function (id, data, global) { 29 | if (global) { 30 | chrome.tabs.query({}, function (tabs) { 31 | tabs.forEach(function (tab) { 32 | chrome.tabs.sendMessage(tab.id, {method: id, data: data}, function () {}); 33 | }); 34 | }); 35 | } 36 | else if ('id' in this && 'windowId' in this) { 37 | chrome.tabs.sendMessage(this.id, {method: id, data: data}, function () {}); 38 | } 39 | else { 40 | chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { 41 | tabs.forEach(function (tab) { 42 | chrome.tabs.sendMessage(tab.id, {method: id, data: data}, function () {}); 43 | }); 44 | }); 45 | } 46 | }, 47 | receive: function (id, callback) { 48 | chrome.runtime.onMessage.addListener(function (message, sender) { 49 | if (message.method === id && sender.tab && sender.tab.url.indexOf('http') === 0) { 50 | callback.call(sender.tab, message.data); 51 | } 52 | }); 53 | } 54 | }; 55 | 56 | app.tab = { 57 | open: function (url) { 58 | chrome.tabs.create({ 59 | url: url 60 | }); 61 | } 62 | }; 63 | 64 | app.context_menu = { 65 | create: function (title, img, type, callback) { 66 | chrome.contextMenus.create({ 67 | 'type': 'normal', 68 | 'title': title, 69 | 'contexts': [type === 'img' ? 'image' : 'page'], 70 | 'onclick': function (info) { 71 | callback(info.srcUrl); 72 | } 73 | }); 74 | } 75 | }; 76 | 77 | app.version = function () { 78 | return chrome[chrome.runtime && chrome.runtime.getManifest ? 'runtime' : 'extension'].getManifest().version; 79 | }; 80 | 81 | app.timer = window; 82 | 83 | app.screenshot = function (left, top, width, height, devicePixelRatio) { 84 | var d = Promise.defer(); 85 | left = left * devicePixelRatio; 86 | top = top * devicePixelRatio; 87 | width = width * devicePixelRatio; 88 | height = height * devicePixelRatio; 89 | 90 | chrome.tabs.query({ 91 | active: true, 92 | currentWindow: true 93 | }, function (tab) { 94 | chrome.tabs.captureVisibleTab(tab.windowId, {format: 'png'}, function (dataUrl) { 95 | var canvas = document.createElement('canvas'); 96 | var ctx = canvas.getContext('2d'); 97 | var img = new Image(); 98 | img.onload = function () { 99 | canvas.width = width || img.width; 100 | canvas.height = height || img.height; 101 | if (width && height) { 102 | ctx.drawImage(img, left, top, width, height, 0, 0, width, height); 103 | } 104 | else { 105 | ctx.drawImage(img, 0, 0); 106 | } 107 | canvas.toBlob(function (blob) { 108 | d.resolve(blob); 109 | }); 110 | }; 111 | img.src = dataUrl; 112 | }); 113 | }); 114 | return d.promise; 115 | }; 116 | 117 | app.FormData = window.FormData; 118 | app.XMLHttpRequest = window.XMLHttpRequest; 119 | -------------------------------------------------------------------------------- /old/builds/unpacked/chrome/lib/chrome/toBlob.js: -------------------------------------------------------------------------------- 1 | if (!HTMLCanvasElement.prototype.toBlob) { 2 | Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { 3 | value: function (callback, type, quality) { 4 | 5 | var binStr = atob( this.toDataURL(type, quality).split(',')[1] ), 6 | len = binStr.length, 7 | arr = new Uint8Array(len); 8 | 9 | for (var i=0; i" 12 | ], 13 | "web_accessible_resources": [], 14 | "background": { 15 | "page": "lib/chrome/background.html" 16 | }, 17 | "content_scripts": [ 18 | { 19 | "matches": [""], 20 | "css": ["data/content_script/inject.css"], 21 | "js": ["data/content_script/inject.js"], 22 | "run_at": "document_start", 23 | "all_frames": false 24 | } 25 | ], 26 | "homepage_url": "http://mybrowseraddon.com/tineye.html", 27 | "icons": { 28 | "16": "data/icons/16.png", 29 | "48": "data/icons/48.png", 30 | "128": "data/icons/128.png" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /old/drawings/Screen Shot 2015-04-08 at 15.27.30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/Screen Shot 2015-04-08 at 15.27.30.png -------------------------------------------------------------------------------- /old/drawings/chrome-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/chrome-screenshot.png -------------------------------------------------------------------------------- /old/drawings/large-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/large-tile.png -------------------------------------------------------------------------------- /old/drawings/large-tile.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/large-tile.xcf -------------------------------------------------------------------------------- /old/drawings/marquee.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/marquee.png -------------------------------------------------------------------------------- /old/drawings/marquee.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/marquee.xcf -------------------------------------------------------------------------------- /old/drawings/promo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/promo.png -------------------------------------------------------------------------------- /old/drawings/promo.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/promo.xcf -------------------------------------------------------------------------------- /old/drawings/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/screenshot.png -------------------------------------------------------------------------------- /old/drawings/screenshot.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/screenshot.xcf -------------------------------------------------------------------------------- /old/drawings/small-tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/small-tile.png -------------------------------------------------------------------------------- /old/drawings/small-tile.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/drawings/small-tile.xcf -------------------------------------------------------------------------------- /old/gulpfile.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var gulp = require('gulp'); 4 | var change = require('gulp-change'); 5 | var babel = require('gulp-babel'); 6 | var gulpif = require('gulp-if'); 7 | var gulpFilter = require('gulp-filter'); 8 | var shell = require('gulp-shell'); 9 | var wait = require('gulp-wait'); 10 | var clean = require('gulp-clean'); 11 | var zip = require('gulp-zip'); 12 | var rename = require('gulp-rename'); 13 | var util = require('gulp-util'); 14 | var runSequence = require('run-sequence'); 15 | 16 | /* clean */ 17 | gulp.task('clean', function () { 18 | return gulp.src([ 19 | 'builds/unpacked/chrome/*', 20 | 'builds/unpacked/firefox/*', 21 | ], {read: false}) 22 | .pipe(clean()); 23 | }); 24 | /* chrome build */ 25 | gulp.task('chrome-build', function () { 26 | gulp.src([ 27 | 'src/**/*' 28 | ]) 29 | .pipe(gulpFilter(function (f) { 30 | if (f.relative.indexOf('.DS_Store') !== -1 || f.relative.indexOf('Thumbs.db') !== -1) { 31 | return false; 32 | } 33 | if (f.relative.indexOf('firefox') !== -1 && f.relative.indexOf('firefox.png') === -1) { 34 | return false; 35 | } 36 | if (f.path.indexOf('/locale') !== -1) { 37 | return false; 38 | } 39 | if (f.relative.indexOf('safari') !== -1) { 40 | return false; 41 | } 42 | if (f.relative.split('/').length === 1) { 43 | return f.relative === 'manifest.json' ? true : false; 44 | } 45 | return true; 46 | })) 47 | .pipe(gulpif(function (f) { 48 | return f.path.indexOf('.js') !== -1 && f.path.indexOf('.json') === -1; 49 | }, change(function (content) { 50 | return content.replace(/\/\*\* wrapper[\s\S]*\\*\*\*\//m, ''); 51 | }))) 52 | .pipe(gulpif(function (f) { 53 | return f.path.indexOf('.html') !== -1; 54 | }, change(function (content) { 55 | return content.replace(/.*shadow_index\.js.*/, ' \n '); 56 | }))) 57 | .pipe(gulp.dest('builds/unpacked/chrome')) 58 | .pipe(zip('chrome.zip')) 59 | .pipe(gulp.dest('builds/packed')); 60 | }); 61 | gulp.task('chrome-install', function () { 62 | gulp.src('') 63 | .pipe(wait(1000)) 64 | .pipe(shell([ 65 | '"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --load-and-launch-app=`pwd` &' 66 | ], { 67 | cwd: './builds/unpacked/chrome' 68 | })); 69 | }); 70 | 71 | /* firefox build */ 72 | gulp.task('firefox-build', function () { 73 | gulp.src([ 74 | 'src/**/*' 75 | ]) 76 | .pipe(gulpFilter(function (f) { 77 | if (f.relative.indexOf('.DS_Store') !== -1 || f.relative.indexOf('Thumbs.db') !== -1) { 78 | return false; 79 | } 80 | if (f.path.indexOf('_locales') !== -1) { 81 | return false; 82 | } 83 | if (f.relative.indexOf('chrome') !== -1 && 84 | f.relative !== 'chrome.manifest' && 85 | f.relative.indexOf('chrome.png') === -1 && 86 | f.relative.indexOf('firefox/chrome') === -1 87 | ) { 88 | return false; 89 | } 90 | if (f.relative.indexOf('shadow_index.js') !== -1) { 91 | return false; 92 | } 93 | if (f.relative.indexOf('safari') !== -1) { 94 | return false; 95 | } 96 | if (f.relative.split('/').length === 1) { 97 | return ['package.json', 'chrome.manifest'].indexOf(f.relative) !== -1; 98 | } 99 | return true; 100 | })) 101 | .pipe(gulpif(function (f) { 102 | return f.path.indexOf('.html') !== -1; 103 | }, change(function (content) { 104 | return content.replace(/\n.*shadow_index\.js.*/, ''); 105 | }))) 106 | .pipe(gulp.dest('builds/unpacked/firefox')); 107 | }); 108 | /* firefox pack */ 109 | gulp.task('firefox-pack', function () { 110 | gulp.src('') 111 | .pipe(wait(1000)) 112 | .pipe(shell([ 113 | 'jpm xpi', 114 | 'mv *.xpi ../../packed/firefox.xpi', 115 | 'jpm post --post-url http://localhost:8888/' 116 | ], { 117 | cwd: './builds/unpacked/firefox' 118 | })) 119 | .pipe(shell([ 120 | 'zip firefox.xpi icon.png icon64.png', 121 | ], { 122 | cwd: './builds/packed' 123 | })); 124 | }); 125 | /* */ 126 | gulp.task('chrome', function (callback) { 127 | runSequence('clean', 'chrome-build', 'chrome-install', callback); 128 | }); 129 | gulp.task('firefox', function (callback) { 130 | runSequence('clean', 'firefox-build', 'firefox-pack', callback); 131 | }); 132 | -------------------------------------------------------------------------------- /old/src/Icon-64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/Icon-64.png -------------------------------------------------------------------------------- /old/src/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Author 6 | Jeremy Schomery 7 | Builder Version 8 | 10600.4.10.7 9 | CFBundleDisplayName 10 | Reverse Image Search for TinEye 11 | CFBundleIdentifier 12 | com.mybrowseraddon.tineye 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleShortVersionString 16 | 0.1.0 17 | CFBundleVersion 18 | 1 19 | Chrome 20 | 21 | Database Quota 22 | 5242880 23 | Global Page 24 | lib/safari/background.html 25 | 26 | Content 27 | 28 | Scripts 29 | 30 | Start 31 | 32 | data/content_script/inject.js 33 | 34 | 35 | Stylesheets 36 | 37 | data/content_script/inject.css 38 | 39 | 40 | Description 41 | is a powerful reverse image search tool built on top of TinEye engine 42 | DeveloperIdentifier 43 | 2C3W8RZ8ND 44 | ExtensionInfoDictionaryVersion 45 | 1.0 46 | Permissions 47 | 48 | Website Access 49 | 50 | Include Secure Pages 51 | 52 | Level 53 | All 54 | 55 | 56 | Update Manifest URL 57 | https://raw.githubusercontent.com/schomery/easy-screenshot/master/src.safariextension/update.plist 58 | Website 59 | http://mybrowseraddon.com/tineye.html 60 | 61 | 62 | -------------------------------------------------------------------------------- /old/src/data/content_script/inject.css: -------------------------------------------------------------------------------- 1 | .itrisearch-box { 2 | box-sizing: border-box; 3 | position: fixed; 4 | z-index: 2147483646; 5 | border: gray 1px dotted; 6 | box-shadow: 0 0 0 50000px rgba(0, 0, 0, 0.2); 7 | } 8 | .itrisearch-box:before { 9 | content: ''; 10 | display: block; 11 | width: calc(100% + 20px); 12 | height: calc(100% + 20px); 13 | margin-left: -10px; 14 | margin-top: -10px; 15 | cursor: crosshair; 16 | } 17 | 18 | .itrisearch-guide-1, 19 | .itrisearch-guide-2 { 20 | box-sizing: border-box; 21 | position: fixed; 22 | z-index: 2147483646; 23 | } 24 | .itrisearch-guide-1 { 25 | border-right: dashed 1px gray; 26 | top: 0; 27 | left: 0; 28 | height: 100%; 29 | } 30 | .itrisearch-guide-2 { 31 | border-bottom: dashed 1px gray; 32 | top: 0; 33 | width: 100%; 34 | } 35 | .itrisearch-guide-3 { 36 | z-index: 2147483645; 37 | position: fixed; 38 | top: 0; 39 | left: 0; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | 44 | .itrisearch-notification { 45 | position: fixed; 46 | z-index: 2147483646; 47 | right: 20px; 48 | bottom: 20px; 49 | width: 250px; 50 | height: 40px; 51 | line-height: 40px; 52 | background-color: #FFF; 53 | border: solid 2px gray; 54 | padding: 0 5px; 55 | box-shadow: 1px 1px 5px #aaa; 56 | color: #222; 57 | font-family: arial,sans-serif !important; 58 | font-size: 12px !important; 59 | } 60 | .itrisearch-notification button { 61 | position: absolute; 62 | top: 2px; 63 | right: 2px; 64 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAAilBMVEVEREBEREBEREBEREBEREBEREBEREAlJSMSEhE3NzQAAAAAAAAQEA8BAQEAAAAAAAAAAAAAAAAvLywBAQEAAAAAAAAcHBoAAAAAAAAAAAAAAAAAAAAyMi8AAAAAAAABAQELCwo4ODUAAAAAAAAWFhUsLCkKCgkDAwIAAAAAAAAtLSoCAgIHBwcAAACgX5xYAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAXEYAAFxGARSUQ0EAAAAHdElNRQffBAgGGDS4qporAAAAG0lEQVQI12NgZGRgAGJGMGBggJAMCA5YDsQEAAJ2AB0Q4n77AAAAAElFTkSuQmCC) center center no-repeat; 65 | width: 12px; 66 | height: 12px; 67 | margin: 0; 68 | padding: 0; 69 | border: 0; 70 | cursor: pointer; 71 | opacity: 0.5; 72 | } 73 | .itrisearch-notification button:hover { 74 | opacity: 1.0; 75 | } 76 | -------------------------------------------------------------------------------- /old/src/data/content_script/inject.js: -------------------------------------------------------------------------------- 1 | /* global self, safari*/ 2 | 'use strict'; 3 | 4 | var background = {}, manifest = {}; 5 | /**** wrapper (start) ****/ 6 | if (typeof self !== 'undefined' && self.port) { //Firefox 7 | background.send = function (id, data) { 8 | self.port.emit(id, data); 9 | }; 10 | background.receive = function (id, callback) { 11 | self.port.on(id, callback); 12 | }; 13 | manifest.url = self.options.base; 14 | self.port.on('detach', function () { 15 | try { 16 | capture.remove(); 17 | guide.remove(); 18 | notification.remove(); 19 | } 20 | catch (e) {} 21 | }); 22 | } 23 | else if (typeof safari !== 'undefined') { // Safari 24 | background.send = function (id, obj) { 25 | safari.self.tab.dispatchMessage('message', { 26 | id: id, 27 | data: obj 28 | }); 29 | }; 30 | background.receive = (function () { 31 | var callbacks = {}; 32 | safari.self.addEventListener('message', function (e) { 33 | if (callbacks[e.name]) { 34 | callbacks[e.name](e.message); 35 | } 36 | }, false); 37 | 38 | return function (id, callback) { 39 | callbacks[id] = callback; 40 | }; 41 | })(); 42 | manifest.url = safari.extension.baseURI; 43 | 44 | document.addEventListener('contextmenu', function (e) { 45 | safari.self.tab.setContextMenuEventUserInfo(event, { 46 | nodeName: e.target.nodeName, 47 | src: e.target.src 48 | }); 49 | }, false); 50 | } 51 | else { // Chrome 52 | background.send = function (id, data) { 53 | chrome.runtime.sendMessage({method: id, data: data}); 54 | }; 55 | background.receive = function (id, callback) { 56 | chrome.runtime.onMessage.addListener(function (request) { 57 | if (request.method === id) { 58 | callback(request.data); 59 | } 60 | }); 61 | }; 62 | manifest.url = chrome.extension.getURL('./'); 63 | } 64 | /**** wrapper (end) ****/ 65 | 66 | var capture = (function () { 67 | var box, _left, _top, left, top, width, height; 68 | 69 | function update (e) { 70 | left = (e.clientX > _left ? _left : e.clientX - 1); 71 | top = (e.clientY > _top ? _top : e.clientY - 1); 72 | width = Math.abs(e.clientX - _left); 73 | height = Math.abs(e.clientY - _top); 74 | box.style.left = left + 'px'; 75 | box.style.top = top + 'px'; 76 | box.style.width = width + 'px'; 77 | box.style.height = height + 'px'; 78 | } 79 | function remove () { 80 | background.send('capture', { 81 | left: left + 1, 82 | top: top + 1, 83 | width: width - 2, 84 | height: height - 2, 85 | devicePixelRatio: window.devicePixelRatio 86 | }); 87 | guide.remove(); 88 | capture.remove(); 89 | monitor.remove(); 90 | } 91 | function mousedown(e) { 92 | // prevent content selection on Firefox 93 | e.stopPropagation(); 94 | e.preventDefault(); 95 | box = document.createElement('div'); 96 | box.setAttribute('class', 'itrisearch-box'); 97 | 98 | _left = e.clientX; 99 | _top = e.clientY; 100 | 101 | document.addEventListener('mousemove', update, false); 102 | document.addEventListener('mouseup', remove, false); 103 | document.body.appendChild(box); 104 | } 105 | 106 | return { 107 | install: function () { 108 | document.addEventListener('mousedown', mousedown, false); 109 | }, 110 | remove: function () { 111 | document.removeEventListener('mousedown', mousedown, false); 112 | document.removeEventListener('mousemove', update, false); 113 | document.removeEventListener('mouseup', remove, false); 114 | if (box && box.parentNode) { 115 | box.parentNode.removeChild(box); 116 | } 117 | } 118 | }; 119 | })(); 120 | 121 | var guide = (function () { 122 | var guide1, guide2, guide3; 123 | function position (left, top) { 124 | guide1.style.width = left + 'px'; 125 | guide2.style.height = top + 'px'; 126 | } 127 | function update (e) { 128 | position(e.clientX, e.clientY); 129 | } 130 | return { 131 | install: function () { 132 | guide1 = document.createElement('div'); 133 | guide2 = document.createElement('div'); 134 | guide3 = document.createElement('div'); 135 | guide1.setAttribute('class', 'itrisearch-guide-1'); 136 | guide2.setAttribute('class', 'itrisearch-guide-2'); 137 | guide3.setAttribute('class', 'itrisearch-guide-3'); 138 | document.body.appendChild(guide3); 139 | document.body.appendChild(guide1); 140 | document.body.appendChild(guide2); 141 | document.addEventListener('mousemove', update, false); 142 | }, 143 | remove: function () { 144 | document.removeEventListener('mousemove', update, false); 145 | if (guide1 && guide1.parentNode) { 146 | guide1.parentNode.removeChild(guide1); 147 | } 148 | if (guide2 && guide2.parentNode) { 149 | guide2.parentNode.removeChild(guide2); 150 | } 151 | if (guide3 && guide3.parentNode) { 152 | guide3.parentNode.removeChild(guide3); 153 | } 154 | capture.remove(); 155 | } 156 | }; 157 | })(); 158 | 159 | var monitor = (function () { 160 | function keydown (e) { 161 | if (e.keyCode === 27) { 162 | guide.remove(); 163 | capture.remove(); 164 | monitor.remove(); 165 | } 166 | } 167 | return { 168 | install: function () { 169 | window.addEventListener('keydown', keydown, false); 170 | }, 171 | remove: function () { 172 | window.removeEventListener('keydown', keydown, false); 173 | } 174 | }; 175 | })(); 176 | 177 | var notification = (function () { 178 | var box; 179 | return { 180 | install: function () { 181 | box = document.createElement('div'); 182 | box.setAttribute('class', 'itrisearch-notification'); 183 | var span = document.createElement('span'); 184 | var button = document.createElement('button'); 185 | button.setAttribute('type', 'close'); 186 | button.addEventListener('click', function () { 187 | notification.hide(); 188 | }); 189 | box.appendChild(span); 190 | box.appendChild(button); 191 | document.body.appendChild(box); 192 | }, 193 | display: function (msg) { 194 | if (!box) { 195 | this.install(); 196 | } 197 | box.style.display = 'block'; 198 | box.querySelector('span').textContent = msg; 199 | }, 200 | hide: function () { 201 | box.style.display = 'none'; 202 | }, 203 | remove: function () { 204 | if (box && box.parentNode) { 205 | box.parentNode.removeChild(box); 206 | } 207 | } 208 | }; 209 | })(); 210 | 211 | background.receive('notify', function (msg) { 212 | if (msg) { 213 | notification.display(msg); 214 | } 215 | else { 216 | notification.hide(); 217 | } 218 | }); 219 | 220 | background.receive('capture', function () { 221 | guide.install(); 222 | capture.install(); 223 | monitor.install(); 224 | }); 225 | -------------------------------------------------------------------------------- /old/src/data/firefox/chrome.js: -------------------------------------------------------------------------------- 1 | /* globals content, addMessageListener, removeMessageListener, sendAsyncMessage */ 2 | 'use strict'; 3 | 4 | (function (observers) { 5 | var active = true; 6 | var id = 'itrisearch'; 7 | 8 | function connect (obj) { 9 | sendAsyncMessage(id + '-connect', obj); 10 | } 11 | 12 | observers.screenshot = function (e) { 13 | var thumbnail = content.document.createElementNS('http://www.w3.org/1999/xhtml', 'canvas'); 14 | var left = e.data.left || 0; 15 | var top = e.data.top || 0; 16 | var width = e.data.width || content.innerWidth; 17 | var height = e.data.height || content.innerHeight; 18 | thumbnail.width = width; 19 | thumbnail.height = height; 20 | var ctx = thumbnail.getContext('2d'); 21 | ctx.drawWindow(content, content.scrollX + left, content.scrollY + top, width, height, '#fff'); 22 | thumbnail.toBlob(function (blob) { 23 | connect(blob); 24 | }); 25 | }; 26 | 27 | function detach () { 28 | for (var name in observers) { 29 | removeMessageListener(id + '-' + name, observers[name]); 30 | } 31 | removeMessageListener(id + '-detach', detach); 32 | active = false; 33 | } 34 | if (active) { 35 | for (var name in observers) { 36 | addMessageListener(id + '-' + name, observers[name]); 37 | } 38 | addMessageListener(id + '-detach', detach); 39 | } 40 | })({}); 41 | -------------------------------------------------------------------------------- /old/src/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/128.png -------------------------------------------------------------------------------- /old/src/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/16.png -------------------------------------------------------------------------------- /old/src/data/icons/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/24.png -------------------------------------------------------------------------------- /old/src/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/256.png -------------------------------------------------------------------------------- /old/src/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/32.png -------------------------------------------------------------------------------- /old/src/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/48.png -------------------------------------------------------------------------------- /old/src/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/old/src/data/icons/64.png -------------------------------------------------------------------------------- /old/src/lib/chrome/background.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /old/src/lib/chrome/chrome.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var app = {}; 4 | 5 | if (!Promise.defer) { 6 | Promise.defer = function () { 7 | var deferred = {}; 8 | var promise = new Promise(function (resolve, reject) { 9 | deferred.resolve = resolve; 10 | deferred.reject = reject; 11 | }); 12 | deferred.promise = promise; 13 | return deferred; 14 | }; 15 | } 16 | app.Promise = Promise; 17 | 18 | app.storage = { 19 | read: function (id) { 20 | return localStorage[id] || null; 21 | }, 22 | write: function (id, data) { 23 | localStorage[id] = data + ''; 24 | } 25 | }; 26 | 27 | app.inject = { 28 | send: function (id, data, global) { 29 | if (global) { 30 | chrome.tabs.query({}, function (tabs) { 31 | tabs.forEach(function (tab) { 32 | chrome.tabs.sendMessage(tab.id, {method: id, data: data}, function () {}); 33 | }); 34 | }); 35 | } 36 | else if ('id' in this && 'windowId' in this) { 37 | chrome.tabs.sendMessage(this.id, {method: id, data: data}, function () {}); 38 | } 39 | else { 40 | chrome.tabs.query({active: true, currentWindow: true}, function (tabs) { 41 | tabs.forEach(function (tab) { 42 | chrome.tabs.sendMessage(tab.id, {method: id, data: data}, function () {}); 43 | }); 44 | }); 45 | } 46 | }, 47 | receive: function (id, callback) { 48 | chrome.runtime.onMessage.addListener(function (message, sender) { 49 | if (message.method === id && sender.tab && sender.tab.url.indexOf('http') === 0) { 50 | callback.call(sender.tab, message.data); 51 | } 52 | }); 53 | } 54 | }; 55 | 56 | app.tab = { 57 | open: function (url) { 58 | chrome.tabs.create({ 59 | url: url 60 | }); 61 | } 62 | }; 63 | 64 | app.context_menu = { 65 | create: function (title, img, type, callback) { 66 | chrome.contextMenus.create({ 67 | 'type': 'normal', 68 | 'title': title, 69 | 'contexts': [type === 'img' ? 'image' : 'page'], 70 | 'onclick': function (info) { 71 | callback(info.srcUrl); 72 | } 73 | }); 74 | } 75 | }; 76 | 77 | app.version = function () { 78 | return chrome[chrome.runtime && chrome.runtime.getManifest ? 'runtime' : 'extension'].getManifest().version; 79 | }; 80 | 81 | app.timer = window; 82 | 83 | app.screenshot = function (left, top, width, height, devicePixelRatio) { 84 | var d = Promise.defer(); 85 | left = left * devicePixelRatio; 86 | top = top * devicePixelRatio; 87 | width = width * devicePixelRatio; 88 | height = height * devicePixelRatio; 89 | 90 | chrome.tabs.query({ 91 | active: true, 92 | currentWindow: true 93 | }, function (tab) { 94 | chrome.tabs.captureVisibleTab(tab.windowId, {format: 'png'}, function (dataUrl) { 95 | var canvas = document.createElement('canvas'); 96 | var ctx = canvas.getContext('2d'); 97 | var img = new Image(); 98 | img.onload = function () { 99 | canvas.width = width || img.width; 100 | canvas.height = height || img.height; 101 | if (width && height) { 102 | ctx.drawImage(img, left, top, width, height, 0, 0, width, height); 103 | } 104 | else { 105 | ctx.drawImage(img, 0, 0); 106 | } 107 | canvas.toBlob(function (blob) { 108 | d.resolve(blob); 109 | }); 110 | }; 111 | img.src = dataUrl; 112 | }); 113 | }); 114 | return d.promise; 115 | }; 116 | 117 | app.FormData = window.FormData; 118 | app.XMLHttpRequest = window.XMLHttpRequest; 119 | -------------------------------------------------------------------------------- /old/src/lib/chrome/toBlob.js: -------------------------------------------------------------------------------- 1 | if (!HTMLCanvasElement.prototype.toBlob) { 2 | Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { 3 | value: function (callback, type, quality) { 4 | 5 | var binStr = atob( this.toDataURL(type, quality).split(',')[1] ), 6 | len = binStr.length, 7 | arr = new Uint8Array(len); 8 | 9 | for (var i=0; i 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /old/src/lib/safari/q.js: -------------------------------------------------------------------------------- 1 | // vim:ts=4:sts=4:sw=4: 2 | /*! 3 | * 4 | * Copyright 2009-2012 Kris Kowal under the terms of the MIT 5 | * license found at http://github.com/kriskowal/q/raw/master/LICENSE 6 | * 7 | * With parts by Tyler Close 8 | * Copyright 2007-2009 Tyler Close under the terms of the MIT X license found 9 | * at http://www.opensource.org/licenses/mit-license.html 10 | * Forked at ref_send.js version: 2009-05-11 11 | * 12 | * With parts by Mark Miller 13 | * Copyright (C) 2011 Google Inc. 14 | * 15 | * Licensed under the Apache License, Version 2.0 (the "License"); 16 | * you may not use this file except in compliance with the License. 17 | * You may obtain a copy of the License at 18 | * 19 | * http://www.apache.org/licenses/LICENSE-2.0 20 | * 21 | * Unless required by applicable law or agreed to in writing, software 22 | * distributed under the License is distributed on an "AS IS" BASIS, 23 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24 | * See the License for the specific language governing permissions and 25 | * limitations under the License. 26 | * 27 | */ 28 | 29 | (function (definition) { 30 | // Turn off strict mode for this function so we can assign to global.Q 31 | /* jshint strict: false */ 32 | 33 | // This file will function properly as a 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /old/src/lib/safari/safari.js: -------------------------------------------------------------------------------- 1 | /* globals Q, safari */ 2 | 3 | 'use strict'; 4 | 5 | var app = {}; 6 | 7 | app.Promise = Q.promise; 8 | app.Promise.defer = Q.defer; 9 | 10 | app.storage = { 11 | read: function (id) { 12 | return localStorage[id] || null; 13 | }, 14 | write: function (id, data) { 15 | localStorage[id] = data + ''; 16 | } 17 | }; 18 | 19 | app.button = (function () { 20 | var callback, 21 | toolbarItem = safari.extension.toolbarItems[0]; 22 | safari.application.addEventListener('command', function (e) { 23 | if (e.command === 'toolbarbutton' && callback) { 24 | app.popup.show(); 25 | } 26 | }, false); 27 | 28 | return { 29 | set label (val) { 30 | toolbarItem.toolTip = val; 31 | }, 32 | set badge (val) { 33 | toolbarItem.badge = (val ? val : '') + ''; 34 | } 35 | }; 36 | })(); 37 | 38 | app.tab = { 39 | open: function (url, inBackground, inCurrent) { 40 | if (inCurrent) { 41 | safari.application.activeBrowserWindow.activeTab.url = url; 42 | } 43 | else { 44 | safari.application.activeBrowserWindow.openTab(inBackground ? 'background' : 'foreground').url = url; 45 | } 46 | }, 47 | openOptions: function () { 48 | 49 | } 50 | }; 51 | 52 | app.version = function () { 53 | return safari.extension.displayVersion; 54 | }; 55 | 56 | app.timer = window; 57 | 58 | app.content_script = (function () { 59 | var callbacks = {}; 60 | safari.application.addEventListener('message', function (e) { 61 | if (callbacks[e.message.id]) { 62 | callbacks[e.message.id].call(e.target, e.message.data); 63 | } 64 | }, false); 65 | return { 66 | send: function (id, data, global) { 67 | if (global) { 68 | safari.application.browserWindows.forEach(function (browserWindow) { 69 | browserWindow.tabs.forEach(function (tab) { 70 | if (tab.page && tab.url.indexOf('http') === 0) { 71 | tab.page.dispatchMessage(id, data); 72 | } 73 | }); 74 | }); 75 | } 76 | else if ('page' in this) { 77 | this.page.dispatchMessage(id, data); 78 | } 79 | else { 80 | safari.application.activeBrowserWindow.activeTab.page.dispatchMessage(id, data); 81 | } 82 | }, 83 | receive: function (id, callback) { 84 | callbacks[id] = callback; 85 | } 86 | }; 87 | })(); 88 | 89 | app.context_menu = (function () { 90 | var onPage = []; 91 | 92 | safari.application.addEventListener('contextmenu', function (e) { 93 | onPage.forEach(function (arr, i) { 94 | if (arr[2] !== 'img' && e.userInfo.nodeName === 'IMG' || arr[2] === 'img' && e.userInfo.nodeName !== 'IMG') { 95 | return; 96 | } 97 | e.contextMenu.appendContextMenuItem('contextmenu' + i, arr[0], i + (e.userInfo.src || '')); 98 | }); 99 | }, false); 100 | safari.application.addEventListener('command', function (e) { 101 | onPage[+e.command.substr(0, 1)][3](e.command.substr(1)); 102 | }, false); 103 | 104 | return { 105 | create: function (label, img, type, callback) { 106 | onPage.push([label, img, type, callback]); 107 | } 108 | }; 109 | })(); 110 | 111 | app.screenshot = function (left, top, width, height, devicePixelRatio) { 112 | var d = app.Promise.defer(); 113 | 114 | left = left * devicePixelRatio; 115 | top = top * devicePixelRatio; 116 | width = width * devicePixelRatio; 117 | height = height * devicePixelRatio; 118 | 119 | var tab = safari.application.activeBrowserWindow.activeTab; 120 | tab.visibleContentsAsDataURL(function (dataUrl) { 121 | var canvas = document.createElement('canvas'); 122 | var ctx = canvas.getContext('2d'); 123 | var img = new Image(); 124 | img.onload = function () { 125 | canvas.width = width || img.width; 126 | canvas.height = height || img.height; 127 | if (width && height) { 128 | ctx.drawImage(img, left, top, width, height, 0, 0, width, height); 129 | } 130 | else { 131 | ctx.drawImage(img, 0, 0); 132 | } 133 | d.resolve(canvas); 134 | }; 135 | img.src = dataUrl; 136 | }); 137 | 138 | return d.promise; 139 | }; 140 | 141 | app.FormData = window.FormData; 142 | app.XMLHttpRequest = window.XMLHttpRequest; 143 | -------------------------------------------------------------------------------- /old/src/lib/safari/toBlob.js: -------------------------------------------------------------------------------- 1 | if (!HTMLCanvasElement.prototype.toBlob) { 2 | Object.defineProperty(HTMLCanvasElement.prototype, 'toBlob', { 3 | value: function (callback, type, quality) { 4 | 5 | var binStr = atob( this.toDataURL(type, quality).split(',')[1] ), 6 | len = binStr.length, 7 | arr = new Uint8Array(len); 8 | 9 | for (var i=0; i" 12 | ], 13 | "web_accessible_resources": [], 14 | "background": { 15 | "page": "lib/chrome/background.html" 16 | }, 17 | "content_scripts": [ 18 | { 19 | "matches": [""], 20 | "css": ["data/content_script/inject.css"], 21 | "js": ["data/content_script/inject.js"], 22 | "run_at": "document_start", 23 | "all_frames": false 24 | } 25 | ], 26 | "homepage_url": "http://mybrowseraddon.com/tineye.html", 27 | "icons": { 28 | "16": "data/icons/16.png", 29 | "48": "data/icons/48.png", 30 | "128": "data/icons/128.png" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /old/src/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Capture, TinEye Reverse Image Search", 3 | "name": "itrisearch", 4 | "description": "is a powerful capturing reverse image search tool built on top of TinEye engine.", 5 | "id": "jid0-iiiWfb12bgHj8iKloOou74fb6jh@jetpack", 6 | "license": "mozilla public license 1.1", 7 | "version": "0.1.1", 8 | "author": "Jeremy Schomery", 9 | "main": "./lib/common.js", 10 | "url": "http://mybrowseraddon.com/tineye.html", 11 | "permissions": { 12 | "private-browsing": true, 13 | "multiprocess": true 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /old/src/update.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Extension Updates 6 | 7 | 8 | CFBundleIdentifier 9 | com.add0n.template 10 | Developer Identifier 11 | KGRSEG9P68 12 | CFBundleVersion 13 | 1 14 | CFBundleShortVersionString 15 | 0.1.0 16 | URL 17 | https://github.com/schomery/easy-screenshot/blob/master/src.safariextension/executables/extension.safariextz?raw=true 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /v2/background.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | function onStart() { 4 | chrome.contextMenus.create({ 5 | 'id': 'search-link-google', 6 | 'title': 'Google Images (Image URL)', 7 | 'contexts': ['image'] 8 | }); 9 | chrome.contextMenus.create({ 10 | 'id': 'search-link-tineye', 11 | 'title': 'Tineye (Image URL)', 12 | 'contexts': ['image'] 13 | }); 14 | chrome.contextMenus.create({ 15 | 'id': 'capture-google', 16 | 'title': 'Google Images (Capture)', 17 | 'contexts': ['page'] 18 | }); 19 | // chrome.contextMenus.create({ 20 | // 'id': 'capture-tineye', 21 | // 'title': 'TinEye (Capture)', 22 | // 'contexts': ['page'] 23 | // }); 24 | chrome.contextMenus.create({ 25 | 'id': 'preview', 26 | 'title': 'Usage Preview', 27 | 'contexts': ['browser_action'] 28 | }); 29 | } 30 | chrome.runtime.onInstalled.addListener(onStart); 31 | chrome.runtime.onStartup.addListener(onStart); 32 | 33 | function notify(id, msg) { 34 | chrome.tabs.insertCSS(id, { 35 | file: 'data/inject/notify.css' 36 | }, () => { 37 | chrome.tabs.executeScript(id, { 38 | file: 'data/inject/notify.js' 39 | }, () => { 40 | chrome.tabs.executeScript(id, { 41 | code: msg ? `notify.display('${msg}');` : `notify.hide();` 42 | }); 43 | }); 44 | }); 45 | } 46 | 47 | const onClick = (info, tab) => { 48 | if (info.menuItemId === 'preview') { 49 | chrome.tabs.create({ 50 | url: 'https://www.youtube.com/watch?v=KiBNYb3yuSo' 51 | }); 52 | } 53 | else if (info.menuItemId.startsWith('capture-')) { 54 | chrome.tabs.insertCSS(tab.id, { 55 | file: 'data/inject/inject.css' 56 | }, () => { 57 | chrome.tabs.executeScript(tab.id, { 58 | code: `window.service = '${info.menuItemId.endsWith('tineye') ? 'TinEye' : 'Google'}';` 59 | }, () => { 60 | chrome.tabs.executeScript(tab.id, { 61 | file: 'data/inject/inject.js' 62 | }); 63 | }); 64 | }); 65 | } 66 | else if (info.menuItemId.startsWith('search-link-')) { 67 | const tineye = info.menuItemId.endsWith('tineye'); 68 | 69 | if (tineye) { 70 | chrome.tabs.create({ 71 | url: 'https://tineye.com/search/?pluginver=chrome-1.3.0&url=' + encodeURIComponent(info.srcUrl) 72 | }); 73 | } 74 | else { 75 | chrome.tabs.create({ 76 | url: 'https://www.google.com/searchbyimage?image_url=' + encodeURIComponent(info.srcUrl) 77 | }); 78 | } 79 | } 80 | }; 81 | 82 | chrome.contextMenus.onClicked.addListener(onClick); 83 | chrome.browserAction.onClicked.addListener(tab => { 84 | onClick({ 85 | menuItemId: 'capture-google' 86 | }, tab); 87 | }); 88 | 89 | function capture(request, sender) { 90 | const {devicePixelRatio, service = 'TinEye'} = request; 91 | 92 | let {left, top, width, height} = request; 93 | left *= devicePixelRatio; 94 | top *= devicePixelRatio; 95 | width *= devicePixelRatio; 96 | height *= devicePixelRatio; 97 | 98 | notify(sender.tab.id, `Uploading image to ${service}. Please wait ...`); 99 | 100 | chrome.tabs.captureVisibleTab(sender.tab.windowId, {format: 'png'}, dataUrl => { 101 | const canvas = document.createElement('canvas'); 102 | const ctx = canvas.getContext('2d'); 103 | const img = new Image(); 104 | img.onload = () => { 105 | canvas.width = width || img.width; 106 | canvas.height = height || img.height; 107 | if (width && height) { 108 | ctx.drawImage(img, left, top, width, height, 0, 0, width, height); 109 | } 110 | else { 111 | ctx.drawImage(img, 0, 0); 112 | } 113 | canvas.toBlob(blob => { 114 | const formData = new window.FormData(); 115 | formData.processData = false; 116 | formData.contentType = false; 117 | if (service === 'Google') { 118 | formData.append('encoded_image', blob, 'screenshot.png'); 119 | } 120 | else { 121 | formData.append('image', blob, 'screenshot.png'); 122 | } 123 | 124 | const req = new window.XMLHttpRequest(); 125 | 126 | req.onload = () => { 127 | chrome.tabs.create({ 128 | url: req.responseURL 129 | }); 130 | notify(sender.tab.id); 131 | }; 132 | req.onerror = e => notify(sender.tab.id, 'Failed! ' + (e.message || e)); 133 | if (service === 'Google') { 134 | req.open('POST', 'https://www.google.com/searchbyimage/upload', true); 135 | } 136 | else { 137 | req.open('POST', 'https://tineye.com/result_json/?token=', true); 138 | } 139 | req.send(formData); 140 | }); 141 | }; 142 | img.src = dataUrl; 143 | }); 144 | } 145 | 146 | chrome.runtime.onMessage.addListener(capture); 147 | 148 | /* FAQs & Feedback */ 149 | { 150 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 151 | if (navigator.webdriver !== true) { 152 | const page = getManifest().homepage_url; 153 | const {name, version} = getManifest(); 154 | onInstalled.addListener(({reason, previousVersion}) => { 155 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 156 | 'faqs': true, 157 | 'last-update': 0 158 | }, prefs => { 159 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 160 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 161 | if (doUpdate && previousVersion !== version) { 162 | tabs.query({active: true, currentWindow: true}, tbs => tabs.create({ 163 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 164 | active: reason === 'install', 165 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 166 | })); 167 | storage.local.set({'last-update': Date.now()}); 168 | } 169 | } 170 | })); 171 | }); 172 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /v2/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/128.png -------------------------------------------------------------------------------- /v2/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/16.png -------------------------------------------------------------------------------- /v2/data/icons/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/24.png -------------------------------------------------------------------------------- /v2/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/256.png -------------------------------------------------------------------------------- /v2/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/32.png -------------------------------------------------------------------------------- /v2/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/48.png -------------------------------------------------------------------------------- /v2/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v2/data/icons/64.png -------------------------------------------------------------------------------- /v2/data/inject/inject.css: -------------------------------------------------------------------------------- 1 | .itrisearch-box { 2 | box-sizing: border-box; 3 | position: fixed; 4 | z-index: 2147483646; 5 | border: gray 1px dotted; 6 | box-shadow: 0 0 0 50000px rgba(0, 0, 0, 0.2); 7 | } 8 | .itrisearch-box::before { 9 | content: ''; 10 | display: block; 11 | width: calc(100% + 20px); 12 | height: calc(100% + 20px); 13 | margin-left: -10px; 14 | margin-top: -10px; 15 | cursor: crosshair; 16 | } 17 | 18 | .itrisearch-guide-1, 19 | .itrisearch-guide-2 { 20 | box-sizing: border-box; 21 | position: fixed; 22 | z-index: 2147483646; 23 | } 24 | .itrisearch-guide-1 { 25 | border-right: dashed 1px gray; 26 | top: 0; 27 | left: 0; 28 | height: 100%; 29 | } 30 | .itrisearch-guide-2 { 31 | border-bottom: dashed 1px gray; 32 | top: 0; 33 | width: 100%; 34 | } 35 | .itrisearch-guide-3 { 36 | z-index: 2147483645; 37 | position: fixed; 38 | top: 0; 39 | left: 0; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | -------------------------------------------------------------------------------- /v2/data/inject/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var monitor = window.monitor; 4 | var capture = window.capture; 5 | var guide = window.guide; 6 | 7 | try { 8 | guide.remove(); 9 | capture.remove(); 10 | monitor.remove(); 11 | } 12 | catch (e) {} 13 | 14 | capture = (function() { 15 | var box, _left, _top, left, top, width, height; 16 | 17 | function update(e) { 18 | left = (e.clientX > _left ? _left : e.clientX - 1); 19 | top = (e.clientY > _top ? _top : e.clientY - 1); 20 | width = Math.abs(e.clientX - _left); 21 | height = Math.abs(e.clientY - _top); 22 | box.style.left = left + 'px'; 23 | box.style.top = top + 'px'; 24 | box.style.width = width + 'px'; 25 | box.style.height = height + 'px'; 26 | } 27 | function remove() { 28 | chrome.runtime.sendMessage({ 29 | method: 'captured', 30 | left: left + 1, 31 | top: top + 1, 32 | width: width - 2, 33 | height: height - 2, 34 | devicePixelRatio: window.devicePixelRatio, 35 | title: document.title, 36 | service: window.service // used by Reverse Image Search extension 37 | }); 38 | guide.remove(); 39 | capture.remove(); 40 | monitor.remove(); 41 | } 42 | function mousedown(e) { 43 | // prevent content selection on Firefox 44 | e.stopPropagation(); 45 | e.preventDefault(); 46 | box = document.createElement('div'); 47 | box.setAttribute('class', 'itrisearch-box'); 48 | 49 | _left = e.clientX; 50 | _top = e.clientY; 51 | 52 | document.addEventListener('mousemove', update, false); 53 | document.addEventListener('mouseup', remove, false); 54 | document.body.appendChild(box); 55 | } 56 | 57 | return { 58 | install: function() { 59 | document.addEventListener('mousedown', mousedown, false); 60 | }, 61 | remove: function() { 62 | document.removeEventListener('mousedown', mousedown, false); 63 | document.removeEventListener('mousemove', update, false); 64 | document.removeEventListener('mouseup', remove, false); 65 | if (box && box.parentNode) { 66 | box.parentNode.removeChild(box); 67 | } 68 | } 69 | }; 70 | })(); 71 | 72 | guide = (function() { 73 | var guide1, guide2, guide3; 74 | function position(left, top) { 75 | guide1.style.width = left + 'px'; 76 | guide2.style.height = top + 'px'; 77 | } 78 | function update(e) { 79 | position(e.clientX, e.clientY); 80 | } 81 | return { 82 | install: function() { 83 | guide1 = document.createElement('div'); 84 | guide2 = document.createElement('div'); 85 | guide3 = document.createElement('div'); 86 | guide1.setAttribute('class', 'itrisearch-guide-1'); 87 | guide2.setAttribute('class', 'itrisearch-guide-2'); 88 | guide3.setAttribute('class', 'itrisearch-guide-3'); 89 | document.body.appendChild(guide3); 90 | document.body.appendChild(guide1); 91 | document.body.appendChild(guide2); 92 | document.addEventListener('mousemove', update, false); 93 | }, 94 | remove: function() { 95 | document.removeEventListener('mousemove', update, false); 96 | if (guide1 && guide1.parentNode) { 97 | guide1.parentNode.removeChild(guide1); 98 | } 99 | if (guide2 && guide2.parentNode) { 100 | guide2.parentNode.removeChild(guide2); 101 | } 102 | if (guide3 && guide3.parentNode) { 103 | guide3.parentNode.removeChild(guide3); 104 | } 105 | capture.remove(); 106 | } 107 | }; 108 | })(); 109 | 110 | monitor = (function() { 111 | function keydown(e) { 112 | if (e.keyCode === 27) { 113 | guide.remove(); 114 | capture.remove(); 115 | monitor.remove(); 116 | } 117 | } 118 | return { 119 | install: function() { 120 | window.addEventListener('keydown', keydown, false); 121 | }, 122 | remove: function() { 123 | window.removeEventListener('keydown', keydown, false); 124 | } 125 | }; 126 | })(); 127 | 128 | guide.install(); 129 | capture.install(); 130 | monitor.install(); 131 | -------------------------------------------------------------------------------- /v2/data/inject/notify.css: -------------------------------------------------------------------------------- 1 | .itrisearch-notification { 2 | position: fixed; 3 | z-index: 2147483646; 4 | right: 20px; 5 | bottom: 20px; 6 | width: 250px; 7 | height: 40px; 8 | line-height: 40px; 9 | background-color: #FFF; 10 | border: solid 2px gray; 11 | padding: 0 5px; 12 | box-shadow: 1px 1px 5px #aaa; 13 | color: #222; 14 | font-family: arial,sans-serif !important; 15 | font-size: 12px !important; 16 | } 17 | .itrisearch-notification button { 18 | position: absolute; 19 | top: 2px; 20 | right: 2px; 21 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAAilBMVEVEREBEREBEREBEREBEREBEREBEREAlJSMSEhE3NzQAAAAAAAAQEA8BAQEAAAAAAAAAAAAAAAAvLywBAQEAAAAAAAAcHBoAAAAAAAAAAAAAAAAAAAAyMi8AAAAAAAABAQELCwo4ODUAAAAAAAAWFhUsLCkKCgkDAwIAAAAAAAAtLSoCAgIHBwcAAACgX5xYAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAXEYAAFxGARSUQ0EAAAAHdElNRQffBAgGGDS4qporAAAAG0lEQVQI12NgZGRgAGJGMGBggJAMCA5YDsQEAAJ2AB0Q4n77AAAAAElFTkSuQmCC) center center no-repeat; 22 | width: 12px; 23 | height: 12px; 24 | margin: 0; 25 | padding: 0; 26 | border: 0; 27 | cursor: pointer; 28 | opacity: 0.5; 29 | } 30 | .itrisearch-notification button:hover { 31 | opacity: 1.0; 32 | } 33 | -------------------------------------------------------------------------------- /v2/data/inject/notify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var notify = (function () { 4 | let box = document.querySelector('.itrisearch-notification'); 5 | return { 6 | install: function () { 7 | box = document.createElement('div'); 8 | box.setAttribute('class', 'itrisearch-notification'); 9 | const span = document.createElement('span'); 10 | const button = document.createElement('button'); 11 | button.setAttribute('type', 'close'); 12 | button.addEventListener('click', function () { 13 | notify.hide(); 14 | }); 15 | box.appendChild(span); 16 | box.appendChild(button); 17 | document.body.appendChild(box); 18 | }, 19 | display: function (msg) { 20 | if (!document.querySelector('.itrisearch-notification')) { 21 | this.install(); 22 | } 23 | box.style.display = 'block'; 24 | box.querySelector('span').textContent = msg; 25 | }, 26 | hide: function () { 27 | box.style.display = 'none'; 28 | }, 29 | remove: function () { 30 | if (box && box.parentNode) { 31 | box.parentNode.removeChild(box); 32 | box = null; 33 | } 34 | } 35 | }; 36 | })(); 37 | -------------------------------------------------------------------------------- /v2/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Reverse Image Search", 3 | "description": "a powerful capturing reverse image search tool built on top of TinEye/Google Images engines", 4 | "version": "0.2.3", 5 | "manifest_version": 2, 6 | "permissions": [ 7 | "storage", 8 | "", 9 | "contextMenus" 10 | ], 11 | "background":{ 12 | "persistent": false, 13 | "scripts":[ 14 | "background.js" 15 | ] 16 | }, 17 | "homepage_url": "https://add0n.com/reverse-image-search.html", 18 | "icons": { 19 | "16": "data/icons/16.png", 20 | "24": "data/icons/24.png", 21 | "32": "data/icons/32.png", 22 | "48": "data/icons/48.png", 23 | "64": "data/icons/64.png", 24 | "128": "data/icons/128.png", 25 | "256": "data/icons/256.png" 26 | }, 27 | "browser_action": {} 28 | } 29 | -------------------------------------------------------------------------------- /v3/_locales/bg/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "мощен инструмент за обратно търсене на изображения, изграден на базата на двигателите TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/de/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "uein leistungsfähiges Tool für die umgekehrte Bildsuche, das auf den Suchmaschinen von TinEye/Google Images aufbaut" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/el/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "ένα ισχυρό εργαλείο αντίστροφης αναζήτησης εικόνων που βασίζεται στις μηχανές TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/en/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "a powerful capturing reverse image search tool built on top of TinEye/Google Images engines" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/es/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "una poderosa herramienta de búsqueda inversa de imágenes construida sobre los motores TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/fr/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "un puissant outil de recherche d'images inversées par capture, construit sur la base des moteurs TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/it/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "un potente strumento di ricerca inversa delle immagini costruito sulla base dei motori TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/ja/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "TinEye/Google Imagesエンジンをベースに開発された強力なキャプチャーリバースイメージサーチツールです。" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/nl/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "een krachtig zoekprogramma voor omgekeerde afbeeldingen, gebouwd bovenop de TinEye/Google Afbeeldingen-engines" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/pt_BR/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "uma poderosa ferramenta de busca de imagens invertidas de captura construída sobre os motores TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/ru/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "мощный инструмент обратного поиска изображений, построенный на базе движков TinEye/Google Images" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/_locales/zh_CN/messages.json: -------------------------------------------------------------------------------- 1 | { 2 | "description": { 3 | "message": "一个建立在TinEye/Google图像引擎之上的强大的捕获反向图像搜索工具" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /v3/data/icons/128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/128.png -------------------------------------------------------------------------------- /v3/data/icons/16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/16.png -------------------------------------------------------------------------------- /v3/data/icons/24.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/24.png -------------------------------------------------------------------------------- /v3/data/icons/256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/256.png -------------------------------------------------------------------------------- /v3/data/icons/32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/32.png -------------------------------------------------------------------------------- /v3/data/icons/48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/48.png -------------------------------------------------------------------------------- /v3/data/icons/64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/schomery/reverse-image-search/ccafab4b2896ad0e012be609b3a655751cfec86c/v3/data/icons/64.png -------------------------------------------------------------------------------- /v3/data/inject/inject.css: -------------------------------------------------------------------------------- 1 | .itrisearch-box { 2 | box-sizing: border-box; 3 | position: fixed; 4 | z-index: 2147483646; 5 | border: gray 1px dotted; 6 | box-shadow: 0 0 0 50000px rgba(0, 0, 0, 0.2); 7 | } 8 | .itrisearch-box::before { 9 | content: ''; 10 | display: block; 11 | width: calc(100% + 20px); 12 | height: calc(100% + 20px); 13 | margin-left: -10px; 14 | margin-top: -10px; 15 | cursor: crosshair; 16 | } 17 | 18 | .itrisearch-guide-1, 19 | .itrisearch-guide-2 { 20 | box-sizing: border-box; 21 | position: fixed; 22 | z-index: 2147483646; 23 | } 24 | .itrisearch-guide-1 { 25 | border-right: dashed 1px gray; 26 | top: 0; 27 | left: 0; 28 | height: 100%; 29 | } 30 | .itrisearch-guide-2 { 31 | border-bottom: dashed 1px gray; 32 | top: 0; 33 | width: 100%; 34 | } 35 | .itrisearch-guide-3 { 36 | z-index: 2147483645; 37 | position: fixed; 38 | top: 0; 39 | left: 0; 40 | width: 100%; 41 | height: 100%; 42 | } 43 | -------------------------------------------------------------------------------- /v3/data/inject/inject.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var monitor = window.monitor; 4 | var capture = window.capture; 5 | var guide = window.guide; 6 | 7 | try { 8 | guide.remove(); 9 | capture.remove(); 10 | monitor.remove(); 11 | } 12 | catch (e) {} 13 | 14 | capture = (function() { 15 | var box, _left, _top, left, top, width, height; 16 | 17 | function update(e) { 18 | left = (e.clientX > _left ? _left : e.clientX - 1); 19 | top = (e.clientY > _top ? _top : e.clientY - 1); 20 | width = Math.abs(e.clientX - _left); 21 | height = Math.abs(e.clientY - _top); 22 | box.style.left = left + 'px'; 23 | box.style.top = top + 'px'; 24 | box.style.width = width + 'px'; 25 | box.style.height = height + 'px'; 26 | } 27 | function remove() { 28 | const name = `${document.title}-${left}.${top}.${width}.${height}`; 29 | // create a port to keep bg active while uploading 30 | chrome.runtime.connect({name}); 31 | chrome.runtime.sendMessage({ 32 | method: 'captured', 33 | name, 34 | left: left + 1, 35 | top: top + 1, 36 | width: width - 2, 37 | height: height - 2, 38 | devicePixelRatio: window.devicePixelRatio, 39 | title: document.title, 40 | service: window.service // used by Reverse Image Search extension 41 | }); 42 | guide.remove(); 43 | capture.remove(); 44 | monitor.remove(); 45 | } 46 | function mousedown(e) { 47 | // prevent content selection on Firefox 48 | e.stopPropagation(); 49 | e.preventDefault(); 50 | box = document.createElement('div'); 51 | box.setAttribute('class', 'itrisearch-box'); 52 | 53 | _left = e.clientX; 54 | _top = e.clientY; 55 | 56 | document.addEventListener('mousemove', update, false); 57 | document.addEventListener('mouseup', remove, false); 58 | document.body.appendChild(box); 59 | } 60 | 61 | return { 62 | install: function() { 63 | document.addEventListener('mousedown', mousedown, false); 64 | }, 65 | remove: function() { 66 | document.removeEventListener('mousedown', mousedown, false); 67 | document.removeEventListener('mousemove', update, false); 68 | document.removeEventListener('mouseup', remove, false); 69 | if (box && box.parentNode) { 70 | box.parentNode.removeChild(box); 71 | } 72 | } 73 | }; 74 | })(); 75 | 76 | guide = (function() { 77 | var guide1, guide2, guide3; 78 | function position(left, top) { 79 | guide1.style.width = left + 'px'; 80 | guide2.style.height = top + 'px'; 81 | } 82 | function update(e) { 83 | position(e.clientX, e.clientY); 84 | } 85 | return { 86 | install: function() { 87 | guide1 = document.createElement('div'); 88 | guide2 = document.createElement('div'); 89 | guide3 = document.createElement('div'); 90 | guide1.setAttribute('class', 'itrisearch-guide-1'); 91 | guide2.setAttribute('class', 'itrisearch-guide-2'); 92 | guide3.setAttribute('class', 'itrisearch-guide-3'); 93 | document.body.appendChild(guide3); 94 | document.body.appendChild(guide1); 95 | document.body.appendChild(guide2); 96 | document.addEventListener('mousemove', update, false); 97 | }, 98 | remove: function() { 99 | document.removeEventListener('mousemove', update, false); 100 | if (guide1 && guide1.parentNode) { 101 | guide1.parentNode.removeChild(guide1); 102 | } 103 | if (guide2 && guide2.parentNode) { 104 | guide2.parentNode.removeChild(guide2); 105 | } 106 | if (guide3 && guide3.parentNode) { 107 | guide3.parentNode.removeChild(guide3); 108 | } 109 | capture.remove(); 110 | } 111 | }; 112 | })(); 113 | 114 | monitor = (function() { 115 | function keydown(e) { 116 | if (e.keyCode === 27) { 117 | guide.remove(); 118 | capture.remove(); 119 | monitor.remove(); 120 | } 121 | } 122 | return { 123 | install: function() { 124 | window.addEventListener('keydown', keydown, false); 125 | }, 126 | remove: function() { 127 | window.removeEventListener('keydown', keydown, false); 128 | } 129 | }; 130 | })(); 131 | 132 | guide.install(); 133 | capture.install(); 134 | monitor.install(); 135 | -------------------------------------------------------------------------------- /v3/data/inject/notify.css: -------------------------------------------------------------------------------- 1 | .itrisearch-notification { 2 | position: fixed; 3 | z-index: 2147483646; 4 | right: 20px; 5 | bottom: 20px; 6 | width: 250px; 7 | height: 40px; 8 | line-height: 40px; 9 | background-color: #FFF; 10 | border: solid 2px gray; 11 | padding: 0 5px; 12 | box-shadow: 1px 1px 5px #aaa; 13 | color: #222; 14 | font-family: arial,sans-serif !important; 15 | font-size: 12px !important; 16 | } 17 | .itrisearch-notification button { 18 | position: absolute; 19 | top: 2px; 20 | right: 2px; 21 | background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAYAAAAGCAMAAADXEh96AAAAilBMVEVEREBEREBEREBEREBEREBEREBEREAlJSMSEhE3NzQAAAAAAAAQEA8BAQEAAAAAAAAAAAAAAAAvLywBAQEAAAAAAAAcHBoAAAAAAAAAAAAAAAAAAAAyMi8AAAAAAAABAQELCwo4ODUAAAAAAAAWFhUsLCkKCgkDAwIAAAAAAAAtLSoCAgIHBwcAAACgX5xYAAAAAXRSTlMAQObYZgAAAAFiS0dEAIgFHUgAAAAJcEhZcwAAXEYAAFxGARSUQ0EAAAAHdElNRQffBAgGGDS4qporAAAAG0lEQVQI12NgZGRgAGJGMGBggJAMCA5YDsQEAAJ2AB0Q4n77AAAAAElFTkSuQmCC) center center no-repeat; 22 | width: 12px; 23 | height: 12px; 24 | margin: 0; 25 | padding: 0; 26 | border: 0; 27 | cursor: pointer; 28 | opacity: 0.5; 29 | } 30 | .itrisearch-notification button:hover { 31 | opacity: 1.0; 32 | } 33 | -------------------------------------------------------------------------------- /v3/data/inject/notify.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var notify = (function () { 4 | let box = document.querySelector('.itrisearch-notification'); 5 | return { 6 | install: function () { 7 | box = document.createElement('div'); 8 | box.setAttribute('class', 'itrisearch-notification'); 9 | const span = document.createElement('span'); 10 | const button = document.createElement('button'); 11 | button.setAttribute('type', 'close'); 12 | button.addEventListener('click', function () { 13 | notify.hide(); 14 | }); 15 | box.appendChild(span); 16 | box.appendChild(button); 17 | document.body.appendChild(box); 18 | }, 19 | display: function (msg) { 20 | if (!document.querySelector('.itrisearch-notification')) { 21 | this.install(); 22 | } 23 | box.style.display = 'block'; 24 | box.querySelector('span').textContent = msg; 25 | }, 26 | hide: function () { 27 | box.style.display = 'none'; 28 | }, 29 | remove: function () { 30 | if (box && box.parentNode) { 31 | box.parentNode.removeChild(box); 32 | box = null; 33 | } 34 | } 35 | }; 36 | })(); 37 | -------------------------------------------------------------------------------- /v3/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Reverse Image Search", 3 | "description": "__MSG_description__", 4 | "default_locale": "en", 5 | "version": "0.3.0", 6 | "manifest_version": 3, 7 | "permissions": [ 8 | "storage", 9 | "activeTab", 10 | "scripting", 11 | "contextMenus" 12 | ], 13 | "host_permissions": [ 14 | "https://www.google.com/searchbyimage/upload" 15 | ], 16 | "background":{ 17 | "service_worker": "worker.js" 18 | }, 19 | "homepage_url": "https://add0n.com/reverse-image-search.html", 20 | "icons": { 21 | "16": "data/icons/16.png", 22 | "24": "data/icons/24.png", 23 | "32": "data/icons/32.png", 24 | "48": "data/icons/48.png", 25 | "64": "data/icons/64.png", 26 | "128": "data/icons/128.png", 27 | "256": "data/icons/256.png" 28 | }, 29 | "action": {}, 30 | "commands": { 31 | "_execute_action": { 32 | "description": "Press the action button" 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /v3/worker.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | { 4 | const once = () => { 5 | chrome.contextMenus.create({ 6 | 'id': 'search-link-google', 7 | 'title': 'Google Images (Image URL)', 8 | 'contexts': ['image'] 9 | }); 10 | chrome.contextMenus.create({ 11 | 'id': 'search-link-tineye', 12 | 'title': 'Tineye (Image URL)', 13 | 'contexts': ['image'] 14 | }); 15 | chrome.contextMenus.create({ 16 | 'id': 'capture-google', 17 | 'title': 'Google Images (Capture)', 18 | 'contexts': ['page'] 19 | }); 20 | // chrome.contextMenus.create({ 21 | // 'id': 'capture-tineye', 22 | // 'title': 'TinEye (Capture)', 23 | // 'contexts': ['page'] 24 | // }); 25 | chrome.contextMenus.create({ 26 | 'id': 'preview', 27 | 'title': 'Usage Preview', 28 | 'contexts': ['action'] 29 | }); 30 | }; 31 | chrome.runtime.onInstalled.addListener(once); 32 | chrome.runtime.onStartup.addListener(once); 33 | } 34 | 35 | const notify = async (tabId, msg = '') => { 36 | await chrome.scripting.insertCSS({ 37 | target: {tabId}, 38 | files: ['data/inject/notify.css'] 39 | }); 40 | await chrome.scripting.executeScript({ 41 | target: {tabId}, 42 | files: ['data/inject/notify.js'] 43 | }); 44 | chrome.scripting.executeScript({ 45 | target: {tabId}, 46 | func: msg => { 47 | if (msg) { 48 | notify.display(msg); 49 | } 50 | else { 51 | notify.hide(); 52 | } 53 | }, 54 | args: [msg] 55 | }); 56 | }; 57 | 58 | const ports = {}; 59 | const onClick = async (info, tab) => { 60 | if (info.menuItemId === 'preview') { 61 | chrome.tabs.create({ 62 | url: 'https://www.youtube.com/watch?v=KiBNYb3yuSo' 63 | }); 64 | } 65 | else if (info.menuItemId.startsWith('capture-')) { 66 | await chrome.scripting.insertCSS({ 67 | target: {tabId: tab.id}, 68 | files: ['data/inject/inject.css'] 69 | }); 70 | await chrome.scripting.executeScript({ 71 | target: {tabId: tab.id}, 72 | func: service => { 73 | window.service = service; 74 | }, 75 | args: [info.menuItemId.endsWith('tineye') ? 'TinEye' : 'Google'] 76 | }); 77 | chrome.scripting.executeScript({ 78 | target: {tabId: tab.id}, 79 | files: ['data/inject/inject.js'] 80 | }); 81 | } 82 | else if (info.menuItemId.startsWith('search-link-')) { 83 | const tineye = info.menuItemId.endsWith('tineye'); 84 | 85 | if (tineye) { 86 | chrome.tabs.create({ 87 | url: 'https://tineye.com/search/?pluginver=chrome-1.3.0&url=' + encodeURIComponent(info.srcUrl) 88 | }); 89 | } 90 | else { 91 | chrome.tabs.create({ 92 | url: 'https://www.google.com/searchbyimage?image_url=' + encodeURIComponent(info.srcUrl) 93 | }); 94 | } 95 | } 96 | }; 97 | 98 | // keep bg active until file is sent to the server 99 | chrome.runtime.onConnect.addListener(port => { 100 | ports[port.name] = port; 101 | }); 102 | 103 | chrome.contextMenus.onClicked.addListener(onClick); 104 | chrome.action.onClicked.addListener(tab => { 105 | onClick({ 106 | menuItemId: 'capture-google' 107 | }, tab); 108 | }); 109 | 110 | function capture(request, sender) { 111 | const {devicePixelRatio, service = 'TinEye'} = request; 112 | 113 | let {left, top, width, height, name} = request; 114 | left *= devicePixelRatio; 115 | top *= devicePixelRatio; 116 | width *= devicePixelRatio; 117 | height *= devicePixelRatio; 118 | 119 | notify(sender.tab.id, `Uploading image to ${service}. Please wait ...`); 120 | 121 | chrome.tabs.captureVisibleTab(sender.tab.windowId, {format: 'png'}, async href => { 122 | const img = await createImageBitmap(await fetch(href).then(r => r.blob())); 123 | 124 | const canvas = new OffscreenCanvas(width, height); 125 | const ctx = canvas.getContext('2d'); 126 | if (width && height) { 127 | ctx.drawImage(img, left, top, width, height, 0, 0, width, height); 128 | } 129 | else { 130 | ctx.drawImage(img, 0, 0); 131 | } 132 | 133 | const blob = await canvas.convertToBlob({ 134 | type: 'image/png', 135 | quality: 1.00 136 | }); 137 | 138 | const body = new FormData(); 139 | body.processData = false; 140 | body.contentType = false; 141 | 142 | let response; 143 | try { 144 | if (service === 'Google') { 145 | body.append('encoded_image', blob, 'screenshot.png'); 146 | response = await fetch('https://www.google.com/searchbyimage/upload', { 147 | method: 'POST', 148 | body 149 | }); 150 | } 151 | else { 152 | body.append('image', blob, 'screenshot.png'); 153 | response = await fetch('https://tineye.com/result_json/?token=', { 154 | method: 'POST', 155 | body 156 | }); 157 | } 158 | chrome.tabs.create({ 159 | url: response.url 160 | }); 161 | notify(sender.tab.id); 162 | } 163 | catch (e) { 164 | console.warn(e); 165 | notify(sender.tab.id, 'Failed! ' + (e.message || e)); 166 | } 167 | // disconnect port 168 | const p = ports[name]; 169 | if (p) { 170 | delete ports[name]; 171 | console.log(p, name); 172 | p.disconnect(); 173 | } 174 | }); 175 | } 176 | 177 | chrome.runtime.onMessage.addListener(capture); 178 | 179 | /* FAQs & Feedback */ 180 | { 181 | const {management, runtime: {onInstalled, setUninstallURL, getManifest}, storage, tabs} = chrome; 182 | if (navigator.webdriver !== true) { 183 | const page = getManifest().homepage_url; 184 | const {name, version} = getManifest(); 185 | onInstalled.addListener(({reason, previousVersion}) => { 186 | management.getSelf(({installType}) => installType === 'normal' && storage.local.get({ 187 | 'faqs': true, 188 | 'last-update': 0 189 | }, prefs => { 190 | if (reason === 'install' || (prefs.faqs && reason === 'update')) { 191 | const doUpdate = (Date.now() - prefs['last-update']) / 1000 / 60 / 60 / 24 > 45; 192 | if (doUpdate && previousVersion !== version) { 193 | tabs.query({active: true, currentWindow: true}, tbs => tabs.create({ 194 | url: page + '?version=' + version + (previousVersion ? '&p=' + previousVersion : '') + '&type=' + reason, 195 | active: reason === 'install', 196 | ...(tbs && tbs.length && {index: tbs[0].index + 1}) 197 | })); 198 | storage.local.set({'last-update': Date.now()}); 199 | } 200 | } 201 | })); 202 | }); 203 | setUninstallURL(page + '?rd=feedback&name=' + encodeURIComponent(name) + '&version=' + version); 204 | } 205 | } 206 | --------------------------------------------------------------------------------