├── .browserslistrc ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── config.yml │ └── feature_request.md ├── label-actions.yml ├── pull_request_template.md └── workflows │ ├── ci.yml │ ├── label-actions.yml │ └── lockdown.yml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── .prettierrc.yml ├── CHANGELOG.md ├── LICENSE ├── README.md ├── babel.config.js ├── gulpfile.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── src ├── action │ ├── App.vue │ ├── index.html │ └── main.js ├── assets │ ├── fonts │ │ └── roboto.css │ ├── icons │ │ ├── app │ │ │ └── icon.svg │ │ ├── engines │ │ │ ├── 123rf-dark.svg │ │ │ ├── 123rf.svg │ │ │ ├── adobestock.svg │ │ │ ├── alamy-dark.svg │ │ │ ├── alamy.svg │ │ │ ├── alibabaChina.svg │ │ │ ├── allEngines.svg │ │ │ ├── ascii2d-dark.svg │ │ │ ├── ascii2d.svg │ │ │ ├── auDesign.svg │ │ │ ├── auTrademark.svg │ │ │ ├── baidu.svg │ │ │ ├── bing.svg │ │ │ ├── clipretrieval-dark.svg │ │ │ ├── depositphotos-dark.svg │ │ │ ├── depositphotos.svg │ │ │ ├── dreamstime.svg │ │ │ ├── esearch.svg │ │ │ ├── freepik-dark.svg │ │ │ ├── freepik.svg │ │ │ ├── getty-dark.svg │ │ │ ├── getty.svg │ │ │ ├── googleImages.svg │ │ │ ├── googleLens.svg │ │ │ ├── icons8.svg │ │ │ ├── ikea.svg │ │ │ ├── immerse-dark.svg │ │ │ ├── iqdb.png │ │ │ ├── istock-dark.svg │ │ │ ├── istock.svg │ │ │ ├── jpDesign-dark.svg │ │ │ ├── jpDesign.svg │ │ │ ├── kagi.svg │ │ │ ├── lenso-dark.svg │ │ │ ├── lenso.svg │ │ │ ├── lexica-dark.svg │ │ │ ├── lexica.svg │ │ │ ├── lykdat-dark.svg │ │ │ ├── lykdat.svg │ │ │ ├── nzTrademark-dark.svg │ │ │ ├── nzTrademark.svg │ │ │ ├── pimeyes-dark.svg │ │ │ ├── pimeyes.svg │ │ │ ├── pinterest.svg │ │ │ ├── pixta-dark.svg │ │ │ ├── pixta.svg │ │ │ ├── pond5-dark.svg │ │ │ ├── pond5.svg │ │ │ ├── qihoo.svg │ │ │ ├── repostSleuth.png │ │ │ ├── saucenao-dark.svg │ │ │ ├── saucenao.svg │ │ │ ├── shein.svg │ │ │ ├── shutterstock.svg │ │ │ ├── sogou.svg │ │ │ ├── stocksy-dark.svg │ │ │ ├── stocksy.svg │ │ │ ├── taobao.svg │ │ │ ├── tineye.png │ │ │ ├── tmview-dark.svg │ │ │ ├── tmview.svg │ │ │ ├── unsplash-dark.svg │ │ │ ├── unsplash.svg │ │ │ ├── whatanime.png │ │ │ ├── wildberries.svg │ │ │ ├── wipo.svg │ │ │ └── yandex.svg │ │ ├── misc │ │ │ ├── broken-image-dark.svg │ │ │ ├── broken-image.svg │ │ │ ├── close.svg │ │ │ ├── content-copy.svg │ │ │ ├── crop-free-light.svg │ │ │ ├── error.svg │ │ │ ├── favorite-filled.svg │ │ │ ├── favorite-light.svg │ │ │ ├── help-light.svg │ │ │ ├── image-light.svg │ │ │ ├── image-link-light.svg │ │ │ ├── image.svg │ │ │ ├── ios-share-light.svg │ │ │ ├── ios-share.svg │ │ │ ├── keep-filled-light.svg │ │ │ ├── keep-light.svg │ │ │ ├── link-light.svg │ │ │ ├── more-vert.svg │ │ │ ├── open-in-new-light.svg │ │ │ ├── open-in-new.svg │ │ │ ├── open-with.svg │ │ │ ├── settings-light.svg │ │ │ ├── settings.svg │ │ │ ├── share-light.svg │ │ │ ├── share.svg │ │ │ ├── spinner.svg │ │ │ ├── swap-horiz.svg │ │ │ ├── swap-vert.svg │ │ │ └── upload-light.svg │ │ └── sponsors │ │ │ ├── lenso-dark.svg │ │ │ └── lenso.svg │ ├── locales │ │ └── en │ │ │ ├── messages-firefox.json │ │ │ ├── messages-safari.json │ │ │ └── messages.json │ └── manifest │ │ ├── chrome.json │ │ ├── edge.json │ │ ├── firefox.json │ │ ├── opera.json │ │ ├── safari.json │ │ └── samsung.json ├── background │ ├── index.html │ └── main.js ├── base │ └── main.js ├── browse │ ├── App.vue │ ├── index.html │ └── main.js ├── capture │ ├── App.vue │ ├── index.html │ └── main.js ├── confirm │ ├── App.vue │ ├── index.html │ └── main.js ├── content │ ├── main.js │ └── style.css ├── contribute │ ├── App.vue │ ├── index.html │ └── main.js ├── engines │ ├── 123rf.js │ ├── adobestock.js │ ├── alamy.js │ ├── alibabaChina.js │ ├── ascii2d.js │ ├── auDesign.js │ ├── auTrademark.js │ ├── baidu.js │ ├── bing.js │ ├── branddb.js │ ├── css │ │ └── bing.css │ ├── depositphotos.js │ ├── dreamstime.js │ ├── esearch.js │ ├── freepik.js │ ├── getty.js │ ├── googleImages.js │ ├── googleLens.js │ ├── icons8.js │ ├── ikea.js │ ├── iqdb.js │ ├── istock.js │ ├── jpDesign.js │ ├── kagi.js │ ├── lenso.js │ ├── lexica.js │ ├── lykdat.js │ ├── madridMonitor.js │ ├── nzTrademark.js │ ├── pimeyes.js │ ├── pixta.js │ ├── pond5.js │ ├── qihoo.js │ ├── repostSleuth.js │ ├── saucenao.js │ ├── shein.js │ ├── shutterstock.js │ ├── sogou.js │ ├── stocksy.js │ ├── taobao.js │ ├── tineye.js │ ├── tmview.js │ ├── unsplash.js │ ├── whatanime.js │ ├── wildberries.js │ └── yandex.js ├── options │ ├── App.vue │ ├── index.html │ └── main.js ├── parse │ └── main.js ├── search │ ├── App.vue │ ├── index.html │ └── main.js ├── select │ ├── App.vue │ ├── index.html │ ├── main.js │ └── pointer.css ├── storage │ ├── config.json │ ├── init.js │ ├── revisions │ │ ├── local │ │ │ ├── 20210820184257_support_event_pages.js │ │ │ ├── 20210919175209_add_image_sharing_options.js │ │ │ ├── 20210928090443_add_unsplash.js │ │ │ ├── 20211011114043_configure_engines.js │ │ │ ├── 20211106103932_add_shein.js │ │ │ ├── 20211111071410_add_lykdat.js │ │ │ ├── 20211204124507_add_wildberries.js │ │ │ ├── 20211213014048_add_lastengineaccesscheck.js │ │ │ ├── 20211213191049_add_setcontextmenuevent.js │ │ │ ├── 20220108063511_add_google_lens.js │ │ │ ├── 20220321163741_add_clipboard_support.js │ │ │ ├── 20220505174634_detect_alternative_image_sizes.js │ │ │ ├── 20220516051842_update_search_mode_ids.js │ │ │ ├── 20220516124148_open_images.js │ │ │ ├── 20220814142801_configure_engines.js │ │ │ ├── 20220924083846_add_lexica.js │ │ │ ├── 20230415111029_add_theme_support.js │ │ │ ├── 20230531103023_remove_search_engines.js │ │ │ ├── 20230601115626_add_kagi.js │ │ │ ├── 20230624145626_add_search_engines.js │ │ │ ├── 20230716121432_add_freepik.js │ │ │ ├── 20231009185955_remove_unsplash.js │ │ │ ├── 20231102164602_add_icons8.js │ │ │ ├── 20240514170322_add_appversion.js │ │ │ ├── 20240529183556_update_search_engines.js │ │ │ ├── 20240619180111_add_menuchangeevent.js │ │ │ ├── 20240624161944_remove_search_engines.js │ │ │ ├── 20250102095603_add_lenso.ai.js │ │ │ ├── 20250218121640_remove_google_images.js │ │ │ ├── 20250317134215_add_back_google_images.js │ │ │ ├── 20250413115022_add_unsplash.js │ │ │ ├── 20250511143502_remove_karma_decay.js │ │ │ ├── BJXdcUXOG.js │ │ │ ├── BJguWEHcbz.js │ │ │ ├── Bk42MzXdW.js │ │ │ ├── HJ5MKKGhW.js │ │ │ ├── Hy1tD8ANb.js │ │ │ ├── IMMjiccGj.js │ │ │ ├── K9Esw2jiQ3.js │ │ │ ├── LX20x6G8l.js │ │ │ ├── S18hLi5tG.js │ │ │ ├── S1ebtZ3Uzz.js │ │ │ ├── S1kNNadHZ.js │ │ │ ├── SJzIWmjKz.js │ │ │ ├── ShjDMM87.js │ │ │ ├── SklYTwQOYf.js │ │ │ ├── SkwaU8NlX.js │ │ │ ├── Syy800KkQ.js │ │ │ ├── TshBYj8anA.js │ │ │ ├── UhWEtK9gMh.js │ │ │ ├── d8CIdomCW.js │ │ │ ├── d8IK4KCtVm.js │ │ │ ├── ekhOvNiTsF.js │ │ │ ├── ggrr9C9pgV.js │ │ │ ├── r1H3rgx1X.js │ │ │ ├── r1Pvd36nz.js │ │ │ ├── ryY8H0EWf.js │ │ │ ├── ryekyizAg.js │ │ │ └── yLciyvS5He.js │ │ └── session │ │ │ └── 20240514122825_initial_version.js │ └── storage.js ├── tab │ ├── index.html │ └── main.js ├── utils │ ├── app.js │ ├── common.js │ ├── config.js │ ├── data.js │ ├── engines.js │ ├── registry.js │ ├── scripts.js │ └── vuetify.js └── view │ ├── App.vue │ ├── index.html │ └── main.js └── webpack.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | [chrome] 2 | Chrome >= 123 3 | Edge >= 123 4 | Opera >= 109 5 | 6 | [edge] 7 | Edge >= 123 8 | 9 | [firefox] 10 | Firefox >= 115 11 | FirefoxAndroid >= 115 12 | 13 | [opera] 14 | Opera >= 109 15 | 16 | [safari] 17 | Safari >= 17 18 | iOS >= 17 19 | 20 | [samsung] 21 | Samsung >= 14 22 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: dessant 2 | patreon: dessant 3 | custom: 4 | - https://armin.dev/go/paypal 5 | - https://armin.dev/go/bitcoin 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report if something isn't working as expected 4 | 5 | --- 6 | 7 | **System** 8 | 9 | 10 | * OS name: [e.g. Windows, Ubuntu] 11 | * OS version: [e.g. 10] 12 | * Browser name: [e.g. Chrome, Firefox] 13 | * Browser version: [e.g. 60] 14 | * Extension version: [e.g. 1.0.0] 15 | 16 | **Bug description** 17 | 23 | 24 | **Logs** 25 | 26 | 30 | ``` 31 | // REPLACE WITH LOGS 32 | ``` 33 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | blank_issues_enabled: false 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | 9 | 10 | **Describe the solution you'd like** 11 | 12 | 13 | **Describe alternatives you've considered** 14 | 15 | 16 | **Additional context** 17 | 18 | -------------------------------------------------------------------------------- /.github/label-actions.yml: -------------------------------------------------------------------------------- 1 | # Configuration for Label Actions - https://github.com/dessant/label-actions 2 | 3 | incomplete: 4 | issues: 5 | comment: > 6 | @{issue-author}, the issue does not contain enough information 7 | to reproduce the bug. Please open a new bug report and fill out 8 | the issue template with the requested data. 9 | close: true 10 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | This project does not accept pull requests. Please use issues to report bugs or suggest new features. 2 | -------------------------------------------------------------------------------- /.github/workflows/label-actions.yml: -------------------------------------------------------------------------------- 1 | name: 'Label Actions' 2 | 3 | on: 4 | issues: 5 | types: [labeled, unlabeled] 6 | 7 | permissions: 8 | contents: read 9 | issues: write 10 | 11 | jobs: 12 | action: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: dessant/label-actions@v4 16 | -------------------------------------------------------------------------------- /.github/workflows/lockdown.yml: -------------------------------------------------------------------------------- 1 | name: 'Repo Lockdown' 2 | 3 | on: 4 | pull_request_target: 5 | types: opened 6 | 7 | permissions: 8 | pull-requests: write 9 | 10 | jobs: 11 | action: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: dessant/repo-lockdown@v4 15 | with: 16 | exclude-pr-created-before: '2021-08-18T00:00:00Z' 17 | pr-comment: 'This project does not accept pull requests. Please use issues to report bugs or suggest new features.' 18 | process-only: 'prs' 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.assets/ 2 | /app/ 3 | /artifacts/ 4 | /dist/ 5 | 6 | /report.json 7 | /report.html 8 | 9 | /web-ext-config.mjs 10 | 11 | node_modules/ 12 | /npm-debug.log 13 | 14 | /.vscode 15 | 16 | xcuserdata/ 17 | 18 | .DS_Store 19 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 22.12.0 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | package.json 2 | *.md 3 | -------------------------------------------------------------------------------- /.prettierrc.yml: -------------------------------------------------------------------------------- 1 | singleQuote: true 2 | bracketSpacing: false 3 | arrowParens: 'avoid' 4 | trailingComma: 'none' 5 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | const path = require('node:path'); 2 | 3 | const corejsVersion = require( 4 | path.join(path.dirname(require.resolve('core-js')), 'package.json') 5 | ).version; 6 | 7 | module.exports = function (api) { 8 | api.cache(true); 9 | 10 | const presets = [ 11 | [ 12 | '@babel/env', 13 | { 14 | modules: false, 15 | bugfixes: true, 16 | useBuiltIns: 'usage', 17 | corejs: {version: corejsVersion} 18 | } 19 | ] 20 | ]; 21 | 22 | const plugins = []; 23 | 24 | const ignore = [ 25 | new RegExp(`node_modules\\${path.sep}(?!(vueton|wesa)\\${path.sep}).*`) 26 | ]; 27 | 28 | const parserOpts = {plugins: ['importAttributes']}; 29 | 30 | return {presets, plugins, ignore, parserOpts}; 31 | }; 32 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | const postcssPresetEnv = require('postcss-preset-env'); 2 | const cssnano = require('cssnano'); 3 | 4 | module.exports = function (api) { 5 | const plugins = [postcssPresetEnv()]; 6 | 7 | if (api.env === 'production') { 8 | plugins.push(cssnano({zindex: false, discardUnused: false})); 9 | } 10 | 11 | return {plugins}; 12 | }; 13 | -------------------------------------------------------------------------------- /src/action/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/action/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/assets/fonts/roboto.css: -------------------------------------------------------------------------------- 1 | @font-face { 2 | font-family: 'Roboto'; 3 | font-style: normal; 4 | font-weight: 400; 5 | src: url('./files/roboto-latin-400-normal.woff2') format('woff2'), 6 | local('Roboto'), local('Roboto-Regular'); 7 | } 8 | 9 | @font-face { 10 | font-family: 'Roboto'; 11 | font-style: normal; 12 | font-weight: 500; 13 | src: url('./files/roboto-latin-500-normal.woff2') format('woff2'), 14 | local('Roboto Medium'), local('Roboto-Medium'); 15 | } 16 | 17 | @font-face { 18 | font-family: 'Roboto'; 19 | font-style: normal; 20 | font-weight: 700; 21 | src: url('./files/roboto-latin-700-normal.woff2') format('woff2'), 22 | local('Roboto Bold'), local('Roboto-Bold'); 23 | } 24 | -------------------------------------------------------------------------------- /src/assets/icons/app/icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/engines/123rf-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/123rf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/adobestock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/engines/alamy-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/alamy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/alibabaChina.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/allEngines.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/ascii2d-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/ascii2d.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/auDesign.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/auTrademark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/baidu.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/assets/icons/engines/clipretrieval-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/depositphotos-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/depositphotos.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/dreamstime.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/freepik-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/freepik.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/getty-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/getty.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/googleImages.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /src/assets/icons/engines/googleLens.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/icons8.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/ikea.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/engines/immerse-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/iqdb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dessant/search-by-image/870fb4b5196c0b60711822206f457910824037ef/src/assets/icons/engines/iqdb.png -------------------------------------------------------------------------------- /src/assets/icons/engines/istock-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/istock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/kagi.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/lenso-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/engines/lenso.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /src/assets/icons/engines/lexica-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/lexica.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/lykdat-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/lykdat.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pimeyes-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pimeyes.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pinterest.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pixta-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pixta.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pond5-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/pond5.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/repostSleuth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dessant/search-by-image/870fb4b5196c0b60711822206f457910824037ef/src/assets/icons/engines/repostSleuth.png -------------------------------------------------------------------------------- /src/assets/icons/engines/saucenao-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/saucenao.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/shein.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/engines/shutterstock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/assets/icons/engines/sogou.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /src/assets/icons/engines/tineye.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dessant/search-by-image/870fb4b5196c0b60711822206f457910824037ef/src/assets/icons/engines/tineye.png -------------------------------------------------------------------------------- /src/assets/icons/engines/tmview-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/tmview.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/unsplash-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/unsplash.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/engines/whatanime.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dessant/search-by-image/870fb4b5196c0b60711822206f457910824037ef/src/assets/icons/engines/whatanime.png -------------------------------------------------------------------------------- /src/assets/icons/engines/wildberries.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/assets/icons/engines/yandex.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/broken-image-dark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/misc/broken-image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/assets/icons/misc/close.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/content-copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/crop-free-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/favorite-filled.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/favorite-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/help-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/image-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/image-link-light.svg: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/assets/icons/misc/image.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/ios-share-light.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/misc/ios-share.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/assets/icons/misc/keep-filled-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/keep-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/link-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/more-vert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/open-in-new-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/open-in-new.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/open-with.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/settings-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/share-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/share.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/spinner.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /src/assets/icons/misc/swap-horiz.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/swap-vert.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/icons/misc/upload-light.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/assets/locales/en/messages-firefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "menuItemTitle_allEngines": { 3 | "message": "All Search Engines", 4 | "description": "Title of the menu item." 5 | }, 6 | 7 | "menuItemTitle_viewImage": { 8 | "message": "Open Image", 9 | "description": "Title of the menu item." 10 | }, 11 | 12 | "menuItemTitle_shareImage": { 13 | "message": "Share Image", 14 | "description": "Title of the menu item." 15 | }, 16 | 17 | "optionValue_action_searchModeAction_selectImage": { 18 | "message": "Select Image", 19 | "description": "Value of the option." 20 | }, 21 | 22 | "mainMenuItemTitle_allEngines": { 23 | "message": "Search All Engines for Image", 24 | "description": "Title of the menu item." 25 | }, 26 | 27 | "mainMenuItemTitle_engine": { 28 | "message": "Search $ENGINE$ for Image", 29 | "description": "Title of the menu item.", 30 | "placeholders": { 31 | "engine": { 32 | "content": "$1", 33 | "example": "Google" 34 | } 35 | } 36 | }, 37 | 38 | "optionSectionTitle_engines": { 39 | "message": "Search Engines", 40 | "description": "Title of the options section." 41 | }, 42 | 43 | "optionSectionTitle_contextmenu": { 44 | "message": "Context Menu", 45 | "description": "Title of the options section." 46 | }, 47 | 48 | "optionSectionTitle_toolbar": { 49 | "message": "Browser Toolbar", 50 | "description": "Title of the options section." 51 | }, 52 | 53 | "optionSectionTitleMobile_toolbar": { 54 | "message": "Browser Menu", 55 | "description": "Title of the options section." 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/assets/locales/en/messages-safari.json: -------------------------------------------------------------------------------- 1 | { 2 | "extensionDescription": { 3 | "message": "Effortless reverse image searches, with support for a vast selection of search engines.", 4 | "description": "Description of the extension." 5 | }, 6 | 7 | "menuItemTitle_allEngines": { 8 | "message": "All Search Engines", 9 | "description": "Title of the menu item." 10 | }, 11 | 12 | "menuItemTitle_viewImage": { 13 | "message": "Open Image", 14 | "description": "Title of the menu item." 15 | }, 16 | 17 | "menuItemTitle_shareImage": { 18 | "message": "Share Image", 19 | "description": "Title of the menu item." 20 | }, 21 | 22 | "optionValue_action_searchModeAction_selectImage": { 23 | "message": "Select Image", 24 | "description": "Value of the option." 25 | }, 26 | 27 | "mainMenuItemTitle_allEngines": { 28 | "message": "Search All Engines for Image", 29 | "description": "Title of the menu item." 30 | }, 31 | 32 | "mainMenuItemTitle_engine": { 33 | "message": "Search $ENGINE$ for Image", 34 | "description": "Title of the menu item.", 35 | "placeholders": { 36 | "engine": { 37 | "content": "$1", 38 | "example": "Google" 39 | } 40 | } 41 | }, 42 | 43 | "optionSectionTitle_engines": { 44 | "message": "Search Engines", 45 | "description": "Title of the options section." 46 | }, 47 | 48 | "optionSectionTitle_contextmenu": { 49 | "message": "Context Menu", 50 | "description": "Title of the options section." 51 | }, 52 | 53 | "optionSectionTitle_toolbar": { 54 | "message": "Browser Toolbar", 55 | "description": "Title of the options section." 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/background/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /src/browse/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/browse/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/capture/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/capture/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/confirm/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/confirm/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/content/style.css: -------------------------------------------------------------------------------- 1 | :host { 2 | all: initial !important; 3 | width: 0px !important; 4 | height: 0px !important; 5 | } 6 | 7 | #select { 8 | all: initial; 9 | position: fixed; 10 | bottom: env(safe-area-inset-bottom, 0px); 11 | left: 50%; 12 | width: 100%; 13 | max-width: 416px; 14 | height: 64px; 15 | transform: translateX(-50%); 16 | z-index: 2147483647; 17 | } 18 | 19 | #capture, 20 | #confirm { 21 | all: initial; 22 | position: fixed; 23 | top: 0; 24 | right: 0; 25 | bottom: 0; 26 | left: 0; 27 | width: 100%; 28 | height: 100%; 29 | z-index: 2147483647; 30 | } 31 | 32 | .hidden { 33 | display: none !important; 34 | } 35 | 36 | .fade-in:not(.hidden) { 37 | animation-name: fadeIn !important; 38 | animation-timing-function: cubic-bezier(0.5, 0, 0.75, 0) !important; 39 | animation-fill-mode: both !important; 40 | animation-duration: 0.1s !important; 41 | animation-delay: 0.05s !important; 42 | } 43 | 44 | @keyframes fadeIn { 45 | 0% { 46 | opacity: 0; 47 | } 48 | 49 | 100% { 50 | opacity: 1; 51 | } 52 | } 53 | 54 | .theme-light { 55 | color-scheme: light !important; 56 | } 57 | 58 | .theme-dark { 59 | color-scheme: dark !important; 60 | } 61 | -------------------------------------------------------------------------------- /src/contribute/App.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 58 | 59 | 69 | -------------------------------------------------------------------------------- /src/contribute/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/contribute/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto', '700 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/engines/123rf.js: -------------------------------------------------------------------------------- 1 | import {validateUrl} from 'utils/app'; 2 | import {runOnce} from 'utils/common'; 3 | import {initSearch, prepareImageForUpload, sendReceipt} from 'utils/engines'; 4 | 5 | const engine = '123rf'; 6 | 7 | async function search({session, search, image, storageIds}) { 8 | image = await prepareImageForUpload({ 9 | image, 10 | engine, 11 | target: 'api' 12 | }); 13 | 14 | const data = new FormData(); 15 | data.append('image_base64', image.imageDataUrl); 16 | 17 | const rsp = await fetch( 18 | 'https://www.123rf.com/apicore/search/reverse/upload', 19 | { 20 | mode: 'cors', 21 | method: 'POST', 22 | body: data 23 | } 24 | ); 25 | 26 | if (rsp.status !== 200) { 27 | throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`); 28 | } 29 | 30 | const rspText = await rsp.text(); 31 | 32 | // JSON response may start with HTML 33 | const searchData = JSON.parse(rspText.substring(rspText.indexOf('{'))); 34 | 35 | const tabUrl = 36 | 'https://www.123rf.com/reverse-search/?fid=' + searchData.data.fid; 37 | 38 | await sendReceipt(storageIds); 39 | 40 | if (validateUrl(tabUrl)) { 41 | window.location.replace(tabUrl); 42 | } 43 | } 44 | 45 | function init() { 46 | initSearch(search, engine, taskId); 47 | } 48 | 49 | if (runOnce('search')) { 50 | init(); 51 | } 52 | -------------------------------------------------------------------------------- /src/engines/adobestock.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'adobestock'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('button[aria-label="Find similar"]')).click(); 8 | 9 | const inputSelector = 'input[type=file]'; 10 | const input = await findNode(inputSelector); 11 | 12 | await setFileInputData(inputSelector, input, image); 13 | 14 | await sendReceipt(storageIds); 15 | 16 | input.dispatchEvent(new Event('change', {bubbles: true})); 17 | } 18 | 19 | function init() { 20 | initSearch(search, engine, taskId); 21 | } 22 | 23 | if (runOnce('search')) { 24 | init(); 25 | } 26 | -------------------------------------------------------------------------------- /src/engines/alamy.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'alamy'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('button[data-testid="searchByImageLong"]')).click(); 8 | 9 | const inputSelector = '#upload-an-image-tab input[type=file]'; 10 | const input = await findNode(inputSelector); 11 | 12 | await setFileInputData(inputSelector, input, image); 13 | 14 | await sendReceipt(storageIds); 15 | 16 | input.dispatchEvent(new Event('change', {bubbles: true})); 17 | } 18 | 19 | function init() { 20 | initSearch(search, engine, taskId); 21 | } 22 | 23 | if (runOnce('search')) { 24 | init(); 25 | } 26 | -------------------------------------------------------------------------------- /src/engines/alibabaChina.js: -------------------------------------------------------------------------------- 1 | import {findNode, executeScriptMainContext, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'alibabaChina'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | await executeScriptMainContext({func: 'alibabaChinaPatchContextScript'}); 8 | 9 | const inputSelector = 'input.image-file-reader-wrapper'; 10 | const input = await findNode(inputSelector); 11 | 12 | input.click(); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | window.setTimeout(() => { 19 | input.dispatchEvent(new Event('change', {bubbles: true})); 20 | }, 100); 21 | } 22 | 23 | function init() { 24 | initSearch(search, engine, taskId); 25 | } 26 | 27 | if (runOnce('search')) { 28 | init(); 29 | } 30 | -------------------------------------------------------------------------------- /src/engines/ascii2d.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'ascii2d'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = '#file-form'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | await sendReceipt(storageIds); 13 | 14 | (await findNode('#file_upload')).submit(); 15 | } 16 | 17 | function init() { 18 | initSearch(search, engine, taskId); 19 | } 20 | 21 | if (runOnce('search')) { 22 | init(); 23 | } 24 | -------------------------------------------------------------------------------- /src/engines/auDesign.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'auDesign'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = 'input[type=file]'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | input.dispatchEvent(new Event('change')); 13 | 14 | (await findNode('.popup-content .buttons button')).click(); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | ( 19 | await Promise.race([ 20 | findNode('.as-search-button'), // desktop 21 | findNode('.qs-search-button') // mobile 22 | ]) 23 | ).click(); 24 | } 25 | 26 | function init() { 27 | initSearch(search, engine, taskId); 28 | } 29 | 30 | if (runOnce('search')) { 31 | init(); 32 | } 33 | -------------------------------------------------------------------------------- /src/engines/auTrademark.js: -------------------------------------------------------------------------------- 1 | import {findNode, processNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'auTrademark'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | // go to desktop version on mobile 8 | processNode( 9 | '#pageContent a[onclick^="document.cookie=\'_fullMobile=true"]', 10 | node => node.click(), 11 | {throwError: false} 12 | ); 13 | 14 | // go to advanced search on mobile 15 | if (window.location.pathname.startsWith('/trademarks/search/quick')) { 16 | await processNode('a#goToAdvancedSearch', node => node.click()); 17 | } 18 | 19 | await findNode('.advanced-search div#sideImageUpload'); 20 | 21 | const inputSelector = 'input.dz-hidden-input'; 22 | const input = await findNode(inputSelector); 23 | 24 | await setFileInputData(inputSelector, input, image); 25 | 26 | input.dispatchEvent(new Event('change')); 27 | 28 | await findNode('div.cropper-container'); 29 | 30 | await sendReceipt(storageIds); 31 | 32 | ( 33 | await findNode('#qa-search-submit:not(.disabled)', { 34 | observerOptions: {attributes: true, attributeFilter: ['class']} 35 | }) 36 | ).click(); 37 | } 38 | 39 | function init() { 40 | initSearch(search, engine, taskId); 41 | } 42 | 43 | if (runOnce('search')) { 44 | init(); 45 | } 46 | -------------------------------------------------------------------------------- /src/engines/baidu.js: -------------------------------------------------------------------------------- 1 | import {validateUrl} from 'utils/app'; 2 | import {findNode, isMobile, runOnce} from 'utils/common'; 3 | import { 4 | initSearch, 5 | prepareImageForUpload, 6 | setFileInputData, 7 | sendReceipt 8 | } from 'utils/engines'; 9 | 10 | const engine = 'baidu'; 11 | 12 | async function search({session, search, image, storageIds}) { 13 | const mobile = await isMobile(); 14 | 15 | image = await prepareImageForUpload({ 16 | image, 17 | engine, 18 | target: mobile ? 'api' : 'ui' 19 | }); 20 | 21 | if (mobile) { 22 | const data = new FormData(); 23 | data.append('tn', 'pc'); 24 | data.append('from', 'pc'); 25 | data.append('range', '{"page_from": "searchIndex"}'); 26 | 27 | if (search.assetType === 'image') { 28 | data.append('image', image.imageBlob, image.imageFilename); 29 | data.append('image_source', 'PC_UPLOAD_SEARCH_FILE'); 30 | } else { 31 | data.append('image', image.imageUrl); 32 | data.append('image_source', 'PC_UPLOAD_SEARCH_URL'); 33 | } 34 | 35 | const rsp = await fetch('https://graph.baidu.com/upload', { 36 | mode: 'cors', 37 | method: 'POST', 38 | body: data 39 | }); 40 | 41 | if (rsp.status !== 200) { 42 | throw new Error(`API response: ${rsp.status}, ${await rsp.text()}`); 43 | } 44 | 45 | const tabUrl = (await rsp.json()).data.url; 46 | 47 | await sendReceipt(storageIds); 48 | 49 | if (validateUrl(tabUrl)) { 50 | window.location.replace(tabUrl); 51 | } 52 | } else { 53 | (await findNode('.soutu-btn', {timeout: 120000})).click(); 54 | 55 | if (search.assetType === 'image') { 56 | const inputSelector = 'input.upload-pic'; 57 | const input = await findNode(inputSelector); 58 | 59 | await setFileInputData(inputSelector, input, image); 60 | 61 | await sendReceipt(storageIds); 62 | 63 | input.dispatchEvent(new Event('change')); 64 | } else { 65 | const input = await findNode('input#soutu-url-kw'); 66 | input.value = image.imageUrl; 67 | 68 | await sendReceipt(storageIds); 69 | 70 | (await findNode('.soutu-url-btn')).click(); 71 | } 72 | } 73 | } 74 | 75 | function init() { 76 | initSearch(search, engine, taskId); 77 | } 78 | 79 | if (runOnce('search')) { 80 | init(); 81 | } 82 | -------------------------------------------------------------------------------- /src/engines/bing.js: -------------------------------------------------------------------------------- 1 | import {findNode, isMobile, runOnce} from 'utils/common'; 2 | import { 3 | initSearch, 4 | prepareImageForUpload, 5 | setFileInputData, 6 | sendReceipt 7 | } from 'utils/engines'; 8 | 9 | const engine = 'bing'; 10 | 11 | async function search({session, search, image, storageIds}) { 12 | const mobile = await isMobile(); 13 | 14 | image = await prepareImageForUpload({ 15 | image, 16 | engine, 17 | target: mobile ? 'api' : 'ui', 18 | newType: 'image/jpeg' 19 | }); 20 | 21 | if (mobile) { 22 | const form = document.createElement('form'); 23 | form.setAttribute('data-c45ng3u9', ''); 24 | form.id = 'sbi-upload-form'; 25 | form.method = 'POST'; 26 | form.enctype = 'multipart/form-data'; 27 | form.action = `https://www.bing.com/images/search?view=detailv2&iss=sbiupload&FORM=SBIHMP&sbifnm=${image.imageFilename}`; 28 | 29 | const input = document.createElement('input'); 30 | input.setAttribute('data-c45ng3u9', ''); 31 | input.name = 'imageBin'; 32 | input.type = 'hidden'; 33 | 34 | form.appendChild(input); 35 | document.body.appendChild(form); 36 | 37 | await sendReceipt(storageIds); 38 | 39 | input.value = image.imageDataUrl.substring( 40 | image.imageDataUrl.indexOf(',') + 1 41 | ); 42 | 43 | form.submit(); 44 | } else { 45 | (await findNode('#sb_sbi')).click(); 46 | 47 | const inputSelector = 'input#sb_fileinput'; 48 | const input = await findNode(inputSelector); 49 | 50 | await setFileInputData(inputSelector, input, image); 51 | 52 | await sendReceipt(storageIds); 53 | 54 | input.dispatchEvent(new Event('change')); 55 | } 56 | } 57 | 58 | function init() { 59 | initSearch(search, engine, taskId); 60 | } 61 | 62 | if (runOnce('search')) { 63 | init(); 64 | } 65 | -------------------------------------------------------------------------------- /src/engines/branddb.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'branddb'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | await findNode('body[style^="opacity: 1"]', { 8 | observerOptions: {attributes: true, attributeFilter: ['class']} 9 | }); 10 | 11 | const inputSelector = 'input#fileInput'; 12 | const input = await findNode(inputSelector); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | input.dispatchEvent(new Event('change')); 17 | 18 | // wait for image to load 19 | await findNode('.b-icon--edit'); 20 | 21 | await sendReceipt(storageIds); 22 | 23 | window.setTimeout(async () => { 24 | ( 25 | await findNode( 26 | 'button.search.b-button--is-type_primary:not(.b-button--is-disabled)' 27 | ) 28 | ).click(); 29 | }, 100); 30 | } 31 | 32 | function init() { 33 | initSearch(search, engine, taskId); 34 | } 35 | 36 | if (runOnce('search')) { 37 | init(); 38 | } 39 | -------------------------------------------------------------------------------- /src/engines/css/bing.css: -------------------------------------------------------------------------------- 1 | body > form#sbi-upload-form[data-c45ng3u9], 2 | body > form#sbi-upload-form[data-c45ng3u9] > input[data-c45ng3u9] { 3 | all: initial !important; 4 | display: none !important; 5 | } 6 | -------------------------------------------------------------------------------- /src/engines/depositphotos.js: -------------------------------------------------------------------------------- 1 | import {findNode, makeDocumentVisible, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'depositphotos'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | ( 8 | await findNode( 9 | 'body:not(.preload) button[data-qa="SearchByImageButtonV2"]', 10 | { 11 | observerOptions: {attributes: true, attributeFilter: ['class']} 12 | } 13 | ) 14 | ).click(); 15 | 16 | const inputSelector = 'input[type=file]'; 17 | const input = await findNode(inputSelector); 18 | 19 | await setFileInputData(inputSelector, input, image); 20 | 21 | await sendReceipt(storageIds); 22 | 23 | input.dispatchEvent(new Event('change', {bubbles: true})); 24 | } 25 | 26 | function init() { 27 | makeDocumentVisible(); 28 | initSearch(search, engine, taskId); 29 | } 30 | 31 | if (runOnce('search')) { 32 | init(); 33 | } 34 | -------------------------------------------------------------------------------- /src/engines/dreamstime.js: -------------------------------------------------------------------------------- 1 | import { 2 | findNode, 3 | processNode, 4 | isMobile, 5 | executeScriptMainContext, 6 | runOnce 7 | } from 'utils/common'; 8 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 9 | import {targetEnv} from 'utils/config'; 10 | 11 | const engine = 'dreamstime'; 12 | 13 | async function search({session, search, image, storageIds}) { 14 | if (targetEnv === 'safari' && (await isMobile())) { 15 | // hide noncritical upload error 16 | executeScriptMainContext({func: 'hideAlert'}); 17 | } 18 | 19 | const inputSelector = 'input[type="file"]'; 20 | 21 | processNode(inputSelector, function (node) { 22 | node.addEventListener('click', ev => ev.preventDefault(), { 23 | capture: true, 24 | once: true 25 | }); 26 | }); 27 | 28 | (await findNode('button.search-by-image__btn')).click(); 29 | 30 | const input = await findNode(inputSelector); 31 | 32 | await setFileInputData(inputSelector, input, image, {patchInput: true}); 33 | 34 | await sendReceipt(storageIds); 35 | 36 | input.dispatchEvent(new Event('change')); 37 | } 38 | 39 | function init() { 40 | initSearch(search, engine, taskId); 41 | } 42 | 43 | if (runOnce('search')) { 44 | init(); 45 | } 46 | -------------------------------------------------------------------------------- /src/engines/esearch.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'esearch'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = 'input.fileUploader-basic'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | input.dispatchEvent(new Event('change')); 13 | 14 | await findNode('div.imageViewer'); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | (await findNode('#basicSearchBigButton')).click(); 19 | } 20 | 21 | function init() { 22 | initSearch(search, engine, taskId); 23 | } 24 | 25 | if (runOnce('search')) { 26 | init(); 27 | } 28 | -------------------------------------------------------------------------------- /src/engines/freepik.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce, sleep} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'freepik'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | ( 8 | await findNode('form > div > button[data-state="closed"]:last-of-type') 9 | ).click(); 10 | 11 | const inputSelector = 'input[type=file]'; 12 | const input = await findNode(inputSelector); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | input.dispatchEvent(new Event('change', {bubbles: true})); 19 | 20 | await sleep(1000); 21 | 22 | (await findNode('.bg-surface-1 button[type=submit]')).click(); 23 | } 24 | 25 | function init() { 26 | initSearch(search, engine, taskId); 27 | } 28 | 29 | if (runOnce('search')) { 30 | init(); 31 | } 32 | -------------------------------------------------------------------------------- /src/engines/getty.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce, sleep} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'getty'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | // wait for search service to load 8 | await findNode('#onetrust-consent-sdk'); 9 | await sleep(1000); 10 | 11 | ( 12 | await Promise.race([ 13 | findNode('button[class*="SearchByImageButton"]'), 14 | findNode('button[data-testid="search-by-image-button"]') // new layout 15 | ]) 16 | ).click(); 17 | 18 | const inputSelector = 'input[type=file]'; 19 | const input = await findNode(inputSelector); 20 | 21 | await setFileInputData(inputSelector, input, image); 22 | 23 | await sendReceipt(storageIds); 24 | 25 | input.dispatchEvent(new Event('change', {bubbles: true})); 26 | } 27 | 28 | function init() { 29 | initSearch(search, engine, taskId); 30 | } 31 | 32 | if (runOnce('search')) { 33 | init(); 34 | } 35 | -------------------------------------------------------------------------------- /src/engines/googleImages.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'googleImages'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | await sendReceipt(storageIds); 8 | 9 | const targetNode = await Promise.race([ 10 | findNode('div#search', {timeout: 10000, throwError: false}), // desktop 11 | findNode('div#rso', {timeout: 10000, throwError: false}) // mobile 12 | ]); 13 | 14 | if (targetNode && !targetNode.children.length) { 15 | targetNode.style.margin = '6px'; 16 | targetNode.style.padding = '16px'; 17 | targetNode.style.border = '1px solid #fdd663'; 18 | targetNode.style.borderRadius = '24px'; 19 | targetNode.style.backgroundColor = '#feefc3'; 20 | 21 | targetNode.style.fontSize = '13px'; 22 | targetNode.style.fontWeight = '700'; 23 | targetNode.style.lineHeight = '20px'; 24 | targetNode.style.color = '#2d3436'; 25 | 26 | targetNode.innerHTML = `Google has replaced the legacy reverse image search service with Google Lens.

27 | It may still be possible to view the legacy search results by updating your Google Account settings, 28 | visit our help page for more details.

29 | Search by Image supports a wide variety of image search services, including Google Lens. 30 | Visit the extension's options to enable additional search engines.`; 31 | } 32 | } 33 | 34 | function init() { 35 | initSearch(search, engine, taskId); 36 | } 37 | 38 | if (runOnce('search')) { 39 | init(); 40 | } 41 | -------------------------------------------------------------------------------- /src/engines/googleLens.js: -------------------------------------------------------------------------------- 1 | import {findNode, processNode, runOnce, sleep} from 'utils/common'; 2 | import { 3 | setFileInputData, 4 | initSearch, 5 | sendReceipt, 6 | unsetUserAgent 7 | } from 'utils/engines'; 8 | 9 | const engine = 'googleLens'; 10 | 11 | async function search({session, search, image, storageIds}) { 12 | const inputSelector = 'input[type="file"]'; 13 | 14 | async function clickButton() { 15 | await processNode('div[data-base-lens-url]', async function (node) { 16 | await sleep(1000); 17 | 18 | if (!document.querySelector(inputSelector)) { 19 | node.click(); 20 | } 21 | }); 22 | } 23 | 24 | // handle consent popup 25 | processNode( 26 | `//div[@role="dialog" 27 | and contains(., "g.co/privacytools") 28 | and .//a[starts-with(@href, "https://policies.google.com/technologies/cookies")] 29 | and .//a[starts-with(@href, "https://policies.google.com/privacy")] 30 | and .//a[starts-with(@href, "https://policies.google.com/terms")] 31 | ]`, 32 | function (node) { 33 | if (node) { 34 | node.querySelectorAll('button')[2].click(); 35 | 36 | clickButton(); 37 | } 38 | }, 39 | {throwError: false, selectorType: 'xpath'} 40 | ); 41 | 42 | await clickButton(); 43 | 44 | await unsetUserAgent(storageIds); 45 | 46 | if (search.assetType === 'image') { 47 | const input = await findNode(inputSelector); 48 | 49 | await setFileInputData(inputSelector, input, image); 50 | 51 | await sendReceipt(storageIds); 52 | 53 | input.dispatchEvent(new Event('change')); 54 | } else { 55 | const input = await findNode( 56 | `//div[count(child::*)=2]/input[following-sibling::div[@role="button"]]`, 57 | {selectorType: 'xpath'} 58 | ); 59 | 60 | input.value = image.imageUrl; 61 | 62 | await sendReceipt(storageIds); 63 | 64 | input.nextElementSibling.click(); 65 | } 66 | } 67 | 68 | function init() { 69 | initSearch(search, engine, taskId); 70 | } 71 | 72 | if (runOnce('search')) { 73 | init(); 74 | } 75 | -------------------------------------------------------------------------------- /src/engines/icons8.js: -------------------------------------------------------------------------------- 1 | import {findNode, makeDocumentVisible, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'icons8'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('button.search-autocomplete__img-trigger')).click(); 8 | 9 | const inputSelector = 'input#file-input'; 10 | const input = await findNode(inputSelector); 11 | 12 | await setFileInputData(inputSelector, input, image); 13 | 14 | await sendReceipt(storageIds); 15 | 16 | input.dispatchEvent(new Event('input')); 17 | } 18 | 19 | function init() { 20 | makeDocumentVisible(); 21 | initSearch(search, engine, taskId); 22 | } 23 | 24 | if (runOnce('search')) { 25 | init(); 26 | } 27 | -------------------------------------------------------------------------------- /src/engines/ikea.js: -------------------------------------------------------------------------------- 1 | import { 2 | findNode, 3 | processNode, 4 | makeDocumentVisible, 5 | runOnce 6 | } from 'utils/common'; 7 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 8 | 9 | const engine = 'ikea'; 10 | 11 | async function search({session, search, image, storageIds}) { 12 | // go to regional site 13 | processNode('.region-picker a.website-link', node => node.click(), { 14 | throwError: false 15 | }); 16 | 17 | (await findNode('#search-box__visualsearch')).click(); 18 | 19 | const inputSelector = 'input[type=file]'; 20 | const input = await findNode(inputSelector); 21 | 22 | await setFileInputData(inputSelector, input, image); 23 | 24 | await sendReceipt(storageIds); 25 | 26 | input.dispatchEvent(new Event('change')); 27 | } 28 | 29 | function init() { 30 | makeDocumentVisible(); 31 | initSearch(search, engine, taskId); 32 | } 33 | 34 | if (runOnce('search')) { 35 | init(); 36 | } 37 | -------------------------------------------------------------------------------- /src/engines/iqdb.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'iqdb'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = '#file'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | await sendReceipt(storageIds); 13 | 14 | (await findNode('form')).submit(); 15 | } 16 | 17 | function init() { 18 | initSearch(search, engine, taskId); 19 | } 20 | 21 | if (runOnce('search')) { 22 | init(); 23 | } 24 | -------------------------------------------------------------------------------- /src/engines/istock.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce, sleep} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'istock'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | // wait for search service to load 8 | await findNode('#onetrust-consent-sdk'); 9 | await sleep(1000); 10 | 11 | ( 12 | await Promise.race([ 13 | findNode('button[class*="SearchByImageButton"]'), 14 | findNode('button[data-testid="search-by-image-button"]') // new layout 15 | ]) 16 | ).click(); 17 | 18 | const inputSelector = 'input[type=file]'; 19 | const input = await findNode(inputSelector); 20 | 21 | await setFileInputData(inputSelector, input, image); 22 | 23 | await sendReceipt(storageIds); 24 | 25 | input.dispatchEvent(new Event('change', {bubbles: true})); 26 | } 27 | 28 | function init() { 29 | initSearch(search, engine, taskId); 30 | } 31 | 32 | if (runOnce('search')) { 33 | init(); 34 | } 35 | -------------------------------------------------------------------------------- /src/engines/jpDesign.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'jpDesign'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | // wait for page 8 | await findNode('#photo > img'); 9 | 10 | const inputSelector = '#ImageFile'; 11 | const input = await findNode(inputSelector); 12 | 13 | await setFileInputData(inputSelector, input, image); 14 | 15 | input.dispatchEvent(new Event('change')); 16 | 17 | await findNode('#photo_image'); 18 | 19 | await sendReceipt(storageIds); 20 | 21 | (await findNode('#searchForm')).removeAttribute('target'); 22 | (await findNode('.action input[type=submit]')).click(); 23 | } 24 | 25 | function init() { 26 | initSearch(search, engine, taskId); 27 | } 28 | 29 | if (runOnce('search')) { 30 | init(); 31 | } 32 | -------------------------------------------------------------------------------- /src/engines/kagi.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {initSearch, setFileInputData, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'kagi'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('.img_search_state_checkbox_label')).click(); 8 | 9 | if (search.assetType === 'image') { 10 | const inputSelector = 'input[type="file"]'; 11 | const input = await findNode(inputSelector); 12 | 13 | await setFileInputData(inputSelector, input, image); 14 | 15 | await sendReceipt(storageIds); 16 | 17 | input.dispatchEvent(new Event('change')); 18 | 19 | // Upload image on mobile 20 | const button = document.querySelector( 21 | '.iu_form_box form:not([hidden]) button[type="submit"]' 22 | ); 23 | if (button) { 24 | function submit() { 25 | button.click(); 26 | } 27 | 28 | const intervalId = window.setInterval(submit, 2000); 29 | window.setTimeout(function () { 30 | window.clearInterval(intervalId); 31 | }, 10000); 32 | 33 | window.setTimeout(submit, 600); 34 | } 35 | } else { 36 | const input = await findNode('.iu_url_search_box form input#url'); 37 | 38 | input.value = image.imageUrl; 39 | 40 | await sendReceipt(storageIds); 41 | 42 | (await findNode('.iu_url_search_box form button')).click(); 43 | } 44 | } 45 | 46 | function init() { 47 | initSearch(search, engine, taskId); 48 | } 49 | 50 | if (runOnce('search')) { 51 | init(); 52 | } 53 | -------------------------------------------------------------------------------- /src/engines/lenso.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'lenso'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | if (search.assetType === 'image') { 8 | const inputSelector = 'input[type=file]'; 9 | const input = await findNode(inputSelector); 10 | 11 | await setFileInputData(inputSelector, input, image); 12 | 13 | await sendReceipt(storageIds); 14 | 15 | input.dispatchEvent(new Event('change')); 16 | 17 | const modal = await findNode('.manage-consents-modal', { 18 | timeout: 10000, 19 | throwError: false 20 | }); 21 | 22 | if (modal) { 23 | ( 24 | await findNode('label[for="privacy-policy"]', {rootNode: modal}) 25 | ).click(); 26 | 27 | ( 28 | await findNode('button.perfom-search-btn:not([disabled])', { 29 | rootNode: modal, 30 | observerOptions: {attributes: true} 31 | }) 32 | ).click(); 33 | } 34 | } else { 35 | await sendReceipt(storageIds); 36 | 37 | ( 38 | await findNode('.search-by-url .cta-btn', { 39 | timeout: 10000, 40 | throwError: false 41 | }) 42 | ).click(); 43 | } 44 | } 45 | 46 | function init() { 47 | initSearch(search, engine, taskId); 48 | } 49 | 50 | if (runOnce('search')) { 51 | init(); 52 | } 53 | -------------------------------------------------------------------------------- /src/engines/lexica.js: -------------------------------------------------------------------------------- 1 | import {findNode, executeScriptMainContext, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'lexica'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | await executeScriptMainContext({func: 'lexicaOverrideEventDispatch'}); 8 | 9 | (await findNode('input#main-search')).nextElementSibling.click(); 10 | 11 | const inputSelector = 'input[type="file"]'; 12 | const input = await findNode(inputSelector); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | input.dispatchEvent(new Event('change')); 19 | } 20 | 21 | function init() { 22 | initSearch(search, engine, taskId); 23 | } 24 | 25 | if (runOnce('search')) { 26 | init(); 27 | } 28 | -------------------------------------------------------------------------------- /src/engines/lykdat.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'lykdat'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = '.top-search-image-box input[type=file]'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | await sendReceipt(storageIds); 13 | 14 | input.dispatchEvent(new Event('change', {bubbles: true})); 15 | 16 | (await findNode('.finisher button')).click(); 17 | } 18 | 19 | function init() { 20 | initSearch(search, engine, taskId); 21 | } 22 | 23 | if (runOnce('search')) { 24 | init(); 25 | } 26 | -------------------------------------------------------------------------------- /src/engines/madridMonitor.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | import {targetEnv} from 'utils/config'; 4 | 5 | const engine = 'madridMonitor'; 6 | 7 | async function search({session, search, image, storageIds}) { 8 | (await findNode('#imageModeLink')).click(); 9 | 10 | await findNode('.fileTarget-open'); 11 | 12 | const inputSelector = 'input#imageFileUpload'; 13 | const input = await findNode(inputSelector); 14 | 15 | await setFileInputData(inputSelector, input, image, { 16 | patchInput: targetEnv === 'safari' 17 | }); 18 | 19 | input.dispatchEvent(new Event('change')); 20 | 21 | // wait for image to load 22 | await findNode('.ui-icon-pencil'); 23 | 24 | // select Concept strategy 25 | (await findNode('a[data-hasqtip="82"]')).click(); 26 | 27 | // deselect all image types 28 | (await findNode('a[data-hasqtip="88"]')).click(); 29 | (await findNode('a[data-hasqtip="89"]')).click(); 30 | 31 | await sendReceipt(storageIds); 32 | 33 | window.setTimeout(async () => { 34 | ( 35 | await findNode('#image_search_container .searchButtonContainer a') 36 | ).click(); 37 | }, 100); 38 | } 39 | 40 | function init() { 41 | initSearch(search, engine, taskId); 42 | } 43 | 44 | if (runOnce('search')) { 45 | init(); 46 | } 47 | -------------------------------------------------------------------------------- /src/engines/nzTrademark.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'nzTrademark'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('#logoCheckButton')).click(); 8 | 9 | const inputSelector = '#imageSearchDialogUploadButton'; 10 | const input = await findNode(inputSelector); 11 | 12 | await setFileInputData(inputSelector, input, image); 13 | 14 | input.dispatchEvent(new Event('change')); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | ( 19 | await findNode('#imageSearchDialogNextButton:not([disabled])', { 20 | observerOptions: {attributes: true, attributeFilter: ['disabled']} 21 | }) 22 | ).click(); 23 | 24 | const features = await findNode( 25 | '#imageSearchDialogMainStep1_2:not(.hidden)', 26 | { 27 | timeout: 30000, 28 | throwError: false, 29 | observerOptions: {attributes: true, attributeFilter: ['class']} 30 | } 31 | ); 32 | if (features) { 33 | (await findNode('#imageSearchDialogSkipButton')).click(); 34 | } 35 | } 36 | 37 | function init() { 38 | initSearch(search, engine, taskId); 39 | } 40 | 41 | if (runOnce('search')) { 42 | init(); 43 | } 44 | -------------------------------------------------------------------------------- /src/engines/pimeyes.js: -------------------------------------------------------------------------------- 1 | import {findNode, processNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'pimeyes'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = '.upload-file input#file-input'; 8 | 9 | processNode(inputSelector, function (node) { 10 | node.addEventListener('click', ev => ev.preventDefault(), { 11 | capture: true, 12 | once: true 13 | }); 14 | }); 15 | 16 | (await findNode('.upload-bar button[aria-label="upload photo" i]')).click(); 17 | 18 | const input = await findNode(inputSelector); 19 | 20 | await setFileInputData(inputSelector, input, image); 21 | 22 | await sendReceipt(storageIds); 23 | 24 | input.dispatchEvent(new Event('change')); 25 | 26 | const searchButton = await findNode('.start-search-inner > button', { 27 | throwError: false 28 | }); 29 | 30 | // button is missing when no faces were detected 31 | if (searchButton) { 32 | if (searchButton.classList.contains('disabled')) { 33 | await findNode('.permissions input[type=checkbox]'); 34 | 35 | for (const checkbox of document.querySelectorAll( 36 | '.permissions input[type=checkbox]' 37 | )) { 38 | if (!checkbox.checked) { 39 | checkbox.click(); 40 | } 41 | } 42 | 43 | ( 44 | await findNode('.start-search-inner > button:not(.disabled)', { 45 | observerOptions: {attributes: true, attributeFilter: ['class']} 46 | }) 47 | ).click(); 48 | } else { 49 | searchButton.click(); 50 | } 51 | } 52 | } 53 | 54 | function init() { 55 | initSearch(search, engine, taskId); 56 | } 57 | 58 | if (runOnce('search')) { 59 | init(); 60 | } 61 | -------------------------------------------------------------------------------- /src/engines/pixta.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'pixta'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const mobileLayout = window.matchMedia('(max-width: 768px)').matches; 8 | 9 | if (mobileLayout) { 10 | (await findNode('div.global-header-sp i.fa-search')).click(); 11 | 12 | ( 13 | await findNode('.global-search-form-sp__search-by-image-btn i.fa-camera') 14 | ).click(); 15 | } else { 16 | ( 17 | await findNode('div.search-image-button.search-image-button--top') 18 | ).click(); 19 | } 20 | 21 | const inputSelector = mobileLayout 22 | ? 'input#upload-photo[type="file"]' 23 | : 'input#image[type="file"]'; 24 | const input = await findNode(inputSelector); 25 | 26 | await setFileInputData(inputSelector, input, image); 27 | 28 | await sendReceipt(storageIds); 29 | 30 | input.dispatchEvent(new Event('change')); 31 | } 32 | 33 | function init() { 34 | initSearch(search, engine, taskId); 35 | } 36 | 37 | if (runOnce('search')) { 38 | init(); 39 | } 40 | -------------------------------------------------------------------------------- /src/engines/pond5.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'pond5'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | ( 8 | await findNode('div#main form.SiteSearch button.js-reverseSearchInputIcon') 9 | ).click(); 10 | 11 | const inputSelector = 'input#vissimFileSelector'; 12 | const input = await findNode(inputSelector); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | input.dispatchEvent(new Event('change', {bubbles: true})); 19 | } 20 | 21 | function init() { 22 | initSearch(search, engine, taskId); 23 | } 24 | 25 | if (runOnce('search')) { 26 | init(); 27 | } 28 | -------------------------------------------------------------------------------- /src/engines/qihoo.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'qihoo'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | let inputSelector; 8 | let input; 9 | if (document.head.querySelector('meta[name^="apple-mobile"]')) { 10 | // mobile 11 | inputSelector = '.g-header-st-form input[type="file"]'; 12 | input = await findNode(inputSelector, {timeout: 120000}); 13 | } else { 14 | // desktop 15 | inputSelector = 'input#stUpload'; 16 | input = await findNode(inputSelector); 17 | } 18 | 19 | await setFileInputData(inputSelector, input, image); 20 | 21 | await sendReceipt(storageIds); 22 | 23 | input.dispatchEvent(new Event('change')); 24 | } 25 | 26 | function init() { 27 | initSearch(search, engine, taskId); 28 | } 29 | 30 | if (runOnce('search')) { 31 | init(); 32 | } 33 | -------------------------------------------------------------------------------- /src/engines/repostSleuth.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'repostSleuth'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | if (search.assetType === 'image') { 8 | const inputSelector = 'input[type=file]'; 9 | const input = await findNode(inputSelector); 10 | 11 | await setFileInputData(inputSelector, input, image); 12 | 13 | await sendReceipt(storageIds); 14 | 15 | input.dispatchEvent(new Event('change')); 16 | } else { 17 | const input = await findNode( 18 | '//input[preceding-sibling::label[contains(., "Image URL")]]', 19 | {selectorType: 'xpath'} 20 | ); 21 | 22 | input.value = image.imageUrl; 23 | 24 | await sendReceipt(storageIds); 25 | 26 | input.dispatchEvent(new Event('input')); 27 | } 28 | 29 | const button = await findNode( 30 | '.v-main button.primary:not(.v-btn--disabled)', 31 | {observerOptions: {attributes: true, attributeFilter: ['class']}} 32 | ); 33 | 34 | window.setTimeout(() => button.click(), 300); 35 | } 36 | 37 | function init() { 38 | initSearch(search, engine, taskId); 39 | } 40 | 41 | if (runOnce('search')) { 42 | init(); 43 | } 44 | -------------------------------------------------------------------------------- /src/engines/saucenao.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'saucenao'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const autoSubmit = await findNode('input#auto-cb'); 8 | if (!autoSubmit.checked) { 9 | autoSubmit.click(); 10 | } 11 | 12 | if (search.assetType === 'image') { 13 | const inputSelector = 'input#fileInput'; 14 | const input = await findNode(inputSelector); 15 | 16 | await setFileInputData(inputSelector, input, image); 17 | 18 | await sendReceipt(storageIds); 19 | 20 | input.dispatchEvent(new Event('change')); 21 | } else { 22 | const input = await findNode('input#urlInput'); 23 | 24 | await sendReceipt(storageIds); 25 | 26 | input.value = image.imageUrl; 27 | 28 | // input.blur() is only dispatched when the tab is active 29 | input.dispatchEvent(new Event('blur')); 30 | } 31 | } 32 | 33 | function init() { 34 | initSearch(search, engine, taskId); 35 | } 36 | 37 | if (runOnce('search')) { 38 | init(); 39 | } 40 | -------------------------------------------------------------------------------- /src/engines/shein.js: -------------------------------------------------------------------------------- 1 | import {findNode, makeDocumentVisible, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'shein'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('.search-input_upload span.sui-icon-common__wrap')).click(); 8 | 9 | const inputSelector = '.search-upload input[type="file"]'; 10 | const input = await findNode(inputSelector); 11 | 12 | await setFileInputData(inputSelector, input, image); 13 | 14 | await sendReceipt(storageIds); 15 | 16 | input.dispatchEvent(new Event('change')); 17 | } 18 | 19 | function init() { 20 | makeDocumentVisible(); 21 | initSearch(search, engine, taskId); 22 | } 23 | 24 | if (runOnce('search')) { 25 | init(); 26 | } 27 | -------------------------------------------------------------------------------- /src/engines/shutterstock.js: -------------------------------------------------------------------------------- 1 | import {findNode, isMobile, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'shutterstock'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | if (await isMobile()) { 8 | // some elements are loaded only after the first user interaction 9 | window.dispatchEvent(new Event('touchstart')); 10 | } 11 | 12 | (await findNode('.MuiSelect-select[aria-label*="image"]')).dispatchEvent( 13 | new MouseEvent('mousedown', {bubbles: true}) 14 | ); 15 | 16 | (await findNode('li[data-value="reverseImageSearch"]')).click(); 17 | 18 | const inputSelector = 'input[type="file"]'; 19 | const input = await findNode(inputSelector); 20 | 21 | await setFileInputData(inputSelector, input, image); 22 | 23 | await sendReceipt(storageIds); 24 | 25 | input.dispatchEvent(new Event('change', {bubbles: true})); 26 | } 27 | 28 | function init() { 29 | initSearch(search, engine, taskId); 30 | } 31 | 32 | if (runOnce('search')) { 33 | init(); 34 | } 35 | -------------------------------------------------------------------------------- /src/engines/sogou.js: -------------------------------------------------------------------------------- 1 | import {findNode, isMobile, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'sogou'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | if (!(await isMobile())) { 8 | (await findNode('a#cameraIco', {timeout: 120000})).click(); 9 | } 10 | 11 | const inputSelector = 'input[type="file"]'; 12 | const input = await findNode(inputSelector); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | input.dispatchEvent(new Event('change')); 19 | } 20 | 21 | function init() { 22 | initSearch(search, engine, taskId); 23 | } 24 | 25 | if (runOnce('search')) { 26 | init(); 27 | } 28 | -------------------------------------------------------------------------------- /src/engines/stocksy.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'stocksy'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | (await findNode('button[name="triggerVisualSearch"]')).click(); 8 | 9 | const inputSelector = 'input#vs-file'; 10 | const input = await findNode(inputSelector); 11 | 12 | await setFileInputData(inputSelector, input, image); 13 | 14 | await sendReceipt(storageIds); 15 | 16 | input.dispatchEvent(new Event('change', {bubbles: true})); 17 | } 18 | 19 | function init() { 20 | initSearch(search, engine, taskId); 21 | } 22 | 23 | if (runOnce('search')) { 24 | init(); 25 | } 26 | -------------------------------------------------------------------------------- /src/engines/taobao.js: -------------------------------------------------------------------------------- 1 | import {findNode, executeScriptMainContext, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'taobao'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | await executeScriptMainContext({func: 'taobaoPatchContext'}); 8 | 9 | (await findNode('div.image-search-icon-wrapper', {timeout: 120000})).click(); 10 | 11 | const inputSelector = 'input[type="file"]'; 12 | const input = await findNode(inputSelector); 13 | 14 | await setFileInputData(inputSelector, input, image); 15 | 16 | await sendReceipt(storageIds); 17 | 18 | window.setTimeout(() => { 19 | input.dispatchEvent(new Event('change', {bubbles: true})); 20 | }, 100); 21 | 22 | ( 23 | await findNode('div#image-search-upload-button.upload-button-active', { 24 | observerOptions: {attributes: true, attributeFilter: ['class']} 25 | }) 26 | ).click(); 27 | } 28 | 29 | function init() { 30 | initSearch(search, engine, taskId); 31 | } 32 | 33 | if (runOnce('search')) { 34 | init(); 35 | } 36 | -------------------------------------------------------------------------------- /src/engines/tineye.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'tineye'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = 'input#upload-box'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | await sendReceipt(storageIds); 13 | 14 | input.dispatchEvent(new Event('change')); 15 | } 16 | 17 | function init() { 18 | // skip Cloudflare challenge 19 | if ( 20 | !document 21 | .querySelector('noscript') 22 | .textContent.includes('
') 23 | ) { 24 | initSearch(search, engine, taskId); 25 | } 26 | } 27 | 28 | if (runOnce('search')) { 29 | init(); 30 | } 31 | -------------------------------------------------------------------------------- /src/engines/tmview.js: -------------------------------------------------------------------------------- 1 | import {findNode, processNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'tmview'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | // previous search may be cached 8 | let removeImage = true; 9 | processNode( 10 | '.image-remove button', 11 | function (node) { 12 | if (node && removeImage) { 13 | node.click(); 14 | } 15 | }, 16 | {throwError: false} 17 | ); 18 | 19 | const inputSelector = 'input[type=file]'; 20 | const input = await findNode(inputSelector); 21 | 22 | removeImage = false; 23 | await setFileInputData(inputSelector, input, image); 24 | 25 | input.dispatchEvent(new Event('change', {bubbles: true})); 26 | 27 | await findNode('.image-remove button'); 28 | 29 | await sendReceipt(storageIds); 30 | 31 | (await findNode('button[data-test-id=search-button]')).click(); 32 | } 33 | 34 | function init() { 35 | initSearch(search, engine, taskId); 36 | } 37 | 38 | if (runOnce('search')) { 39 | init(); 40 | } 41 | -------------------------------------------------------------------------------- /src/engines/whatanime.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'whatanime'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | const inputSelector = 'input[type=file]'; 8 | const input = await findNode(inputSelector); 9 | 10 | await setFileInputData(inputSelector, input, image); 11 | 12 | await sendReceipt(storageIds); 13 | 14 | input.dispatchEvent(new Event('change', {bubbles: true})); 15 | } 16 | 17 | function init() { 18 | initSearch(search, engine, taskId); 19 | } 20 | 21 | if (runOnce('search')) { 22 | init(); 23 | } 24 | -------------------------------------------------------------------------------- /src/engines/wildberries.js: -------------------------------------------------------------------------------- 1 | import {findNode, runOnce, sleep} from 'utils/common'; 2 | import {setFileInputData, initSearch, sendReceipt} from 'utils/engines'; 3 | 4 | const engine = 'wildberries'; 5 | 6 | async function search({session, search, image, storageIds}) { 7 | // wait for search service to load 8 | await sleep(1000); 9 | 10 | const inputSelector = 'input[type=file][data-link*="searchByImage"]'; 11 | const input = await findNode(inputSelector); 12 | 13 | await setFileInputData(inputSelector, input, image); 14 | 15 | await sendReceipt(storageIds); 16 | 17 | input.dispatchEvent(new Event('change')); 18 | } 19 | 20 | function init() { 21 | initSearch(search, engine, taskId); 22 | } 23 | 24 | if (runOnce('search')) { 25 | init(); 26 | } 27 | -------------------------------------------------------------------------------- /src/engines/yandex.js: -------------------------------------------------------------------------------- 1 | import {validateUrl, getContentXHR} from 'utils/app'; 2 | import {makeDocumentVisible, runOnce} from 'utils/common'; 3 | import { 4 | initSearch, 5 | prepareImageForUpload, 6 | sendReceipt, 7 | getValidHostname, 8 | uploadCallback 9 | } from 'utils/engines'; 10 | 11 | const engine = 'yandex'; 12 | 13 | function showResults(xhr) { 14 | if (xhr.status === 413) { 15 | largeImageNotify(engine, '8'); 16 | return; 17 | } 18 | 19 | const params = JSON.parse(xhr.responseText).blocks[0].params.url; 20 | const tabUrl = `https://${getValidHostname()}/images/search?${params}`; 21 | 22 | if (validateUrl(tabUrl)) { 23 | window.location.replace(tabUrl); 24 | } 25 | } 26 | 27 | async function searchApi({image, storageIds} = {}) { 28 | const hostname = getValidHostname(); 29 | const url = 30 | `https://${hostname}/images/touch/search?rpt=imageview&format=json` + 31 | `&request={"blocks":[{"block":"cbir-uploader__get-cbir-id"}]}`; 32 | 33 | const data = new FormData(); 34 | data.append('upfile', image.imageBlob); 35 | 36 | const xhr = getContentXHR(); 37 | xhr.addEventListener('load', function () { 38 | sendReceipt(storageIds); 39 | 40 | uploadCallback(this, showResults, engine); 41 | }); 42 | xhr.open('POST', url); 43 | xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); 44 | xhr.setRequestHeader( 45 | 'Accept', 46 | 'application/json, text/javascript, */*; q=0.01' 47 | ); 48 | xhr.send(data); 49 | } 50 | 51 | async function search({session, search, image, storageIds}) { 52 | image = await prepareImageForUpload({ 53 | image, 54 | engine, 55 | target: 'api' 56 | }); 57 | 58 | await searchApi({image, storageIds}); 59 | } 60 | 61 | function init() { 62 | makeDocumentVisible(); 63 | if (!window.location.pathname.startsWith('/showcaptcha')) { 64 | initSearch(search, engine, taskId); 65 | } 66 | } 67 | 68 | if (runOnce('search')) { 69 | init(); 70 | } 71 | -------------------------------------------------------------------------------- /src/options/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/options/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/search/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/search/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/select/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/select/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | -------------------------------------------------------------------------------- /src/select/pointer.css: -------------------------------------------------------------------------------- 1 | video, 2 | audio { 3 | pointer-events: none !important; 4 | } 5 | 6 | @media (hover: hover) { 7 | * { 8 | cursor: pointer !important; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/storage/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "revisions": { 3 | "local": [ 4 | "ryekyizAg", 5 | "Hy1tD8ANb", 6 | "S1kNNadHZ", 7 | "Bk42MzXdW", 8 | "HJ5MKKGhW", 9 | "ryY8H0EWf", 10 | "BJguWEHcbz", 11 | "S1ebtZ3Uzz", 12 | "BJXdcUXOG", 13 | "SklYTwQOYf", 14 | "S18hLi5tG", 15 | "SJzIWmjKz", 16 | "r1Pvd36nz", 17 | "r1H3rgx1X", 18 | "Syy800KkQ", 19 | "SkwaU8NlX", 20 | "yLciyvS5He", 21 | "ekhOvNiTsF", 22 | "ShjDMM87", 23 | "LX20x6G8l", 24 | "d8CIdomCW", 25 | "d8IK4KCtVm", 26 | "UhWEtK9gMh", 27 | "TshBYj8anA", 28 | "ggrr9C9pgV", 29 | "IMMjiccGj", 30 | "K9Esw2jiQ3", 31 | "20210820184257_support_event_pages", 32 | "20210919175209_add_image_sharing_options", 33 | "20210928090443_add_unsplash", 34 | "20211011114043_configure_engines", 35 | "20211106103932_add_shein", 36 | "20211111071410_add_lykdat", 37 | "20211204124507_add_wildberries", 38 | "20211213014048_add_lastengineaccesscheck", 39 | "20211213191049_add_setcontextmenuevent", 40 | "20220108063511_add_google_lens", 41 | "20220321163741_add_clipboard_support", 42 | "20220505174634_detect_alternative_image_sizes", 43 | "20220516051842_update_search_mode_ids", 44 | "20220516124148_open_images", 45 | "20220814142801_configure_engines", 46 | "20220924083846_add_lexica", 47 | "20230415111029_add_theme_support", 48 | "20230531103023_remove_search_engines", 49 | "20230601115626_add_kagi", 50 | "20230624145626_add_search_engines", 51 | "20230716121432_add_freepik", 52 | "20231009185955_remove_unsplash", 53 | "20231102164602_add_icons8", 54 | "20240514170322_add_appversion", 55 | "20240529183556_update_search_engines", 56 | "20240619180111_add_menuchangeevent", 57 | "20240624161944_remove_search_engines", 58 | "20250102095603_add_lenso.ai", 59 | "20250218121640_remove_google_images", 60 | "20250317134215_add_back_google_images", 61 | "20250413115022_add_unsplash", 62 | "20250511143502_remove_karma_decay" 63 | ], 64 | "session": [ 65 | "20240514122825_initial_version" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/storage/init.js: -------------------------------------------------------------------------------- 1 | import {migrate} from 'wesa'; 2 | 3 | import {isStorageArea} from './storage'; 4 | 5 | async function initStorage({area = 'local', data = null, silent = false} = {}) { 6 | const context = { 7 | getAvailableRevisions: async ({area} = {}) => 8 | ( 9 | await import(/* webpackMode: "eager" */ 'storage/config.json', { 10 | with: {type: 'json'} 11 | }) 12 | ).revisions[area], 13 | getCurrentRevision: async ({area} = {}) => 14 | (await browser.storage[area].get('storageVersion')).storageVersion, 15 | getRevision: async ({area, revision} = {}) => 16 | import( 17 | /* webpackMode: "eager" */ `storage/revisions/${area}/${revision}.js` 18 | ) 19 | }; 20 | 21 | if (area === 'local') { 22 | await migrateLegacyStorage(); 23 | } 24 | 25 | return migrate(context, {area, data, silent}); 26 | } 27 | 28 | async function migrateLegacyStorage() { 29 | if (await isStorageArea({area: 'sync'})) { 30 | const {storageVersion: syncVersion} = 31 | await browser.storage.sync.get('storageVersion'); 32 | if (syncVersion && syncVersion.length < 14) { 33 | const {storageVersion: localVersion} = 34 | await browser.storage.local.get('storageVersion'); 35 | 36 | if (!localVersion || localVersion.length < 14) { 37 | const syncData = await browser.storage.sync.get(null); 38 | await browser.storage.local.clear(); 39 | await browser.storage.local.set(syncData); 40 | await browser.storage.sync.clear(); 41 | } 42 | } 43 | } 44 | } 45 | 46 | export {initStorage}; 47 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20210820184257_support_event_pages.js: -------------------------------------------------------------------------------- 1 | const message = 'Support event pages'; 2 | 3 | const revision = '20210820184257_support_event_pages'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | 8 | changes.taskRegistry = {lastTaskStart: 0, tabs: {}, tasks: {}}; 9 | changes.storageRegistry = {}; 10 | changes.lastStorageCleanup = 0; 11 | 12 | changes.storageVersion = revision; 13 | return browser.storage.local.set(changes); 14 | } 15 | 16 | export {message, revision, upgrade}; 17 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20210919175209_add_image_sharing_options.js: -------------------------------------------------------------------------------- 1 | const message = 'Add image sharing options'; 2 | 3 | const revision = '20210919175209_add_image_sharing_options'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | shareImageContextMenu: true, 8 | shareImageAction: true, 9 | convertSharedImage: true 10 | }; 11 | 12 | changes.storageVersion = revision; 13 | return browser.storage.local.set(changes); 14 | } 15 | 16 | export {message, revision, upgrade}; 17 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20210928090443_add_unsplash.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Unsplash'; 2 | 3 | const revision = '20210928090443_add_unsplash'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['unsplash']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20211011114043_configure_engines.js: -------------------------------------------------------------------------------- 1 | import {targetEnv} from 'utils/config'; 2 | 3 | const message = 'Configure engines'; 4 | 5 | const revision = '20211011114043_configure_engines'; 6 | 7 | async function upgrade() { 8 | const changes = {}; 9 | 10 | if (targetEnv === 'safari') { 11 | const {engines, disabledEngines} = await browser.storage.local.get([ 12 | 'engines', 13 | 'disabledEngines' 14 | ]); 15 | 16 | const restoreEngines = ['iqdb', 'jpDesign']; 17 | 18 | changes.engines = engines.concat(restoreEngines); 19 | changes.disabledEngines = disabledEngines.concat(restoreEngines); 20 | } 21 | 22 | changes.storageVersion = revision; 23 | return browser.storage.local.set(changes); 24 | } 25 | 26 | export {message, revision, upgrade}; 27 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20211106103932_add_shein.js: -------------------------------------------------------------------------------- 1 | const message = 'Add SHEIN'; 2 | 3 | const revision = '20211106103932_add_shein'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['shein']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20211111071410_add_lykdat.js: -------------------------------------------------------------------------------- 1 | const message = 'Add LykDat'; 2 | 3 | const revision = '20211111071410_add_lykdat'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['lykdat']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20211204124507_add_wildberries.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Wildberries'; 2 | 3 | const revision = '20211204124507_add_wildberries'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['wildberries']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20211213014048_add_lastengineaccesscheck.js: -------------------------------------------------------------------------------- 1 | const message = 'Add lastEngineAccessCheck'; 2 | 3 | const revision = '20211213014048_add_lastengineaccesscheck'; 4 | 5 | async function upgrade() { 6 | const changes = {lastEngineAccessCheck: 0}; 7 | 8 | changes.storageVersion = revision; 9 | return browser.storage.local.set(changes); 10 | } 11 | 12 | export {message, revision, upgrade}; 13 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20211213191049_add_setcontextmenuevent.js: -------------------------------------------------------------------------------- 1 | const message = 'Add setContextMenuEvent'; 2 | 3 | const revision = '20211213191049_add_setcontextmenuevent'; 4 | 5 | async function upgrade() { 6 | const changes = {setContextMenuEvent: 0}; 7 | 8 | changes.storageVersion = revision; 9 | return browser.storage.local.set(changes); 10 | } 11 | 12 | export {message, revision, upgrade}; 13 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220108063511_add_google_lens.js: -------------------------------------------------------------------------------- 1 | import {isMobile} from 'utils/common'; 2 | import {targetEnv} from 'utils/config'; 3 | 4 | const message = 'Add Google Lens'; 5 | 6 | const revision = '20220108063511_add_google_lens'; 7 | 8 | async function upgrade() { 9 | const changes = {}; 10 | const {engines, disabledEngines} = await browser.storage.local.get([ 11 | 'engines', 12 | 'disabledEngines' 13 | ]); 14 | if (!(targetEnv === 'safari' && (await isMobile()))) { 15 | const newEngines = ['googleLens']; 16 | 17 | if (targetEnv === 'samsung') { 18 | engines.push('mailru'); 19 | disabledEngines.push('mailru'); 20 | } 21 | 22 | changes.engines = engines.concat(newEngines); 23 | changes.disabledEngines = disabledEngines.concat(newEngines); 24 | } 25 | 26 | changes.storageVersion = revision; 27 | return browser.storage.local.set(changes); 28 | } 29 | 30 | export {message, revision, upgrade}; 31 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220321163741_add_clipboard_support.js: -------------------------------------------------------------------------------- 1 | const message = 'Add clipboard support'; 2 | 3 | const revision = '20220321163741_add_clipboard_support'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | autoPasteAction: true, 8 | confirmPaste: true 9 | }; 10 | 11 | changes.storageVersion = revision; 12 | return browser.storage.local.set(changes); 13 | } 14 | 15 | export {message, revision, upgrade}; 16 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220505174634_detect_alternative_image_sizes.js: -------------------------------------------------------------------------------- 1 | const message = 'Detect alternative image sizes'; 2 | 3 | const revision = '20220505174634_detect_alternative_image_sizes'; 4 | 5 | async function upgrade() { 6 | const changes = {detectAltImageDimension: false}; 7 | 8 | changes.storageVersion = revision; 9 | return browser.storage.local.set(changes); 10 | } 11 | 12 | export {message, revision, upgrade}; 13 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220516051842_update_search_mode_ids.js: -------------------------------------------------------------------------------- 1 | const message = 'Update search mode IDs'; 2 | 3 | const revision = '20220516051842_update_search_mode_ids'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | 8 | const {searchModeContextMenu, searchModeAction} = 9 | await browser.storage.local.get([ 10 | 'searchModeContextMenu', 11 | 'searchModeAction' 12 | ]); 13 | 14 | const searchModes = { 15 | select: 'selectUrl', 16 | selectUpload: 'selectImage', 17 | upload: 'browse' 18 | }; 19 | 20 | if (searchModes[searchModeContextMenu]) { 21 | changes.searchModeContextMenu = searchModes[searchModeContextMenu]; 22 | } 23 | if (searchModes[searchModeAction]) { 24 | changes.searchModeAction = searchModes[searchModeAction]; 25 | } 26 | 27 | changes.storageVersion = revision; 28 | return browser.storage.local.set(changes); 29 | } 30 | 31 | export {message, revision, upgrade}; 32 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220516124148_open_images.js: -------------------------------------------------------------------------------- 1 | const message = 'Open images'; 2 | 3 | const revision = '20220516124148_open_images'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | viewImageContextMenu: true, 8 | viewImageAction: true, 9 | viewImageUseViewer: true 10 | }; 11 | 12 | changes.storageVersion = revision; 13 | return browser.storage.local.set(changes); 14 | } 15 | 16 | export {message, revision, upgrade}; 17 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220814142801_configure_engines.js: -------------------------------------------------------------------------------- 1 | import {isMobile} from 'utils/common'; 2 | import {targetEnv} from 'utils/config'; 3 | 4 | const message = 'Configure engines'; 5 | 6 | const revision = '20220814142801_configure_engines'; 7 | 8 | async function upgrade() { 9 | const changes = {}; 10 | 11 | if (targetEnv === 'safari' && (await isMobile())) { 12 | const {engines, disabledEngines} = await browser.storage.local.get([ 13 | 'engines', 14 | 'disabledEngines' 15 | ]); 16 | 17 | const newEngines = ['googleLens']; 18 | 19 | changes.engines = engines.concat(newEngines); 20 | changes.disabledEngines = disabledEngines.concat(newEngines); 21 | } 22 | 23 | changes.storageVersion = revision; 24 | return browser.storage.local.set(changes); 25 | } 26 | 27 | export {message, revision, upgrade}; 28 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20220924083846_add_lexica.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Lexica'; 2 | 3 | const revision = '20220924083846_add_lexica'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['lexica']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20230415111029_add_theme_support.js: -------------------------------------------------------------------------------- 1 | import {getDayPrecisionEpoch} from 'utils/common'; 2 | 3 | const message = 'Add theme support'; 4 | 5 | const revision = '20230415111029_add_theme_support'; 6 | 7 | async function upgrade() { 8 | const changes = { 9 | appTheme: 'auto', // auto, light, dark 10 | showContribPage: true, 11 | showEngineIcons: true, 12 | contribPageLastAutoOpen: 0, 13 | pinActionToolbarViewImage: true, 14 | pinActionToolbarShareImage: false, 15 | pinActionToolbarOptions: false, 16 | pinActionToolbarContribute: true 17 | }; 18 | 19 | const {installTime, searchCount} = await browser.storage.local.get([ 20 | 'installTime', 21 | 'searchCount' 22 | ]); 23 | changes.installTime = getDayPrecisionEpoch(installTime); 24 | changes.useCount = searchCount; 25 | 26 | await browser.storage.local.remove([ 27 | 'searchCount', 28 | 'viewImageAction', 29 | 'shareImageAction' 30 | ]); 31 | 32 | changes.storageVersion = revision; 33 | return browser.storage.local.set(changes); 34 | } 35 | 36 | export {message, revision, upgrade}; 37 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20230531103023_remove_search_engines.js: -------------------------------------------------------------------------------- 1 | const message = 'Remove search engines'; 2 | 3 | const revision = '20230531103023_remove_search_engines'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | const removeEngines = ['jingdong', 'mailru']; 13 | 14 | changes.engines = engines.filter(function (item) { 15 | return !removeEngines.includes(item); 16 | }); 17 | changes.disabledEngines = disabledEngines.filter(function (item) { 18 | return !removeEngines.includes(item); 19 | }); 20 | 21 | changes.storageVersion = revision; 22 | return browser.storage.local.set(changes); 23 | } 24 | 25 | export {message, revision, upgrade}; 26 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20230601115626_add_kagi.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Kagi'; 2 | 3 | const revision = '20230601115626_add_kagi'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['kagi']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20230624145626_add_search_engines.js: -------------------------------------------------------------------------------- 1 | const message = 'Add search engines'; 2 | 3 | const revision = '20230624145626_add_search_engines'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = [ 12 | 'haveibeentrained', 13 | 'enterpix', 14 | 'immerse', 15 | 'clipretrieval' 16 | ]; 17 | 18 | changes.engines = engines.concat(newEngines); 19 | changes.disabledEngines = disabledEngines.concat(newEngines); 20 | 21 | changes.storageVersion = revision; 22 | return browser.storage.local.set(changes); 23 | } 24 | 25 | export {message, revision, upgrade}; 26 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20230716121432_add_freepik.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Freepik'; 2 | 3 | const revision = '20230716121432_add_freepik'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['freepik']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20231009185955_remove_unsplash.js: -------------------------------------------------------------------------------- 1 | const message = 'Remove Unsplash'; 2 | 3 | const revision = '20231009185955_remove_unsplash'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | const removeEngines = ['unsplash']; 13 | 14 | changes.engines = engines.filter(function (item) { 15 | return !removeEngines.includes(item); 16 | }); 17 | changes.disabledEngines = disabledEngines.filter(function (item) { 18 | return !removeEngines.includes(item); 19 | }); 20 | 21 | changes.storageVersion = revision; 22 | return browser.storage.local.set(changes); 23 | } 24 | 25 | export {message, revision, upgrade}; 26 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20231102164602_add_icons8.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Icons8'; 2 | 3 | const revision = '20231102164602_add_icons8'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['icons8']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20240514170322_add_appversion.js: -------------------------------------------------------------------------------- 1 | const message = 'Add appVersion'; 2 | 3 | const revision = '20240514170322_add_appversion'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | appVersion: '', 8 | menuItems: [], 9 | privateMenuItems: [] 10 | }; 11 | 12 | changes.storageVersion = revision; 13 | return browser.storage.local.set(changes); 14 | } 15 | 16 | export {message, revision, upgrade}; 17 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20240529183556_update_search_engines.js: -------------------------------------------------------------------------------- 1 | const message = 'Update search engines'; 2 | 3 | const revision = '20240529183556_update_search_engines'; 4 | 5 | async function upgrade(context) { 6 | const changes = {}; 7 | 8 | const {engines, disabledEngines} = await browser.storage.local.get([ 9 | 'engines', 10 | 'disabledEngines' 11 | ]); 12 | 13 | // Move Google Lens to the top 14 | engines.splice(0, 0, engines.splice(engines.indexOf('googleLens'), 1)[0]); 15 | 16 | // Enable Google Lens if Google Images is enabled 17 | if ( 18 | disabledEngines.includes('googleLens') && 19 | !disabledEngines.includes('google') 20 | ) { 21 | disabledEngines.splice(disabledEngines.indexOf('googleLens'), 1); 22 | } 23 | 24 | // Disable Google Images for new installations 25 | if (context.install) { 26 | disabledEngines.push('google'); 27 | } 28 | 29 | changes.engines = engines; 30 | changes.disabledEngines = disabledEngines; 31 | 32 | changes.storageVersion = revision; 33 | return browser.storage.local.set(changes); 34 | } 35 | 36 | export {message, revision, upgrade}; 37 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20240619180111_add_menuchangeevent.js: -------------------------------------------------------------------------------- 1 | const message = 'Add menuChangeEvent'; 2 | 3 | const revision = '20240619180111_add_menuchangeevent'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | menuChangeEvent: 0, 8 | privateMenuChangeEvent: 0 9 | }; 10 | 11 | await browser.storage.local.remove('setContextMenuEvent'); 12 | 13 | changes.storageVersion = revision; 14 | return browser.storage.local.set(changes); 15 | } 16 | 17 | export {message, revision, upgrade}; 18 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20240624161944_remove_search_engines.js: -------------------------------------------------------------------------------- 1 | const message = 'Remove search engines'; 2 | 3 | const revision = '20240624161944_remove_search_engines'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | const removeEngines = [ 13 | 'haveibeentrained', 14 | 'enterpix', 15 | 'immerse', 16 | 'clipretrieval' 17 | ]; 18 | 19 | changes.engines = engines.filter(function (item) { 20 | return !removeEngines.includes(item); 21 | }); 22 | changes.disabledEngines = disabledEngines.filter(function (item) { 23 | return !removeEngines.includes(item); 24 | }); 25 | 26 | changes.storageVersion = revision; 27 | return browser.storage.local.set(changes); 28 | } 29 | 30 | export {message, revision, upgrade}; 31 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20250102095603_add_lenso.ai.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Lenso.ai'; 2 | 3 | const revision = '20250102095603_add_lenso.ai'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngine = 'lenso'; 12 | 13 | const enabledEngineCount = engines.length - disabledEngines.length; 14 | 15 | engines.splice(1, 0, newEngine); 16 | changes.engines = engines; 17 | 18 | if (enabledEngineCount <= 1) { 19 | disabledEngines.push(newEngine); 20 | } 21 | if (enabledEngineCount === 8 && !disabledEngines.includes('alamy')) { 22 | disabledEngines.push('alamy'); 23 | } 24 | changes.disabledEngines = disabledEngines; 25 | 26 | changes.storageVersion = revision; 27 | return browser.storage.local.set(changes); 28 | } 29 | 30 | export {message, revision, upgrade}; 31 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20250218121640_remove_google_images.js: -------------------------------------------------------------------------------- 1 | const message = 'Remove Google Images'; 2 | 3 | const revision = '20250218121640_remove_google_images'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | engines.splice(engines.indexOf('google'), 1); 13 | changes.engines = engines; 14 | 15 | if (disabledEngines.includes('google')) { 16 | disabledEngines.splice(disabledEngines.indexOf('google'), 1); 17 | } else if (disabledEngines.includes('googleLens')) { 18 | disabledEngines.splice(disabledEngines.indexOf('googleLens'), 1); 19 | } 20 | changes.disabledEngines = disabledEngines; 21 | 22 | changes.storageVersion = revision; 23 | return browser.storage.local.set(changes); 24 | } 25 | 26 | export {message, revision, upgrade}; 27 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20250317134215_add_back_google_images.js: -------------------------------------------------------------------------------- 1 | import {targetEnv} from 'utils/config'; 2 | 3 | const message = 'Add back Google Images'; 4 | 5 | const revision = '20250317134215_add_back_google_images'; 6 | 7 | async function upgrade() { 8 | const changes = {}; 9 | const {engines, disabledEngines} = await browser.storage.local.get([ 10 | 'engines', 11 | 'disabledEngines' 12 | ]); 13 | 14 | if (!['chrome', 'opera'].includes(targetEnv)) { 15 | engines.push('googleImages'); 16 | disabledEngines.push('googleImages'); 17 | 18 | changes.engines = engines; 19 | changes.disabledEngines = disabledEngines; 20 | } 21 | 22 | changes.storageVersion = revision; 23 | return browser.storage.local.set(changes); 24 | } 25 | 26 | export {message, revision, upgrade}; 27 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20250413115022_add_unsplash.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Unsplash'; 2 | 3 | const revision = '20250413115022_add_unsplash'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['unsplash']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/20250511143502_remove_karma_decay.js: -------------------------------------------------------------------------------- 1 | const message = 'Remove Karma Decay'; 2 | 3 | const revision = '20250511143502_remove_karma_decay'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | const removeEngines = ['karmaDecay']; 13 | 14 | changes.engines = engines.filter(function (item) { 15 | return !removeEngines.includes(item); 16 | }); 17 | changes.disabledEngines = disabledEngines.filter(function (item) { 18 | return !removeEngines.includes(item); 19 | }); 20 | 21 | changes.storageVersion = revision; 22 | return browser.storage.local.set(changes); 23 | } 24 | 25 | export {message, revision, upgrade}; 26 | -------------------------------------------------------------------------------- /src/storage/revisions/local/BJXdcUXOG.js: -------------------------------------------------------------------------------- 1 | const message = 'Add WhatAnime'; 2 | 3 | const revision = 'BJXdcUXOG'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('whatanime'); 13 | changes.disabledEngines = disabledEngines.concat('whatanime'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/BJguWEHcbz.js: -------------------------------------------------------------------------------- 1 | const message = 'Add installTime and searchCount'; 2 | 3 | const revision = 'BJguWEHcbz'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | changes.installTime = Date.now(); 8 | changes.searchCount = 0; 9 | 10 | changes.storageVersion = revision; 11 | return browser.storage.local.set(changes); 12 | } 13 | 14 | export {message, revision, upgrade}; 15 | -------------------------------------------------------------------------------- /src/storage/revisions/local/Bk42MzXdW.js: -------------------------------------------------------------------------------- 1 | const message = 'Merge searchAllEngines options'; 2 | 3 | const revision = 'Bk42MzXdW'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | 8 | const {searchAllEngines, searchAllEnginesLocation} = 9 | await browser.storage.local.get([ 10 | 'searchAllEngines', 11 | 'searchAllEnginesLocation' 12 | ]); 13 | 14 | await browser.storage.local.remove([ 15 | 'searchAllEngines', 16 | 'searchAllEnginesLocation' 17 | ]); 18 | 19 | if (searchAllEngines) { 20 | changes.searchAllEnginesContextMenu = 21 | searchAllEnginesLocation === 'menu' ? 'main' : 'sub'; 22 | } else { 23 | changes.searchAllEnginesContextMenu = 'false'; 24 | } 25 | 26 | changes.storageVersion = revision; 27 | return browser.storage.local.set(changes); 28 | } 29 | 30 | export {message, revision, upgrade}; 31 | -------------------------------------------------------------------------------- /src/storage/revisions/local/HJ5MKKGhW.js: -------------------------------------------------------------------------------- 1 | const message = 2 | 'Add showInContextMenu, searchAllEnginesAction and searchModeAction options'; 3 | 4 | const revision = 'HJ5MKKGhW'; 5 | 6 | async function upgrade() { 7 | const changes = { 8 | showInContextMenu: true, 9 | searchAllEnginesAction: 'sub', // 'main', 'sub', 'false' 10 | searchModeAction: 'select' // 'select', 'selectUpload', 'capture', 'upload', 'url' 11 | }; 12 | 13 | changes.storageVersion = revision; 14 | return browser.storage.local.set(changes); 15 | } 16 | 17 | export {message, revision, upgrade}; 18 | -------------------------------------------------------------------------------- /src/storage/revisions/local/Hy1tD8ANb.js: -------------------------------------------------------------------------------- 1 | const message = 'Add imgFullParse option'; 2 | 3 | const revision = 'Hy1tD8ANb'; 4 | 5 | async function upgrade() { 6 | const changes = {imgFullParse: false}; 7 | 8 | changes.storageVersion = revision; 9 | return browser.storage.local.set(changes); 10 | } 11 | 12 | export {message, revision, upgrade}; 13 | -------------------------------------------------------------------------------- /src/storage/revisions/local/IMMjiccGj.js: -------------------------------------------------------------------------------- 1 | const message = 'Hide unavailable engines'; 2 | 3 | const revision = 'IMMjiccGj'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const missingEngines = [ 12 | 'wayfair', 13 | 'birchlane', 14 | 'allmodern', 15 | 'jossandmain', 16 | 'perigold' 17 | ]; 18 | 19 | changes.engines = engines.filter(function (item) { 20 | return !missingEngines.includes(item); 21 | }); 22 | changes.disabledEngines = disabledEngines.filter(function (item) { 23 | return !missingEngines.includes(item); 24 | }); 25 | 26 | changes.storageVersion = revision; 27 | return browser.storage.local.set(changes); 28 | } 29 | 30 | export {message, revision, upgrade}; 31 | -------------------------------------------------------------------------------- /src/storage/revisions/local/K9Esw2jiQ3.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Reddit Repost Sleuth'; 2 | 3 | const revision = 'K9Esw2jiQ3'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('repostSleuth'); 13 | changes.disabledEngines = disabledEngines.concat('repostSleuth'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/LX20x6G8l.js: -------------------------------------------------------------------------------- 1 | const message = 'Add trademark and design patent search engines'; 2 | 3 | const revision = 'LX20x6G8l'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = [ 12 | 'esearch', 13 | 'tmview', 14 | 'branddb', 15 | 'madridMonitor', 16 | 'auTrademark', 17 | 'auDesign', 18 | 'nzTrademark', 19 | 'jpDesign' 20 | ]; 21 | 22 | changes.engines = engines.concat(newEngines); 23 | changes.disabledEngines = disabledEngines.concat(newEngines); 24 | 25 | changes.storageVersion = revision; 26 | return browser.storage.local.set(changes); 27 | } 28 | 29 | export {message, revision, upgrade}; 30 | -------------------------------------------------------------------------------- /src/storage/revisions/local/S18hLi5tG.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Iqdb'; 2 | 3 | const revision = 'S18hLi5tG'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('iqdb'); 13 | changes.disabledEngines = disabledEngines.concat('iqdb'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/S1ebtZ3Uzz.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Karma Decay'; 2 | 3 | const revision = 'S1ebtZ3Uzz'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('karmaDecay'); 13 | changes.disabledEngines = disabledEngines.concat('karmaDecay'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/S1kNNadHZ.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Sogou engine'; 2 | 3 | const revision = 'S1kNNadHZ'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('sogou'); 13 | changes.disabledEngines = disabledEngines.concat('sogou'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/SJzIWmjKz.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Ascii2d'; 2 | 3 | const revision = 'SJzIWmjKz'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('ascii2d'); 13 | changes.disabledEngines = disabledEngines.concat('ascii2d'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/ShjDMM87.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Dreamstime, Alamy and 123RF'; 2 | 3 | const revision = 'ShjDMM87'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['dreamstime', 'alamy', '123rf']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/SklYTwQOYf.js: -------------------------------------------------------------------------------- 1 | const message = 'Add SauceNAO'; 2 | 3 | const revision = 'SklYTwQOYf'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('saucenao'); 13 | changes.disabledEngines = disabledEngines.concat('saucenao'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/SkwaU8NlX.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Qihoo'; 2 | 3 | const revision = 'SkwaU8NlX'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('qihoo'); 13 | changes.disabledEngines = disabledEngines.concat('qihoo'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/Syy800KkQ.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Pinterest'; 2 | 3 | const revision = 'Syy800KkQ'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('pinterest'); 13 | changes.disabledEngines = disabledEngines.concat('pinterest'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/TshBYj8anA.js: -------------------------------------------------------------------------------- 1 | import {isAndroid} from 'utils/common'; 2 | import {targetEnv} from 'utils/config'; 3 | 4 | const message = 'Configure search engines'; 5 | 6 | const revision = 'TshBYj8anA'; 7 | 8 | async function upgrade() { 9 | const changes = {}; 10 | 11 | const {installTime} = await browser.storage.local.get('installTime'); 12 | 13 | let {engines, disabledEngines} = await browser.storage.local.get([ 14 | 'engines', 15 | 'disabledEngines' 16 | ]); 17 | 18 | const hiddenEngines = []; 19 | 20 | if (targetEnv === 'samsung') { 21 | engines.splice(engines.indexOf('auDesign'), 0, 'auTrademark'); 22 | disabledEngines.push('auTrademark', 'dreamstime'); 23 | } else if (targetEnv === 'safari') { 24 | hiddenEngines.push('iqdb', 'jpDesign'); 25 | } 26 | 27 | if ((await isAndroid()) || targetEnv === 'safari') { 28 | hiddenEngines.push('jingdong', 'taobao', 'alibabaChina'); 29 | } 30 | 31 | if (hiddenEngines.length) { 32 | engines = engines.filter(function (item) { 33 | return !hiddenEngines.includes(item); 34 | }); 35 | disabledEngines = disabledEngines.filter(function (item) { 36 | return !hiddenEngines.includes(item); 37 | }); 38 | } 39 | 40 | if (Date.now() - installTime < 60000) { 41 | if (targetEnv === 'samsung') { 42 | disabledEngines.push('dreamstime'); 43 | } 44 | 45 | const enabledEngines = ['sogou', 'shutterstock', 'alamy']; 46 | 47 | disabledEngines = disabledEngines.filter(function (item) { 48 | return !enabledEngines.includes(item); 49 | }); 50 | } 51 | 52 | engines.splice( 53 | engines.indexOf('baidu') + 1, 54 | 0, 55 | engines.splice(engines.indexOf('sogou'), 1)[0] 56 | ); 57 | 58 | changes.engines = engines; 59 | changes.disabledEngines = disabledEngines; 60 | 61 | changes.storageVersion = revision; 62 | return browser.storage.local.set(changes); 63 | } 64 | 65 | export {message, revision, upgrade}; 66 | -------------------------------------------------------------------------------- /src/storage/revisions/local/UhWEtK9gMh.js: -------------------------------------------------------------------------------- 1 | const message = 'Add bypassImageHostBlocking'; 2 | 3 | const revision = 'UhWEtK9gMh'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | changes.bypassImageHostBlocking = true; 8 | 9 | changes.storageVersion = revision; 10 | return browser.storage.local.set(changes); 11 | } 12 | 13 | export {message, revision, upgrade}; 14 | -------------------------------------------------------------------------------- /src/storage/revisions/local/d8CIdomCW.js: -------------------------------------------------------------------------------- 1 | const message = 'Add PimEyes, Stocksy United, Pond5, PIXTA and Wayfair'; 2 | 3 | const revision = 'd8CIdomCW'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['pimeyes', 'stocksy', 'pond5', 'pixta', 'wayfair']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/local/d8IK4KCtVm.js: -------------------------------------------------------------------------------- 1 | import {targetEnv} from 'utils/config'; 2 | 3 | const message = 'Configure engines for Samsung Internet'; 4 | 5 | const revision = 'd8IK4KCtVm'; 6 | 7 | async function upgrade() { 8 | const changes = {}; 9 | 10 | if (targetEnv === 'samsung') { 11 | const {engines, disabledEngines} = await browser.storage.local.get([ 12 | 'engines', 13 | 'disabledEngines' 14 | ]); 15 | 16 | const enabledEngines = ['shutterstock', 'dreamstime']; 17 | 18 | const hiddenEngines = [ 19 | 'mailru', 20 | 'jingdong', 21 | 'taobao', 22 | 'alibabaChina', 23 | 'auTrademark' 24 | ]; 25 | 26 | changes.engines = engines.filter(function (item) { 27 | return !hiddenEngines.includes(item); 28 | }); 29 | changes.disabledEngines = disabledEngines.filter(function (item) { 30 | return !enabledEngines.includes(item) && !hiddenEngines.includes(item); 31 | }); 32 | } 33 | 34 | changes.storageVersion = revision; 35 | return browser.storage.local.set(changes); 36 | } 37 | 38 | export {message, revision, upgrade}; 39 | -------------------------------------------------------------------------------- /src/storage/revisions/local/ekhOvNiTsF.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Mail.ru'; 2 | 3 | const revision = 'ekhOvNiTsF'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | 12 | changes.engines = engines.concat('mailru'); 13 | changes.disabledEngines = disabledEngines.concat('mailru'); 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/ggrr9C9pgV.js: -------------------------------------------------------------------------------- 1 | const message = 'Add new search engines'; 2 | 3 | const revision = 'ggrr9C9pgV'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = [ 12 | 'birchlane', 13 | 'allmodern', 14 | 'jossandmain', 15 | 'perigold', 16 | 'ikea' 17 | ]; 18 | 19 | changes.engines = engines.concat(newEngines); 20 | changes.disabledEngines = disabledEngines.concat(newEngines); 21 | 22 | changes.storageVersion = revision; 23 | return browser.storage.local.set(changes); 24 | } 25 | 26 | export {message, revision, upgrade}; 27 | -------------------------------------------------------------------------------- /src/storage/revisions/local/r1H3rgx1X.js: -------------------------------------------------------------------------------- 1 | const message = 'Add stock photo engines'; 2 | 3 | const revision = 'r1H3rgx1X'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | 8 | const {engines, disabledEngines} = await browser.storage.local.get([ 9 | 'engines', 10 | 'disabledEngines' 11 | ]); 12 | const newEngines = [ 13 | 'getty', 14 | 'istock', 15 | 'shutterstock', 16 | 'adobestock', 17 | 'depositphotos' 18 | ]; 19 | 20 | changes.engines = engines.concat(newEngines); 21 | changes.disabledEngines = disabledEngines.concat(newEngines); 22 | 23 | changes.storageVersion = revision; 24 | return browser.storage.local.set(changes); 25 | } 26 | 27 | export {message, revision, upgrade}; 28 | -------------------------------------------------------------------------------- /src/storage/revisions/local/r1Pvd36nz.js: -------------------------------------------------------------------------------- 1 | const message = 'Add searchModeContextMenu'; 2 | 3 | const revision = 'r1Pvd36nz'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | searchModeContextMenu: 'select' // 'select', 'selectUpload', 'capture' 8 | }; 9 | 10 | changes.storageVersion = revision; 11 | return browser.storage.local.set(changes); 12 | } 13 | 14 | export {message, revision, upgrade}; 15 | -------------------------------------------------------------------------------- /src/storage/revisions/local/ryY8H0EWf.js: -------------------------------------------------------------------------------- 1 | const message = 'Add contribPageLastOpen'; 2 | 3 | const revision = 'ryY8H0EWf'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | changes.contribPageLastOpen = 0; 8 | 9 | changes.storageVersion = revision; 10 | return browser.storage.local.set(changes); 11 | } 12 | 13 | export {message, revision, upgrade}; 14 | -------------------------------------------------------------------------------- /src/storage/revisions/local/ryekyizAg.js: -------------------------------------------------------------------------------- 1 | const message = 'Initial version'; 2 | 3 | const revision = 'ryekyizAg'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | engines: ['google', 'bing', 'yandex', 'baidu', 'tineye'], 8 | disabledEngines: [], 9 | searchAllEngines: true, 10 | searchAllEnginesLocation: 'submenu', // 'menu', 'submenu' 11 | tabInBackgound: false, 12 | localGoogle: true 13 | }; 14 | 15 | changes.storageVersion = revision; 16 | return browser.storage.local.set(changes); 17 | } 18 | 19 | export {message, revision, upgrade}; 20 | -------------------------------------------------------------------------------- /src/storage/revisions/local/yLciyvS5He.js: -------------------------------------------------------------------------------- 1 | const message = 'Add Jingdong, Taobao and Alibaba China'; 2 | 3 | const revision = 'yLciyvS5He'; 4 | 5 | async function upgrade() { 6 | const changes = {}; 7 | const {engines, disabledEngines} = await browser.storage.local.get([ 8 | 'engines', 9 | 'disabledEngines' 10 | ]); 11 | const newEngines = ['jingdong', 'taobao', 'alibabaChina']; 12 | 13 | changes.engines = engines.concat(newEngines); 14 | changes.disabledEngines = disabledEngines.concat(newEngines); 15 | 16 | changes.storageVersion = revision; 17 | return browser.storage.local.set(changes); 18 | } 19 | 20 | export {message, revision, upgrade}; 21 | -------------------------------------------------------------------------------- /src/storage/revisions/session/20240514122825_initial_version.js: -------------------------------------------------------------------------------- 1 | const message = 'Initial version'; 2 | 3 | const revision = '20240514122825_initial_version'; 4 | 5 | async function upgrade() { 6 | const changes = { 7 | platformInfo: null, 8 | menuChangeEvent: 0, 9 | privateMenuChangeEvent: 0, 10 | tabRevisions: [] 11 | }; 12 | 13 | changes.storageVersion = revision; 14 | return browser.storage.session.set(changes); 15 | } 16 | 17 | export {message, revision, upgrade}; 18 | -------------------------------------------------------------------------------- /src/tab/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | New Tab 6 | 7 | 8 | 9 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/tab/main.js: -------------------------------------------------------------------------------- 1 | async function getLocationData() { 2 | const token = new URL(window.location.href).searchParams.get('id'); 3 | 4 | return browser.runtime.sendMessage({ 5 | id: 'storageRequest', 6 | asyncResponse: true, 7 | saveReceipt: true, 8 | storageId: token 9 | }); 10 | } 11 | 12 | function setLocation(tabUrl, keepHistory) { 13 | if (keepHistory) { 14 | window.location.href = tabUrl; 15 | } else { 16 | window.location.replace(tabUrl); 17 | } 18 | } 19 | 20 | async function setupTab(data) { 21 | return browser.runtime.sendMessage({id: 'setupTab', data}); 22 | } 23 | 24 | async function start() { 25 | const data = await getLocationData(); 26 | 27 | if (data.tabSetupData) { 28 | await setupTab(data.tabSetupData); 29 | } 30 | 31 | setLocation(data.tabUrl, data.keepHistory); 32 | } 33 | 34 | function init() { 35 | start(); 36 | } 37 | 38 | init(); 39 | -------------------------------------------------------------------------------- /src/utils/config.js: -------------------------------------------------------------------------------- 1 | const targetEnv = process.env.TARGET_ENV; 2 | 3 | const enableContributions = process.env.ENABLE_CONTRIBUTIONS === 'true'; 4 | 5 | const storageRevisions = { 6 | local: process.env.STORAGE_REVISION_LOCAL, 7 | session: process.env.STORAGE_REVISION_SESSION 8 | }; 9 | 10 | const appVersion = process.env.APP_VERSION; 11 | 12 | const mv3 = process.env.MV3 === 'true'; 13 | 14 | export {targetEnv, enableContributions, storageRevisions, appVersion, mv3}; 15 | -------------------------------------------------------------------------------- /src/utils/vuetify.js: -------------------------------------------------------------------------------- 1 | import {createVuetify} from 'vuetify'; 2 | 3 | import {getAppTheme, addThemeListener} from 'utils/app'; 4 | 5 | const LightTheme = { 6 | dark: false, 7 | colors: { 8 | background: '#FFFFFF', 9 | surface: '#FFFFFF', 10 | primary: '#6750A4', 11 | secondary: '#625B71' 12 | } 13 | }; 14 | 15 | const DarkTheme = { 16 | dark: true, 17 | colors: { 18 | background: '#1C1B1F', 19 | surface: '#1C1B1F', 20 | primary: '#D0BCFF', 21 | secondary: '#CCC2DC' 22 | } 23 | }; 24 | 25 | async function configTheme(vuetify, {theme = ''} = {}) { 26 | async function setTheme({theme = '', dispatchChange = true} = {}) { 27 | if (!theme) { 28 | theme = await getAppTheme(); 29 | } 30 | 31 | document.documentElement.style.setProperty('color-scheme', theme); 32 | vuetify.theme.global.name.value = theme; 33 | 34 | if (dispatchChange) { 35 | document.dispatchEvent(new CustomEvent('themeChange', {detail: theme})); 36 | } 37 | } 38 | 39 | addThemeListener(setTheme); 40 | 41 | await setTheme({theme, dispatchChange: false}); 42 | } 43 | 44 | async function configVuetify(app) { 45 | const theme = await getAppTheme(); 46 | 47 | const vuetify = createVuetify({ 48 | theme: { 49 | themes: {light: LightTheme, dark: DarkTheme}, 50 | defaultTheme: theme 51 | }, 52 | defaults: { 53 | VDialog: { 54 | eager: true 55 | }, 56 | VSelect: { 57 | eager: true 58 | }, 59 | VSnackbar: { 60 | eager: true 61 | }, 62 | VMenu: { 63 | eager: true 64 | } 65 | } 66 | }); 67 | 68 | await configTheme(vuetify, {theme}); 69 | 70 | app.use(vuetify); 71 | } 72 | 73 | export {configTheme, configVuetify}; 74 | -------------------------------------------------------------------------------- /src/view/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/view/main.js: -------------------------------------------------------------------------------- 1 | import {createApp} from 'vue'; 2 | 3 | import {configApp, loadFonts} from 'utils/app'; 4 | import {configVuetify} from 'utils/vuetify'; 5 | import App from './App'; 6 | 7 | async function init() { 8 | await loadFonts(['400 14px Roboto', '500 14px Roboto']); 9 | 10 | const app = createApp(App); 11 | 12 | await configApp(app); 13 | await configVuetify(app); 14 | 15 | app.mount('body'); 16 | } 17 | 18 | init(); 19 | --------------------------------------------------------------------------------