Compare commits

...

12 Commits

14 changed files with 1310 additions and 928 deletions

3
.gitignore vendored
View File

@ -6,6 +6,7 @@ yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
public/opensearch.xml
node_modules
dist
@ -24,6 +25,8 @@ dist-ssr
*.sw?
# ENV
docker/.env
!docker/.env.example
.env
.env*
!.env.example

View File

@ -9,38 +9,36 @@ This fork allows anyone to easily self host this service & add bangs for their o
- Searxng *!s*
- Gitea *!tea*
- OpenWebUI *!ai*
- Nextcloud App Store *!cloudapp*
- Plex *!plex*
- Overseerr *!ov*
- Sonarr *!tv*
- Radarr *!mv*
- Lidarr *!mp3*
- ~Overseerr *!ov*~ *Overseerr doesn't seem to work correctly*
*Note: We have replaced some of the default bangs from duckduckgo with our own bangs in order to have more simple bangs for the self-hosted websites.*
All you have to do is fill out the environment variables in the .env.example files located in the root directory & the docker directory & rename them to .env.
You can also easily add your own bangs by adding a new entry in the bangs.ts file.
## How to self host
1. Clone the repo & fill out the .env.example files in the root directory & the docker directory & rename them to .env.
- *Note: Our docker compose assumes you plan to select an external network.*
2. Run the bash script `host-bang` in the docker directory with the root directory of the project as an argument.
- *Note: You can also simply run it from the root or docker directory without an argument.*
### How to update the website
1. Run the bash script `update-bang` in the docker directory with the root directory of the project as an argument.
- *Note: You can also simply run it from the root or docker directory without an argument.*
## Theo's words
DuckDuckGo's bang redirects are too slow. Add the following URL as a custom search engine to your browser. Enables all of DuckDuckGo's bangs to work, but much faster.
```
https://bang.gbrown.org?q=%s
```
## How is it that much faster?
### How is it that much faster?
DuckDuckGo does their redirects server side. Their DNS is...not always great. Result is that it often takes ages.
Theo solved this by doing all of the work client side. Once you've went to https://unduck.link once, the JS is all cache'd and will never need to be downloaded again. Your device does the redirects, not me.
### How to self host
1. Clone the repo
2. Fill out the .env.example file & rename it to .env
3. Run `pnpm install` from the root of the repo
4. Run `pnpm build` from the root of the repo
5. Navigate to the docker folder & run `sudo docker-compose up -d`
### How to update the website
1. Run `pnpm build` from the root of the repo
2. Navigate to the docker folder & run `sudo docker compose down && sudo docker-compose up -d`

3
docker/.env.example Normal file
View File

@ -0,0 +1,3 @@
PORT=5000
DOMAIN=bang.mydomain.com
NETWORK=bridge

View File

@ -13,6 +13,7 @@ RUN pnpm install
COPY . .
# Build the project
RUN pnpm run prebuild
RUN pnpm run build
# Stage 2: Serve the app using the same version of Node

View File

@ -1,23 +1,21 @@
version: '3.8'
services:
bang-web-server:
build:
context: ../ # point to the parent directory where package.json and source code reside
dockerfile: docker/Dockerfile # specific path to the Dockerfile
context: ../
dockerfile: docker/Dockerfile
container_name: bang
hostname: bang.gib
domainname: bang.gbrown.org
hostname: bang
networks:
- node_apps
ports:
- "5000:5000"
- nginx-bridge
#ports:
#- 5000:5000
tty: true
restart: unless-stopped
volumes:
- ../:/app # mount the parent directory to /app in the container
- ../:/app
command: serve -s /app/dist -l 5000
networks:
node_apps:
nginx-bridge:
external: true

51
docker/host-bang Executable file
View File

@ -0,0 +1,51 @@
#!/bin/bash
# Function to check if we're in the correct directory
check_directory() {
if [ -d "docker" ] && [ -f "package.json" ]; then
return 0 # We're in the root directory
fi
return 1
}
# Initialize root_dir
root_dir=""
# Check if argument is provided
if [ $# -eq 1 ]; then
# Use provided path
if [ -d "$1" ]; then
root_dir="$1"
cd "$root_dir" || exit 1
else
echo "Error: Provided directory does not exist"
exit 1
fi
else
# No argument provided, try to determine location
current_dir=$(basename "$(pwd)")
if [ "$current_dir" = "docker" ]; then
cd .. || exit 1
elif [ "$current_dir" != "Bang" ]; then
echo "Error: Not in the correct directory and no valid path provided"
echo "Please either:"
echo "1. Run this script from the Bang root directory"
echo "2. Run this script from the docker directory"
echo "3. Provide the path to the Bang root directory as an argument"
exit 1
fi
fi
# Verify we're in the correct directory
if ! check_directory; then
echo "Error: Not in the correct directory structure"
echo "Make sure you're in a directory with 'docker' folder and package.json"
exit 1
fi
pnpm install
pnpm build
cd docker || exit 1
sudo docker compose up -d
cd ..

52
docker/update-bang Executable file
View File

@ -0,0 +1,52 @@
#!/bin/bash
# Function to check if we're in the correct directory
check_directory() {
if [ -d "docker" ] && [ -f "package.json" ]; then
return 0 # We're in the root directory
fi
return 1
}
# Initialize root_dir
root_dir=""
# Check if argument is provided
if [ $# -eq 1 ]; then
# Use provided path
if [ -d "$1" ]; then
root_dir="$1"
cd "$root_dir" || exit 1
else
echo "Error: Provided directory does not exist"
exit 1
fi
else
# No argument provided, try to determine location
current_dir=$(basename "$(pwd)")
if [ "$current_dir" = "docker" ]; then
cd .. || exit 1
elif [ "$current_dir" != "Bang" ]; then
echo "Error: Not in the correct directory and no valid path provided"
echo "Please either:"
echo "1. Run this script from the Bang root directory"
echo "2. Run this script from the docker directory"
echo "3. Provide the path to the Bang root directory as an argument"
exit 1
fi
fi
# Verify we're in the correct directory
if ! check_directory; then
echo "Error: Not in the correct directory structure"
echo "Make sure you're in a directory with 'docker' folder and package.json"
exit 1
fi
git pull
pnpm build
cd docker || exit 1
sudo docker compose down
sudo docker compose up -d
cd ..

View File

@ -24,6 +24,12 @@
media="print"
onload="this.media='all'"
/>
<link
rel="search"
type="application/opensearchdescription+xml"
title="Bang!"
href="/opensearch.xml"
/>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Bang!</title>
<meta

View File

@ -1,21 +1,25 @@
{
"name": "unduck",
"name": "bang",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"generate-opensearch": "ts-node scripts/generateOpenSearch.ts",
"prebuild": "pnpm generate-opensearch",
"build": "tsc && vite build",
"preview": "vite preview"
},
"devDependencies": {
"typescript": "~5.7.2",
"vite": "^6.1.0"
"@types/node": "^22.15.17",
"ts-node": "^10.9.2",
"typescript": "~5.7.3",
"vite": "^6.3.5"
},
"dependencies": {
"vite-plugin-pwa": "^0.21.1"
"vite-plugin-pwa": "^0.21.2"
},
"packageManager": "pnpm@10.5.2+sha512.da9dc28cd3ff40d0592188235ab25d3202add8a207afbedc682220e4a0029ffbff4562102b9e6e46b4e3f9e8bd53e6d05de48544b0c57d4b0179e22c76d1199b",
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
"pnpm": {
"onlyBuiltDependencies": [
"esbuild"

1904
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

10
public/opensearch.xml Normal file
View File

@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Bang!</ShortName>
<Description>A better default search engine with bangs</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16" type="image/svg+xml">https://bang.gbrown.org/bang.svg</Image>
<Url type="text/html" method="get" template="https://bang.gbrown.org?q={searchTerms}"/>
<Url type="application/opensearchdescription+xml" rel="self" template="https://bang.gbrown.org/opensearch.xml"/>
<moz:SearchForm xmlns:moz="http://www.mozilla.org/2006/browser/search/">https://bang.gbrown.org</moz:SearchForm>
</OpenSearchDescription>

98
scripts/files_to_clipboard Executable file
View File

@ -0,0 +1,98 @@
#!/usr/bin/env python3
import os
import sys
import argparse
from pathlib import Path
import pyperclip
import questionary
# List of directories to exclude
EXCLUDED_DIRS = {'node_modules', '.next', '.venv', '.git', '__pycache__', '.idea', '.vscode', 'ui'}
def collect_files(project_path):
"""
Collects files from the project directory, excluding specified directories and filtering by extensions.
Returns a list of file paths relative to the project directory.
"""
collected_files = []
for root, dirs, files in os.walk(project_path):
# Exclude specified directories
dirs[:] = [d for d in dirs if d not in EXCLUDED_DIRS]
for file in files:
file_path = Path(root) / file
relative_path = file_path.relative_to(project_path)
collected_files.append(relative_path)
return collected_files
def main():
# Parse command-line arguments
parser = argparse.ArgumentParser(description='Generate Markdown from selected files.')
parser.add_argument('path', nargs='?', default='.', help='Path to the project directory')
args = parser.parse_args()
project_path = Path(args.path).resolve()
if not project_path.is_dir():
print(f"Error: '{project_path}' is not a directory.")
sys.exit(1)
# Collect files from the project directory
file_list = collect_files(project_path)
if not file_list:
print("No files found in the project directory with the specified extensions.")
sys.exit(1)
# Sort file_list for better organization
file_list.sort()
# Interactive file selection using questionary
print("\nSelect the files you want to include:")
selected_files = questionary.checkbox(
"Press space to select files, and Enter when you're done:",
choices=[str(f) for f in file_list]
).ask()
if not selected_files:
print("No files selected.")
sys.exit(1)
# Generate markdown
markdown_lines = []
markdown_lines.append('')
for selected_file in selected_files:
file_path = project_path / selected_file
try:
with open(file_path, 'r', encoding='utf-8') as f:
content = f.read()
# Determine the language for code block from file extension
language = file_path.suffix.lstrip('.')
markdown_lines.append(f'{selected_file}')
markdown_lines.append(f'```{language}')
markdown_lines.append(content)
markdown_lines.append('```')
markdown_lines.append('')
except Exception as e:
print(f"Error reading file {selected_file}: {e}")
# Copy markdown content to clipboard
markdown_text = '\n'.join(markdown_lines)
pyperclip.copy(markdown_text)
print("Markdown content has been copied to the clipboard.")
if __name__ == "__main__":
# Check if required libraries are installed
try:
import questionary
import pyperclip
except ImportError as e:
missing_module = e.name
print(f"Error: Missing required module '{missing_module}'.")
print(f"Please install it by running: pip install {missing_module}")
sys.exit(1)
main()

View File

@ -0,0 +1,33 @@
import * as fs from 'node:fs';
import * as path from 'node:path';
import { fileURLToPath } from 'node:url';
// Get the current file's directory
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Get the Bang URL from environment or use a default for local development
const bangUrl = process.env.VITE_BANG_URL || 'https://bang.gbrown.org';
// Create the OpenSearch XML content
const openSearchXml = `<?xml version="1.0" encoding="UTF-8"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
<ShortName>Bang!</ShortName>
<Description>A better default search engine with bangs</Description>
<InputEncoding>UTF-8</InputEncoding>
<Image width="16" height="16" type="image/svg+xml">${bangUrl}/bang.svg</Image>
<Url type="text/html" method="get" template="${bangUrl}?q={searchTerms}"/>
<Url type="application/opensearchdescription+xml" rel="self" template="${bangUrl}/opensearch.xml"/>
<moz:SearchForm xmlns:moz="http://www.mozilla.org/2006/browser/search/">${bangUrl}</moz:SearchForm>
</OpenSearchDescription>`;
// Ensure the public directory exists
const publicDir = path.resolve(__dirname, '../public');
if (!fs.existsSync(publicDir)) {
fs.mkdirSync(publicDir, { recursive: true });
}
// Write the OpenSearch XML file
fs.writeFileSync(path.join(publicDir, 'opensearch.xml'), openSearchXml);
console.log('OpenSearch XML file generated successfully!');

View File

@ -3,7 +3,6 @@
const SEARXNG_URL = import.meta.env.VITE_SEARXNG_URL as string;
const GITEA_URL = import.meta.env.VITE_GITEA_URL as string;
const OPENWEBUI_URL = import.meta.env.VITE_OPENWEBUI_URL as string;
const NEXTCLOUD_APPS_URL = import.meta.env.VITE_NEXTCLOUD_URL as string ?? "https://apps.nextcloud.com";
const PLEX_URL = import.meta.env.VITE_PLEX_URL as string;
const OVERSEERR_URL = import.meta.env.VITE_OVERSEERR_URL as string;
const SONARR_URL = import.meta.env.VITE_SONARR_URL as string;
@ -41,7 +40,7 @@ export const bangs = [
s: "Plex",
sc: "Movies",
t: "plex",
u: `${PLEX_URL}/web/index.html#!/search?pivot=top&query=query={{{s}}}`,
u: `${PLEX_URL}/web/index.html#!/search?pivot=top&query={{{s}}}`,
},
{
c: "Entertainment",
@ -73583,12 +73582,12 @@ export const bangs = [
},
{
c: "Tech",
d: NEXTCLOUD_APPS_URL,
d: "apps.nextcloud.com",
r: 0,
s: "Nextcloud App Store",
sc: "Sysadmin",
t: "cloudapp",
u: `${NEXTCLOUD_APPS_URL}/?q={{{s}}}`,
u: "https://apps.nextcloud.com/search/?q={{{s}}}",
},
{
c: "Entertainment",