Initial commit: quoter CLI with 96 seed quotes
- quoter: bash CLI with random, list, search, add, delete, browse, TUI, import - quotes.sql: 96 quotes (The Boys, Breaking Bad, Dark Knight, MCU, LOTR, GOT, games) - install.sh: install/uninstall with --user/--system flags - .gitignore: exclude .opencode, docs/superpowers, *.db, text.txt
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
.opencode/
|
||||
docs/superpowers/
|
||||
*.db
|
||||
text.txt
|
||||
Executable
+198
@@ -0,0 +1,198 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
BINARY="quoter"
|
||||
|
||||
check_sqlite3() {
|
||||
if ! command -v sqlite3 &>/dev/null; then
|
||||
echo "Error: sqlite3 is required but not installed." >&2
|
||||
echo "" >&2
|
||||
echo "Install it with one of:" >&2
|
||||
echo " Debian/Ubuntu: sudo apt install sqlite3" >&2
|
||||
echo " Fedora: sudo dnf install sqlite" >&2
|
||||
echo " macOS: brew install sqlite3" >&2
|
||||
echo " Arch: sudo pacman -S sqlite" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " sqlite3: $(sqlite3 --version | head -c1; echo)"
|
||||
}
|
||||
|
||||
check_optional_deps() {
|
||||
local missing=()
|
||||
|
||||
if ! command -v fzf &>/dev/null; then
|
||||
missing+=("fzf")
|
||||
else
|
||||
echo " fzf: $(fzf --version 2>/dev/null | head -c1; echo)"
|
||||
fi
|
||||
|
||||
if ! command -v gum &>/dev/null; then
|
||||
missing+=("gum")
|
||||
else
|
||||
echo " gum: $(gum --version 2>/dev/null | head -c1; echo)"
|
||||
fi
|
||||
|
||||
if [[ ${#missing[@]} -gt 0 ]]; then
|
||||
echo ""
|
||||
echo " Note: Optional TUI dependencies not found: ${missing[*]}"
|
||||
echo " quoter works without them, but for the best experience:"
|
||||
echo ""
|
||||
if [[ " ${missing[*]} " =~ " fzf " ]]; then
|
||||
echo " fzf (interactive browsing & deletion):"
|
||||
echo " Debian/Ubuntu: sudo apt install fzf"
|
||||
echo " macOS: brew install fzf"
|
||||
echo " Arch: sudo pacman -S fzf"
|
||||
echo " Other: https://github.com/junegunn/fzf"
|
||||
echo ""
|
||||
fi
|
||||
if [[ " ${missing[*]} " =~ " gum " ]]; then
|
||||
echo " gum (styled display & interactive adding):"
|
||||
echo " macOS: brew install gum"
|
||||
echo " Arch: pacman -S gum"
|
||||
echo " Other: https://github.com/charmbracelet/gum"
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
do_install() {
|
||||
local install_dir="$1"
|
||||
|
||||
echo "Installing ${BINARY}..."
|
||||
|
||||
check_sqlite3
|
||||
|
||||
if [[ "$install_dir" == "/usr/local/bin" ]]; then
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo " System install requires sudo. Re-running with elevated privileges..."
|
||||
exec sudo "$0" --system
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ ! -d "$install_dir" ]]; then
|
||||
echo " Creating directory: $install_dir"
|
||||
mkdir -p "$install_dir"
|
||||
fi
|
||||
|
||||
if [[ -f "$install_dir/$BINARY" ]]; then
|
||||
echo " Updating existing installation at $install_dir/$BINARY"
|
||||
else
|
||||
echo " Installing to $install_dir/$BINARY"
|
||||
fi
|
||||
|
||||
cp "$SCRIPT_DIR/$BINARY" "$install_dir/$BINARY"
|
||||
chmod +x "$install_dir/$BINARY"
|
||||
|
||||
local data_dir="$HOME/.local/share/quoter"
|
||||
mkdir -p "$data_dir"
|
||||
if [[ -f "$SCRIPT_DIR/quotes.sql" ]]; then
|
||||
cp "$SCRIPT_DIR/quotes.sql" "$data_dir/quotes.sql"
|
||||
echo " Default quotes installed to $data_dir/quotes.sql"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo " ${BINARY} installed successfully to $install_dir/$BINARY"
|
||||
echo ""
|
||||
check_optional_deps
|
||||
echo ""
|
||||
|
||||
if ! echo "$PATH" | tr ':' '\n' | grep -qx "$install_dir"; then
|
||||
echo " Note: $install_dir is not in your PATH."
|
||||
echo " Add it by running:"
|
||||
echo " echo 'export PATH=\"$install_dir:\$PATH\"' >> ~/.bashrc"
|
||||
echo " source ~/.bashrc"
|
||||
echo ""
|
||||
fi
|
||||
|
||||
echo " Run ${BINARY} to get a random quote!"
|
||||
}
|
||||
|
||||
do_uninstall() {
|
||||
local found=0
|
||||
|
||||
echo "Uninstalling ${BINARY}..."
|
||||
|
||||
for dir in "$HOME/.local/bin" "/usr/local/bin"; do
|
||||
if [[ -f "$dir/$BINARY" ]]; then
|
||||
echo " Removing $dir/$BINARY"
|
||||
rm -f "$dir/$BINARY"
|
||||
found=1
|
||||
fi
|
||||
done
|
||||
|
||||
local data_dir="$HOME/.local/share/quoter"
|
||||
if [[ -d "$data_dir" ]]; then
|
||||
read -rp " Remove all quote data at $data_dir? [y/N] " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
rm -rf "$data_dir"
|
||||
echo " Data directory removed."
|
||||
else
|
||||
echo " Data directory kept at $data_dir"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ "$found" -eq 0 ]]; then
|
||||
echo " ${BINARY} was not found in standard locations."
|
||||
else
|
||||
echo ""
|
||||
echo " ${BINARY} has been uninstalled."
|
||||
fi
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << HELP
|
||||
${BINARY} installer
|
||||
|
||||
Usage:
|
||||
$(basename "$0") [option]
|
||||
|
||||
Options:
|
||||
--user Install to ~/.local/bin (default)
|
||||
--system Install to /usr/local/bin (requires sudo)
|
||||
--uninstall Remove ${BINARY} and optionally its data
|
||||
--help Show this help
|
||||
|
||||
Data is stored in: ~/.local/share/quoter/quoter.db
|
||||
HELP
|
||||
}
|
||||
|
||||
ACTION="user"
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--user)
|
||||
ACTION="user"
|
||||
shift
|
||||
;;
|
||||
--system)
|
||||
ACTION="system"
|
||||
shift
|
||||
;;
|
||||
--uninstall)
|
||||
ACTION="uninstall"
|
||||
shift
|
||||
;;
|
||||
--help|-h)
|
||||
show_help
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
show_help >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
case "$ACTION" in
|
||||
user)
|
||||
do_install "$HOME/.local/bin"
|
||||
;;
|
||||
system)
|
||||
do_install "/usr/local/bin"
|
||||
;;
|
||||
uninstall)
|
||||
do_uninstall
|
||||
;;
|
||||
esac
|
||||
@@ -0,0 +1,846 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
DB_DIR="$HOME/.local/share/quoter"
|
||||
DB="$DB_DIR/quoter.db"
|
||||
|
||||
BOLD=$(tput bold 2>/dev/null || echo "")
|
||||
CYAN=$(tput setaf 6 2>/dev/null || echo "")
|
||||
YELLOW=$(tput setaf 3 2>/dev/null || echo "")
|
||||
GREEN=$(tput setaf 2 2>/dev/null || echo "")
|
||||
DIM=$(tput dim 2>/dev/null || echo "")
|
||||
RESET=$(tput sgr0 2>/dev/null || echo "")
|
||||
|
||||
HAS_FZF=false
|
||||
HAS_GUM=false
|
||||
command -v fzf &>/dev/null && HAS_FZF=true
|
||||
command -v gum &>/dev/null && HAS_GUM=true
|
||||
|
||||
find_quotes_sql() {
|
||||
if [[ -f "$DB_DIR/quotes.sql" ]]; then
|
||||
echo "$DB_DIR/quotes.sql"
|
||||
elif [[ -f "$(dirname "$(realpath "$0" 2>/dev/null || echo "$0")")/quotes.sql" ]]; then
|
||||
echo "$(dirname "$(realpath "$0" 2>/dev/null || echo "$0")")/quotes.sql"
|
||||
elif [[ -z "${QUOTER_SEED:-}" ]] && [[ -f "$QUOTER_SEED" ]]; then
|
||||
echo "$QUOTER_SEED"
|
||||
else
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
db_init() {
|
||||
mkdir -p "$DB_DIR"
|
||||
sqlite3 "$DB" "CREATE TABLE IF NOT EXISTS quotes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
quote TEXT NOT NULL,
|
||||
source TEXT NOT NULL,
|
||||
character TEXT NOT NULL,
|
||||
season TEXT,
|
||||
type TEXT DEFAULT 'tv',
|
||||
added_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);"
|
||||
|
||||
local has_type
|
||||
has_type=$(sqlite3 "$DB" "PRAGMA table_info(quotes);" 2>/dev/null | grep -c '^.*|type|.*$' || echo "0")
|
||||
if [[ "$has_type" -eq 0 ]]; then
|
||||
sqlite3 "$DB" "ALTER TABLE quotes ADD COLUMN type TEXT DEFAULT 'tv';"
|
||||
sqlite3 "$DB" "UPDATE quotes SET type = 'movie' WHERE source IN ('The Dark Knight');"
|
||||
sqlite3 "$DB" "UPDATE quotes SET type = 'tv' WHERE source IN ('Breaking Bad', 'The Boys');"
|
||||
fi
|
||||
}
|
||||
|
||||
db_seed() {
|
||||
local count
|
||||
count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes;")
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
local seed_file
|
||||
seed_file=$(find_quotes_sql)
|
||||
if [[ -n "$seed_file" ]]; then
|
||||
sqlite3 "$DB" < "$seed_file"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
import_quotes() {
|
||||
local file="$ARG_IMPORT"
|
||||
if [[ ! -f "$file" ]]; then
|
||||
echo "Error: file not found: $file" >&2
|
||||
exit 1
|
||||
fi
|
||||
sqlite3 "$DB" < "$file"
|
||||
echo "Quotes imported from $file"
|
||||
}
|
||||
|
||||
format_type() {
|
||||
local t="$1"
|
||||
case "$t" in
|
||||
movie) echo "Movie" ;;
|
||||
tv) echo "TV Show" ;;
|
||||
game) echo "Game" ;;
|
||||
*) echo "${t^}" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
show_random() {
|
||||
local result
|
||||
result=$(sqlite3 "$DB" "SELECT id, quote, source, character, COALESCE(season, ''), COALESCE(type, 'tv') FROM quotes ORDER BY RANDOM() LIMIT 1;" 2>/dev/null)
|
||||
if [[ -z "$result" ]]; then
|
||||
echo "No quotes yet. Add one with ${BOLD}quoter --add${RESET}"
|
||||
return
|
||||
fi
|
||||
IFS='|' read -r id quote source character season qtype <<< "$result"
|
||||
|
||||
if [[ "$ARG_SIMPLE" == "true" ]]; then
|
||||
echo "\"${quote}\" — ${character}, ${source}"
|
||||
return
|
||||
fi
|
||||
|
||||
local season_part=""
|
||||
if [[ -n "$season" ]]; then
|
||||
season_part=" ${DIM}(${season})${RESET}"
|
||||
fi
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
|
||||
if $HAS_GUM; then
|
||||
local season_display=""
|
||||
[[ -n "$season" ]] && season_display=" ($season)"
|
||||
echo ""
|
||||
printf '"%s"\n — %s, %s [%s]%s\n' "$quote" "$character" "$source" "$type_label" "$season_display" | gum style --border rounded --margin "1 2" --padding "1 3"
|
||||
echo ""
|
||||
else
|
||||
echo ""
|
||||
echo " ${BOLD}\"${quote}\"${RESET}"
|
||||
echo " — ${CYAN}${character}${RESET}, ${YELLOW}${source}${RESET} ${DIM}[${type_label}]${RESET}${season_part}"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
add_interactive() {
|
||||
stty echo 2>/dev/null || true
|
||||
local quote source character season qtype
|
||||
|
||||
if $HAS_GUM; then
|
||||
quote=$(gum input --placeholder "Enter the quote")
|
||||
[[ -z "$quote" ]] && { gum style --foreground 1 "Error: quote is required."; exit 1; }
|
||||
|
||||
source=$(gum input --placeholder "Source (movie, TV show, or game title)")
|
||||
[[ -z "$source" ]] && { gum style --foreground 1 "Error: source is required."; exit 1; }
|
||||
|
||||
character=$(gum input --placeholder "Character who said it")
|
||||
[[ -z "$character" ]] && { gum style --foreground 1 "Error: character is required."; exit 1; }
|
||||
|
||||
qtype=$(gum choose "tv" "movie" "game" --header="Select type")
|
||||
|
||||
if [[ "$qtype" == "tv" ]]; then
|
||||
season=$(gum input --placeholder "Season/Episode (e.g. S01E01) or leave empty")
|
||||
else
|
||||
season=""
|
||||
fi
|
||||
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
local season_display=""
|
||||
[[ -n "$season" ]] && season_display=" ($season)"
|
||||
echo ""
|
||||
printf '"%s"\n — %s, %s [%s]%s\n' "$quote" "$character" "$source" "$type_label" "$season_display" | gum style --border rounded --margin "1 2" --padding "0 2"
|
||||
echo ""
|
||||
|
||||
if gum confirm "Save this quote?"; then
|
||||
if [[ -n "$season" ]]; then
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, season, type) VALUES ('$(sqlite3_escape "$quote")', '$(sqlite3_escape "$source")', '$(sqlite3_escape "$character")', '$(sqlite3_escape "$season")', '$(sqlite3_escape "$qtype")');"
|
||||
else
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, type) VALUES ('$(sqlite3_escape "$quote")', '$(sqlite3_escape "$source")', '$(sqlite3_escape "$character")', '$(sqlite3_escape "$qtype")');"
|
||||
fi
|
||||
gum style --foreground 2 "Quote added!"
|
||||
else
|
||||
gum style --foreground 3 "Cancelled."
|
||||
fi
|
||||
else
|
||||
echo "Add a new quote:"
|
||||
echo ""
|
||||
read -rp " Quote: " quote
|
||||
read -rp " Source (movie/TV show/game): " source
|
||||
read -rp " Character: " character
|
||||
read -rp " Season/Episode (optional, e.g. S01E01): " season
|
||||
|
||||
echo " Type:"
|
||||
echo " 1) tv"
|
||||
echo " 2) movie"
|
||||
echo " 3) game"
|
||||
read -rp " Choose [1-3]: " type_choice
|
||||
case "$type_choice" in
|
||||
2) qtype="movie" ;;
|
||||
3) qtype="game" ;;
|
||||
*) qtype="tv" ;;
|
||||
esac
|
||||
|
||||
if [[ -z "$quote" || -z "$source" || -z "$character" ]]; then
|
||||
echo "Error: quote, source, and character are required." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -n "$season" ]]; then
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, season, type) VALUES ('$(sqlite3_escape "$quote")', '$(sqlite3_escape "$source")', '$(sqlite3_escape "$character")', '$(sqlite3_escape "$season")', '$(sqlite3_escape "$qtype")');"
|
||||
else
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, type) VALUES ('$(sqlite3_escape "$quote")', '$(sqlite3_escape "$source")', '$(sqlite3_escape "$character")', '$(sqlite3_escape "$qtype")');"
|
||||
fi
|
||||
echo "Quote added!"
|
||||
fi
|
||||
}
|
||||
|
||||
add_noninteractive() {
|
||||
if [[ -z "$ARG_QUOTE" || -z "$ARG_SOURCE" || -z "$ARG_CHARACTER" ]]; then
|
||||
echo "Error: --quote, --source, and --character are required when using --add non-interactively." >&2
|
||||
echo "Run ${BOLD}quoter --help${RESET} for usage." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local qtype="${ARG_TYPE:-tv}"
|
||||
|
||||
if [[ -n "$ARG_SEASON" ]]; then
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, season, type) VALUES ('$(sqlite3_escape "$ARG_QUOTE")', '$(sqlite3_escape "$ARG_SOURCE")', '$(sqlite3_escape "$ARG_CHARACTER")', '$(sqlite3_escape "$ARG_SEASON")', '$(sqlite3_escape "$qtype")');"
|
||||
else
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, type) VALUES ('$(sqlite3_escape "$ARG_QUOTE")', '$(sqlite3_escape "$ARG_SOURCE")', '$(sqlite3_escape "$ARG_CHARACTER")', '$(sqlite3_escape "$qtype")');"
|
||||
fi
|
||||
echo "Quote added!"
|
||||
}
|
||||
|
||||
sqlite3_escape() {
|
||||
local str="$1"
|
||||
str="${str//\'/\'\'}"
|
||||
printf '%s' "$str"
|
||||
}
|
||||
|
||||
list_quotes() {
|
||||
local count
|
||||
count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes;")
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
echo "No quotes yet. Add one with ${BOLD}quoter --add${RESET}"
|
||||
return
|
||||
fi
|
||||
|
||||
local where=""
|
||||
local conditions=()
|
||||
|
||||
if [[ -n "$ARG_SOURCE" ]]; then
|
||||
conditions+=("source LIKE '%$(sqlite3_escape "$ARG_SOURCE")%' COLLATE NOCASE")
|
||||
fi
|
||||
if [[ -n "$ARG_TYPE" ]]; then
|
||||
conditions+=("type = '$(sqlite3_escape "$ARG_TYPE")'")
|
||||
fi
|
||||
if [[ -n "$ARG_CHARACTER" ]]; then
|
||||
conditions+=("character LIKE '%$(sqlite3_escape "$ARG_CHARACTER")%' COLLATE NOCASE")
|
||||
fi
|
||||
|
||||
if [[ ${#conditions[@]} -gt 0 ]]; then
|
||||
where="WHERE $(IFS=' AND '; echo "${conditions[*]}")"
|
||||
fi
|
||||
|
||||
if [[ "$ARG_SIMPLE" == "true" ]]; then
|
||||
sqlite3 "$DB" "SELECT quote, character, source FROM quotes $where ORDER BY id;" | while IFS='|' read -r quote character source; do
|
||||
echo "\"${quote}\" — ${character}, ${source}"
|
||||
done
|
||||
return
|
||||
fi
|
||||
|
||||
printf "${BOLD}%-4s %-42s %-12s %-20s %-6s %-8s${RESET}\n" "ID" "Quote" "Type" "Source" "Season" "Char"
|
||||
printf '%-4s %-42s %-12s %-20s %-6s %-8s\n' "----" "------------------------------------------" "------------" "--------------------" "------" "--------"
|
||||
|
||||
sqlite3 "$DB" "SELECT id, quote, type, source, COALESCE(season, '-'), character FROM quotes $where ORDER BY id;" | while IFS='|' read -r id quote qtype source season character; do
|
||||
if [[ ${#quote} -gt 40 ]]; then
|
||||
quote="${quote:0:37}..."
|
||||
fi
|
||||
if [[ ${#source} -gt 20 ]]; then
|
||||
source="${source:0:17}..."
|
||||
fi
|
||||
if [[ ${#character} -gt 8 ]]; then
|
||||
character="${character:0:5}..."
|
||||
fi
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
printf "%-4s %-42s %-12s %-20s %-6s %-8s\n" "$id" "$quote" "$type_label" "$source" "$season" "$character"
|
||||
done
|
||||
}
|
||||
|
||||
browse_quotes() {
|
||||
if ! $HAS_FZF; then
|
||||
echo "Error: --browse requires fzf. Install it from https://github.com/junegunn/fzf" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local count
|
||||
count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes;")
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
echo "No quotes yet. Add one with ${BOLD}quoter --add${RESET}"
|
||||
return
|
||||
fi
|
||||
|
||||
local selected
|
||||
selected=$(sqlite3 "$DB" "SELECT id, quote, character, source, COALESCE(season, ''), COALESCE(type, 'tv') FROM quotes ORDER BY id;" | \
|
||||
while IFS='|' read -r id quote character source season qtype; do
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
local season_info=""
|
||||
[[ -n "$season" ]] && season_info=" ($season)"
|
||||
printf "%-4s %-50s — %s, %s [%s]%s\n" "$id" "$quote" "$character" "$source" "$type_label" "$season_info"
|
||||
done | fzf \
|
||||
--height=~80% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="Browse quotes (Enter to view, Esc to cancel)" \
|
||||
--no-multi \
|
||||
--ansi \
|
||||
--cycle \
|
||||
--query="${ARG_SEARCH:-}" 2>/dev/null)
|
||||
|
||||
if [[ -n "$selected" ]]; then
|
||||
local sel_id
|
||||
sel_id=$(echo "$selected" | awk '{print $1}')
|
||||
show_quote_by_id "$sel_id"
|
||||
fi
|
||||
}
|
||||
|
||||
show_quote_by_id() {
|
||||
local id="$1"
|
||||
local result
|
||||
result=$(sqlite3 "$DB" "SELECT quote, source, character, COALESCE(season, ''), COALESCE(type, 'tv') FROM quotes WHERE id = $id;" 2>/dev/null)
|
||||
if [[ -z "$result" ]]; then
|
||||
echo "Quote not found." >&2
|
||||
return
|
||||
fi
|
||||
IFS='|' read -r quote source character season qtype <<< "$result"
|
||||
local season_part=""
|
||||
if [[ -n "$season" ]]; then
|
||||
season_part=" ($season)"
|
||||
fi
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
|
||||
if $HAS_GUM; then
|
||||
echo ""
|
||||
printf '"%s"\n — %s, %s [%s]%s\n' "$quote" "$character" "$source" "$type_label" "$season_part" | gum style --border rounded --margin "1 2" --padding "1 3"
|
||||
echo ""
|
||||
else
|
||||
echo ""
|
||||
echo " ${BOLD}\"${quote}\"${RESET}"
|
||||
echo " — ${CYAN}${character}${RESET}, ${YELLOW}${source}${RESET} ${DIM}[${type_label}]${RESET}${DIM}${season_part}${RESET}"
|
||||
echo ""
|
||||
fi
|
||||
}
|
||||
|
||||
search_quotes() {
|
||||
if [[ -z "$ARG_SEARCH" ]]; then
|
||||
echo "Error: --search requires a search term." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local escaped
|
||||
escaped=$(sqlite3_escape "$ARG_SEARCH")
|
||||
local where="WHERE (quote LIKE '%${escaped}%' COLLATE NOCASE OR character LIKE '%${escaped}%' COLLATE NOCASE OR source LIKE '%${escaped}%' COLLATE NOCASE)"
|
||||
|
||||
local count
|
||||
count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes $where;")
|
||||
if [[ "$count" -eq 0 ]]; then
|
||||
echo "No quotes found matching '${ARG_SEARCH}'."
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$ARG_SIMPLE" == "true" ]]; then
|
||||
sqlite3 "$DB" "SELECT quote, character, source FROM quotes $where ORDER BY id;" | while IFS='|' read -r quote character source; do
|
||||
echo "\"${quote}\" — ${character}, ${source}"
|
||||
done
|
||||
return
|
||||
fi
|
||||
|
||||
printf "${BOLD}%-4s %-42s %-12s %-20s %-6s %-8s${RESET}\n" "ID" "Quote" "Type" "Source" "Season" "Char"
|
||||
printf '%-4s %-42s %-12s %-20s %-6s %-8s\n' "----" "------------------------------------------" "------------" "--------------------" "------" "--------"
|
||||
|
||||
sqlite3 "$DB" "SELECT id, quote, type, source, COALESCE(season, '-'), character FROM quotes $where ORDER BY id;" | while IFS='|' read -r id quote qtype source season character; do
|
||||
if [[ ${#quote} -gt 40 ]]; then
|
||||
quote="${quote:0:37}..."
|
||||
fi
|
||||
if [[ ${#source} -gt 20 ]]; then
|
||||
source="${source:0:17}..."
|
||||
fi
|
||||
if [[ ${#character} -gt 8 ]]; then
|
||||
character="${character:0:5}..."
|
||||
fi
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
printf "%-4s %-42s %-12s %-20s %-6s %-8s\n" "$id" "$quote" "$type_label" "$source" "$season" "$character"
|
||||
done
|
||||
}
|
||||
|
||||
delete_quote() {
|
||||
if [[ -n "$ARG_DELETE" ]]; then
|
||||
local result
|
||||
result=$(sqlite3 "$DB" "SELECT id, quote, source, character FROM quotes WHERE id = $ARG_DELETE;" 2>/dev/null)
|
||||
if [[ -z "$result" ]]; then
|
||||
echo "Quote with ID $ARG_DELETE not found." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
IFS='|' read -r id quote source character <<< "$result"
|
||||
|
||||
if $HAS_GUM; then
|
||||
echo ""
|
||||
printf '"%s"\n — %s, %s\n' "$quote" "$character" "$source" | gum style --border rounded --margin "1 2" --padding "0 2"
|
||||
echo ""
|
||||
if gum confirm "Delete this quote?"; then
|
||||
sqlite3 "$DB" "DELETE FROM quotes WHERE id = $ARG_DELETE;"
|
||||
gum style --foreground 2 "Quote deleted."
|
||||
else
|
||||
gum style --foreground 3 "Cancelled."
|
||||
fi
|
||||
else
|
||||
stty echo 2>/dev/null || true
|
||||
echo "Delete this quote?"
|
||||
echo " ${BOLD}\"${quote}\"${RESET} — ${CYAN}${character}${RESET}, ${YELLOW}${source}${RESET}"
|
||||
echo ""
|
||||
read -rp " Are you sure? [y/N] " confirm
|
||||
if [[ "$confirm" =~ ^[Yy]$ ]]; then
|
||||
sqlite3 "$DB" "DELETE FROM quotes WHERE id = $ARG_DELETE;"
|
||||
echo "Quote deleted."
|
||||
else
|
||||
echo "Cancelled."
|
||||
fi
|
||||
fi
|
||||
else
|
||||
if ! $HAS_FZF; then
|
||||
echo "Error: --delete without ID requires fzf. Install it or use --delete <id>." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local selected
|
||||
selected=$(sqlite3 "$DB" "SELECT id, quote, character, source FROM quotes ORDER BY id;" | \
|
||||
while IFS='|' read -r id quote character source; do
|
||||
printf "%-4s %-50s — %s, %s\n" "$id" "$quote" "$character" "$source"
|
||||
done | fzf \
|
||||
--height=~80% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="Select a quote to delete (Esc to cancel)" \
|
||||
--no-multi \
|
||||
--ansi \
|
||||
--cycle 2>/dev/null)
|
||||
|
||||
if [[ -n "$selected" ]]; then
|
||||
local sel_id
|
||||
sel_id=$(echo "$selected" | awk '{print $1}')
|
||||
ARG_DELETE="$sel_id" delete_quote
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
tui_mode() {
|
||||
if ! $HAS_FZF; then
|
||||
echo "Error: --tui requires fzf." >&2
|
||||
echo "Install it from https://github.com/junegunn/fzf" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local quote_count
|
||||
quote_count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes;" 2>/dev/null || echo "0")
|
||||
|
||||
local use_gum=false
|
||||
$HAS_GUM && use_gum=true
|
||||
|
||||
local header
|
||||
header=" quoter — ${quote_count} quotes in your collection"
|
||||
|
||||
tui_wait() {
|
||||
fzf \
|
||||
--height=~10% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="$1" \
|
||||
--no-multi \
|
||||
--disabled \
|
||||
--prompt="" \
|
||||
< /dev/null 2>/dev/null || true
|
||||
}
|
||||
|
||||
tui_input() {
|
||||
local label="$1"
|
||||
fzf \
|
||||
--height=~15% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="$label" \
|
||||
--no-multi \
|
||||
--disabled \
|
||||
--prompt="> " \
|
||||
--no-info \
|
||||
< /dev/null 2>/dev/null || true
|
||||
}
|
||||
|
||||
tui_confirm() {
|
||||
local label="$1"
|
||||
local result
|
||||
result=$(printf "Yes\nNo\n" | fzf \
|
||||
--height=~20% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="$label" \
|
||||
--no-multi \
|
||||
--prompt="> " \
|
||||
2>/dev/null) || result="No"
|
||||
[[ "$result" == "Yes" ]]
|
||||
}
|
||||
|
||||
tui_add() {
|
||||
local quote source character season qtype
|
||||
|
||||
quote=$(tui_input "Enter the quote")
|
||||
[[ -z "$quote" ]] && { echo "Cancelled."; return; }
|
||||
|
||||
source=$(tui_input "Source (movie, TV show, or game title)")
|
||||
[[ -z "$source" ]] && { echo "Cancelled."; return; }
|
||||
|
||||
character=$(tui_input "Character who said it")
|
||||
[[ -z "$character" ]] && { echo "Cancelled."; return; }
|
||||
|
||||
qtype=$(printf "tv\nmovie\ngame\n" | fzf \
|
||||
--height=~25% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="Select type" \
|
||||
--no-multi \
|
||||
--prompt="> " \
|
||||
--cycle \
|
||||
2>/dev/null) || qtype="tv"
|
||||
|
||||
season=""
|
||||
if [[ "$qtype" == "tv" ]]; then
|
||||
season=$(tui_input "Season/Episode (e.g. S01E01) — leave empty to skip")
|
||||
fi
|
||||
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
local season_display=""
|
||||
[[ -n "$season" ]] && season_display=" ($season)"
|
||||
echo ""
|
||||
printf '"%s"\n — %s, %s [%s]%s\n' "$quote" "$character" "$source" "$type_label" "$season_display"
|
||||
|
||||
if tui_confirm "Save this quote?"; then
|
||||
if [[ -n "$season" ]]; then
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, season, type) VALUES ('$(sqlite3_escape "$quote")', '$(sqlite3_escape "$source")', '$(sqlite3_escape "$character")', '$(sqlite3_escape "$season")', '$(sqlite3_escape "$qtype")');"
|
||||
else
|
||||
sqlite3 "$DB" "INSERT INTO quotes (quote, source, character, type) VALUES ('$(sqlite3_escape "$quote")', '$(sqlite3_escape "$source")', '$(sqlite3_escape "$character")', '$(sqlite3_escape "$qtype")');"
|
||||
fi
|
||||
echo "Quote added!"
|
||||
else
|
||||
echo "Cancelled."
|
||||
fi
|
||||
}
|
||||
|
||||
while true; do
|
||||
local choice
|
||||
choice=$(printf "%s\n%s\n%s\n%s\n%s\n%s\n%s" \
|
||||
"Random quote" \
|
||||
"Search quotes" \
|
||||
"List all quotes" \
|
||||
"Browse quotes" \
|
||||
"Add a quote" \
|
||||
"Delete a quote" \
|
||||
"Exit" \
|
||||
| fzf \
|
||||
--height=~40% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="$header" \
|
||||
--prompt="> " \
|
||||
--cycle \
|
||||
--no-multi \
|
||||
--ansi \
|
||||
--marker="" \
|
||||
--pointer=">" \
|
||||
2>/dev/null)
|
||||
|
||||
[[ -z "$choice" ]] && break
|
||||
|
||||
case "$choice" in
|
||||
"Random quote")
|
||||
echo ""
|
||||
show_random
|
||||
echo ""
|
||||
tui_wait "Press Enter to continue..."
|
||||
;;
|
||||
"Search quotes")
|
||||
local selected
|
||||
selected=$(sqlite3 "$DB" "SELECT id, quote, character, source, COALESCE(season, ''), COALESCE(type, 'tv') FROM quotes ORDER BY id;" | \
|
||||
while IFS='|' read -r id quote character source season qtype; do
|
||||
local type_label
|
||||
type_label=$(format_type "$qtype")
|
||||
local season_info=""
|
||||
[[ -n "$season" ]] && season_info=" ($season)"
|
||||
printf "%-4s %-50s — %s, %s [%s]%s\n" "$id" "$quote" "$character" "$source" "$type_label" "$season_info"
|
||||
done | fzf \
|
||||
--height=~70% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="Type to filter quotes — Enter to view, Esc to cancel" \
|
||||
--prompt="> " \
|
||||
--no-multi \
|
||||
--ansi \
|
||||
--cycle \
|
||||
2>/dev/null)
|
||||
if [[ -n "$selected" ]]; then
|
||||
local sel_id
|
||||
sel_id=$(echo "$selected" | awk '{print $1}')
|
||||
echo ""
|
||||
show_quote_by_id "$sel_id"
|
||||
echo ""
|
||||
fi
|
||||
tui_wait "Press Enter to continue..."
|
||||
;;
|
||||
"List all quotes")
|
||||
local filter_type
|
||||
filter_type=$(printf "all\nmovie\ntv\ngame\n" \
|
||||
| fzf \
|
||||
--height=~30% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="Filter by type" \
|
||||
--prompt="> " \
|
||||
--no-multi \
|
||||
--cycle \
|
||||
2>/dev/null)
|
||||
if [[ -n "$filter_type" ]]; then
|
||||
echo ""
|
||||
if [[ "$filter_type" == "all" ]]; then
|
||||
ARG_TYPE="" ARG_SIMPLE="true" list_quotes
|
||||
else
|
||||
ARG_TYPE="$filter_type" ARG_SIMPLE="true" list_quotes
|
||||
fi
|
||||
echo ""
|
||||
fi
|
||||
tui_wait "Press Enter to continue..."
|
||||
;;
|
||||
"Browse quotes")
|
||||
browse_quotes
|
||||
tui_wait "Press Enter to continue..."
|
||||
;;
|
||||
"Add a quote")
|
||||
echo ""
|
||||
tui_add
|
||||
echo ""
|
||||
quote_count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes;" 2>/dev/null || echo "0")
|
||||
header=" quoter — ${quote_count} quotes in your collection"
|
||||
tui_wait "Press Enter to continue..."
|
||||
;;
|
||||
"Delete a quote")
|
||||
echo ""
|
||||
local selected
|
||||
selected=$(sqlite3 "$DB" "SELECT id, quote, character, source FROM quotes ORDER BY id;" | \
|
||||
while IFS='|' read -r id quote character source; do
|
||||
printf "%-4s %-50s — %s, %s\n" "$id" "$quote" "$character" "$source"
|
||||
done | fzf \
|
||||
--height=~60% \
|
||||
--layout=reverse \
|
||||
--border=rounded \
|
||||
--header="Select a quote to delete (Esc to cancel)" \
|
||||
--no-multi \
|
||||
--ansi \
|
||||
--cycle \
|
||||
--prompt="> " \
|
||||
2>/dev/null)
|
||||
if [[ -n "$selected" ]]; then
|
||||
local sel_id
|
||||
sel_id=$(echo "$selected" | awk '{print $1}')
|
||||
local result
|
||||
result=$(sqlite3 "$DB" "SELECT id, quote, source, character FROM quotes WHERE id = $sel_id;" 2>/dev/null)
|
||||
if [[ -n "$result" ]]; then
|
||||
IFS='|' read -r id quote source character <<< "$result"
|
||||
printf '"%s"\n — %s, %s\n' "$quote" "$character" "$source"
|
||||
if tui_confirm "Delete this quote?"; then
|
||||
sqlite3 "$DB" "DELETE FROM quotes WHERE id = $sel_id;"
|
||||
echo "Quote deleted."
|
||||
else
|
||||
echo "Cancelled."
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
echo ""
|
||||
quote_count=$(sqlite3 "$DB" "SELECT COUNT(*) FROM quotes;" 2>/dev/null || echo "0")
|
||||
header=" quoter — ${quote_count} quotes in your collection"
|
||||
tui_wait "Press Enter to continue..."
|
||||
;;
|
||||
"Exit")
|
||||
break
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
echo " Bye!"
|
||||
echo ""
|
||||
}
|
||||
|
||||
show_help() {
|
||||
cat << 'HELP'
|
||||
quoter — Display quotes from movies, TV shows, and games
|
||||
|
||||
Usage:
|
||||
quoter Show a random quote
|
||||
quoter --tui Launch interactive TUI mode
|
||||
quoter --add Add a quote (interactive)
|
||||
quoter --add -q "quote" -s "source" -c "character" [-t type] [-e "S01E01"]
|
||||
Add a quote (non-interactive)
|
||||
quoter --list [-s "source"] [-t type] [-c "character"]
|
||||
List quotes (optionally filtered)
|
||||
quoter --browse Browse quotes interactively (fzf)
|
||||
quoter --search "term" Search quotes, characters, sources
|
||||
quoter --delete [id] Delete a quote (fzf picker if no ID)
|
||||
quoter --import <file.sql> Import quotes from a SQL file
|
||||
quoter --help Show this help
|
||||
|
||||
Options:
|
||||
-a, --add Add a new quote
|
||||
-q, --quote TEXT Quote text (with --add)
|
||||
-s, --source TEXT Source title (with --add or --list)
|
||||
-c, --char TEXT Character name (with --add or --list)
|
||||
-e, --season TEXT Season/episode e.g. S01E01 (with --add, optional)
|
||||
-t, --type TEXT Type: movie, tv, game (with --add or --list)
|
||||
-l, --list List all quotes
|
||||
-b, --browse Browse quotes interactively (requires fzf)
|
||||
-S, --simple Simple output: just quote, character and source
|
||||
-T, --tui Launch interactive TUI mode (requires fzf)
|
||||
-i, --import FILE Import quotes from a SQL file
|
||||
-f, --search TEXT Search quotes, characters, and sources
|
||||
-d, --delete [ID] Delete a quote by ID, or pick interactively
|
||||
-h, --help Show this help message
|
||||
|
||||
TUI features:
|
||||
Install fzf for interactive browsing, deletion, and TUI mode
|
||||
Install gum for styled display and interactive adding
|
||||
|
||||
Data is stored in: ~/.local/share/quoter/quoter.db
|
||||
Default quotes loaded from: ~/.local/share/quoter/quotes.sql
|
||||
HELP
|
||||
}
|
||||
|
||||
ACTION=""
|
||||
ARG_QUOTE=""
|
||||
ARG_SOURCE=""
|
||||
ARG_CHARACTER=""
|
||||
ARG_SEASON=""
|
||||
ARG_TYPE=""
|
||||
ARG_SEARCH=""
|
||||
ARG_DELETE=""
|
||||
ARG_SIMPLE="false"
|
||||
ARG_IMPORT=""
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-a|--add)
|
||||
ACTION="add"
|
||||
shift
|
||||
;;
|
||||
-q|--quote)
|
||||
ARG_QUOTE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-s|--source)
|
||||
ARG_SOURCE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-c|--char|--character)
|
||||
ARG_CHARACTER="$2"
|
||||
shift 2
|
||||
;;
|
||||
-e|--season)
|
||||
ARG_SEASON="$2"
|
||||
shift 2
|
||||
;;
|
||||
-t|--type)
|
||||
ARG_TYPE="$2"
|
||||
shift 2
|
||||
;;
|
||||
-l|--list)
|
||||
ACTION="list"
|
||||
shift
|
||||
;;
|
||||
-b|--browse)
|
||||
ACTION="browse"
|
||||
shift
|
||||
;;
|
||||
-S|--simple)
|
||||
ARG_SIMPLE="true"
|
||||
shift
|
||||
;;
|
||||
-T|--tui)
|
||||
ACTION="tui"
|
||||
shift
|
||||
;;
|
||||
-i|--import)
|
||||
ACTION="import"
|
||||
ARG_IMPORT="$2"
|
||||
shift 2
|
||||
;;
|
||||
-f|--search)
|
||||
ACTION="search"
|
||||
ARG_SEARCH="$2"
|
||||
shift 2
|
||||
;;
|
||||
-d|--delete)
|
||||
ACTION="delete"
|
||||
if [[ $# -gt 1 ]] && [[ "$2" =~ ^[0-9]+$ ]]; then
|
||||
ARG_DELETE="$2"
|
||||
shift 2
|
||||
else
|
||||
shift
|
||||
fi
|
||||
;;
|
||||
-h|--help)
|
||||
ACTION="help"
|
||||
shift
|
||||
;;
|
||||
*)
|
||||
echo "Unknown option: $1" >&2
|
||||
echo "Run ${BOLD}quoter --help${RESET} for usage." >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
db_init
|
||||
db_seed
|
||||
|
||||
case "$ACTION" in
|
||||
add)
|
||||
if [[ -n "$ARG_QUOTE" || -n "$ARG_SOURCE" || -n "$ARG_CHARACTER" || -n "$ARG_SEASON" || -n "$ARG_TYPE" ]]; then
|
||||
add_noninteractive
|
||||
else
|
||||
add_interactive
|
||||
fi
|
||||
;;
|
||||
list)
|
||||
list_quotes
|
||||
;;
|
||||
browse)
|
||||
browse_quotes
|
||||
;;
|
||||
tui)
|
||||
tui_mode
|
||||
;;
|
||||
import)
|
||||
import_quotes
|
||||
;;
|
||||
search)
|
||||
search_quotes
|
||||
;;
|
||||
delete)
|
||||
delete_quote
|
||||
;;
|
||||
help)
|
||||
show_help
|
||||
;;
|
||||
"")
|
||||
show_random
|
||||
;;
|
||||
*)
|
||||
echo "Unknown action: $ACTION" >&2
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
+97
@@ -0,0 +1,97 @@
|
||||
INSERT INTO quotes (quote, source, character, season, type) VALUES
|
||||
('I am the one who knocks.', 'Breaking Bad', 'Walter White', 'S04E06', 'tv'),
|
||||
('Never let them see you bleed.', 'The Boys', 'Homelander', 'S01E01', 'tv'),
|
||||
('Why so serious?', 'The Dark Knight', 'Joker', NULL, 'movie'),
|
||||
('Say my name.', 'Breaking Bad', 'Walter White', 'S05E04', 'tv'),
|
||||
('Some people just want to watch the world burn.', 'The Dark Knight', 'Alfred Pennyworth', NULL, 'movie'),
|
||||
('I am not in danger, Skyler. I am the danger.', 'Breaking Bad', 'Walter White', 'S04E06', 'tv'),
|
||||
('It''s not about money. It''s about sending a message.', 'The Dark Knight', 'Joker', NULL, 'movie'),
|
||||
('We can be heroes, just for one day.', 'The Boys', 'Queen Maeve', 'S01E06', 'tv'),
|
||||
('Boy, that escalated quickly.', 'God of War', 'Kratos', NULL, 'game'),
|
||||
('The right man in the wrong place can make all the difference.', 'Half-Life 2', 'G-Man', NULL, 'game'),
|
||||
('The cake is a lie.', 'Portal', 'GLaDOS', NULL, 'game'),
|
||||
('War. War never changes.', 'Fallout', 'Narrator', NULL, 'game'),
|
||||
('A hero need not speak. When he is gone, the world will speak for him.', 'Halo', 'Narrator', NULL, 'game'),
|
||||
('With great power comes the absolute certainty that you''ll turn into a right c**t.', 'The Boys', 'Butcher', 'S03E06', 'tv'),
|
||||
('We''ll cross that bridge when we burn it.', 'The Boys', 'Butcher', 'S01E03', 'tv'),
|
||||
('What''s Sporty Spice up to?', 'The Boys', 'Butcher', 'S01E04', 'tv'),
|
||||
('A stranger is just a friend you ain''t met yet.', 'The Boys', 'Butcher', 'S02E03', 'tv'),
|
||||
('Kid''s a weeper. Don''t want him to get snot on me jacket.', 'The Boys', 'Butcher', 'S02E04', 'tv'),
|
||||
('For once, I''ve leveled the f**king playing field.', 'The Boys', 'Butcher', 'S03E05', 'tv'),
|
||||
('Not the kid.', 'The Boys', 'Butcher', 'S03E08', 'tv'),
|
||||
('You really are the spitting image of my little brother.', 'The Boys', 'Butcher', 'S03E08', 'tv'),
|
||||
('I just had to pop down to the shop. I was running a bit low on mind your own f**king business.', 'The Boys', 'Butcher', 'S01E05', 'tv'),
|
||||
('What have you got to lose that you ain''t already lost?', 'The Boys', 'Butcher', 'S01E01', 'tv'),
|
||||
('People love that cozy feeling that Supes give them. Some golden c**t to swoop out of the sky and save the day so you don''t got to do it yourself.', 'The Boys', 'Butcher', 'S01E01', 'tv'),
|
||||
('Never go into shark-infested waters without chum.', 'The Boys', 'Butcher', 'S02E06', 'tv'),
|
||||
('It''s not power. It''s punishment.', 'The Boys', 'Butcher', 'S03E04', 'tv'),
|
||||
('Don''t be a c**t.', 'The Boys', 'Butcher', 'S03E08', 'tv'),
|
||||
('Congress. Please. What a bunch of corrupt f**king c**ts they are.', 'The Boys', 'Butcher', 'S02E08', 'tv'),
|
||||
('Holy f**k. That was diabolical.', 'The Boys', 'Butcher', 'S01E05', 'tv'),
|
||||
('Expecting a happy ending, were we? Well, I''m sorry, Hughie. It ain''t that kind of massage parlor.', 'The Boys', 'Butcher', 'S02E05', 'tv'),
|
||||
('You''re a bunch of pathetic Supe-worshipping c**ts. I bet you''d thank a Supe if they sh*t on your mum''s best china.', 'The Boys', 'Butcher', 'S01E06', 'tv'),
|
||||
('I''ll tickle your balls till you beg me to stop, and even then I won''t. I just won''t do it.', 'The Boys', 'Butcher', 'S02E03', 'tv'),
|
||||
('Well, you could''ve warned us your pal Sameer was V-ing up a Kentucky Fried f**king massacre!', 'The Boys', 'Butcher', 'S04E05', 'tv'),
|
||||
('Bloody hell, you w**k to your own voice, don''t you?', 'The Boys', 'Butcher', 'S04E01', 'tv'),
|
||||
('You ain''t no God. How''s about I go fetch the virus, and then we''ll watch you sh*t your f**king spine out?', 'The Boys', 'Butcher', 'S05E04', 'tv'),
|
||||
('Not so f**king super are you.', 'The Boys', 'Butcher', 'S05E04', 'tv'),
|
||||
('I promise you, before I die, I''ll f**king have ya.', 'The Boys', 'Butcher', 'S05E04', 'tv'),
|
||||
('No! My cake hole will remain open! You will never command me again. I am done with your cruelty. I deserve respect! And we all deserve paid vacation days, and a dental plan!', 'The Boys', 'Frenchie', 'S03E08', 'tv'),
|
||||
('Nina was right. My papa, he put a chain around my neck. And all that f**king changes is who holds the other end.', 'The Boys', 'Frenchie', 'S03E08', 'tv'),
|
||||
('So you lost your temper and hit a man. Oh, this is nothing. You know, I once took a Spaniard''s ear for jabbering at a screening of 27 Dresses...', 'The Boys', 'Frenchie', 'S03E08', 'tv'),
|
||||
('It will be the greatest sorrow of my life that I missed Herogasm.', 'The Boys', 'Frenchie', 'S03E07', 'tv'),
|
||||
('The Lord hates quitters.', 'The Boys', 'Frenchie', 'S03E07', 'tv'),
|
||||
('It seems no matter how much we try to run, we cannot escape our old lives, huh? I suppose no one can run that fast.', 'The Boys', 'Frenchie', 'S03E06', 'tv'),
|
||||
('Que c''est... Hamburger with a doughnut for a bun? Truly, there is no God here.', 'The Boys', 'Frenchie', 'S03E02', 'tv'),
|
||||
('Girls do get it done.', 'The Boys', 'Frenchie', 'S02E08', 'tv'),
|
||||
('Wile E. Coyote. Always chases Roadrunner, always with an elaborate plan, always fails. You know, I always say, "Why do this, Coyote? All you need is an AR-15, and meep-meep no more."', 'The Boys', 'Frenchie', 'S02E08', 'tv'),
|
||||
('It won''t help you. All you''d be doing is ending his torment. You cannot punish him as much as he punishes himself.', 'The Boys', 'Frenchie', 'S02E06', 'tv'),
|
||||
('Don''t be so closed-minded.', 'The Boys', 'Frenchie', 'S01E06', 'tv'),
|
||||
('Cause all you say is oi, oi, oi, c**t, c**t, c**t!', 'The Boys', 'Kimiko', 'S05E01', 'tv'),
|
||||
('I''m the guy who makes the deals.', 'The Boys', 'Homelander', 'S03E05', 'tv'),
|
||||
('I''m not a superhero, I''m a supervillain.', 'The Boys', 'Butcher', 'S02E03', 'tv'),
|
||||
('I am Iron Man.', 'Iron Man', 'Tony Stark', NULL, 'movie'),
|
||||
('I love you 3000.', 'Avengers: Endgame', 'Tony Stark', NULL, 'movie'),
|
||||
('I can do this all day.', 'Captain America: The First Avenger', 'Steve Rogers', NULL, 'movie'),
|
||||
('Dormammu, I''ve come to bargain.', 'Doctor Strange', 'Doctor Strange', NULL, 'movie'),
|
||||
('I am Groot.', 'Guardians of the Galaxy', 'Groot', NULL, 'movie'),
|
||||
('The universe has put you in this position for a reason.', 'Avengers: Endgame', 'Tony Stark', NULL, 'movie'),
|
||||
('With great power, comes great responsibility.', 'Spider-Man', 'Uncle Ben', NULL, 'movie'),
|
||||
('I have nothing to prove to you.', 'Captain Marvel', 'Carol Danvers', NULL, 'movie'),
|
||||
('Part of the journey is the end.', 'Avengers: Endgame', 'Tony Stark', NULL, 'movie'),
|
||||
('I went for the head.', 'Avengers: Endgame', 'Thor', NULL, 'movie'),
|
||||
('I''m always angry.', 'The Avengers', 'Bruce Banner', NULL, 'movie'),
|
||||
('There was an idea... to bring together a group of remarkable people.', 'The Avengers', 'Nick Fury', NULL, 'movie'),
|
||||
('The greater good.', 'Avengers: Age of Ultron', 'Ultron', NULL, 'movie'),
|
||||
('I''m Mary Poppins, y''all!', 'Avengers: Endgame', 'Scott Lang', NULL, 'movie'),
|
||||
('Don''t do anything I would do. And definitely don''t do anything I wouldn''t do.', 'Spider-Man: Homecoming', 'Tony Stark', NULL, 'movie'),
|
||||
('One does not simply walk into Mordor.', 'The Lord of the Rings: The Fellowship of the Ring', 'Boromir', NULL, 'movie'),
|
||||
('My precious.', 'The Lord of the Rings: The Two Towers', 'Gollum', NULL, 'movie'),
|
||||
('Even the smallest person can change the course of the future.', 'The Lord of the Rings: The Fellowship of the Ring', 'Galadriel', NULL, 'movie'),
|
||||
('A wizard is never late, nor is he early. He arrives precisely when he means to.', 'The Lord of the Rings: The Fellowship of the Ring', 'Gandalf', NULL, 'movie'),
|
||||
('You shall not pass!', 'The Lord of the Rings: The Fellowship of the Ring', 'Gandalf', NULL, 'movie'),
|
||||
('I would have followed you, my brother. My captain. My king.', 'The Lord of the Rings: The Fellowship of the Ring', 'Boromir', NULL, 'movie'),
|
||||
('Not all those who wander are lost.', 'The Lord of the Rings: The Fellowship of the Ring', 'Aragorn', NULL, 'movie'),
|
||||
('There is always hope.', 'The Lord of the Rings: The Two Towers', 'Aragorn', NULL, 'movie'),
|
||||
('Fly, you fools!', 'The Lord of the Rings: The Fellowship of the Ring', 'Gandalf', NULL, 'movie'),
|
||||
('I can avoid being seen if I wish, but to disappear entirely — that is a rare gift.', 'The Lord of the Rings: The Fellowship of the Ring', 'Aragorn', NULL, 'movie'),
|
||||
('End? No, the journey doesn''t end here. Death is just another path, one that we all must take.', 'The Lord of the Rings: The Return of the King', 'Gandalf', NULL, 'movie'),
|
||||
('All we have to decide is what to do with the time that is given to us.', 'The Lord of the Rings: The Fellowship of the Ring', 'Gandalf', NULL, 'movie'),
|
||||
('I will take the ring to Mordor. Though... I do not know the way.', 'The Lord of the Rings: The Fellowship of the Ring', 'Frodo', NULL, 'movie'),
|
||||
('A day may come when the courage of men fails, when we forsake our friends and break all bonds of fellowship. But it is not this day.', 'The Lord of the Rings: The Return of the King', 'Aragorn', NULL, 'movie'),
|
||||
('No, Sam. I can''t recall the taste of food, nor the sound of water, nor the touch of grass.', 'The Lord of the Rings: The Return of the King', 'Frodo', NULL, 'movie'),
|
||||
('When you play the game of thrones, you win or you die. There is no middle ground.', 'Game of Thrones', 'Cersei Lannister', 'S01E07', 'tv'),
|
||||
('Winter is coming.', 'Game of Thrones', 'Ned Stark', 'S01E01', 'tv'),
|
||||
('A lion doesn''t concern himself with the opinions of a sheep.', 'Game of Thrones', 'Tywin Lannister', 'S01E07', 'tv'),
|
||||
('Chaos isn''t a pit. Chaos is a ladder.', 'Game of Thrones', 'Littlefinger', 'S03E06', 'tv'),
|
||||
('The night is dark and full of terrors.', 'Game of Thrones', 'Melisandre', 'S02E05', 'tv'),
|
||||
('I drink and I know things.', 'Game of Thrones', 'Tyrion Lannister', 'S06E02', 'tv'),
|
||||
('Any man who must say "I am the king" is no true king.', 'Game of Thrones', 'Tywin Lannister', 'S03E10', 'tv'),
|
||||
('If you think this has a happy ending, you haven''t been paying attention.', 'Game of Thrones', 'Ramsay Bolton', 'S03E07', 'tv'),
|
||||
('The things I do for love.', 'Game of Thrones', 'Jaime Lannister', 'S01E02', 'tv'),
|
||||
('Never forget what you are. The rest of the world will not. Wear it like armor, and it can never be used to hurt you.', 'Game of Thrones', 'Tyrion Lannister', 'S01E02', 'tv'),
|
||||
('Valar morghulis.', 'Game of Thrones', 'Jaqen H''ghar', 'S02E10', 'tv'),
|
||||
('Power resides where men believe it resides. It''s a trick, a shadow on the wall.', 'Game of Thrones', 'Varys', 'S02E03', 'tv'),
|
||||
('Burn them all.', 'Game of Thrones', 'The Mad King', 'S06E05', 'tv'),
|
||||
('A Lannister always pays his debts.', 'Game of Thrones', 'Tyrion Lannister', 'S01E03', 'tv'),
|
||||
('I am the king! I am the king!', 'Game of Thrones', 'Joffrey Baratheon', 'S02E08', 'tv');
|
||||
Reference in New Issue
Block a user