├── .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 |
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 |
5 |
6 |
7 |
8 |
9 |

10 |

11 |

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 |
5 |
6 |
7 |
8 |
9 |

10 |

11 |

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 |
5 |
6 |
7 |
8 |
9 |

10 |

11 |
12 |

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 |
--------------------------------------------------------------------------------