├── .eslintrc.json ├── .github ├── CODEOWNERS ├── FUNDING.yml ├── actions │ └── yarn │ │ └── action.yml ├── assets │ ├── demo.gif │ └── header.svg ├── guide │ ├── 01.png │ ├── 02.png │ ├── 03.png │ └── 04.png ├── matchers │ ├── eslint.json │ └── tsc.json ├── renovate.json └── workflows │ ├── ci.yml │ ├── cloudflare.yml │ └── release.yml ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── .yarn └── releases │ └── yarn-3.6.1.cjs ├── .yarnrc.yml ├── LICENSE ├── README.md ├── apps ├── extension │ ├── .eslintrc.json │ ├── .gitignore │ ├── .prettierignore │ ├── .prettierrc.cjs │ ├── README.md │ ├── manifest.config.ts │ ├── package.json │ ├── src │ │ ├── assets │ │ │ ├── icon128.png │ │ │ ├── icon16.png │ │ │ ├── icon32.png │ │ │ └── icon48.png │ │ ├── background.ts │ │ └── vite-env.d.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── vite.config.ts └── worker │ ├── .eslintrc.json │ ├── .prettierignore │ ├── .prettierrc.cjs │ ├── README.md │ ├── package.json │ ├── src │ └── index.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ └── wrangler.toml ├── package.json ├── packages └── links │ ├── .eslintrc.json │ ├── .prettierignore │ ├── .prettierrc.js │ ├── README.md │ ├── package.json │ ├── src │ ├── index.ts │ └── util.ts │ ├── tsconfig.eslint.json │ ├── tsconfig.json │ ├── tsup.config.ts │ └── wrangler.toml ├── tsconfig.eslint.json ├── tsconfig.json ├── turbo.json └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "root": true, 3 | "extends": ["neon/common", "neon/node", "neon/typescript", "neon/prettier"], 4 | "parserOptions": { 5 | "project": ["./tsconfig.eslint.json", "./apps/*/tsconfig.eslint.json", "./packages/*/tsconfig.eslint.json"] 6 | }, 7 | "rules": { 8 | "@typescript-eslint/consistent-type-definitions": ["error", "interface"], 9 | "id-length": 0 10 | }, 11 | "ignorePatterns": ["**/dist/*"] 12 | } 13 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @carterhimmel 2 | 3 | /apps/worker @alii 4 | /packages/links @alii 5 | /apps/extension @carterhimmel 6 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [alii, carterhimmel] 2 | -------------------------------------------------------------------------------- /.github/actions/yarn/action.yml: -------------------------------------------------------------------------------- 1 | name: 'yarn install' 2 | description: 'Run yarn install with node_modules linker and cache enabled' 3 | runs: 4 | using: 'composite' 5 | steps: 6 | - name: Expose yarn config as "$GITHUB_OUTPUT" 7 | id: yarn-config 8 | shell: bash 9 | run: | 10 | echo "CACHE_FOLDER=$(yarn config get cacheFolder)" >> $GITHUB_OUTPUT 11 | 12 | - name: Restore yarn cache 13 | uses: actions/cache@v3 14 | id: yarn-download-cache 15 | with: 16 | path: ${{ steps.yarn-config.outputs.CACHE_FOLDER }} 17 | key: yarn-download-cache-${{ hashFiles('yarn.lock') }} 18 | restore-keys: | 19 | yarn-download-cache- 20 | 21 | - name: Restore yarn install state 22 | id: yarn-install-state-cache 23 | uses: actions/cache@v3 24 | with: 25 | path: .yarn/ci-cache/ 26 | key: ${{ runner.os }}-yarn-install-state-cache-${{ hashFiles('yarn.lock', '.yarnrc.yml') }} 27 | 28 | - name: Install dependencies 29 | shell: bash 30 | run: | 31 | yarn install --immutable --inline-builds 32 | env: 33 | YARN_ENABLE_GLOBAL_CACHE: 'false' 34 | YARN_NM_MODE: 'hardlinks-local' 35 | YARN_INSTALL_STATE_PATH: .yarn/ci-cache/install-state.gz 36 | -------------------------------------------------------------------------------- /.github/assets/demo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/.github/assets/demo.gif -------------------------------------------------------------------------------- /.github/assets/header.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /.github/guide/01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/.github/guide/01.png -------------------------------------------------------------------------------- /.github/guide/02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/.github/guide/02.png -------------------------------------------------------------------------------- /.github/guide/03.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/.github/guide/03.png -------------------------------------------------------------------------------- /.github/guide/04.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/.github/guide/04.png -------------------------------------------------------------------------------- /.github/matchers/eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "eslint-stylish", 5 | "pattern": [ 6 | { 7 | "regexp": "^([^\\s].*)$", 8 | "file": 1 9 | }, 10 | { 11 | "regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$", 12 | "line": 1, 13 | "column": 2, 14 | "severity": 3, 15 | "message": 4, 16 | "code": 5, 17 | "loop": true 18 | } 19 | ] 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.github/matchers/tsc.json: -------------------------------------------------------------------------------- 1 | { 2 | "problemMatcher": [ 3 | { 4 | "owner": "tsc", 5 | "pattern": [ 6 | { 7 | "regexp": "^(?:\\s+\\d+\\>)?([^\\s].*)\\((\\d+|\\d+,\\d+|\\d+,\\d+,\\d+,\\d+)\\)\\s*:\\s+(error|warning|info)\\s+(\\w{1,2}\\d+)\\s*:\\s*(.*)$", 8 | "file": 1, 9 | "location": 2, 10 | "severity": 3, 11 | "code": 4, 12 | "message": 5 13 | } 14 | ] 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /.github/renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Tests 2 | on: 3 | push: 4 | pull_request: 5 | concurrency: 6 | group: ${{ github.workflow }}-${{ github.head_ref || github.run_id }} 7 | cancel-in-progress: true 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: Checkout Repository 14 | uses: actions/checkout@v3 15 | with: 16 | fetch-depth: 0 17 | 18 | - name: Setup Volta 19 | uses: volta-cli/action@v3 20 | 21 | - name: Cache turbo 22 | uses: actions/cache@v3 23 | with: 24 | path: .turbo 25 | key: ${{ runner.os }}-turbo-${{ github.sha }} 26 | restore-keys: | 27 | ${{ runner.os }}-turbo- 28 | 29 | - name: Install dependencies 30 | uses: ./.github/actions/yarn 31 | 32 | - name: Build dependencies (PR) 33 | if: ${{ github.event_name != 'push' }} 34 | run: yarn build --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" 35 | 36 | - name: Build dependencies (Push) 37 | if: ${{ github.event_name == 'push' }} 38 | run: yarn build --filter="...[HEAD^1]" 39 | 40 | - name: ESLint (PR) 41 | if: ${{ github.event_name != 'push' }} 42 | run: yarn lint --filter="...[origin/${{ github.event_name == 'pull_request' && github.event.pull_request.base.ref || 'main' }}]" -- --format=compact 43 | 44 | - name: ESLint (Push) 45 | if: ${{ github.event_name == 'push' }} 46 | run: yarn lint --filter="...[HEAD^1]" -- --format=compact 47 | -------------------------------------------------------------------------------- /.github/workflows/cloudflare.yml: -------------------------------------------------------------------------------- 1 | name: Deploy Worker 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | - main 8 | 9 | jobs: 10 | deploy: 11 | runs-on: ubuntu-latest 12 | name: Deploy 13 | steps: 14 | - uses: actions/checkout@v3 15 | 16 | - name: Setup Volta 17 | uses: volta-cli/action@v3 18 | 19 | - name: Cache turbo 20 | uses: actions/cache@v3 21 | with: 22 | path: .turbo 23 | key: ${{ runner.os }}-turbo-${{ github.sha }} 24 | restore-keys: | 25 | ${{ runner.os }}-turbo- 26 | 27 | - name: Install dependencies 28 | uses: ./.github/actions/yarn 29 | 30 | - name: Build Worker 31 | run: yarn turbo build --scope=@searchy/worker 32 | 33 | - name: Publish 34 | uses: cloudflare/wrangler-action@2.0.0 35 | with: 36 | apiToken: ${{ secrets.CF_TOKEN }} 37 | workingDirectory: ./apps/worker 38 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release Extension 2 | 3 | on: 4 | push: 5 | tags: 6 | - v* 7 | 8 | # add permission to create releases 9 | permissions: 10 | contents: write 11 | 12 | jobs: 13 | draft_release: 14 | name: Create Release 15 | runs-on: ubuntu-latest 16 | if: github.repository_owner == 'alii' 17 | outputs: 18 | tag_name: ${{ steps.tag.outputs.tag_name }} 19 | zipfile: ${{ steps.zipfile.outputs.zipfile }} 20 | steps: 21 | - name: Checkout the repo 22 | uses: actions/checkout@v3 23 | 24 | - name: Get tag data 25 | id: tag 26 | run: | 27 | echo "tag_name=${GITHUB_REF#refs/*/}" >> $GITHUB_OUTPUT 28 | if [[ ${{ github.event.ref }} =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+-[0-9]+$ ]]; then 29 | echo "pre_release=true" >> $GITHUB_OUTPUT 30 | fi 31 | 32 | - name: Create full zipfile name 33 | id: zipfile 34 | run: echo "zipfile=searchy-${{ steps.tag.outputs.tag_name }}.zip" >> $GITHUB_OUTPUT 35 | 36 | - name: Create new release 37 | uses: marvinpinto/action-automatic-releases@latest 38 | with: 39 | repo_token: ${{ secrets.GITHUB_TOKEN }} 40 | prerelease: ${{ steps.tag.outputs.pre_release == 'true' }} 41 | title: ${{ steps.tag.outputs.tag_name }} 42 | draft: true 43 | 44 | publish_release: 45 | name: Publish Release 46 | needs: ['draft_release', 'build'] 47 | runs-on: ubuntu-latest 48 | if: github.repository_owner == 'alii' 49 | continue-on-error: true 50 | 51 | steps: 52 | - name: Download Artifacts 53 | uses: actions/download-artifact@v3 54 | 55 | - name: Create Checksums 56 | run: for file in searchy-*/searchy-*; do openssl dgst -sha256 -r "$file" | awk '{print $1}' > "${file}.sha256"; done 57 | 58 | - name: Update Release 59 | run: gh release edit ${{ needs.draft_release.outputs.tag_name }} --draft=false --repo=${{ github.repository }} 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 62 | 63 | - name: Add Artifacts to Release 64 | uses: softprops/action-gh-release@v1 65 | with: 66 | files: searchy-*/searchy-* 67 | tag_name: ${{ needs.draft_release.outputs.tag_name }} 68 | 69 | build: 70 | name: Build 71 | needs: ['draft_release'] 72 | runs-on: ubuntu-latest 73 | if: github.repository_owner == 'alii' 74 | steps: 75 | - uses: actions/checkout@v3 76 | - uses: volta-cli/action@v3 77 | - name: Add problem matchers 78 | run: | 79 | echo "::add-matcher::.github/matchers/eslint.json" 80 | echo "::add-matcher::.github/matchers/tsc.json" 81 | 82 | - name: Install dependencies 83 | uses: ./.github/actions/yarn 84 | 85 | - name: Lint 86 | run: yarn lint 87 | 88 | - name: Build 89 | run: yarn build 90 | 91 | - name: Zip 92 | working-directory: apps/extension/release 93 | run: zip -r ../../../${{ needs.draft_release.outputs.zipfile }} . 94 | 95 | - name: Upload artifact 96 | uses: actions/upload-artifact@v2 97 | with: 98 | name: ${{ needs.draft_release.outputs.zipfile }} 99 | path: ${{ needs.draft_release.outputs.zipfile }} 100 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | dist 3 | .idea 4 | .vscode 5 | .DS_store 6 | .turbo 7 | .yarn/* 8 | !.yarn/releases 9 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | # ignore build: 2 | dist -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "useTabs": true, 3 | "singleQuote": true, 4 | "printWidth": 140, 5 | "bracketSpacing": false, 6 | "arrowParens": "avoid", 7 | "endOfLine": "lf" 8 | } 9 | -------------------------------------------------------------------------------- /.yarnrc.yml: -------------------------------------------------------------------------------- 1 | compressionLevel: mixed 2 | 3 | enableGlobalCache: true 4 | 5 | enableInlineHunks: true 6 | 7 | enableMessageNames: false 8 | 9 | networkConcurrency: 100 10 | 11 | nodeLinker: node-modules 12 | 13 | yarnPath: .yarn/releases/yarn-3.6.1.cjs 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Alistair Smith 4 | Copyright (c) 2023 Carter Himmel 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | searchy logo 5 | 6 |

7 |
8 | 9 | GitHub tag 10 | Test status 11 | Chrome store badge 12 |
13 | 14 | ## About 15 | 16 | Searchy is a Chrome extension for powerful, command-based searching. 17 | 18 | ## Apps 19 | 20 | - `@searchy/extension` ([source/docs](/apps/extension/)) - The Chrome extension 21 | - `@searchy/worker` ([source/docs](/apps/worker/)) - The Cloudflare worker that serves as a search engine 22 | 23 | ## Packages 24 | 25 | - `@searchy/links` ([source/docs](/packages/links/)) - A constant of all templatable links 26 | -------------------------------------------------------------------------------- /apps/extension/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "rules": { 4 | "n/prefer-global/url": 0, 5 | "n/prefer-global/url-search-params": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/extension/.gitignore: -------------------------------------------------------------------------------- 1 | searchy-*.zip 2 | release 3 | -------------------------------------------------------------------------------- /apps/extension/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | release 3 | -------------------------------------------------------------------------------- /apps/extension/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.prettierrc.json'); 2 | -------------------------------------------------------------------------------- /apps/extension/README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | searchy logo 5 | demo video 6 |

7 |
8 | 9 | GitHub tag 10 | Test status 11 | Chrome store badge 12 |
13 | 14 | ## About 15 | 16 | This is the Chrome extension. Instead of sending a request to the Worker, this extension registers an omnibox with Chrome and handles the commands itself. 17 | 18 | ## Privacy Policy 19 | 20 | searchy does not collect any data. 21 | 22 | 32 | -------------------------------------------------------------------------------- /apps/extension/manifest.config.ts: -------------------------------------------------------------------------------- 1 | import {defineManifest} from '@crxjs/vite-plugin'; 2 | import packageJson from './package.json'; 3 | 4 | const {version} = packageJson; 5 | 6 | // Convert from Semver (example: 0.1.0-beta6) 7 | const [major, minor, patch, label = '0'] = version 8 | // can only contain digits, dots, or dash 9 | .replaceAll(/[^\d.-]+/g, '') 10 | // split into version parts 11 | .split(/[.-]/); 12 | 13 | export default defineManifest({ 14 | manifest_version: 3, 15 | icons: { 16 | '16': 'src/assets/icon16.png', 17 | '32': 'src/assets/icon32.png', 18 | '48': 'src/assets/icon48.png', 19 | '128': 'src/assets/icon128.png', 20 | }, 21 | description: 'powerful command-based searching', 22 | background: { 23 | service_worker: 'src/background.ts', 24 | }, 25 | omnibox: { 26 | keyword: '!', 27 | }, 28 | name: 'searchy', 29 | // up to four numbers separated by dots 30 | version: `${major}.${minor}.${patch}.${label}`, 31 | // semver is OK in "version_name" 32 | version_name: version, 33 | }); 34 | -------------------------------------------------------------------------------- /apps/extension/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@searchy/extension", 3 | "version": "0.2.2", 4 | "contributors": [ 5 | "Alistair Smith ", 6 | "Carter Himmel " 7 | ], 8 | "repository": { 9 | "type": "git", 10 | "url": "https://github.com/alii/searchy.git", 11 | "directory": "apps/extension" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/alii/searchy/issues" 15 | }, 16 | "license": "MIT", 17 | "type": "module", 18 | "scripts": { 19 | "dev": "vite --mode development", 20 | "build": "vite build --mode production", 21 | "lint": "prettier --check . && eslint src --ext .mjs,.js,.ts --format=pretty", 22 | "format": "prettier --write . && eslint src --ext .mjs,.js,.ts --fix --format=pretty" 23 | }, 24 | "dependencies": { 25 | "@searchy/links": "workspace:^", 26 | "fuse.js": "^6.6.2" 27 | }, 28 | "devDependencies": { 29 | "@crxjs/vite-plugin": "^1.0.14", 30 | "@types/chrome": "^0.0.242", 31 | "eslint": "^8.46.0", 32 | "eslint-config-neon": "^0.1.47", 33 | "eslint-formatter-pretty": "^5.0.0", 34 | "prettier": "^2.8.8", 35 | "typescript": "^5.1.6", 36 | "vite": "^4.4.7", 37 | "wrangler": "^3.4.0" 38 | }, 39 | "packageManager": "yarn@4.0.0-rc.48" 40 | } 41 | -------------------------------------------------------------------------------- /apps/extension/src/assets/icon128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/apps/extension/src/assets/icon128.png -------------------------------------------------------------------------------- /apps/extension/src/assets/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/apps/extension/src/assets/icon16.png -------------------------------------------------------------------------------- /apps/extension/src/assets/icon32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/apps/extension/src/assets/icon32.png -------------------------------------------------------------------------------- /apps/extension/src/assets/icon48.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/alii/searchy/da106ea492d30414b6cebc7c8fa286dc19e8d27b/apps/extension/src/assets/icon48.png -------------------------------------------------------------------------------- /apps/extension/src/background.ts: -------------------------------------------------------------------------------- 1 | import {SITES, template} from '@searchy/links'; 2 | import Fuse from 'fuse.js'; 3 | 4 | const repo = 'alii/searchy'; 5 | 6 | const fuse = new Fuse(Object.keys(SITES), { 7 | shouldSort: true, 8 | threshold: 0.4, 9 | }); 10 | 11 | // preview search results 12 | chrome.omnibox.onInputChanged.addListener((text, suggest) => { 13 | const split = text.split(' '); 14 | const site = SITES[split[0].toLowerCase().replace('!', '')]; 15 | const suggestions: chrome.omnibox.SuggestResult[] = []; 16 | 17 | // if the first word is a site, use that 18 | if (site) { 19 | const [, ...rest] = split; 20 | const joined = rest.join(' '); 21 | const parsed = typeof site === 'function' ? site(joined) : site; 22 | 23 | const url = template(parsed, joined); 24 | console.log(url); 25 | 26 | suggestions.push({content: url, description: `Preview: ${url}`}); 27 | } else { 28 | // otherwise, use fuzzy to recommend sites 29 | const results = fuse.search(text, {limit: 5}); 30 | 31 | suggestions.push( 32 | ...results.map(result => { 33 | const site = SITES[result.item]!; 34 | const parsed = typeof site === 'function' ? site(text) : site; 35 | 36 | const url = template(parsed, text); 37 | console.log(url); 38 | 39 | return {content: url, description: `${result.item}: ${url}`}; 40 | }) 41 | ); 42 | } 43 | 44 | suggest(suggestions); 45 | }); 46 | 47 | // This event is fired with the user accepts the input in the omnibox. 48 | chrome.omnibox.onInputEntered.addListener(text => { 49 | const split = text.split(' '); 50 | const site = SITES[split[0].toLowerCase().replace('!', '')]; 51 | 52 | if (site) { 53 | const [, ...rest] = split; 54 | const joined = rest.join(' '); 55 | const parsed = typeof site === 'function' ? site(joined) : site; 56 | 57 | const url = parsed.replace('{q}', joined); 58 | console.log(url); 59 | 60 | void navigate(url); 61 | } 62 | }); 63 | 64 | async function navigate(url: string) { 65 | const [tab] = await chrome.tabs.query({active: true, currentWindow: true}); 66 | 67 | if (tab) { 68 | return chrome.tabs.update(tab.id!, {url}); 69 | } 70 | 71 | return chrome.tabs.create({url}); 72 | } 73 | 74 | chrome.runtime.onInstalled.addListener(details => { 75 | const cases: Record = { 76 | [chrome.runtime.OnInstalledReason.INSTALL]: `https://github.com/${repo}/tree/master/apps/extension#readme`, 77 | [chrome.runtime.OnInstalledReason.UPDATE]: `https://github.com/${repo}/releases/latest`, 78 | [chrome.runtime.OnInstalledReason.CHROME_UPDATE]: undefined, 79 | [chrome.runtime.OnInstalledReason.SHARED_MODULE_UPDATE]: undefined, 80 | }; 81 | const url = cases[details.reason]; 82 | if (url) void navigate(url); 83 | }); 84 | -------------------------------------------------------------------------------- /apps/extension/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line @typescript-eslint/triple-slash-reference 2 | /// 3 | -------------------------------------------------------------------------------- /apps/extension/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "**/*.js", 10 | "**/*.mjs", 11 | "**/*.jsx", 12 | "**/*.test.ts", 13 | "**/*.test.js", 14 | "**/*.test.mjs", 15 | "**/*.spec.ts", 16 | "**/*.spec.js", 17 | "**/*.spec.mjs" 18 | ], 19 | "exclude": [] 20 | } 21 | -------------------------------------------------------------------------------- /apps/extension/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "esnext", 5 | "target": "es2021", 6 | "lib": ["dom", "dom.iterable", "esnext"], 7 | "moduleResolution": "node", 8 | "types": ["chrome"], 9 | "baseUrl": ".", 10 | "outDir": "dist" 11 | }, 12 | "include": ["src"] 13 | } 14 | -------------------------------------------------------------------------------- /apps/extension/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {crx} from '@crxjs/vite-plugin'; 2 | import {defineConfig, type PluginOption} from 'vite'; 3 | import manifest from './manifest.config'; 4 | 5 | const createBuildConfig = (outDir: string) => ({ 6 | outDir, 7 | emptyOutDir: true, 8 | }); 9 | 10 | export default defineConfig(({mode, command}) => { 11 | console.log(`mode: ${mode}, command: ${command}`); 12 | 13 | const plugins = [, crx({manifest}) as PluginOption]; 14 | 15 | const server = { 16 | port: 5_174, 17 | strictPort: true, 18 | hmr: { 19 | port: 5_174, 20 | }, 21 | }; 22 | 23 | const cases = { 24 | production: 'release', 25 | development: 'dist', 26 | default: 'dist', 27 | }; 28 | const build = createBuildConfig(cases[mode] || cases.default); 29 | 30 | return {plugins, server, build}; 31 | }); 32 | -------------------------------------------------------------------------------- /apps/worker/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "rules": { 4 | "n/prefer-global/url": 0, 5 | "n/prefer-global/url-search-params": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /apps/worker/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /apps/worker/.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.prettierrc.json'); 2 | -------------------------------------------------------------------------------- /apps/worker/README.md: -------------------------------------------------------------------------------- 1 | # @searchy/worker 2 | 3 | Supercharge your searching with a simple Cloudflare worker. 4 | 5 | ### Setup Guide: 6 | 7 | Add `https://search.alistair.sh/?q=` as a custom search engine to your browser. For Chrome/Chromium and Firefox/Waterfox, you would need to add `%s` on the end of that. Consult your browser's documentation for relevant information. 8 | 9 | #### Fallback Engines 10 | 11 | If you prefer duckduckgo, you can set your URL to the following to have your searched routed through that instead: 12 | 13 | ``` 14 | https://search.alistair.sh/?q=%s&engine=https:%2f%2fduckduckgo.com%2f%3fq={q} 15 | ``` 16 | 17 |   18 | 19 | ### Usage Guide: 20 | 21 | Using your browser as normal will funnel searches through to google's search engine (or a custom set one as shown in 'Fallback Engines'). 22 | 23 | To use the powerful part of this project and search through a variety of mainstream sites use the `![site] [search data]` structure to search any search data on any of the specific sites supported in this project. 24 | 25 | **Example:** `!yt cats` will search for cats on youtube :) 26 | 27 | To see which sites are supported and what their shortcuts are check out [`@searchy/links`](/packages/links/). 28 | 29 |   30 | 31 | ### Self deployment 32 | 33 | #### Clone this repo into a directory of your choosing 34 | 35 | ```console 36 | $ git clone https://github.com/alii/searchy 37 | ``` 38 | 39 | ### Install Dependencies 40 | 41 | Run 42 | 43 | ```console 44 | $ yarn 45 | ``` 46 | 47 | ### Edit wrangler.toml to your liking 48 | 49 | Set `name` to what you want. By default this will be search.`workeraccountname`.workers.dev but you can set a custom domain in the worker dashboard. 50 | 51 | Replace `ad270f797e0a3205ac74136dc5b656b1` with your account ID. 52 | You can find this by following [this guide](https://developers.cloudflare.com/fundamentals/get-started/basic-tasks/find-account-and-zone-ids/). 53 | 54 | #### Publish the worker 55 | 56 | ```console 57 | $ yarn workspace @searchy/worker deploy 58 | ``` 59 | 60 | If it asks you `You are about to publish a Workers Service that was last published via the Cloudflare Dashboard.` Enter `y` 61 | 62 | The worker should now be working at the domain it gives you 63 | 64 | ### Visual Setup Guide 65 | 66 |
67 | 68 | 69 | Chrome/Chromium 70 | 71 | 72 | 73 | 74 | 75 |
76 |
77 | It says: 78 | 79 | ``` 80 | 81 | alii/search (Can be named whatever) 82 | 83 | alii/search (Can also be whatever) 84 | 85 | https://search.alistair.sh/?q=%s 86 | 87 | ``` 88 | 89 |
90 | 91 | 92 |
93 | 94 |
95 | 96 | 97 | Firefox/Waterfox 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 |
", 6 | "Carter Himmel " 7 | ], 8 | "license": "MIT", 9 | "type": "module", 10 | "scripts": { 11 | "deploy": "wrangler deploy", 12 | "build": "wrangler deploy --dry-run --outdir=dist", 13 | "lint": "prettier --check . && eslint src --ext .mjs,.js,.ts --format=pretty", 14 | "format": "prettier --write . && eslint src --ext .mjs,.js,.ts --fix --format=pretty" 15 | }, 16 | "devDependencies": { 17 | "eslint": "^8.46.0", 18 | "eslint-config-neon": "^0.1.47", 19 | "eslint-formatter-pretty": "^5.0.0", 20 | "prettier": "^2.8.8", 21 | "typescript": "^5.1.6", 22 | "wrangler": "^3.4.0" 23 | }, 24 | "packageManager": "yarn@4.0.0-rc.48", 25 | "dependencies": { 26 | "@searchy/links": "workspace:^" 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /apps/worker/src/index.ts: -------------------------------------------------------------------------------- 1 | import {SITES, template} from '@searchy/links'; 2 | 3 | function handleRequest(request: Request) { 4 | const url = new URL(request.url); 5 | 6 | const query = url.searchParams.get('q') ?? ''; 7 | const engine = (url.searchParams.get('engine') ?? SITES.google) as string; 8 | 9 | if (query.startsWith('!')) { 10 | const split = query.split(' '); 11 | const site = SITES[split[0].toLowerCase().replace('!', '')]; 12 | 13 | if (site) { 14 | const [, ...rest] = split; 15 | const joined = rest.join(' '); 16 | const parsed = typeof site === 'function' ? site(joined) : site; 17 | 18 | return Response.redirect(template(parsed, joined), 301); 19 | } 20 | 21 | // The else case here is that they entered a website that doesn't exist 22 | } 23 | 24 | return Response.redirect(template(engine, query), 301); 25 | } 26 | 27 | export default {fetch: (request: Request) => handleRequest(request)}; 28 | -------------------------------------------------------------------------------- /apps/worker/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "**/*.js", 10 | "**/*.mjs", 11 | "**/*.jsx", 12 | "**/*.test.ts", 13 | "**/*.test.js", 14 | "**/*.test.mjs", 15 | "**/*.spec.ts", 16 | "**/*.spec.js", 17 | "**/*.spec.mjs" 18 | ], 19 | "exclude": [] 20 | } 21 | -------------------------------------------------------------------------------- /apps/worker/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "nodenext", 5 | "target": "esnext", 6 | "lib": ["esnext", "webworker"], 7 | "alwaysStrict": true, 8 | "strict": true, 9 | "preserveConstEnums": true, 10 | "moduleResolution": "nodenext", 11 | "sourceMap": true, 12 | "esModuleInterop": true, 13 | "types": ["@cloudflare/workers-types"], 14 | "skipLibCheck": true, 15 | "rootDir": "src" 16 | }, 17 | "include": ["src"] 18 | } 19 | -------------------------------------------------------------------------------- /apps/worker/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "search" 2 | account_id = 'ad270f797e0a3205ac74136dc5b656b1' 3 | usage_model = 'bundled' 4 | main = 'src/index.ts' 5 | workers_dev = true 6 | compatibility_date = "2023-05-18" 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "searchy", 3 | "version": "0.0.0", 4 | "workspaces": [ 5 | "packages/*", 6 | "apps/*" 7 | ], 8 | "repository": "https://github.com/alii/search", 9 | "contributors": [ 10 | "Alistair Smith ", 11 | "Carter Himmel " 12 | ], 13 | "license": "MIT", 14 | "type": "commonjs", 15 | "scripts": { 16 | "build": "turbo run build --cache-dir=.turbo", 17 | "build:affected": "turbo run build --filter='...[origin/main]' --cache-dir=.turbo", 18 | "build:services": "turbo run build:local --filter='...{services/*}' --cache-dir=.turbo", 19 | "build:services:affected": "turbo run build:local --filter='...{services/*}[origin/main]' --cache-dir=.turbo", 20 | "lint": "turbo run lint --parallel", 21 | "lint:affected": "turbo run lint --filter='...[origin/main]' --parallel", 22 | "format": "turbo run format --parallel", 23 | "format:affected": "turbo run format --filter='...[origin/main]' --parallel", 24 | "act": "act pull_request", 25 | "act:m1": "act --container-architecture linux/amd64 pull_request" 26 | }, 27 | "devDependencies": { 28 | "eslint": "^8.46.0", 29 | "eslint-config-neon": "^0.1.47", 30 | "eslint-formatter-pretty": "^5.0.0", 31 | "prettier": "^2.8.8", 32 | "tsup": "^7.1.0", 33 | "turbo": "^1.10.12", 34 | "typescript": "^5.1.6", 35 | "vite": "^4.4.7" 36 | }, 37 | "packageManager": "yarn@3.6.1", 38 | "volta": { 39 | "node": "18.17.0", 40 | "yarn": "4.0.0-rc.48" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /packages/links/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../.eslintrc.json", 3 | "rules": { 4 | "n/prefer-global/url": 0, 5 | "n/prefer-global/url-search-params": 0 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /packages/links/.prettierignore: -------------------------------------------------------------------------------- 1 | dist 2 | -------------------------------------------------------------------------------- /packages/links/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('../../.prettierrc.json'); 2 | -------------------------------------------------------------------------------- /packages/links/README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | searchy logo 5 | 6 |

7 |
8 | 9 | GitHub tag 10 | Test status 11 | 12 | Chrome store badge 13 |
14 | 15 | ## About 16 | 17 | `@searchy/links` is a constant of all templatable links. It's used by both the extension and the worker. 18 | 19 | 29 | -------------------------------------------------------------------------------- /packages/links/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@searchy/links", 3 | "version": "1.0.0", 4 | "description": "a constant of all templatable links", 5 | "main": "./dist/index.js", 6 | "module": "./dist/index.mjs", 7 | "types": "./dist/index.d.ts", 8 | "exports": { 9 | "types": "./dist/index.d.ts", 10 | "import": "./dist/index.mjs", 11 | "require": "./dist/index.js" 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "contributors": [ 17 | "Alistair Smith ", 18 | "Carter Himmel " 19 | ], 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/alii/searchy.git", 23 | "directory": "packages/links" 24 | }, 25 | "bugs": { 26 | "url": "https://github.com/alii/searchy/issues" 27 | }, 28 | "license": "MIT", 29 | "scripts": { 30 | "build": "tsup", 31 | "dev": "tsup --watch", 32 | "lint": "prettier --check . && eslint src --ext .mjs,.js,.ts --format=pretty", 33 | "format": "prettier --write . && eslint src --ext .mjs,.js,.ts --format=pretty" 34 | }, 35 | "devDependencies": { 36 | "@cloudflare/workers-types": "^4.20230724.0", 37 | "eslint": "^8.46.0", 38 | "eslint-config-neon": "^0.1.47", 39 | "eslint-formatter-pretty": "^5.0.0", 40 | "prettier": "^2.8.8", 41 | "tsup": "^7.1.0", 42 | "typescript": "^5.1.6", 43 | "wrangler": "^3.4.0" 44 | }, 45 | "packageManager": "yarn@4.0.0-rc.48" 46 | } 47 | -------------------------------------------------------------------------------- /packages/links/src/index.ts: -------------------------------------------------------------------------------- 1 | import {etherscanTypeMap} from './util.js'; 2 | 3 | /** 4 | * @param link - The link to template 5 | * @param q - The query value to insert 6 | * @returns string - The templated link 7 | * @example 8 | * ```js 9 | * template('https://google.com/search?q={q}', 'hello world'); 10 | * // => 'https://google.com/search?q=hello%20world' 11 | * ``` 12 | */ 13 | export function template(link: string, q: string) { 14 | return link.replace('{q}', encodeURIComponent(q)); 15 | } 16 | 17 | export const SITES: Record string) | undefined> = { 18 | // Default 19 | help: 'https://github.com/alii/search/issues', 20 | 21 | // Package Managers 22 | yarn: 'https://yarnpkg.com/?q={q}&p=1', 23 | npm: 'https://npmjs.org/package/{q}', 24 | packagist: 'https://packagist.org/?query={q}', 25 | pypi: 'https://pypi.org/project/{q}/', 26 | brew: 'https://formulae.brew.sh/formula/{q}#default', 27 | aur: 'https://aur.archlinux.org/packages/?K={q}', 28 | gems: 'https://rubygems.org/search?query={q}', 29 | hex: 'https://hex.pm/packages?search={q}', 30 | nuget: 'https://www.nuget.org/packages?q={q}', 31 | crates: 'https://crates.io/search?q={q}', 32 | crate: 'https://crates.io/crates/{q}', 33 | 34 | // Domains 35 | namelix: 'https://namelix.com/app/?keywords={q}', 36 | namecheap: 'https://www.namecheap.com/domains/registration/results/?domain={q}', 37 | googledomains: 'https://domains.google.com/registrar/search?searchTerm={q}', 38 | godaddy: 'https://se.godaddy.com/domainsearch/find?checkAvail=1&domainToCheck={q}', 39 | porkbun: 'https://porkbun.com/checkout/search?q={q}', 40 | whois: 'https://who.is/whois/{q}', 41 | 42 | // Programming 43 | git: 'https://github.com/search?q={q}', 44 | github: 'https://github.com/{q}', 45 | gh: 'https://github.com/search?q={q}', 46 | stackoverflow: 'https://stackoverflow.com/search?q={q}', 47 | codepen: 'https://codepen.io/search/pens?q={q}', 48 | discordpy: 'https://discordpy.readthedocs.io/en/latest/search.html?q={q}', 49 | dpy: 'https://discordpy.readthedocs.io/en/latest/search.html?q={q}', 50 | discordjs: 'https://discord.js.org/#/docs/main/stable/search?query={q}', 51 | djs: 'https://discord.js.org/#/docs/main/stable/search?query={q}', 52 | gitlab: 'https://gitlab.com/search?search={q}', 53 | javadoc: args => `https://docs.oracle.com/javase/8/docs/api/${args.replaceAll('.', '/')}.html`, 54 | lh: 'http://localhost:3000', 55 | lhp: 'http://localhost:{q}', 56 | rust: 'https://doc.rust-lang.org/book/?search={q}', 57 | gitea: 'https://gitea.com/explore/repos?tab=&sort=recentupdate&q={q}', 58 | gopkg: 'https://pkg.go.dev/search?q={q}', 59 | lighthouse: 'https://developers.google.com/speed/pagespeed/insights/?url={q}', 60 | dns: 'https://www.nslookup.io/dns-records/{q}', 61 | tsplay: 'https://www.typescriptlang.org/play?#code/{q}', 62 | goplay: 'https://goplay.tools/', 63 | docker: 'https://hub.docker.com/_/{q}', 64 | mdn: 'https://developer.mozilla.org/en-US/search?q={q}', 65 | daily: 'https://app.daily.dev/search?q={q}', 66 | wakatime: 'https://wakatime.com/@{q}', 67 | 68 | // Search Engines 69 | google: 'https://google.com/search?q={q}', 70 | duck: 'https://duckduckgo.com/?q={q}', 71 | ddg: 'https://duckduckgo.com/?q={q}', 72 | brave: 'https://search.brave.com/search?q={q}', 73 | startpage: 'https://www.startpage.com/sp/search?q={q}', 74 | ecosia: 'https://www.ecosia.org/search?q={q}', 75 | bing: 'https://www.bing.com/search?q={q}', 76 | wiby: 'https://wiby.me/?q={q}', 77 | qwant: 'https://qwant.com/?q={q}', 78 | 79 | // Music 80 | genius: 'https://genius.com/search?q={q}', 81 | spotify: 'https://open.spotify.com/search/{q}', 82 | deezer: 'https://www.deezer.com/search/{q}', 83 | musicstax: 'https://musicstax.com/search?q={q}', 84 | soundcloud: 'https://soundcloud.com/search?q={q}', 85 | bandcamp: 'https://bandcamp.com/search?q={q}', 86 | applemusic: 'https://music.apple.com/search?term={q}', 87 | ytmusic: 'https://music.youtube.com/search?q={q}', 88 | musixmatch: 'https://www.musixmatch.com/search/{q}', 89 | kanye: 'https://open.spotify.com/artist/5K4W6rqBFWDnAN6FQUkS6x', // till donda drops (if it ever does) 90 | 91 | // Movies 92 | imdb: 'https://www.imdb.com/find?q={q}', 93 | rotten: 'https://www.rottentomatoes.com/search?search={q}', 94 | fandango: 'https://www.fandango.com/search?q={q}', 95 | tmdb: 'https://www.themoviedb.org/search?query={q}', 96 | 97 | // Social 98 | twitter: 'https://twitter.com/search?q={q}&src=typed_query', 99 | fb: 'https://www.facebook.com/search/top/?q={q}', 100 | giggl: 'https://canary.giggl.app/portal/{q}', 101 | subso: 'https://sub.so/{q}', 102 | reddit: 'https://www.reddit.com/search/?q={q}', 103 | 'r/': 'https://www.reddit.com/r/{q}', 104 | 'u/': 'https://www.reddit.com/u/{q}', 105 | pinterest: 'https://www.pinterest.com/search/pins/?q={q}&rs=direct_navigation', 106 | ig: 'https://instagram.com/{q}/', 107 | instagram: 'https://instagram.com/{q}', 108 | tiktok: 'https://www.tiktok.com/search?q={q}', 109 | discord: 'https://discord.gg/{q}', 110 | sub: 'https://reddit.com/r/{q}', 111 | snapchat: 'https://snapchat.com/add/{q}', 112 | sc: 'https://snapchat.com/add/{q}', 113 | 114 | // Video 115 | youtube: 'https://www.youtube.com/results?search_query={q}&page={startPage?}&utm_source=opensearch', 116 | odysee: 'https://odysee.com/search?q={q}', 117 | yt: 'https://www.youtube.com/results?search_query={q}&page={startPage?}&utm_source=opensearch', 118 | gyazo: 'https://gyazo.com/search/{q}', 119 | twitch: 'https://www.twitch.tv/{q}', 120 | netflix: 'https://www.netflix.com/search?q={q}', 121 | pornhub: 'https://www.pornhub.com/video/search?search={q}', 122 | nebula: 'https://nebula.app/search?q={q}', 123 | 124 | // Shopping 125 | amazon: 'https://www.amazon.com/s?k={q}', 126 | geizhals: 'https://geizhals.de/?fs={q}', 127 | ebay: 'https://www.ebay.com/sch/i.html?_nkw={q}', 128 | newegg: 'https://www.newegg.com/p/pl?d={q}', 129 | amazonde: 'https://www.amazon.de/s?k={q}', 130 | amazonnl: 'https://www.amazon.nl/s?k={q}', 131 | amazonfr: 'https://www.amazon.fr/s?k={q}', 132 | amazonit: 'https://www.amazon.it/s?k={q}', 133 | amazones: 'https://www.amazon.es/s?k={q}', 134 | amazonuk: 'https://www.amazon.co.uk/s?k={q}', 135 | amazonca: 'https://www.amazon.ca/s?k={q}', 136 | amazonmx: 'https://www.amazon.mx/s?k={q}', 137 | amazonbr: 'https://www.amazon.com.br/s?k={q}', 138 | amazonau: 'https://www.amazon.com.au/s?k={q}', 139 | amazonjp: 'https://www.amazon.co.jp/s?k={q}', 140 | amazonin: 'https://www.amazon.co.in/s?k={q}', 141 | amazonse: 'https://www.amazon.se/s?k={q}', 142 | thalia: 'https://www.thalia.de/suche?sq={q}', 143 | banggood: 'https://www.banggood.com/search/{q}.html?from=nav', 144 | aliexpress: 'https://www.aliexpress.com/wholesale?SearchText={q}', // Not entirely sure if this one works or not. -H4rldev 145 | wish: 'https://www.wish.com/search/{q}', // This site sucks but why not. -H4rldev 146 | shein: 'https://shein.com/pdsearch/{q}', 147 | 148 | // Anime 149 | anilist: 'https://anilist.co/search/anime?search={q}', 150 | myanimelist: 'https://myanimelist.net/search/all?q={q}', 151 | anidb: 'https://anidb.net/search/anime/?adb.search={q}', 152 | crunchyroll: 'https://www.crunchyroll.com/search?&q={q}', 153 | animixplay: 'https://animixplay.to/?q={q}&sengine=gogo', 154 | sakuta: 'https://sakuta.app/search?q={q}', 155 | animenewsnetwork: 'https://www.animenewsnetwork.com/search?q={q}', 156 | ann: 'https://www.animenewsnetwork.com/search?q={q}', 157 | 158 | zoro: 'https://zoro.to/search?keyword={q}', 159 | mangaupdates: 'https://www.mangaupdates.com/search.html?search={q}', 160 | '9anime': 'https://9anime.to/search?keyword={q}&vrf=Ux9h8vwyLd', 161 | 162 | // .new 163 | figma: 'https://figma.new', 164 | meet: 'https://meet.new', 165 | repo: 'https://repo.new', 166 | gist: 'https://gist.new', 167 | docs: 'https://docs.new', 168 | slides: 'https://slides.new', 169 | repl: 'https://replit.new', 170 | lex: 'https://lex.new', 171 | 172 | // Misc 173 | imp: 'https://impb.in/p/{q}', 174 | imperial: 'https://imperialb.in/p/{p}', 175 | urban: 'https://www.urbandictionary.com/define.php?term={q}', 176 | gdrive: 'https://drive.google.com/drive/search?q={q}', 177 | producthunt: 'https://www.producthunt.com/search?q={q}', 178 | linkedin: 'https://www.linkedin.com/search/results/all/?keywords={q}&origin=GLOBAL_SEARCH_HEADER&sid=*Xz', 179 | lnkdn: 'https://www.linkedin.com/search/results/all/?keywords={q}&origin=GLOBAL_SEARCH_HEADER&sid=*Xz', 180 | premid: 'https://premid.app/store?q={q}', 181 | googleimages: 'https://www.google.com/search?q={q}&tbm=isch', 182 | maps: 'https://www.google.com/maps/search/{q}', 183 | presencedb: 'https://www.presencedb.com/search?q={q}', 184 | polywork: 'https://www.polywork.com/{q}', 185 | imgur: 'https://imgur.com/search?q={q}', 186 | imgflip: 'https://imgflip.com/memesearch?q={q}', 187 | techboard: 'https://boards.4channel.org/search#/{q}/g', 188 | udemy: 'https://www.udemy.com/courses/search/?src=ukw&q={q}', 189 | libgen: 'http://libgen.li/index.php?req={q}&res=25', 190 | gsmarena: 'https://gsmarena.com/res.php3?sSearch={q}', 191 | phone: 'https://gsmarena.com/res.php3?sSearch={q}', 192 | ark: 'https://ark.intel.com/content/www/us/en/ark/search.html?_charset_=UTF-8&q={q}', 193 | intelark: 'https://ark.intel.com/content/www/us/en/ark/search.html?_charset_=UTF-8&q={q}', 194 | nato: 'https://cryptii.com/pipes/nato-phonetic-alphabet', 195 | kymeme: 'https://knowyourmeme.com/search?q={q}', 196 | cheat: 'https://cheat.sh/{q}', 197 | hackernews: 'https://hn.algolia.com/?q={q}', 198 | pixiv: 'https://www.pixiv.net/en/tags/{q}', 199 | translate: 'https://translate.google.com/?sl=auto&tl=en&text={q}&op=translate', 200 | gmail: 'https://mail.google.com/mail/#search/{q}', 201 | wayback: 'https://web.archive.org/web/*/{q}', 202 | 203 | // Discord Bot Lists 204 | topgg: 'https://top.gg/search?q={q}', 205 | dbleu: 'https://discord-botlist.eu/search?q={q}', 206 | discordbotlisteu: 'https://discord-botlist.eu/search?q={q}', 207 | del: 'https://discordextremelist.xyz/en-US/search/?q={q}', 208 | discordextremelist: 'https://discordextremelist.xyz/en-US/search/?q={q}', 209 | dbotsgg: 'https://discord.bots.gg/search?q={q}', 210 | 211 | // Torrent Trackers (Optional) 212 | '1337x': 'https://1337x.to/search/{q}/1/', // Universal for Movies, Shows, Anime, Porn, Software and Games. -H4rldev 213 | rarbg: 'http://rarbg.to/torrents.php?search={q}', // Movies, Shows and Porn + Some Software. -H4rldev 214 | rutracker: 'https://rutracker.org/forum/login.php?redirect=tracker.php?nm={q}', // Is in russian and requires an account to search. -H4rldev 215 | nyaa: 'https://nyaa.si/?f=0&c=0_0&q={q}', // Anime Torrents. -H4rldev 216 | psa: 'https://psa.pm/?s={q}', // Movies and Shows re-encoded to x265. -H4rldev 217 | h10anime: 'https://hi10anime.com/?s={q}', // Anime Torrents. -H4rldev 218 | torrentgalaxy: 'https://torrentgalaxy.mx/', // Filled with popups and ads. Really annoying tracker. -H4rldev 219 | rutor: 'http://rutor.info/search/{q}', // Russian Games Tracker. -H4rldev 220 | gamestorrents: 'https://www.gamestorrents.fm/?s={q}', // Spanish or Central/South American Games tracker. -H4rldev 221 | 'gnarly-repacks': 'https://www.gnarly-repacks.site/?s={q}', // Games Site. -H4rldev 222 | fitgirl: 'https://fitgirl-repacks.site/?s={q}', // One of the most popular sources for repacks of games. Highly recommended. -H4rldev 223 | 'dodi-repacks': 'https://dodi-repacks.site/?s={q}', // A lot of dodi's content is the same as fitgirl's. -H4rldev 224 | kaoskrew: 'https://kaoskrew.org/search.php?keywords=yeah&fid%5B0%5D=13', // Torrenting forum for games. Many known crackers post their content there. -H4rldev 225 | 226 | // Twitter utils 227 | tweet: 'https://twitter.com/intent/tweet?text={q}', 228 | twfollow: 'https://twitter.com/intent/follow?screen_name={q}', 229 | rt: 'https://twitter.com/intent/retweet?tweet_id={q}', 230 | 231 | // Cryptoooo 232 | gas: 'https://etherscan.io/gastracker', 233 | eth: args => { 234 | const [type, query] = args.split(' '); 235 | return `https://etherscan.io/${type in etherscanTypeMap ? etherscanTypeMap[type as keyof typeof etherscanTypeMap] : type}/${query}`; 236 | }, 237 | 238 | // Commands (something that has an action rather than a search) 239 | newportal: args => { 240 | const formatted = args.startsWith('http') ? args : `https://${args}`; 241 | return `https://giggl.to/${formatted}`; 242 | }, 243 | // Inspired by https://github.com/hopinc/cli 244 | hop: (args = '') => { 245 | const domain = 'hop.io'; 246 | const [command, arg, ...rest] = args.split(' '); 247 | 248 | const project = arg ? `?project=${arg}` : ''; 249 | const formattedRest = rest.join('/') || ''; 250 | 251 | switch (command) { 252 | case 'home': 253 | return `https://${domain}`; 254 | case 'pricing': 255 | case 'price': 256 | return `https://${domain}/pricing`; 257 | case 'blog': 258 | case 'b': 259 | return `https://${domain}/blog/${arg}`; 260 | case 'roadmap': 261 | case 'r': 262 | return `https://${domain}/roadmap`; 263 | case 'help': 264 | case 'h': 265 | case 'docs': 266 | case 'd': 267 | case 'doc': 268 | return `https://docs.${domain}/${arg ? `${arg}/` : ''}${formattedRest}`; 269 | case 'payment': 270 | case 'p': 271 | case 'cards': 272 | return `https://console.${domain}/settings/cards`; 273 | case 'settings': 274 | case 's': 275 | if (arg) return `https://console.${domain}/project/settings/${formattedRest}${project}`; 276 | return `https://console.${domain}/settings`; 277 | case 'auth': 278 | case 'a': 279 | return `https://console.${domain}/auth`; 280 | case 'channels': 281 | case 'c': 282 | return `https://console.${domain}/channels${project}`; 283 | case 'ignite': 284 | case 'i': 285 | return `https://console.${domain}/ignite${project}`; 286 | default: 287 | return `https://console.${domain}`; 288 | } 289 | }, 290 | }; 291 | -------------------------------------------------------------------------------- /packages/links/src/util.ts: -------------------------------------------------------------------------------- 1 | export const etherscanTypeMap = { 2 | a: 'address', 3 | }; 4 | -------------------------------------------------------------------------------- /packages/links/tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "**/*.js", 10 | "**/*.mjs", 11 | "**/*.jsx", 12 | "**/*.test.ts", 13 | "**/*.test.js", 14 | "**/*.test.mjs", 15 | "**/*.spec.ts", 16 | "**/*.spec.js", 17 | "**/*.spec.mjs" 18 | ], 19 | "exclude": [] 20 | } 21 | -------------------------------------------------------------------------------- /packages/links/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*.ts"] 4 | } 5 | -------------------------------------------------------------------------------- /packages/links/tsup.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'tsup'; 2 | 3 | export default defineConfig({ 4 | entry: ['src/index.ts'], 5 | platform: 'neutral', 6 | format: ['esm', 'cjs'], 7 | target: 'es2022', 8 | skipNodeModulesBundle: true, 9 | clean: true, 10 | shims: true, 11 | minify: false, 12 | splitting: false, 13 | keepNames: true, 14 | dts: true, 15 | sourcemap: true, 16 | }); 17 | -------------------------------------------------------------------------------- /packages/links/wrangler.toml: -------------------------------------------------------------------------------- 1 | name = "search" 2 | account_id = 'ad270f797e0a3205ac74136dc5b656b1' 3 | usage_model = 'bundled' 4 | main = 'src/index.ts' 5 | workers_dev = true 6 | compatibility_date = "2023-05-18" 7 | -------------------------------------------------------------------------------- /tsconfig.eslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "allowJs": true 5 | }, 6 | "include": [ 7 | "**/*.ts", 8 | "**/*.tsx", 9 | "**/*.js", 10 | "**/*.mjs", 11 | "**/*.jsx", 12 | "**/*.test.ts", 13 | "**/*.test.js", 14 | "**/*.test.mjs", 15 | "**/*.spec.ts", 16 | "**/*.spec.js", 17 | "**/*.spec.mjs" 18 | ], 19 | "exclude": [] 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "esnext", 4 | "target": "esnext", 5 | "lib": ["esnext"], 6 | "alwaysStrict": true, 7 | "strict": true, 8 | "preserveConstEnums": true, 9 | "moduleResolution": "node", 10 | "sourceMap": true, 11 | "esModuleInterop": true, 12 | "skipLibCheck": true 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /turbo.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://turbo.build/schema.json", 3 | "globalDependencies": [ 4 | "yarn.lock", 5 | "**/tsconfig.json" 6 | ], 7 | "pipeline": { 8 | "build": { 9 | "dependsOn": [ 10 | "^build" 11 | ], 12 | "inputs": [ 13 | "src/**/*.ts" 14 | ], 15 | "outputs": [ 16 | "dist/**" 17 | ], 18 | "outputMode": "errors-only" 19 | }, 20 | "lint": { 21 | "inputs": [ 22 | "**/.eslintignore", 23 | "**/.eslintrc.json", 24 | "**/.prettierignore", 25 | "**/.prettierrc.json", 26 | "**/tsconfig.eslint.json", 27 | "src/**/*.tsx", 28 | "src/**/*.ts", 29 | "src/**/*.css", 30 | "src/**/*.mdx", 31 | "scripts/**.js", 32 | "scripts/**.mjs", 33 | ".prettierrc.js", 34 | ".prettierrc.cjs", 35 | "vite.config.ts" 36 | ], 37 | "outputMode": "errors-only" 38 | }, 39 | "format": { 40 | "inputs": [ 41 | "**/.eslintignore", 42 | "**/.eslintrc.json", 43 | "**/.prettierignore", 44 | "**/.prettierrc.json", 45 | "**/tsconfig.eslint.json", 46 | "src/**/*.tsx", 47 | "src/**/*.ts", 48 | "src/**/*.css", 49 | "src/**/*.mdx", 50 | "scripts/**.js", 51 | "scripts/**.mjs", 52 | ".prettierrc.js", 53 | ".prettierrc.cjs", 54 | "vite.config.ts" 55 | ], 56 | "outputMode": "errors-only" 57 | }, 58 | "dev": { 59 | "dependsOn": [ 60 | "^build" 61 | ], 62 | "cache": false, 63 | "persistent": true 64 | } 65 | } 66 | } 67 | --------------------------------------------------------------------------------