diff --git a/.gitignore b/.gitignore
index b9a0f68..b6cf5f0 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,7 +1,3 @@
-.env
-nio_store/
-logs/
__pycache__/
*.pyc
-credentials.json
-wordle_stats.json
+.env
diff --git a/bot.py b/bot.py
deleted file mode 100644
index e35cf89..0000000
--- a/bot.py
+++ /dev/null
@@ -1,204 +0,0 @@
-import asyncio
-import json
-import logging
-import signal
-import sys
-from pathlib import Path
-
-from nio import (
- AsyncClient,
- AsyncClientConfig,
- InviteMemberEvent,
- LoginResponse,
- RoomMemberEvent,
- RoomMessageText,
- UnknownEvent,
-)
-
-from config import (
- MATRIX_HOMESERVER,
- MATRIX_USER_ID,
- MATRIX_ACCESS_TOKEN,
- MATRIX_DEVICE_ID,
- MATRIX_PASSWORD,
- LOG_LEVEL,
- ConfigValidator,
-)
-from callbacks import Callbacks
-from utils import setup_logging
-from welcome import post_welcome_message
-
-logger = setup_logging(LOG_LEVEL)
-
-CREDENTIALS_FILE = Path("credentials.json")
-STORE_PATH = Path("nio_store")
-
-
-def save_credentials(resp, homeserver):
- data = {
- "homeserver": homeserver,
- "user_id": resp.user_id,
- "device_id": resp.device_id,
- "access_token": resp.access_token,
- }
- CREDENTIALS_FILE.write_text(json.dumps(data, indent=2))
- logger.info("Credentials saved to %s", CREDENTIALS_FILE)
-
-
-async def trust_devices(client: AsyncClient):
- """Query keys and trust all devices for all users we share rooms with."""
- if not client.olm:
- logger.warning("Olm not loaded, skipping device trust")
- return
-
- # Collect all users across all joined rooms
- users = set()
- for room in client.rooms.values():
- for user_id in room.users:
- users.add(user_id)
-
- # Fetch device keys so the store is complete
- if users:
- await client.keys_query()
-
- # Trust every device
- for user_id, devices in client.device_store.items():
- for device_id, olm_device in devices.items():
- if not client.olm.is_device_verified(olm_device):
- client.verify_device(olm_device)
- logger.info("Trusted all known devices (%d users)", len(users))
-
-
-async def main():
- errors = ConfigValidator.validate()
- if errors:
- for e in errors:
- logger.error(e)
- sys.exit(1)
-
- STORE_PATH.mkdir(exist_ok=True)
-
- client_config = AsyncClientConfig(
- store_sync_tokens=True,
- encryption_enabled=True,
- store_name="matrixbot",
- )
-
- client = AsyncClient(
- MATRIX_HOMESERVER,
- MATRIX_USER_ID,
- device_id=MATRIX_DEVICE_ID or None,
- config=client_config,
- store_path=str(STORE_PATH),
- )
-
- # Try saved credentials first, then .env token, then password login
- logged_in = False
- has_creds = False
-
- if CREDENTIALS_FILE.exists():
- creds = json.loads(CREDENTIALS_FILE.read_text())
- client.access_token = creds["access_token"]
- client.user_id = creds["user_id"]
- client.device_id = creds["device_id"]
- has_creds = True
- logger.info("Loaded credentials from %s", CREDENTIALS_FILE)
- elif MATRIX_ACCESS_TOKEN and MATRIX_DEVICE_ID:
- client.access_token = MATRIX_ACCESS_TOKEN
- client.user_id = MATRIX_USER_ID
- client.device_id = MATRIX_DEVICE_ID
- has_creds = True
- logger.info("Using access token from .env")
-
- # Load the olm/e2ee store only if we have a device_id
- if has_creds:
- client.load_store()
-
- # Test the token with a sync; if it fails, fall back to password login
- if has_creds and client.access_token:
- logger.info("Testing existing access token...")
- sync_resp = await client.sync(timeout=30000, full_state=True)
- if hasattr(sync_resp, "next_batch"):
- logged_in = True
- logger.info("Existing token is valid")
- else:
- logger.warning("Existing token is invalid, will try password login")
- client.access_token = ""
-
- if not logged_in:
- if not MATRIX_PASSWORD:
- logger.error("No valid token and no MATRIX_PASSWORD set — cannot authenticate")
- await client.close()
- sys.exit(1)
- logger.info("Logging in with password...")
- login_resp = await client.login(MATRIX_PASSWORD, device_name="LotusBot")
- if isinstance(login_resp, LoginResponse):
- logger.info("Password login successful, device_id=%s", login_resp.device_id)
- save_credentials(login_resp, MATRIX_HOMESERVER)
- client.load_store()
- sync_resp = await client.sync(timeout=30000, full_state=True)
- else:
- logger.error("Password login failed: %s", login_resp)
- await client.close()
- sys.exit(1)
-
- callbacks = Callbacks(client)
- client.add_event_callback(callbacks.message, RoomMessageText)
- client.add_event_callback(callbacks.reaction, UnknownEvent)
- client.add_event_callback(callbacks.member, RoomMemberEvent)
-
- # Auto-accept room invites
- async def _auto_accept_invite(room, event):
- if event.membership == "invite" and event.state_key == MATRIX_USER_ID:
- logger.info("Auto-accepting invite to %s", room.room_id)
- await client.join(room.room_id)
-
- client.add_event_callback(_auto_accept_invite, InviteMemberEvent)
-
- # Graceful shutdown
- loop = asyncio.get_running_loop()
- shutdown_event = asyncio.Event()
-
- def _signal_handler():
- logger.info("Shutdown signal received")
- shutdown_event.set()
-
- for sig in (signal.SIGTERM, signal.SIGINT):
- loop.add_signal_handler(sig, _signal_handler)
-
- # Mark startup complete from the initial sync
- if hasattr(sync_resp, "next_batch"):
- callbacks.startup_sync_token = sync_resp.next_batch
- logger.info("Initial sync complete, token: %s", sync_resp.next_batch[:20])
- else:
- logger.error("Initial sync failed: %s", sync_resp)
- await client.close()
- sys.exit(1)
-
- # Trust devices after initial sync loads the device store
- await trust_devices(client)
-
- # Post welcome message (idempotent — only posts if not already stored)
- await post_welcome_message(client)
-
- logger.info("Bot ready as %s — listening for commands", MATRIX_USER_ID)
-
- # Run sync_forever in a task so we can cancel on shutdown
- async def _sync_loop():
- await client.sync_forever(timeout=30000, full_state=False)
-
- sync_task = asyncio.create_task(_sync_loop())
-
- await shutdown_event.wait()
- sync_task.cancel()
- try:
- await sync_task
- except asyncio.CancelledError:
- pass
-
- await client.close()
- logger.info("Bot shut down cleanly")
-
-
-if __name__ == "__main__":
- asyncio.run(main())
diff --git a/callbacks.py b/callbacks.py
deleted file mode 100644
index a3c7d5c..0000000
--- a/callbacks.py
+++ /dev/null
@@ -1,114 +0,0 @@
-import logging
-from functools import wraps
-
-from nio import AsyncClient, RoomMessageText, UnknownEvent
-
-from config import BOT_PREFIX, MATRIX_USER_ID
-from commands import COMMANDS, metrics
-from welcome import handle_welcome_reaction, handle_space_join, SPACE_ROOM_ID
-
-logger = logging.getLogger("matrixbot")
-
-
-def handle_command_errors(func):
- @wraps(func)
- async def wrapper(client, room_id, sender, args):
- try:
- return await func(client, room_id, sender, args)
- except Exception as e:
- logger.error(f"Error in command {func.__name__}: {e}", exc_info=True)
- metrics.record_error(func.__name__)
- try:
- from utils import send_text
- await send_text(client, room_id, "An unexpected error occurred. Please try again later.")
- except Exception as e2:
- logger.error(f"Failed to send error message: {e2}", exc_info=True)
- return wrapper
-
-
-class Callbacks:
- def __init__(self, client: AsyncClient):
- self.client = client
- # Track the sync token so we ignore old messages on startup
- self.startup_sync_token = None
-
- async def message(self, room, event):
- # Ignore messages from before the bot started
- if self.startup_sync_token is None:
- return
-
- # Ignore our own messages
- if event.sender == MATRIX_USER_ID:
- return
-
- body = event.body.strip() if event.body else ""
- if not body.startswith(BOT_PREFIX):
- return
-
- # Parse command and args
- without_prefix = body[len(BOT_PREFIX):]
- parts = without_prefix.split(None, 1)
- cmd_name = parts[0].lower() if parts else ""
- args = parts[1] if len(parts) > 1 else ""
-
- logger.info(f"Command '{cmd_name}' from {event.sender} in {room.room_id}")
-
- handler_entry = COMMANDS.get(cmd_name)
- if handler_entry is None:
- return
-
- handler, _ = handler_entry
- metrics.record_command(cmd_name)
- wrapped = handle_command_errors(handler)
- await wrapped(self.client, room.room_id, event.sender, args)
-
- async def reaction(self, room, event):
- """Handle m.reaction events (sent as UnknownEvent by matrix-nio)."""
- # Ignore events from before startup
- if self.startup_sync_token is None:
- return
-
- # Ignore our own reactions
- if event.sender == MATRIX_USER_ID:
- return
-
- # m.reaction events come as UnknownEvent with type "m.reaction"
- if not hasattr(event, "source"):
- return
-
- content = event.source.get("content", {})
- relates_to = content.get("m.relates_to", {})
- if relates_to.get("rel_type") != "m.annotation":
- return
-
- reacted_event_id = relates_to.get("event_id", "")
- key = relates_to.get("key", "")
-
- await handle_welcome_reaction(
- self.client, room.room_id, event.sender, reacted_event_id, key
- )
-
- async def member(self, room, event):
- """Handle m.room.member events — watch for Space joins."""
- # Ignore events from before startup
- if self.startup_sync_token is None:
- return
-
- # Only care about the Space
- if room.room_id != SPACE_ROOM_ID:
- return
-
- # Ignore our own membership changes
- if event.state_key == MATRIX_USER_ID:
- return
-
- # Only trigger on joins (not leaves, bans, etc.)
- if event.membership != "join":
- return
-
- # Check if this is a new join (prev was not "join")
- prev = event.prev_membership if hasattr(event, "prev_membership") else None
- if prev == "join":
- return # Already was a member, this is a profile update or similar
-
- await handle_space_join(self.client, event.state_key)
diff --git a/cinny/config.json b/cinny/config.json
new file mode 100644
index 0000000..98cdf75
--- /dev/null
+++ b/cinny/config.json
@@ -0,0 +1,17 @@
+{
+ "defaultHomeserver": 0,
+ "homeserverList": [
+ "matrix.lotusguild.org"
+ ],
+ "allowCustomHomeservers": false,
+ "featuredCommunities": {
+ "openAsDefault": false,
+ "spaces": [],
+ "rooms": [],
+ "servers": []
+ },
+ "hashRouter": {
+ "enabled": false,
+ "basename": "/"
+ }
+}
\ No newline at end of file
diff --git a/cinny/dev-update.sh b/cinny/dev-update.sh
new file mode 100644
index 0000000..6e25958
--- /dev/null
+++ b/cinny/dev-update.sh
@@ -0,0 +1,59 @@
+#!/bin/bash
+# Cinny dev branch nightly build and deploy
+set -e
+
+REPO_DIR="/opt/cinny-dev"
+WEB_ROOT="/var/www/html"
+CONFIG_SAVED="/opt/cinny-dev/.cinny-config.json"
+BUILD_DIR="/opt/cinny-dev/dist"
+LOG="/var/log/cinny-dev-update.log"
+
+exec >> "$LOG" 2>&1
+echo "=== $(date) === Starting Cinny dev update ==="
+
+# Save config from live site (skip if web root is already broken)
+if [ -f "$WEB_ROOT/config.json" ]; then
+ cp "$WEB_ROOT/config.json" "$CONFIG_SAVED"
+fi
+
+# Pull latest dev
+cd "$REPO_DIR"
+git fetch --depth=1 origin dev
+PREV=$(git rev-parse HEAD)
+git reset --hard origin/dev
+NEW=$(git rev-parse HEAD)
+
+if [ "$PREV" = "$NEW" ]; then
+ echo "No changes since last build ($NEW), skipping."
+ exit 0
+fi
+
+echo "New commits since $PREV, building $NEW..."
+
+# Clean previous dist so stale files don't get deployed on partial build
+rm -rf "$BUILD_DIR"
+
+# Build — cap at 896MB to stay within 1GB RAM + swap headroom
+export NODE_OPTIONS='--max_old_space_size=896'
+npm ci 2>&1 | tail -5
+npm run build 2>&1 | tail -10
+
+# Verify build actually produced output before touching web root
+if [ ! -f "$BUILD_DIR/index.html" ]; then
+ echo "ERROR: Build failed — dist/index.html missing. Web root untouched."
+ exit 1
+fi
+
+# Deploy only now that we know the build succeeded
+rm -rf "$WEB_ROOT"/*
+cp -r "$BUILD_DIR"/* "$WEB_ROOT/"
+
+# Restore config
+if [ -f "$CONFIG_SAVED" ]; then
+ cp "$CONFIG_SAVED" "$WEB_ROOT/config.json"
+else
+ echo "WARNING: No saved config found — default config from build will be used."
+fi
+
+nginx -s reload
+echo "=== Deploy complete: $NEW ==="
diff --git a/commands.py b/commands.py
deleted file mode 100644
index cde23a6..0000000
--- a/commands.py
+++ /dev/null
@@ -1,630 +0,0 @@
-import asyncio
-import json
-import random
-import time
-import logging
-from collections import Counter
-from datetime import datetime
-from functools import partial
-
-import aiohttp
-
-from nio import AsyncClient
-
-from utils import send_text, send_html, send_reaction, sanitize_input
-from config import (
- MAX_DICE_SIDES, MAX_DICE_COUNT, BOT_PREFIX, ADMIN_USERS,
- OLLAMA_URL, OLLAMA_MODEL, MAX_INPUT_LENGTH, COOLDOWN_SECONDS,
- MINECRAFT_RCON_HOST, MINECRAFT_RCON_PORT, MINECRAFT_RCON_PASSWORD,
- RCON_TIMEOUT, MIN_USERNAME_LENGTH, MAX_USERNAME_LENGTH,
-)
-
-logger = logging.getLogger("matrixbot")
-
-# Registry: name -> (handler, description)
-COMMANDS = {}
-
-
-def command(name, description=""):
- def decorator(func):
- COMMANDS[name] = (func, description)
- return func
- return decorator
-
-
-# ==================== METRICS ====================
-
-
-class MetricsCollector:
- def __init__(self):
- self.command_counts = Counter()
- self.error_counts = Counter()
- self.start_time = datetime.now()
-
- def record_command(self, command_name: str):
- self.command_counts[command_name] += 1
-
- def record_error(self, command_name: str):
- self.error_counts[command_name] += 1
-
- def get_stats(self) -> dict:
- uptime = datetime.now() - self.start_time
- return {
- "uptime_seconds": uptime.total_seconds(),
- "commands_executed": sum(self.command_counts.values()),
- "top_commands": self.command_counts.most_common(5),
- "error_count": sum(self.error_counts.values()),
- }
-
-
-metrics = MetricsCollector()
-
-
-# ==================== COOLDOWNS ====================
-
-
-# sender -> {command: last_used_time}
-_cooldowns: dict[str, dict[str, float]] = {}
-
-
-def check_cooldown(sender: str, cmd_name: str, seconds: int = COOLDOWN_SECONDS) -> int:
- """Return 0 if allowed, otherwise seconds remaining."""
- now = time.monotonic()
- user_cds = _cooldowns.setdefault(sender, {})
- last = user_cds.get(cmd_name, 0)
- remaining = seconds - (now - last)
- if remaining > 0:
- return int(remaining) + 1
- user_cds[cmd_name] = now
- return 0
-
-
-# ==================== COMMANDS ====================
-
-
-@command("help", "Show all available commands")
-async def cmd_help(client: AsyncClient, room_id: str, sender: str, args: str):
- lines_plain = ["Commands:"]
- lines_html = ["
Commands
"]
-
- for cmd_name, (_, desc) in sorted(COMMANDS.items()):
- lines_plain.append(f" {BOT_PREFIX}{cmd_name} - {desc}")
- lines_html.append(f"- {BOT_PREFIX}{cmd_name} — {desc}
")
-
- lines_html.append("
")
- await send_html(client, room_id, "\n".join(lines_plain), "\n".join(lines_html))
-
-
-@command("ping", "Check bot latency")
-async def cmd_ping(client: AsyncClient, room_id: str, sender: str, args: str):
- start = time.monotonic()
- resp = await send_text(client, room_id, "Pong!")
- elapsed = (time.monotonic() - start) * 1000
- # Edit isn't straightforward in Matrix, so just send a follow-up if slow
- if elapsed > 500:
- await send_text(client, room_id, f"(round-trip: {elapsed:.0f}ms)")
-
-
-@command("8ball", "Ask the magic 8-ball a question")
-async def cmd_8ball(client: AsyncClient, room_id: str, sender: str, args: str):
- if not args:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}8ball ")
- return
-
- responses = [
- "It is certain", "Without a doubt", "You may rely on it",
- "Yes definitely", "It is decidedly so", "As I see it, yes",
- "Most likely", "Yes sir!", "Hell yeah my dude", "100% easily",
- "Reply hazy try again", "Ask again later", "Better not tell you now",
- "Cannot predict now", "Concentrate and ask again", "Idk bro",
- "Don't count on it", "My reply is no", "My sources say no",
- "Outlook not so good", "Very doubtful", "Hell no", "Prolly not",
- ]
-
- answer = random.choice(responses)
- plain = f"Question: {args}\nAnswer: {answer}"
- html = (
- f"Magic 8-Ball
"
- f"Q: {args}
"
- f"A: {answer}"
- )
- await send_html(client, room_id, plain, html)
-
-
-@command("fortune", "Get a fortune cookie message")
-async def cmd_fortune(client: AsyncClient, room_id: str, sender: str, args: str):
- fortunes = [
- "If you eat something & nobody sees you eat it, it has no calories",
- "Your pet is plotting world domination",
- "Error 404: Fortune not found. Try again after system reboot",
- "The fortune you seek is in another cookie",
- "A journey of a thousand miles begins with ordering delivery",
- "You will find great fortune... in between your couch cushions",
- "A true friend is someone who tells you when your stream is muted",
- "Your next competitive match will be legendary",
- "The cake is still a lie",
- "Press Alt+F4 for instant success",
- "You will not encounter any campers today",
- "Your tank will have a healer",
- "No one will steal your pentakill",
- "Your random teammate will have a mic",
- "You will find diamonds on your first dig",
- "The boss will drop the rare loot",
- "Your speedrun will be WR pace",
- "No lag spikes in your next match",
- "Your gaming chair will grant you powers",
- "The RNG gods will bless you",
- "You will not get third partied",
- "Your squad will actually stick together",
- "The enemy team will forfeit at 15",
- "Your aim will be crispy today",
- "You will escape the backrooms",
- "The imposter will not sus you",
- "Your Minecraft bed will remain unbroken",
- "You will get Play of the Game",
- "Your next meme will go viral",
- "Someone is talking about you in their Discord server",
- "Your FBI agent thinks you're hilarious",
- "Your next TikTok will hit the FYP, if the government doesn't ban it first",
- "Someone will actually read your Twitter thread",
- "Your DMs will be blessed with quality memes today",
- "Touch grass (respectfully)",
- "The algorithm will be in your favor today",
- "Your next Spotify shuffle will hit different",
- "Someone saved your Instagram post",
- "Your Reddit comment will get gold",
- "POV: You're about to go viral",
- "Main character energy detected",
- "No cap, you're gonna have a great day fr fr",
- "Your rizz levels are increasing",
- "You will not get ratio'd today",
- "Someone will actually use your custom emoji",
- "Your next selfie will be iconic",
- "Buy a dolphin - your life will have a porpoise",
- "Stop procrastinating - starting tomorrow",
- "Catch fire with enthusiasm - people will come for miles to watch you burn",
- "Your code will compile on the first try today",
- "A semicolon will save your day",
- "The bug you've been hunting is just a typo",
- "Your next Git commit will be perfect",
- "You will find the solution on the first StackOverflow link",
- "Your Docker container will build without errors",
- "The cloud is just someone else's computer",
- "Your backup strategy will soon prove its worth",
- "A mechanical keyboard is in your future",
- "You will finally understand regex... maybe",
- "Your CSS will align perfectly on the first try",
- "Someone will star your GitHub repo today",
- "Your Linux installation will not break after updates",
- "You will remember to push your changes before shutdown",
- "Your code comments will actually make sense in 6 months",
- "The missing curly brace is on line 247",
- "Have you tried turning it off and on again?",
- "Your next pull request will be merged without comments",
- "Your keyboard RGB will sync perfectly today",
- "You will find that memory leak",
- "Your next algorithm will have O(1) complexity",
- "The force quit was strong with this one",
- "Ctrl+S will save you today",
- "Your next Python script will need no debugging",
- "Your next API call will return 200 OK",
- ]
-
- fortune = random.choice(fortunes)
- plain = f"Fortune Cookie: {fortune}"
- html = f"Fortune Cookie
{fortune}"
- await send_html(client, room_id, plain, html)
-
-
-@command("flip", "Flip a coin")
-async def cmd_flip(client: AsyncClient, room_id: str, sender: str, args: str):
- result = random.choice(["Heads", "Tails"])
- plain = f"Coin Flip: {result}"
- html = f"Coin Flip: {result}"
- await send_html(client, room_id, plain, html)
-
-
-@command("roll", "Roll dice (e.g. !roll 2d6)")
-async def cmd_roll(client: AsyncClient, room_id: str, sender: str, args: str):
- dice_str = args.strip() if args.strip() else "1d6"
-
- try:
- num, sides = map(int, dice_str.lower().split("d"))
- except ValueError:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}roll NdS (example: 2d6)")
- return
-
- if num < 1 or num > MAX_DICE_COUNT:
- await send_text(client, room_id, f"Number of dice must be 1-{MAX_DICE_COUNT}")
- return
- if sides < 2 or sides > MAX_DICE_SIDES:
- await send_text(client, room_id, f"Sides must be 2-{MAX_DICE_SIDES}")
- return
-
- results = [random.randint(1, sides) for _ in range(num)]
- total = sum(results)
- plain = f"Dice Roll ({dice_str}): {results} = {total}"
- html = (
- f"Dice Roll ({dice_str})
"
- f"Rolls: {results}
"
- f"Total: {total}"
- )
- await send_html(client, room_id, plain, html)
-
-
-@command("random", "Random number (e.g. !random 1 100)")
-async def cmd_random(client: AsyncClient, room_id: str, sender: str, args: str):
- parts = args.split()
- try:
- lo = int(parts[0]) if len(parts) >= 1 else 1
- hi = int(parts[1]) if len(parts) >= 2 else 100
- except ValueError:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}random ")
- return
-
- if lo > hi:
- lo, hi = hi, lo
-
- result = random.randint(lo, hi)
- plain = f"Random ({lo}-{hi}): {result}"
- html = f"Random Number ({lo}\u2013{hi}): {result}"
- await send_html(client, room_id, plain, html)
-
-
-@command("rps", "Rock Paper Scissors")
-async def cmd_rps(client: AsyncClient, room_id: str, sender: str, args: str):
- choices = ["rock", "paper", "scissors"]
- choice = args.strip().lower()
-
- if choice not in choices:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}rps ")
- return
-
- bot_choice = random.choice(choices)
-
- if choice == bot_choice:
- result = "It's a tie!"
- elif (
- (choice == "rock" and bot_choice == "scissors")
- or (choice == "paper" and bot_choice == "rock")
- or (choice == "scissors" and bot_choice == "paper")
- ):
- result = "You win!"
- else:
- result = "Bot wins!"
-
- plain = f"RPS: You={choice}, Bot={bot_choice} -> {result}"
- html = (
- f"Rock Paper Scissors
"
- f"You: {choice.capitalize()} | Bot: {bot_choice.capitalize()}
"
- f"{result}"
- )
- await send_html(client, room_id, plain, html)
-
-
-@command("poll", "Create a yes/no poll")
-async def cmd_poll(client: AsyncClient, room_id: str, sender: str, args: str):
- if not args:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}poll ")
- return
-
- plain = f"Poll: {args}"
- html = f"Poll
{args}"
- resp = await send_html(client, room_id, plain, html)
-
- if hasattr(resp, "event_id"):
- await send_reaction(client, room_id, resp.event_id, "\U0001f44d")
- await send_reaction(client, room_id, resp.event_id, "\U0001f44e")
-
-
-@command("champion", "Random LoL champion (optional: !champion top)")
-async def cmd_champion(client: AsyncClient, room_id: str, sender: str, args: str):
- champions = {
- "Top": [
- "Aatrox", "Ambessa", "Aurora", "Camille", "Cho'Gath", "Darius",
- "Dr. Mundo", "Fiora", "Gangplank", "Garen", "Gnar", "Gragas",
- "Gwen", "Illaoi", "Irelia", "Jax", "Jayce", "K'Sante", "Kennen",
- "Kled", "Malphite", "Mordekaiser", "Nasus", "Olaf", "Ornn",
- "Poppy", "Quinn", "Renekton", "Riven", "Rumble", "Sett", "Shen",
- "Singed", "Sion", "Teemo", "Trundle", "Tryndamere", "Urgot",
- "Vladimir", "Volibear", "Wukong", "Yone", "Yorick",
- ],
- "Jungle": [
- "Amumu", "Bel'Veth", "Briar", "Diana", "Ekko", "Elise",
- "Evelynn", "Fiddlesticks", "Graves", "Hecarim", "Ivern",
- "Jarvan IV", "Kayn", "Kha'Zix", "Kindred", "Lee Sin", "Lillia",
- "Maokai", "Master Yi", "Nidalee", "Nocturne", "Nunu", "Olaf",
- "Rek'Sai", "Rengar", "Sejuani", "Shaco", "Skarner", "Taliyah",
- "Udyr", "Vi", "Viego", "Warwick", "Xin Zhao", "Zac",
- ],
- "Mid": [
- "Ahri", "Akali", "Akshan", "Annie", "Aurelion Sol", "Azir",
- "Cassiopeia", "Corki", "Ekko", "Fizz", "Galio", "Heimerdinger",
- "Hwei", "Irelia", "Katarina", "LeBlanc", "Lissandra", "Lux",
- "Malzahar", "Mel", "Naafiri", "Neeko", "Orianna", "Qiyana",
- "Ryze", "Sylas", "Syndra", "Talon", "Twisted Fate", "Veigar",
- "Vex", "Viktor", "Vladimir", "Xerath", "Yasuo", "Yone", "Zed",
- "Zoe",
- ],
- "Bot": [
- "Aphelios", "Ashe", "Caitlyn", "Draven", "Ezreal", "Jhin",
- "Jinx", "Kai'Sa", "Kalista", "Kog'Maw", "Lucian",
- "Miss Fortune", "Nilah", "Samira", "Sivir", "Smolder",
- "Tristana", "Twitch", "Varus", "Vayne", "Xayah", "Zeri",
- ],
- "Support": [
- "Alistar", "Bard", "Blitzcrank", "Brand", "Braum", "Janna",
- "Karma", "Leona", "Lulu", "Lux", "Milio", "Morgana", "Nami",
- "Nautilus", "Pyke", "Rakan", "Rell", "Renata Glasc", "Senna",
- "Seraphine", "Sona", "Soraka", "Swain", "Taric", "Thresh",
- "Yuumi", "Zilean", "Zyra",
- ],
- }
-
- lane_arg = args.strip().capitalize() if args.strip() else ""
- if lane_arg and lane_arg in champions:
- lane = lane_arg
- else:
- lane = random.choice(list(champions.keys()))
-
- champ = random.choice(champions[lane])
- plain = f"Champion Picker: {champ} ({lane})"
- html = (
- f"League Champion Picker
"
- f"Champion: {champ}
"
- f"Lane: {lane}"
- )
- await send_html(client, room_id, plain, html)
-
-
-@command("agent", "Random Valorant agent (optional: !agent duelist)")
-async def cmd_agent(client: AsyncClient, room_id: str, sender: str, args: str):
- agents = {
- "Duelists": ["Jett", "Phoenix", "Raze", "Reyna", "Yoru", "Neon", "Iso", "Waylay"],
- "Controllers": ["Brimstone", "Viper", "Omen", "Astra", "Harbor", "Clove"],
- "Initiators": ["Sova", "Breach", "Skye", "KAY/O", "Fade", "Gekko", "Tejo"],
- "Sentinels": ["Killjoy", "Cypher", "Sage", "Chamber", "Deadlock", "Vyse", "Veto"],
- }
-
- role_arg = args.strip().capitalize() if args.strip() else ""
- # Allow partial match: "duelist" -> "Duelists"
- role = None
- if role_arg:
- for key in agents:
- if key.lower().startswith(role_arg.lower()):
- role = key
- break
- if role is None:
- role = random.choice(list(agents.keys()))
-
- selected = random.choice(agents[role])
- plain = f"Valorant Agent Picker: {selected} ({role})"
- html = (
- f"Valorant Agent Picker
"
- f"Agent: {selected}
"
- f"Role: {role}"
- )
- await send_html(client, room_id, plain, html)
-
-
-@command("trivia", "Play a trivia game")
-async def cmd_trivia(client: AsyncClient, room_id: str, sender: str, args: str):
- questions = [
- {"q": "What year was the original Super Mario Bros. released?", "options": ["1983", "1985", "1987", "1990"], "answer": 1},
- {"q": "Which game features the quote 'The cake is a lie'?", "options": ["Half-Life 2", "Portal", "BioShock", "Minecraft"], "answer": 1},
- {"q": "What is the max level in League of Legends?", "options": ["16", "18", "20", "25"], "answer": 1},
- {"q": "Which Valorant agent has the codename 'Deadeye'?", "options": ["Jett", "Sova", "Chamber", "Cypher"], "answer": 2},
- {"q": "How many Ender Dragon eggs can exist in a vanilla Minecraft world?", "options": ["1", "2", "Unlimited", "0"], "answer": 0},
- {"q": "What was the first battle royale game to hit mainstream popularity?", "options": ["Fortnite", "PUBG", "H1Z1", "Apex Legends"], "answer": 2},
- {"q": "In Minecraft, what is the rarest ore?", "options": ["Diamond", "Emerald", "Ancient Debris", "Lapis Lazuli"], "answer": 1},
- {"q": "What is the name of the main character in The Legend of Zelda?", "options": ["Zelda", "Link", "Ganondorf", "Epona"], "answer": 1},
- {"q": "Which game has the most registered players of all time?", "options": ["Fortnite", "Minecraft", "League of Legends", "Roblox"], "answer": 1},
- {"q": "What type of animal is Sonic?", "options": ["Fox", "Hedgehog", "Rabbit", "Echidna"], "answer": 1},
- {"q": "In Among Us, what is the maximum number of impostors?", "options": ["1", "2", "3", "4"], "answer": 2},
- {"q": "What does GG stand for in gaming?", "options": ["Get Good", "Good Game", "Go Go", "Great Going"], "answer": 1},
- {"q": "Which company developed Valorant?", "options": ["Blizzard", "Valve", "Riot Games", "Epic Games"], "answer": 2},
- {"q": "What is the highest rank in Valorant?", "options": ["Immortal", "Diamond", "Radiant", "Challenger"], "answer": 2},
- {"q": "In League of Legends, what is Baron Nashor an anagram of?", "options": ["Baron Roshan", "Roshan", "Nashor Baron", "Nash Robot"], "answer": 1},
- {"q": "What does HTTP stand for?", "options": ["HyperText Transfer Protocol", "High Tech Transfer Program", "HyperText Transmission Process", "Home Tool Transfer Protocol"], "answer": 0},
- {"q": "What year was Discord founded?", "options": ["2013", "2015", "2017", "2019"], "answer": 1},
- {"q": "What programming language has a logo that is a snake?", "options": ["Java", "Ruby", "Python", "Go"], "answer": 2},
- {"q": "How many bits are in a byte?", "options": ["4", "8", "16", "32"], "answer": 1},
- {"q": "What does 'RGB' stand for?", "options": ["Really Good Build", "Red Green Blue", "Red Gold Black", "Rapid Gaming Boost"], "answer": 1},
- {"q": "What is the most subscribed YouTube channel?", "options": ["PewDiePie", "MrBeast", "T-Series", "Cocomelon"], "answer": 1},
- {"q": "What does 'AFK' stand for?", "options": ["A Free Kill", "Away From Keyboard", "Always Fun Killing", "Another Fake Knockdown"], "answer": 1},
- {"q": "What animal is the Linux mascot?", "options": ["Fox", "Penguin", "Cat", "Dog"], "answer": 1},
- {"q": "What does 'NPC' stand for?", "options": ["Non-Player Character", "New Player Content", "Normal Playing Conditions", "Never Played Competitively"], "answer": 0},
- {"q": "In what year was the first iPhone released?", "options": ["2005", "2006", "2007", "2008"], "answer": 2},
- ]
-
- labels = ["\U0001f1e6", "\U0001f1e7", "\U0001f1e8", "\U0001f1e9"] # A B C D regional indicators
- label_letters = ["A", "B", "C", "D"]
- question = random.choice(questions)
-
- options_plain = "\n".join(f" {label_letters[i]}. {opt}" for i, opt in enumerate(question["options"]))
- options_html = "".join(f"{label_letters[i]}. {opt}" for i, opt in enumerate(question["options"]))
-
- plain = f"Trivia Time!\n{question['q']}\n{options_plain}\n\nReact with A/B/C/D — answer revealed in 30s!"
- html = (
- f"Trivia Time!
"
- f"{question['q']}
"
- f""
- f"React with A/B/C/D — answer revealed in 30s!"
- )
-
- resp = await send_html(client, room_id, plain, html)
- if hasattr(resp, "event_id"):
- for emoji in labels:
- await send_reaction(client, room_id, resp.event_id, emoji)
-
- # Reveal answer after 30 seconds
- async def reveal():
- await asyncio.sleep(30)
- correct = question["answer"]
- answer_text = f"{label_letters[correct]}. {question['options'][correct]}"
- await send_html(
- client, room_id,
- f"Trivia Answer: {answer_text}",
- f"Trivia Answer: {answer_text}",
- )
-
- asyncio.create_task(reveal())
-
-
-# ==================== INTEGRATIONS ====================
-
-
-@command("ask", "Ask Lotus LLM a question (2min cooldown)")
-async def cmd_ask(client: AsyncClient, room_id: str, sender: str, args: str):
- if not args:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}ask ")
- return
-
- remaining = check_cooldown(sender, "ask")
- if remaining:
- await send_text(client, room_id, f"Command on cooldown. Try again in {remaining}s.")
- return
-
- question = sanitize_input(args)
- if not question:
- await send_text(client, room_id, "Please provide a valid question.")
- return
-
- await send_text(client, room_id, "Thinking...")
-
- try:
- timeout = aiohttp.ClientTimeout(total=60)
- async with aiohttp.ClientSession(timeout=timeout) as session:
- async with session.post(
- f"{OLLAMA_URL}/api/generate",
- json={"model": OLLAMA_MODEL, "prompt": question, "stream": True},
- ) as response:
- full_response = ""
- async for line in response.content:
- try:
- chunk = json.loads(line)
- if "response" in chunk:
- full_response += chunk["response"]
- except json.JSONDecodeError:
- pass
-
- if not full_response:
- full_response = "No response received from server."
-
- plain = f"Lotus LLM\nQ: {question}\nA: {full_response}"
- html = (
- f"Lotus LLM
"
- f"Q: {question}
"
- f"A: {full_response}"
- )
- await send_html(client, room_id, plain, html)
- except asyncio.TimeoutError:
- await send_text(client, room_id, "LLM request timed out. Try again later.")
- except Exception as e:
- logger.error(f"Ollama error: {e}", exc_info=True)
- await send_text(client, room_id, "Failed to reach Lotus LLM. It may be offline.")
-
-
-@command("minecraft", "Whitelist a player on the Minecraft server")
-async def cmd_minecraft(client: AsyncClient, room_id: str, sender: str, args: str):
- username = args.strip()
- if not username:
- await send_text(client, room_id, f"Usage: {BOT_PREFIX}minecraft ")
- return
-
- if not username.replace("_", "").isalnum():
- await send_text(client, room_id, "Invalid username. Use only letters, numbers, and underscores.")
- return
-
- if not (MIN_USERNAME_LENGTH <= len(username) <= MAX_USERNAME_LENGTH):
- await send_text(client, room_id, f"Username must be {MIN_USERNAME_LENGTH}-{MAX_USERNAME_LENGTH} characters.")
- return
-
- if not MINECRAFT_RCON_PASSWORD:
- await send_text(client, room_id, "Minecraft server is not configured.")
- return
-
- await send_text(client, room_id, f"Whitelisting {username}...")
-
- try:
- from mcrcon import MCRcon
-
- def _rcon():
- with MCRcon(MINECRAFT_RCON_HOST, MINECRAFT_RCON_PASSWORD, port=MINECRAFT_RCON_PORT, timeout=3) as mcr:
- return mcr.command(f"whitelist add {username}")
-
- loop = asyncio.get_running_loop()
- response = await asyncio.wait_for(loop.run_in_executor(None, _rcon), timeout=RCON_TIMEOUT)
- logger.info(f"RCON response: {response}")
-
- plain = f"Minecraft\nYou have been whitelisted on the SMP!\nServer: minecraft.lotusguild.org\nUsername: {username}"
- html = (
- f"Minecraft
"
- f"You have been whitelisted on the SMP!
"
- f"Server: minecraft.lotusguild.org
"
- f"Username: {username}"
- )
- await send_html(client, room_id, plain, html)
- except ImportError:
- await send_text(client, room_id, "mcrcon is not installed. Ask an admin to install it.")
- except asyncio.TimeoutError:
- await send_text(client, room_id, "Minecraft server timed out. It may be offline.")
- except Exception as e:
- logger.error(f"RCON error: {e}", exc_info=True)
- await send_text(client, room_id, "Failed to whitelist. The server may be offline (let jared know).")
-
-
-# ==================== ADMIN COMMANDS ====================
-
-
-@command("health", "Bot status and health (admin only)")
-async def cmd_health(client: AsyncClient, room_id: str, sender: str, args: str):
- if sender not in ADMIN_USERS:
- await send_text(client, room_id, "You don't have permission to use this command.")
- return
-
- stats = metrics.get_stats()
- uptime_hours = stats["uptime_seconds"] / 3600
-
- top_cmds = ""
- if stats["top_commands"]:
- top_cmds = ", ".join(f"{name}({count})" for name, count in stats["top_commands"])
-
- services = []
- if OLLAMA_URL:
- services.append("Ollama: configured")
- else:
- services.append("Ollama: N/A")
- if MINECRAFT_RCON_PASSWORD:
- services.append("RCON: configured")
- else:
- services.append("RCON: N/A")
-
- plain = (
- f"Bot Status\n"
- f"Uptime: {uptime_hours:.1f}h\n"
- f"Commands run: {stats['commands_executed']}\n"
- f"Errors: {stats['error_count']}\n"
- f"Top commands: {top_cmds or 'none'}\n"
- f"Services: {', '.join(services)}"
- )
- html = (
- f"Bot Status
"
- f"Uptime: {uptime_hours:.1f}h
"
- f"Commands run: {stats['commands_executed']}
"
- f"Errors: {stats['error_count']}
"
- f"Top commands: {top_cmds or 'none'}
"
- f"Services: {', '.join(services)}"
- )
- await send_html(client, room_id, plain, html)
-
-
-# ---------------------------------------------------------------------------
-# Wordle
-# ---------------------------------------------------------------------------
-from wordle import handle_wordle
-
-
-@command("wordle", "Play Wordle! (!wordle help for details)")
-async def cmd_wordle(client: AsyncClient, room_id: str, sender: str, args: str):
- await handle_wordle(client, room_id, sender, args)
diff --git a/config.py b/config.py
deleted file mode 100644
index 3a66c92..0000000
--- a/config.py
+++ /dev/null
@@ -1,46 +0,0 @@
-import os
-import logging
-from dotenv import load_dotenv
-
-load_dotenv()
-
-
-# Required
-MATRIX_HOMESERVER = os.getenv("MATRIX_HOMESERVER", "https://matrix.lotusguild.org")
-MATRIX_USER_ID = os.getenv("MATRIX_USER_ID", "@lotusbot:matrix.lotusguild.org")
-MATRIX_ACCESS_TOKEN = os.getenv("MATRIX_ACCESS_TOKEN", "")
-MATRIX_DEVICE_ID = os.getenv("MATRIX_DEVICE_ID", "")
-MATRIX_PASSWORD = os.getenv("MATRIX_PASSWORD", "")
-
-# Bot settings
-BOT_PREFIX = os.getenv("BOT_PREFIX", "!")
-ADMIN_USERS = [u.strip() for u in os.getenv("ADMIN_USERS", "").split(",") if u.strip()]
-LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
-
-# Integrations
-OLLAMA_URL = os.getenv("OLLAMA_URL", "http://10.10.10.157:11434")
-OLLAMA_MODEL = os.getenv("OLLAMA_MODEL", "lotusllm")
-MINECRAFT_RCON_HOST = os.getenv("MINECRAFT_RCON_HOST", "10.10.10.67")
-MINECRAFT_RCON_PORT = int(os.getenv("MINECRAFT_RCON_PORT", "25575"))
-MINECRAFT_RCON_PASSWORD = os.getenv("MINECRAFT_RCON_PASSWORD", "")
-
-# Constants
-MAX_INPUT_LENGTH = 500
-MAX_DICE_SIDES = 100
-MAX_DICE_COUNT = 20
-COOLDOWN_SECONDS = int(os.getenv("COOLDOWN_SECONDS", "120"))
-RCON_TIMEOUT = 5.0
-MIN_USERNAME_LENGTH = 3
-MAX_USERNAME_LENGTH = 16
-
-
-class ConfigValidator:
- REQUIRED = ["MATRIX_HOMESERVER", "MATRIX_USER_ID"]
-
- @classmethod
- def validate(cls):
- errors = []
- for var in cls.REQUIRED:
- if not os.getenv(var):
- errors.append(f"Missing required: {var}")
- return errors
diff --git a/draupnir/production.yaml b/draupnir/production.yaml
new file mode 100644
index 0000000..d8479d8
--- /dev/null
+++ b/draupnir/production.yaml
@@ -0,0 +1,43 @@
+homeserverUrl: "https://matrix.lotusguild.org"
+rawHomeserverUrl: "https://matrix.lotusguild.org"
+accessToken: "REDACTED"
+
+pantalaimon:
+ use: false
+ username: draupnir
+ password: ""
+
+experimentalRustCrypto: false
+
+dataPath: "/data/storage"
+
+autojoinOnlyIfManager: true
+
+recordIgnoredInvites: false
+
+managementRoom: "!mEvR5fe3jMmzwd-FwNygD72OY_yu8H3UP_N-57oK7MI"
+
+logLevel: "INFO"
+
+verifyPermissionsOnStartup: true
+
+noop: false
+
+# Don't apply server ACLs (trust local Synapse admin decisions)
+disableServerACL: true
+
+# Protect all rooms the bot is joined to by default
+protectAllJoinedRooms: false
+
+# Synapse admin API access
+admin:
+ enableMakeRoomAdminCommand: true
+
+# Don't send verbose join/leave notifications
+verboseLogging: false
+
+# Background task interval for checking bans
+backgroundDelayMS: 500
+
+# Safe redaction limit per sync
+redactionLimit: 100
diff --git a/hookshot/bazarr.js b/hookshot/bazarr.js
new file mode 100644
index 0000000..9c58bc7
--- /dev/null
+++ b/hookshot/bazarr.js
@@ -0,0 +1,11 @@
+var title = data.title || 'Bazarr';
+var msg = data.message || data.body || '';
+var type = (data.type || 'info').toLowerCase();
+var emoji = type === 'success' ? '✅' : (type === 'warning' ? '⚠️' : (type === 'failure' ? '❌' : '📝'));
+var lines = [emoji + ' ' + title];
+var htmlParts = ['' + emoji + ' ' + title + ''];
+if (msg) {
+ var msgLines = msg.split(/\r?\n/).filter(function(l){ return l.trim(); });
+ for (var i = 0; i < msgLines.length; i++) { lines.push(msgLines[i]); htmlParts.push(msgLines[i]); }
+}
+result = { version: 'v2', plain: lines.join('\n'), html: htmlParts.join('
'), msgtype: 'm.notice' };
\ No newline at end of file
diff --git a/hookshot/deploy.sh b/hookshot/deploy.sh
new file mode 100644
index 0000000..0102b9b
--- /dev/null
+++ b/hookshot/deploy.sh
@@ -0,0 +1,54 @@
+#!/bin/bash
+# Deploy hookshot transformation functions to Matrix room state.
+# Each .js file in this directory maps to a webhook by the same name (case-sensitive).
+#
+# Usage:
+# ./deploy.sh # deploy all hooks
+# ./deploy.sh proxmox.js # deploy one hook
+#
+# Requirements:
+# MATRIX_TOKEN - access token with power level >= 50 in the target room
+# MATRIX_SERVER - homeserver URL (default: https://matrix.lotusguild.org)
+# MATRIX_ROOM - room ID where hooks are registered
+
+MATRIX_SERVER="${MATRIX_SERVER:-https://matrix.lotusguild.org}"
+MATRIX_ROOM="${MATRIX_ROOM:-!GttT4QYd1wlGlkHU3qTmq_P3gbyYKKeSSN6R7TPcJHg}"
+
+if [ -z "$MATRIX_TOKEN" ]; then
+ echo "Error: MATRIX_TOKEN is not set." >&2
+ exit 1
+fi
+
+DIR="$(cd "$(dirname "$0")" && pwd)"
+
+deploy_hook() {
+ local file="$1"
+ local filename="$(basename "$file" .js)"
+ # Capitalize first letter to match state_key (e.g. proxmox -> Proxmox)
+ local state_key="$(echo "$filename" | sed 's/\b\(.\)/\u\1/g' | sed 's/-\(.\)/\u\1/g')"
+ local fn="$(cat "$file")"
+ local encoded_room="$(python3 -c "import urllib.parse; print(urllib.parse.quote('$MATRIX_ROOM'))")"
+ local encoded_key="$(python3 -c "import urllib.parse; print(urllib.parse.quote('$state_key'))")"
+
+ local response
+ response=$(curl -sf -X PUT \
+ "$MATRIX_SERVER/_matrix/client/v3/rooms/$encoded_room/state/uk.half-shot.matrix-hookshot.generic.hook/$encoded_key" \
+ -H "Authorization: Bearer $MATRIX_TOKEN" \
+ -H "Content-Type: application/json" \
+ --data-binary "$(python3 -c "import json; print(json.dumps({'name': '$state_key', 'transformationFunction': open('$file').read()}))")" \
+ 2>&1)
+
+ if echo "$response" | python3 -c "import json,sys; d=json.load(sys.stdin); exit(0 if 'event_id' in d else 1)" 2>/dev/null; then
+ echo "✓ $state_key"
+ else
+ echo "✗ $state_key: $response"
+ fi
+}
+
+if [ -n "$1" ]; then
+ deploy_hook "$DIR/$1"
+else
+ for f in "$DIR"/*.js; do
+ deploy_hook "$f"
+ done
+fi
diff --git a/hookshot/grafana.js b/hookshot/grafana.js
new file mode 100644
index 0000000..6b240f7
--- /dev/null
+++ b/hookshot/grafana.js
@@ -0,0 +1,32 @@
+var status = data.status || 'unknown';
+var emoji = status === 'firing' ? '🔴' : (status === 'resolved' ? '🟢' : '🟡');
+var title = data.title || ('[' + status.toUpperCase() + '] Grafana Alert');
+var alerts = data.alerts || [];
+var lines = [emoji + ' ' + title];
+var htmlParts = ['' + emoji + ' ' + title + ''];
+for (var i = 0; i < alerts.length && i < 5; i++) {
+ var a = alerts[i];
+ var labels = a.labels || {};
+ var ann = a.annotations || {};
+ var name = labels.alertname || '';
+ var summary = ann.summary || ann.description || '';
+ var instance = labels.instance || labels.job || '';
+ var severity = labels.severity || '';
+ var aStatus = a.status || '';
+ var genURL = a.generatorURL || '';
+ var aEmoji = aStatus === 'resolved' ? '🟢' : '🔴';
+ if (alerts.length > 1) {
+ var alertLine = aEmoji + ' ' + (name || 'Alert') + (severity ? ' [' + severity + ']' : '') + (instance ? ' \u2014 ' + instance : '') + (summary ? ': ' + summary : '');
+ lines.push(alertLine);
+ htmlParts.push(alertLine + (genURL ? ' \u2197' : ''));
+ } else {
+ if (name && name !== title) { lines.push('Alert: ' + name); htmlParts.push('Alert: ' + name); }
+ if (severity) { lines.push('Severity: ' + severity); htmlParts.push('Severity: ' + severity); }
+ if (instance) { lines.push('Instance: ' + instance); htmlParts.push('Instance: ' + instance); }
+ if (summary) { lines.push(summary); htmlParts.push(summary); }
+ if (genURL) { lines.push('View: ' + genURL); htmlParts.push('View in Grafana'); }
+ }
+}
+if (alerts.length === 0 && data.message) { lines.push(data.message); htmlParts.push(data.message); }
+if (alerts.length > 5) { var more = '(+' + (alerts.length - 5) + ' more alerts)'; lines.push(more); htmlParts.push('' + more + ''); }
+result = { version: 'v2', plain: lines.join('\n'), html: htmlParts.join('
'), msgtype: 'm.notice' };
\ No newline at end of file
diff --git a/hookshot/lidarr.js b/hookshot/lidarr.js
new file mode 100644
index 0000000..8e369d3
--- /dev/null
+++ b/hookshot/lidarr.js
@@ -0,0 +1,37 @@
+var ev = data.eventType || 'Unknown';
+if (ev === 'Test') {
+ result = { version: 'v2', plain: '🧪 Lidarr: Connection test successful', msgtype: 'm.notice' };
+} else {
+ var artist = (data.artist && data.artist.name) || 'Unknown Artist';
+ var albums = data.albums || (data.album ? [data.album] : []);
+ var albumStr = albums.map(function(a){ return a.title || ''; }).filter(Boolean).join(', ');
+ var quality = (data.release && data.release.quality) || (data.trackFiles && data.trackFiles[0] && data.trackFiles[0].quality) || '';
+ var releaseGroup = (data.release && data.release.releaseGroup) || '';
+ var client = data.downloadClient || '';
+ var upgrade = data.isUpgrade ? ' \u2191upgrade' : '';
+ var healthMsg = data.message || '';
+ var healthWiki = data.wikiUrl || '';
+ var prevVer = data.previousVersion || '';
+ var newVer = data.newVersion || '';
+ var emojiMap = { 'Grab':'📥','Download':'✅','Rename':'✏️','ArtistAdd':'➕','ArtistDelete':'🗑️','AlbumAdd':'➕','AlbumDelete':'🗑️','TrackFileDelete':'🗑️','HealthIssue':'⚠️','HealthRestored':'💚','ApplicationUpdate':'🔄' };
+ var emoji = emojiMap[ev] || '🎵';
+ var plain, html;
+ if (ev === 'HealthIssue' || ev === 'HealthRestored') {
+ plain = emoji + ' Lidarr ' + ev + ': ' + healthMsg + (healthWiki ? '\n' + healthWiki : '');
+ html = '' + emoji + ' Lidarr ' + ev + ': ' + healthMsg + (healthWiki ? '
Wiki' : '');
+ } else if (ev === 'ApplicationUpdate') {
+ plain = emoji + ' Lidarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ html = '' + emoji + ' Lidarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ } else if (ev === 'ArtistAdd' || ev === 'ArtistDelete') {
+ plain = emoji + ' Lidarr ' + ev + ': ' + artist;
+ html = '' + emoji + ' Lidarr ' + ev + ': ' + artist;
+ } else {
+ var albumPart = albumStr ? ' \u2014 ' + albumStr : '';
+ var qualPart = quality ? ' [' + quality + ']' : '';
+ var groupPart = releaseGroup ? ' {' + releaseGroup + '}' : '';
+ var clientPart = client ? ' via ' + client : '';
+ plain = emoji + ' Lidarr ' + ev + ': ' + artist + albumPart + qualPart + groupPart + upgrade + clientPart;
+ html = '' + emoji + ' Lidarr ' + ev + ': ' + artist + (albumStr ? ' \u2014 ' + albumStr + '' : '') + qualPart + groupPart + upgrade + clientPart;
+ }
+ result = { version: 'v2', plain: plain, html: html, msgtype: 'm.notice' };
+}
\ No newline at end of file
diff --git a/hookshot/owncast.js b/hookshot/owncast.js
new file mode 100644
index 0000000..dce8da9
--- /dev/null
+++ b/hookshot/owncast.js
@@ -0,0 +1,21 @@
+var evtype = data.type || 'EVENT';
+var ed = data.eventData || {};
+var streamName = ed.name || ed.streamerName || '';
+var title = ed.streamTitle || ed.title || '';
+var viewers = ed.viewerCount !== undefined ? String(ed.viewerCount) : (ed.viewers !== undefined ? String(ed.viewers) : '');
+var url = ed.externalURL || ed.url || ed.serverURL || '';
+var chatUser = (ed.user && (ed.user.displayName || ed.user.username)) || '';
+var chatMsg = ed.body || '';
+var emoji, label;
+if (evtype === 'STREAM_STARTED') { emoji = '🔴'; label = 'Now Live'; }
+else if (evtype === 'STREAM_STOPPED') { emoji = '⚫'; label = 'Stream Ended'; }
+else if (evtype === 'USER_JOINED') { emoji = '👤'; label = 'Viewer Joined'; }
+else if (evtype === 'CHAT') { emoji = '💬'; label = 'Chat'; }
+else { emoji = '📡'; label = evtype.replace(/_/g, ' '); }
+var lines = [emoji + ' ' + label + (streamName ? ' \u2014 ' + streamName : '')];
+var htmlParts = ['' + emoji + ' ' + label + '' + (streamName ? ': ' + streamName : '')];
+if (title) { lines.push(title); htmlParts.push('' + title + ''); }
+if (viewers) { lines.push(viewers + ' viewers'); htmlParts.push(viewers + ' viewers'); }
+if (chatUser && chatMsg) { lines.push(chatUser + ': ' + chatMsg); htmlParts.push('' + chatUser + ': ' + chatMsg); }
+if (url && evtype === 'STREAM_STARTED') { lines.push(url); htmlParts.push('' + url + ''); }
+result = { version: 'v2', plain: lines.join('\n'), html: htmlParts.join('
'), msgtype: 'm.notice' };
\ No newline at end of file
diff --git a/hookshot/proxmox.js b/hookshot/proxmox.js
new file mode 100644
index 0000000..dee8bea
--- /dev/null
+++ b/hookshot/proxmox.js
@@ -0,0 +1,18 @@
+var embed = (data.embeds && data.embeds[0]) || {};
+var title = embed.title || 'Proxmox Notification';
+var description = embed.description || '';
+var fields = embed.fields || [];
+var fieldMap = {};
+fields.forEach(function(f) { fieldMap[f.name] = f.value; });
+var severity = (fieldMap['Severity'] || 'info').toLowerCase();
+var node = fieldMap['Node'] || '';
+var type = fieldMap['Type'] || '';
+var vmid = fieldMap['VM/CT ID'] || '';
+var emoji = severity === 'error' ? '🔴' : (severity === 'warning' ? '🟡' : 'ℹ️');
+var lines = [emoji + ' ' + title];
+var htmlParts = ['' + emoji + ' ' + title + ''];
+if (node) { lines.push('\uD83D\uDDA5\uFE0F Node: ' + node); htmlParts.push('\uD83D\uDDA5\uFE0F Node: ' + node + ''); }
+if (type) { lines.push('\uD83D\uDCCB Type: ' + type); htmlParts.push('\uD83D\uDCCB Type: ' + type); }
+if (vmid && vmid !== 'N/A') { lines.push('\uD83D\uDD32 VM/CT: ' + vmid); htmlParts.push('\uD83D\uDD32 VM/CT: ' + vmid); }
+if (description) { lines.push(description); htmlParts.push(description); }
+result = { version: 'v2', plain: lines.join('\n'), html: htmlParts.join('
'), msgtype: 'm.notice' };
\ No newline at end of file
diff --git a/hookshot/radarr.js b/hookshot/radarr.js
new file mode 100644
index 0000000..65081c2
--- /dev/null
+++ b/hookshot/radarr.js
@@ -0,0 +1,35 @@
+var ev = data.eventType || 'Unknown';
+if (ev === 'Test') {
+ result = { version: 'v2', plain: '🧪 Radarr: Connection test successful', msgtype: 'm.notice' };
+} else {
+ var m = data.movie || {};
+ var movie = m.title ? m.title + (m.year ? ' (' + m.year + ')' : '') : 'Unknown Movie';
+ var quality = (data.release && data.release.quality) || (data.movieFile && data.movieFile.quality) || '';
+ var releaseGroup = (data.release && data.release.releaseGroup) || (data.movieFile && data.movieFile.releaseGroup) || '';
+ var client = data.downloadClient || '';
+ var upgrade = data.isUpgrade ? ' \u2191upgrade' : '';
+ var healthMsg = data.message || '';
+ var healthWiki = data.wikiUrl || '';
+ var prevVer = data.previousVersion || '';
+ var newVer = data.newVersion || '';
+ var emojiMap = { 'Grab':'📥','Download':'✅','Rename':'✏️','MovieAdded':'➕','MovieDelete':'🗑️','MovieFileDelete':'🗑️','HealthIssue':'⚠️','HealthRestored':'💚','ApplicationUpdate':'🔄','ManualInteractionRequired':'🔔' };
+ var emoji = emojiMap[ev] || '🎬';
+ var plain, html;
+ if (ev === 'HealthIssue' || ev === 'HealthRestored') {
+ plain = emoji + ' Radarr ' + ev + ': ' + healthMsg + (healthWiki ? '\n' + healthWiki : '');
+ html = '' + emoji + ' Radarr ' + ev + ': ' + healthMsg + (healthWiki ? '
Wiki' : '');
+ } else if (ev === 'ApplicationUpdate') {
+ plain = emoji + ' Radarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ html = '' + emoji + ' Radarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ } else if (ev === 'MovieAdded' || ev === 'MovieDelete') {
+ plain = emoji + ' Radarr ' + ev + ': ' + movie;
+ html = '' + emoji + ' Radarr ' + ev + ': ' + movie;
+ } else {
+ var qualPart = quality ? ' [' + quality + ']' : '';
+ var groupPart = releaseGroup ? ' {' + releaseGroup + '}' : '';
+ var clientPart = client ? ' via ' + client : '';
+ plain = emoji + ' Radarr ' + ev + ': ' + movie + qualPart + groupPart + upgrade + clientPart;
+ html = '' + emoji + ' Radarr ' + ev + ': ' + movie + qualPart + groupPart + upgrade + clientPart;
+ }
+ result = { version: 'v2', plain: plain, html: html, msgtype: 'm.notice' };
+}
\ No newline at end of file
diff --git a/hookshot/readarr.js b/hookshot/readarr.js
new file mode 100644
index 0000000..ebbd725
--- /dev/null
+++ b/hookshot/readarr.js
@@ -0,0 +1,37 @@
+var ev = data.eventType || 'Unknown';
+if (ev === 'Test') {
+ result = { version: 'v2', plain: '🧪 Readarr: Connection test successful', msgtype: 'm.notice' };
+} else {
+ var author = (data.author && data.author.name) || 'Unknown Author';
+ var books = data.books || (data.book ? [data.book] : []);
+ var bookStr = books.map(function(b){ return b.title || ''; }).filter(Boolean).join(', ');
+ var quality = (data.release && data.release.quality) || (data.bookFile && data.bookFile.quality) || '';
+ var releaseGroup = (data.release && data.release.releaseGroup) || (data.bookFile && data.bookFile.releaseGroup) || '';
+ var client = data.downloadClient || '';
+ var upgrade = data.isUpgrade ? ' \u2191upgrade' : '';
+ var healthMsg = data.message || '';
+ var healthWiki = data.wikiUrl || '';
+ var prevVer = data.previousVersion || '';
+ var newVer = data.newVersion || '';
+ var emojiMap = { 'Grab':'📥','Download':'✅','Rename':'✏️','AuthorAdd':'➕','AuthorDelete':'🗑️','BookAdd':'➕','BookDelete':'🗑️','BookFileDelete':'🗑️','HealthIssue':'⚠️','HealthRestored':'💚','ApplicationUpdate':'🔄' };
+ var emoji = emojiMap[ev] || '📚';
+ var plain, html;
+ if (ev === 'HealthIssue' || ev === 'HealthRestored') {
+ plain = emoji + ' Readarr ' + ev + ': ' + healthMsg + (healthWiki ? '\n' + healthWiki : '');
+ html = '' + emoji + ' Readarr ' + ev + ': ' + healthMsg + (healthWiki ? '
Wiki' : '');
+ } else if (ev === 'ApplicationUpdate') {
+ plain = emoji + ' Readarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ html = '' + emoji + ' Readarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ } else if (ev === 'AuthorAdd' || ev === 'AuthorDelete') {
+ plain = emoji + ' Readarr ' + ev + ': ' + author;
+ html = '' + emoji + ' Readarr ' + ev + ': ' + author;
+ } else {
+ var titlePart = bookStr ? bookStr + ' by ' + author : author;
+ var qualPart = quality ? ' [' + quality + ']' : '';
+ var groupPart = releaseGroup ? ' {' + releaseGroup + '}' : '';
+ var clientPart = client ? ' via ' + client : '';
+ plain = emoji + ' Readarr ' + ev + ': ' + titlePart + qualPart + groupPart + upgrade + clientPart;
+ html = '' + emoji + ' Readarr ' + ev + ': ' + (bookStr ? '' + bookStr + ' by ' + author : author) + qualPart + groupPart + upgrade + clientPart;
+ }
+ result = { version: 'v2', plain: plain, html: html, msgtype: 'm.notice' };
+}
\ No newline at end of file
diff --git a/hookshot/seerr.js b/hookshot/seerr.js
new file mode 100644
index 0000000..8b84a36
--- /dev/null
+++ b/hookshot/seerr.js
@@ -0,0 +1,29 @@
+var evtype = data.notification_type || data.event || 'Notification';
+var subject = data.subject || evtype;
+var message = data.message || '';
+var media = data.media || {};
+var request = data.request || {};
+var issue = data.issue || {};
+var comment = data.comment || {};
+var user = request.requestedBy_username || request.requestedBy_email || (data.account && data.account.username) || '';
+var mt = media.media_type || '';
+var status4k = media.status4k || 0;
+var typeEmoji = mt === 'movie' ? '🎬' : (mt === 'tv' ? '📺' : '📋');
+var statusEmoji;
+if (evtype.indexOf('PENDING') >= 0) statusEmoji = '🟡';
+else if (evtype.indexOf('APPROVED') >= 0 || evtype.indexOf('AVAILABLE') >= 0) statusEmoji = '✅';
+else if (evtype.indexOf('DECLINED') >= 0 || evtype.indexOf('FAILED') >= 0) statusEmoji = '❌';
+else if (evtype.indexOf('ISSUE') >= 0) statusEmoji = '⚠️';
+else if (evtype.indexOf('COMMENT') >= 0) statusEmoji = '💬';
+else statusEmoji = typeEmoji;
+var lines = [statusEmoji + ' ' + subject];
+var htmlParts = ['' + statusEmoji + ' ' + subject + ''];
+if (user) { lines.push('Requested by: ' + user); htmlParts.push('Requested by: ' + user); }
+if (message && message !== subject) { lines.push(message); htmlParts.push(message); }
+if (status4k) { lines.push('4K request'); htmlParts.push('4K request'); }
+if (issue.issue_type) {
+ var issueLine = 'Issue: ' + issue.issue_type + (issue.message ? ' \u2014 ' + issue.message : '');
+ lines.push(issueLine); htmlParts.push(issueLine);
+}
+if (comment.message) { lines.push('Comment: ' + comment.message); htmlParts.push('Comment: ' + comment.message); }
+result = { version: 'v2', plain: lines.join('\n'), html: htmlParts.join('
'), msgtype: 'm.notice' };
\ No newline at end of file
diff --git a/hookshot/sonarr.js b/hookshot/sonarr.js
new file mode 100644
index 0000000..95baecd
--- /dev/null
+++ b/hookshot/sonarr.js
@@ -0,0 +1,41 @@
+var ev = data.eventType || 'Unknown';
+if (ev === 'Test') {
+ result = { version: 'v2', plain: '🧪 Sonarr: Connection test successful', msgtype: 'm.notice' };
+} else {
+ var series = (data.series && data.series.title) || 'Unknown Series';
+ var network = (data.series && data.series.network) || '';
+ var eps = data.episodes || [];
+ var epStrs = eps.map(function(ep) {
+ var s = 'S' + ('0'+(ep.seasonNumber||0)).slice(-2) + 'E' + ('0'+(ep.episodeNumber||0)).slice(-2);
+ return ep.title ? s + ' \u2013 ' + ep.title : s;
+ });
+ var quality = (data.release && data.release.quality) || (data.episodeFile && data.episodeFile.quality) || '';
+ var releaseGroup = (data.release && data.release.releaseGroup) || (data.episodeFile && data.episodeFile.releaseGroup) || '';
+ var client = data.downloadClient || '';
+ var upgrade = data.isUpgrade ? ' \u2191upgrade' : '';
+ var healthMsg = data.message || '';
+ var healthWiki = data.wikiUrl || '';
+ var prevVer = data.previousVersion || '';
+ var newVer = data.newVersion || '';
+ var emojiMap = { 'Grab':'📥','Download':'✅','Rename':'✏️','SeriesAdd':'➕','SeriesDelete':'🗑️','EpisodeFileDelete':'🗑️','HealthIssue':'⚠️','HealthRestored':'💚','ApplicationUpdate':'🔄','ManualInteractionRequired':'🔔' };
+ var emoji = emojiMap[ev] || '📺';
+ var plain, html;
+ if (ev === 'HealthIssue' || ev === 'HealthRestored') {
+ plain = emoji + ' Sonarr ' + ev + ': ' + healthMsg + (healthWiki ? '\n' + healthWiki : '');
+ html = '' + emoji + ' Sonarr ' + ev + ': ' + healthMsg + (healthWiki ? '
Wiki' : '');
+ } else if (ev === 'ApplicationUpdate') {
+ plain = emoji + ' Sonarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ html = '' + emoji + ' Sonarr updated: ' + prevVer + ' \u2192 ' + newVer;
+ } else if (ev === 'SeriesAdd' || ev === 'SeriesDelete' || ev === 'Rename') {
+ plain = emoji + ' Sonarr ' + ev + ': ' + series + (network ? ' (' + network + ')' : '');
+ html = '' + emoji + ' Sonarr ' + ev + ': ' + series + (network ? ' (' + network + ')' : '');
+ } else {
+ var epPart = epStrs.length ? ' \u2014 ' + epStrs.join(', ') : '';
+ var qualPart = quality ? ' [' + quality + ']' : '';
+ var groupPart = releaseGroup ? ' {' + releaseGroup + '}' : '';
+ var clientPart = client ? ' via ' + client : '';
+ plain = emoji + ' Sonarr ' + ev + ': ' + series + epPart + qualPart + groupPart + upgrade + clientPart;
+ html = '' + emoji + ' Sonarr ' + ev + ': ' + series + (epStrs.length ? ' \u2014 ' + epStrs.join(', ') + '' : '') + qualPart + groupPart + upgrade + clientPart;
+ }
+ result = { version: 'v2', plain: plain, html: html, msgtype: 'm.notice' };
+}
\ No newline at end of file
diff --git a/hookshot/tinker-tickets.js b/hookshot/tinker-tickets.js
new file mode 100644
index 0000000..7767be1
--- /dev/null
+++ b/hookshot/tinker-tickets.js
@@ -0,0 +1,31 @@
+var id = data.ticket_id || '?';
+var title = data.title || 'Untitled';
+var priority = parseInt(data.priority) || 4;
+var category = data.category || 'General';
+var type = data.type || 'Issue';
+var source = data.source || '';
+var url = data.url || '';
+var trigger = data.trigger || 'manual';
+var notifyUsers = data.notify_users || [];
+var priorityEmojis = ['', '🔴', '🟠', '🔵', '🟢', '⚫'];
+var priorityLabels = ['', 'P1 Critical', 'P2 High', 'P3 Medium', 'P4 Low', 'P5 Info'];
+var emoji = (priority >= 1 && priority <= 5) ? priorityEmojis[priority] : '⚫';
+var pLabel = (priority >= 1 && priority <= 5) ? priorityLabels[priority] : 'P' + priority;
+var tLabel = trigger === 'automated' ? 'Automated' : 'Manual';
+var meta = pLabel + ' \u00b7 ' + category + ' \u00b7 ' + type + (source ? ' \u00b7 ' + source : '') + ' [' + tLabel + ']';
+var mentionPlain = '';
+var mentionHtml = '';
+if (notifyUsers.length > 0) {
+ var pParts = [], hParts = [];
+ for (var i = 0; i < notifyUsers.length; i++) {
+ var uid = notifyUsers[i];
+ var disp = uid.replace(/^@/, '').split(':')[0];
+ pParts.push(uid);
+ hParts.push('' + disp + '');
+ }
+ mentionPlain = '\n' + pParts.join(' ');
+ mentionHtml = '
' + hParts.join(' ');
+}
+var plain = emoji + ' New Ticket #' + id + ': ' + title + '\n' + meta + (url ? '\n' + url : '') + mentionPlain;
+var html = '' + emoji + ' #' + id + ': ' + title + '
' + meta + mentionHtml;
+result = { version: 'v2', plain: plain, html: html, msgtype: 'm.text' };
\ No newline at end of file
diff --git a/hookshot/uptime-kuma.js b/hookshot/uptime-kuma.js
new file mode 100644
index 0000000..5f14f85
--- /dev/null
+++ b/hookshot/uptime-kuma.js
@@ -0,0 +1,18 @@
+var monitor = data.monitor || {};
+var hb = data.heartbeat || {};
+var name = monitor.name || monitor.url || 'Monitor';
+var url = monitor.url || '';
+var status = hb.status;
+var emoji = status === 1 ? '🟢' : (status === 0 ? '🔴' : '🟡');
+var state = status === 1 ? 'UP' : (status === 0 ? 'DOWN' : 'Unknown');
+var reason = hb.msg || '';
+var ping = (hb.ping !== undefined && hb.ping !== null) ? hb.ping + 'ms' : '';
+var duration = hb.duration ? hb.duration + 's' : '';
+if (reason === 'OK' || reason === '200 - OK' || reason === '200') reason = '';
+var lines = [emoji + ' ' + name + ' is ' + state];
+var htmlParts = ['' + emoji + ' ' + name + ' is ' + state + ''];
+if (url) { lines.push(url); htmlParts.push('' + url + ''); }
+if (reason) { lines.push('Reason: ' + reason); htmlParts.push('Reason: ' + reason); }
+if (ping && status === 1) { lines.push('Ping: ' + ping); htmlParts.push('Ping: ' + ping); }
+if (duration && status === 0) { lines.push('Down for: ' + duration); htmlParts.push('Down for: ' + duration); }
+result = { version: 'v2', plain: lines.join('\n'), html: htmlParts.join('
'), msgtype: 'm.notice' };
\ No newline at end of file
diff --git a/landing/index.html b/landing/index.html
new file mode 100644
index 0000000..c2bf7de
--- /dev/null
+++ b/landing/index.html
@@ -0,0 +1,623 @@
+
+
+
+
+
+ Lotus Guild Matrix
+
+
+
+
+

+
+
Lotus Guild
+
Private Communications
+
+
+
How to Join
+
+ -
+ 1
+ Open chat.lotusguild.org — your homeserver is pre-configured
+
+ -
+ 2
+ Register with a token from Jared —
@jared:matrix.lotusguild.org
+
+ -
+ 3
+ Join the Lotus Guild Space to access all rooms
+
+
+
+
or join from another server
+
+
+
+
+
+
Recommended Client
+
+
+
+
+
+
+
+
+
+
+
+
+
Server Details
+
+
+
+
Max Upload
+
200 MB / file
+
+
+
Message History
+
Kept indefinitely
+
+
+
Media Retention
+
3 yr local · 1 yr remote
+
+
+
Federation
+
Fully federated
+
+
+
Minimum Age
+
13+ (COPPA)
+
+
+
+ No ads or tracking
+ No data sold
+ E2EE — server cannot read encrypted rooms
+
+
+
+
+ This service is provided "as-is" with no uptime guarantee. Not for emergency use — do not use to contact emergency services (e.g. 911). Use is governed by our
Terms of Service and
Privacy Policy. Governing law: State of Ohio, United States.
+
+
+
+
+
+
+
+
diff --git a/requirements.txt b/requirements.txt
deleted file mode 100644
index b15fa00..0000000
--- a/requirements.txt
+++ /dev/null
@@ -1,5 +0,0 @@
-matrix-nio[e2e]
-python-dotenv
-aiohttp
-markdown
-mcrcon
diff --git a/systemd/cinny-dev-update.cron b/systemd/cinny-dev-update.cron
new file mode 100644
index 0000000..331b83b
--- /dev/null
+++ b/systemd/cinny-dev-update.cron
@@ -0,0 +1 @@
+0 3 * * * root /usr/local/bin/cinny-dev-update.sh
diff --git a/systemd/draupnir.service b/systemd/draupnir.service
new file mode 100644
index 0000000..a4f238a
--- /dev/null
+++ b/systemd/draupnir.service
@@ -0,0 +1,22 @@
+[Unit]
+Description=Draupnir Matrix Moderation Bot
+After=network.target
+Wants=network-online.target
+
+[Service]
+Type=simple
+User=root
+WorkingDirectory=/opt/draupnir
+ExecStart=/usr/bin/node /opt/draupnir/lib/index.js --draupnir-config /opt/draupnir/config/production.yaml
+Restart=on-failure
+RestartSec=10
+StandardOutput=journal
+StandardError=journal
+SyslogIdentifier=draupnir
+
+# Resource limits
+MemoryMax=512M
+CPUQuota=80%
+
+[Install]
+WantedBy=multi-user.target
diff --git a/systemd/livekit-clear-port.sh b/systemd/livekit-clear-port.sh
new file mode 100644
index 0000000..c743069
--- /dev/null
+++ b/systemd/livekit-clear-port.sh
@@ -0,0 +1,3 @@
+#\!/bin/bash
+pkill -x livekit-server 2>/dev/null && sleep 1
+exit 0
diff --git a/systemd/livekit-server.service b/systemd/livekit-server.service
new file mode 100644
index 0000000..672cbfd
--- /dev/null
+++ b/systemd/livekit-server.service
@@ -0,0 +1,15 @@
+[Unit]
+Description=LiveKit SFU Server
+After=network.target
+
+[Service]
+Type=simple
+ExecStartPre=-/bin/bash -c 'pkill -x livekit-server; sleep 1'
+ExecStart=/usr/local/bin/livekit-server --config /etc/livekit/config.yaml
+Restart=on-failure
+RestartSec=5
+KillMode=control-group
+LimitNOFILE=65535
+
+[Install]
+WantedBy=multi-user.target
diff --git a/utils.py b/utils.py
deleted file mode 100644
index 3c43e52..0000000
--- a/utils.py
+++ /dev/null
@@ -1,132 +0,0 @@
-import logging
-from logging.handlers import RotatingFileHandler
-from pathlib import Path
-
-from nio import AsyncClient, RoomSendResponse
-from nio.exceptions import OlmUnverifiedDeviceError
-
-from config import MAX_INPUT_LENGTH
-
-
-def setup_logging(level="INFO"):
- Path("logs").mkdir(exist_ok=True)
-
- logger = logging.getLogger("matrixbot")
- logger.setLevel(getattr(logging, level.upper(), logging.INFO))
-
- file_handler = RotatingFileHandler(
- "logs/matrixbot.log",
- maxBytes=10 * 1024 * 1024,
- backupCount=5,
- )
- file_handler.setFormatter(
- logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
- )
-
- stream_handler = logging.StreamHandler()
- stream_handler.setFormatter(
- logging.Formatter("%(asctime)s - %(levelname)s - %(message)s")
- )
-
- logger.addHandler(file_handler)
- logger.addHandler(stream_handler)
- return logger
-
-
-def _trust_all(client: AsyncClient):
- """Trust all devices in the device store."""
- if not client.olm:
- return
- for user_id, devices in client.device_store.items():
- for device_id, olm_device in devices.items():
- if not client.olm.is_device_verified(olm_device):
- client.verify_device(olm_device)
-
-
-async def _room_send_trusted(client: AsyncClient, room_id: str, message_type: str, content: dict):
- """Send a message, auto-trusting devices on OlmUnverifiedDeviceError."""
- try:
- return await client.room_send(
- room_id, message_type=message_type, content=content,
- ignore_unverified_devices=True,
- )
- except OlmUnverifiedDeviceError:
- _trust_all(client)
- return await client.room_send(
- room_id, message_type=message_type, content=content,
- ignore_unverified_devices=True,
- )
-
-
-async def send_text(client: AsyncClient, room_id: str, text: str):
- logger = logging.getLogger("matrixbot")
- resp = await _room_send_trusted(
- client, room_id,
- message_type="m.room.message",
- content={"msgtype": "m.text", "body": text},
- )
- if not isinstance(resp, RoomSendResponse):
- logger.error("send_text failed: %s", resp)
- return resp
-
-
-async def send_html(client: AsyncClient, room_id: str, plain: str, html: str):
- logger = logging.getLogger("matrixbot")
- resp = await _room_send_trusted(
- client, room_id,
- message_type="m.room.message",
- content={
- "msgtype": "m.text",
- "body": plain,
- "format": "org.matrix.custom.html",
- "formatted_body": html,
- },
- )
- if not isinstance(resp, RoomSendResponse):
- logger.error("send_html failed: %s", resp)
- return resp
-
-
-async def send_reaction(client: AsyncClient, room_id: str, event_id: str, emoji: str):
- return await _room_send_trusted(
- client, room_id,
- message_type="m.reaction",
- content={
- "m.relates_to": {
- "rel_type": "m.annotation",
- "event_id": event_id,
- "key": emoji,
- }
- },
- )
-
-
-async def get_or_create_dm(client: AsyncClient, user_id: str) -> str | None:
- """Find an existing DM room with user_id, or create one. Returns room_id."""
- logger = logging.getLogger("matrixbot")
-
- # Check existing rooms for a DM with this user
- for room_id, room in client.rooms.items():
- if room.member_count == 2 and user_id in (m.user_id for m in room.users.values()):
- return room_id
-
- # Create a new DM room
- from nio import RoomCreateResponse
- resp = await client.room_create(
- is_direct=True,
- invite=[user_id],
- )
- if isinstance(resp, RoomCreateResponse):
- logger.info("Created DM room %s with %s", resp.room_id, user_id)
- # Sync so the new room appears in client.rooms before we try to send
- await client.sync(timeout=5000)
- return resp.room_id
-
- logger.error("Failed to create DM room with %s: %s", user_id, resp)
- return None
-
-
-def sanitize_input(text: str, max_length: int = MAX_INPUT_LENGTH) -> str:
- text = text.strip()[:max_length]
- text = "".join(char for char in text if char.isprintable())
- return text
diff --git a/welcome.py b/welcome.py
deleted file mode 100644
index 1d4333d..0000000
--- a/welcome.py
+++ /dev/null
@@ -1,150 +0,0 @@
-"""Welcome module — DM new Space members.
-
-When a user joins the Space, the bot sends them a DM with a welcome
-message and a reaction button. When they react, the bot invites them
-to the standard public channels (General, Commands, Memes).
-"""
-
-import json
-import logging
-from pathlib import Path
-
-from nio import AsyncClient
-
-from utils import send_html, send_reaction, get_or_create_dm
-from config import MATRIX_USER_ID
-
-logger = logging.getLogger("matrixbot")
-
-# The Space room to watch for new members
-SPACE_ROOM_ID = "!-1ZBnAH-JiCOV8MGSKN77zDGTuI3pgSdy8Unu_DrDyc"
-
-# Public channels to invite new members to (skip Management + Cool Kids)
-INVITE_ROOMS = [
- "!wfokQ1-pE896scu_AOcCBA2s3L4qFo-PTBAFTd0WMI0", # General (v12)
- "!ou56mVZQ8ZB7AhDYPmBV5_BR28WMZ4x5zwZkPCqjq1s", # Commands (v12)
- "!GK6v5cLEEnowIooQJv5jECfISUjADjt8aKhWv9VbG5U", # Memes (v12)
-]
-
-WELCOME_EMOJI = "\u2705" # checkmark
-
-STATE_FILE = Path("welcome_state.json")
-
-
-def _load_state() -> dict:
- if STATE_FILE.exists():
- try:
- return json.loads(STATE_FILE.read_text())
- except (json.JSONDecodeError, OSError):
- pass
- return {}
-
-
-def _save_state(state: dict):
- try:
- tmp = STATE_FILE.with_suffix(".tmp")
- tmp.write_text(json.dumps(state, indent=2))
- tmp.rename(STATE_FILE)
- except OSError as e:
- logger.error("Failed to save welcome state: %s", e)
-
-
-async def handle_space_join(client: AsyncClient, sender: str):
- """Called when a new user joins the Space. DM them a welcome message."""
- state = _load_state()
- welcomed = state.get("welcomed_users", [])
-
- if sender in welcomed:
- return
-
- logger.info("New Space member %s — sending welcome DM", sender)
-
- dm_room = await get_or_create_dm(client, sender)
- if not dm_room:
- logger.error("Could not create DM with %s for welcome", sender)
- return
-
- plain = (
- "Welcome to The Lotus Guild!\n\n"
- f"React to this message with {WELCOME_EMOJI} to get invited to all channels.\n\n"
- "You'll be added to General, Commands, and Memes."
- )
- html = (
- "Welcome to The Lotus Guild!
"
- f"React to this message with {WELCOME_EMOJI} to get invited to all channels.
"
- "You'll be added to General, Commands, and Memes.
"
- )
-
- resp = await send_html(client, dm_room, plain, html)
- if hasattr(resp, "event_id"):
- # Track the welcome message per user so we can match their reaction
- dm_messages = state.get("dm_welcome_messages", {})
- dm_messages[resp.event_id] = {"user": sender, "dm_room": dm_room}
- state["dm_welcome_messages"] = dm_messages
- _save_state(state)
-
- # React to our own message to show what to click
- await send_reaction(client, dm_room, resp.event_id, WELCOME_EMOJI)
- logger.info("Sent welcome DM to %s (event %s)", sender, resp.event_id)
- else:
- logger.error("Failed to send welcome DM to %s: %s", sender, resp)
-
-
-async def handle_welcome_reaction(
- client: AsyncClient, room_id: str, sender: str, reacted_event_id: str, key: str
-):
- """Handle a reaction to a welcome DM. Invite user to channels."""
- if sender == MATRIX_USER_ID:
- return
-
- if key != WELCOME_EMOJI:
- return
-
- state = _load_state()
- dm_messages = state.get("dm_welcome_messages", {})
- entry = dm_messages.get(reacted_event_id)
-
- if not entry:
- return
-
- if entry["user"] != sender:
- return
-
- logger.info("Welcome reaction from %s — sending invites", sender)
-
- invited_count = 0
- for invite_room_id in INVITE_ROOMS:
- room = client.rooms.get(invite_room_id)
- if room and sender in (m.user_id for m in room.users.values()):
- logger.debug("%s already in %s, skipping", sender, invite_room_id)
- continue
-
- try:
- resp = await client.room_invite(invite_room_id, sender)
- logger.info("Invited %s to %s: %s", sender, invite_room_id, resp)
- invited_count += 1
- except Exception as e:
- logger.error("Failed to invite %s to %s: %s", sender, invite_room_id, e)
-
- # Mark user as welcomed
- welcomed = state.get("welcomed_users", [])
- if sender not in welcomed:
- welcomed.append(sender)
- state["welcomed_users"] = welcomed
-
- # Remove the DM message entry (one-time use)
- del dm_messages[reacted_event_id]
- state["dm_welcome_messages"] = dm_messages
- _save_state(state)
-
- # Confirm in DM
- from utils import send_text
- if invited_count > 0:
- await send_text(client, room_id, f"You've been invited to {invited_count} channel(s). Check your invites!")
- else:
- await send_text(client, room_id, "You're already in all the channels!")
-
-
-async def post_welcome_message(client: AsyncClient):
- """No-op kept for backward compatibility with bot.py startup."""
- logger.info("Welcome module ready — watching Space for new members")
diff --git a/wordle.py b/wordle.py
deleted file mode 100644
index b95a0b2..0000000
--- a/wordle.py
+++ /dev/null
@@ -1,822 +0,0 @@
-"""Wordle game for Matrix bot.
-
-Full implementation with daily puzzles, statistics tracking,
-hard mode, shareable results, and rich HTML rendering.
-"""
-
-import json
-import logging
-import time
-from dataclasses import dataclass, field
-from datetime import date
-from pathlib import Path
-
-import aiohttp
-from nio import AsyncClient
-
-from utils import send_text, send_html, get_or_create_dm
-from config import BOT_PREFIX
-
-from wordlist_answers import ANSWERS
-from wordlist_valid import VALID_GUESSES
-
-logger = logging.getLogger("matrixbot")
-
-# ---------------------------------------------------------------------------
-# Constants
-# ---------------------------------------------------------------------------
-
-_WORDLE_EPOCH = date(2021, 6, 19)
-
-# Cache: date string -> (word, puzzle_number)
-_nyt_cache: dict[str, tuple[str, int]] = {}
-
-# Build lookup sets at import time
-_ANSWER_LIST = [w.upper() for w in ANSWERS]
-_VALID_SET = frozenset(w.upper() for w in VALID_GUESSES) | frozenset(_ANSWER_LIST)
-
-# Tile colors (Wordle official palette)
-_TILE = {
- 2: {"bg": "#538d4e", "label": "correct"}, # Green
- 1: {"bg": "#b59f3b", "label": "present"}, # Yellow
- 0: {"bg": "#3a3a3c", "label": "absent"}, # Gray
-}
-_EMPTY_BG = "#121213"
-_EMPTY_BORDER = "#3a3a3c"
-
-# Emoji squares for plain-text fallback & share
-_EMOJI = {2: "\U0001f7e9", 1: "\U0001f7e8", 0: "\u2b1b"}
-
-# Keyboard layout
-_KB_ROWS = ["QWERTYUIOP", "ASDFGHJKL", "ZXCVBNM"]
-
-# Stats file
-STATS_FILE = Path("wordle_stats.json")
-
-# Congratulations messages by guess number
-_CONGRATS = {
- 1: "Genius!",
- 2: "Magnificent!",
- 3: "Impressive!",
- 4: "Splendid!",
- 5: "Great!",
- 6: "Phew!",
-}
-
-# ---------------------------------------------------------------------------
-# Data structures
-# ---------------------------------------------------------------------------
-
-@dataclass
-class WordleGame:
- player_id: str
- room_id: str
- target: str
- guesses: list = field(default_factory=list)
- results: list = field(default_factory=list)
- hard_mode: bool = False
- daily_number: int = 0
- started_at: float = field(default_factory=time.time)
- finished: bool = False
- won: bool = False
- origin_room_id: str = "" # Public room where game was started (for share)
-
-
-# Module-level state
-_active_games: dict[str, WordleGame] = {}
-_all_stats: dict[str, dict] = {}
-
-# ---------------------------------------------------------------------------
-# Stats persistence
-# ---------------------------------------------------------------------------
-
-def _load_stats():
- global _all_stats
- if STATS_FILE.exists():
- try:
- _all_stats = json.loads(STATS_FILE.read_text())
- except (json.JSONDecodeError, OSError) as e:
- logger.error("Failed to load wordle stats: %s", e)
- _all_stats = {}
- else:
- _all_stats = {}
-
-
-def _save_stats():
- try:
- tmp = STATS_FILE.with_suffix(".tmp")
- tmp.write_text(json.dumps(_all_stats, indent=2))
- tmp.rename(STATS_FILE)
- except OSError as e:
- logger.error("Failed to save wordle stats: %s", e)
-
-
-def _get_player_stats(player_id: str) -> dict:
- if player_id not in _all_stats:
- _all_stats[player_id] = {
- "games_played": 0,
- "games_won": 0,
- "current_streak": 0,
- "max_streak": 0,
- "guess_distribution": {str(i): 0 for i in range(1, 7)},
- "last_daily": -1,
- "hard_mode": False,
- "last_daily_result": None,
- "last_daily_guesses": None,
- }
- return _all_stats[player_id]
-
-
-def _record_game_result(player_id: str, game: WordleGame):
- stats = _get_player_stats(player_id)
- stats["games_played"] += 1
-
- if game.won:
- stats["games_won"] += 1
- stats["current_streak"] += 1
- stats["max_streak"] = max(stats["max_streak"], stats["current_streak"])
- num_guesses = str(len(game.guesses))
- stats["guess_distribution"][num_guesses] = (
- stats["guess_distribution"].get(num_guesses, 0) + 1
- )
- else:
- stats["current_streak"] = 0
-
- stats["last_daily"] = game.daily_number
- stats["last_daily_result"] = game.results
- stats["last_daily_guesses"] = game.guesses
- stats["last_daily_won"] = game.won
- stats["last_daily_hard"] = game.hard_mode
- stats["last_origin_room"] = game.origin_room_id
-
- _save_stats()
-
-
-# ---------------------------------------------------------------------------
-# Core algorithms
-# ---------------------------------------------------------------------------
-
-async def get_daily_word() -> tuple[str, int]:
- """Return (word, puzzle_number) for today's daily puzzle.
-
- Fetches from the NYT Wordle API. Falls back to the local word list
- if the request fails.
- """
- today = date.today()
- date_str = today.strftime("%Y-%m-%d")
-
- if date_str in _nyt_cache:
- return _nyt_cache[date_str]
-
- try:
- url = f"https://www.nytimes.com/svc/wordle/v2/{date_str}.json"
- timeout = aiohttp.ClientTimeout(total=5)
- async with aiohttp.ClientSession(timeout=timeout) as session:
- async with session.get(url) as resp:
- if resp.status == 200:
- data = await resp.json(content_type=None)
- word = data["solution"].upper()
- puzzle_number = int(data["id"])
- _nyt_cache[date_str] = (word, puzzle_number)
- logger.info("NYT Wordle #%d: %s", puzzle_number, word)
- return word, puzzle_number
- else:
- logger.warning("NYT Wordle API returned %d, falling back to local list", resp.status)
- except Exception as e:
- logger.warning("Failed to fetch NYT Wordle word: %s — falling back to local list", e)
-
- # Fallback: use local answer list
- puzzle_number = (today - _WORDLE_EPOCH).days
- word = _ANSWER_LIST[puzzle_number % len(_ANSWER_LIST)]
- return word, puzzle_number
-
-
-def evaluate_guess(guess: str, target: str) -> list[int]:
- """Evaluate a guess against the target. Returns list of 5 scores:
- 2 = correct position (green), 1 = wrong position (yellow), 0 = absent (gray).
- Handles duplicate letters correctly with a two-pass approach.
- """
- result = [0] * 5
- target_remaining = list(target)
-
- # Pass 1: mark exact matches (green)
- for i in range(5):
- if guess[i] == target[i]:
- result[i] = 2
- target_remaining[i] = None
-
- # Pass 2: mark present-but-wrong-position (yellow)
- for i in range(5):
- if result[i] == 2:
- continue
- if guess[i] in target_remaining:
- result[i] = 1
- target_remaining[target_remaining.index(guess[i])] = None
-
- return result
-
-
-def validate_hard_mode(
- guess: str,
- previous_guesses: list[str],
- previous_results: list[list[int]],
-) -> str | None:
- """Return None if valid, or an error message if hard mode violated."""
- for prev_guess, prev_result in zip(previous_guesses, previous_results):
- for i, (letter, score) in enumerate(zip(prev_guess, prev_result)):
- if score == 2 and guess[i] != letter:
- return (
- f"Hard mode: position {i + 1} must be "
- f"'{letter}' (green from previous guess)"
- )
- if score == 1 and letter not in guess:
- return (
- f"Hard mode: guess must contain "
- f"'{letter}' (yellow from previous guess)"
- )
- return None
-
-
-# ---------------------------------------------------------------------------
-# HTML rendering
-# ---------------------------------------------------------------------------
-
-def _tile_span(letter: str, bg: str) -> str:
- """Render a single letter tile using Matrix-compatible attributes."""
- return (
- f''
- f"\u00a0{letter}\u00a0"
- )
-
-
-def render_grid_html(game: WordleGame) -> str:
- """Render the Wordle grid as inline spans (compatible with Cinny)."""
- rows = []
- for row_idx in range(6):
- tiles = []
- if row_idx < len(game.guesses):
- guess = game.guesses[row_idx]
- result = game.results[row_idx]
- for letter, score in zip(guess, result):
- bg = _TILE[score]["bg"]
- tiles.append(_tile_span(letter, bg))
- else:
- for _ in range(5):
- tiles.append(_tile_span("\u00a0", _EMPTY_BG))
- rows.append("".join(tiles))
-
- return "
".join(rows)
-
-
-def render_keyboard_html(game: WordleGame) -> str:
- """Render a virtual keyboard showing letter states."""
- letter_states: dict[str, int] = {}
- for guess, result in zip(game.guesses, game.results):
- for letter, score in zip(guess, result):
- letter_states[letter] = max(letter_states.get(letter, -1), score)
-
- kb_rows = []
- for row in _KB_ROWS:
- keys = []
- for letter in row:
- state = letter_states.get(letter, -1)
- if state == -1:
- bg, color = "#818384", "#ffffff"
- elif state == 0:
- bg, color = "#3a3a3c", "#555555"
- elif state == 1:
- bg, color = "#b59f3b", "#ffffff"
- else:
- bg, color = "#538d4e", "#ffffff"
- keys.append(
- f''
- f"{letter}"
- )
- kb_rows.append(" ".join(keys))
-
- return "
" + "
".join(kb_rows)
-
-
-def render_grid_plain(game: WordleGame) -> str:
- """Plain text grid with emoji squares and letter markers."""
- _marker = {2: "!", 1: "?", 0: "."} # ! = correct, ? = wrong spot, . = absent
- lines = []
- for guess, result in zip(game.guesses, game.results):
- emoji_row = "".join(_EMOJI[s] for s in result)
- # Show each letter with a marker: [C!] = correct, [R?] = wrong spot, [A.] = absent
- marked = " ".join(f"{letter}{_marker[score]}" for letter, score in zip(guess, result))
- lines.append(f"{emoji_row} {marked}")
- return "\n".join(lines)
-
-
-def render_keyboard_plain(game: WordleGame) -> str:
- """Plain text keyboard status."""
- letter_states: dict[str, int] = {}
- for guess, result in zip(game.guesses, game.results):
- for letter, score in zip(guess, result):
- letter_states[letter] = max(letter_states.get(letter, -1), score)
-
- lines = []
- symbols = {-1: " ", 0: "\u2717", 1: "?", 2: "\u2713"}
- for row in _KB_ROWS:
- chars = []
- for letter in row:
- state = letter_states.get(letter, -1)
- if state == 0:
- chars.append("\u00b7") # dimmed
- elif state >= 1:
- chars.append(letter)
- else:
- chars.append(letter.lower())
- lines.append(" ".join(chars))
- return "\n".join(lines)
-
-
-def render_stats_html(stats: dict) -> str:
- """Render player statistics as HTML (Matrix-compatible)."""
- played = stats["games_played"]
- won = stats["games_won"]
- win_pct = (won / max(played, 1)) * 100
- streak = stats["current_streak"]
- max_streak = stats["max_streak"]
- dist = stats["guess_distribution"]
- max_count = max((int(v) for v in dist.values()), default=1) or 1
-
- html = "Wordle Statistics
"
- html += (
- f"{played} Played | "
- f"{win_pct:.0f}% Win | "
- f"{streak} Streak | "
- f"{max_streak} Best
"
- )
-
- html += "Guess Distribution
"
- for i in range(1, 7):
- count = int(dist.get(str(i), 0))
- is_max = count == max_count and count > 0
- bar_len = max(int((count / max_count) * 10), 1) if max_count > 0 else 1
- bar = "\u2588" * bar_len # Block character for bar
- if is_max and count > 0:
- html += f'{i} {bar} {count}
'
- else:
- html += f'{i} {bar} {count}
'
- return html
-
-
-def render_stats_plain(stats: dict) -> str:
- """Plain text stats."""
- played = stats["games_played"]
- won = stats["games_won"]
- win_pct = (won / max(played, 1)) * 100
- streak = stats["current_streak"]
- max_streak = stats["max_streak"]
- dist = stats["guess_distribution"]
- max_count = max((int(v) for v in dist.values()), default=1) or 1
-
- lines = [
- "Wordle Statistics",
- f"Played: {played} | Win: {win_pct:.0f}% | Streak: {streak} | Max: {max_streak}",
- "",
- "Guess Distribution:",
- ]
- for i in range(1, 7):
- count = int(dist.get(str(i), 0))
- bar_len = max(round((count / max_count) * 16), 1) if count > 0 else 0
- bar = "\u2588" * bar_len
- lines.append(f" {i}: {bar} {count}")
-
- return "\n".join(lines)
-
-
-def generate_share(stats: dict) -> str:
- """Generate the shareable emoji grid from last completed daily."""
- results = stats.get("last_daily_result")
- if not results:
- return ""
-
- won = stats.get("last_daily_won", False)
- hard = stats.get("last_daily_hard", False)
- daily_num = stats.get("last_daily", 0)
- score = str(len(results)) if won else "X"
- mode = "*" if hard else ""
-
- header = f"Wordle {daily_num} {score}/6{mode}\n\n"
- rows = []
- for result in results:
- rows.append("".join(_EMOJI[s] for s in result))
- return header + "\n".join(rows)
-
-
-# ---------------------------------------------------------------------------
-# Subcommand handlers
-# ---------------------------------------------------------------------------
-
-async def wordle_help(client: AsyncClient, room_id: str):
- """Show help text with rules and commands."""
- p = BOT_PREFIX
- plain = (
- f"Wordle - Guess the 5-letter word in 6 tries!\n\n"
- f"Commands:\n"
- f" {p}wordle Start today's daily puzzle (or show current game)\n"
- f" {p}wordle Submit a 5-letter guess\n"
- f" {p}wordle stats View your statistics\n"
- f" {p}wordle hard Toggle hard mode\n"
- f" {p}wordle share Share your last daily result\n"
- f" {p}wordle give up Forfeit current game\n"
- f" {p}wordle help Show this help\n\n"
- f"Rules:\n"
- f" - Each guess must be a valid 5-letter English word\n"
- f" - Green = correct letter, correct position\n"
- f" - Yellow = correct letter, wrong position\n"
- f" - Gray = letter not in the word\n"
- f" - Hard mode: must use all revealed hints in subsequent guesses\n"
- f" - Everyone gets the same daily word!"
- )
- html = (
- "Wordle
"
- "Guess the 5-letter word in 6 tries!
"
- "Commands:"
- ""
- f"{p}wordle — Start today's daily puzzle "
- f"{p}wordle <word> — Submit a guess "
- f"{p}wordle stats — View your statistics "
- f"{p}wordle hard — Toggle hard mode "
- f"{p}wordle share — Share your last result "
- f"{p}wordle give up — Forfeit current game "
- "
"
- "How to play:"
- ""
- '- G '
- "Green = correct letter, correct position
"
- '- Y '
- "Yellow = correct letter, wrong position
"
- '- X '
- "Gray = letter not in the word
"
- "
"
- "Hard mode: You must use all revealed hints in subsequent guesses.
"
- "Everyone gets the same daily word!
"
- )
- await send_html(client, room_id, plain, html)
-
-
-async def wordle_start_or_status(client: AsyncClient, room_id: str, sender: str, origin_room_id: str = ""):
- """Start a new daily game or show current game status."""
- # Check for active game
- if sender in _active_games:
- game = _active_games[sender]
- if not game.finished:
- guesses_left = 6 - len(game.guesses)
- grid_plain = render_grid_plain(game)
- kb_plain = render_keyboard_plain(game)
- plain = (
- f"Wordle {game.daily_number} — "
- f"Guess {len(game.guesses) + 1}/6\n\n"
- f"{grid_plain}\n\n{kb_plain}"
- )
- grid_html = render_grid_html(game)
- kb_html = render_keyboard_html(game)
- mode = " (Hard Mode)" if game.hard_mode else ""
- html = (
- f''
- f"Wordle {game.daily_number}{mode} — "
- f"Guess {len(game.guesses) + 1}/6
"
- f"{grid_html}{kb_html}"
- )
- await send_html(client, room_id, plain, html)
- return
-
- # Check if already completed today's puzzle
- word, puzzle_number = await get_daily_word()
- stats = _get_player_stats(sender)
-
- if stats["last_daily"] == puzzle_number:
- await send_text(
- client, room_id,
- f"You already completed today's Wordle (#{puzzle_number})! "
- f"Use {BOT_PREFIX}wordle stats to see your results "
- f"or {BOT_PREFIX}wordle share to share them."
- )
- return
-
- # Start new game
- hard_mode = stats.get("hard_mode", False)
- game = WordleGame(
- player_id=sender,
- room_id=room_id,
- target=word,
- hard_mode=hard_mode,
- daily_number=puzzle_number,
- origin_room_id=origin_room_id or room_id,
- )
- _active_games[sender] = game
-
- mode_str = " (Hard Mode)" if hard_mode else ""
- grid_html = render_grid_html(game)
- kb_html = render_keyboard_html(game)
- plain = (
- f"Wordle #{puzzle_number}{mode_str}\n"
- f"Guess a 5-letter word! You have 6 attempts.\n"
- f"Type {BOT_PREFIX}wordle to guess."
- )
- html = (
- f''
- f"Wordle #{puzzle_number}{mode_str}
"
- f"Guess a 5-letter word! You have 6 attempts.
"
- f"Type {BOT_PREFIX}wordle <word> to guess."
- f"
{grid_html}{kb_html}"
- )
- await send_html(client, room_id, plain, html)
-
-
-async def wordle_guess(
- client: AsyncClient, room_id: str, sender: str, guess: str
-):
- """Process a guess."""
- if sender not in _active_games:
- await send_text(
- client, room_id,
- f"No active game. Start one with {BOT_PREFIX}wordle"
- )
- return
-
- game = _active_games[sender]
- if game.finished:
- await send_text(
- client, room_id,
- f"Your game is already finished! "
- f"Use {BOT_PREFIX}wordle to start a new daily puzzle."
- )
- return
-
- # Validate word
- if guess not in _VALID_SET:
- await send_text(client, room_id, f"'{guess.lower()}' is not in the word list. Try again.")
- return
-
- # Hard mode validation
- if game.hard_mode and game.guesses:
- violation = validate_hard_mode(guess, game.guesses, game.results)
- if violation:
- await send_text(client, room_id, violation)
- return
-
- # Evaluate
- result = evaluate_guess(guess, game.target)
- game.guesses.append(guess)
- game.results.append(result)
-
- # Check win
- if all(s == 2 for s in result):
- game.finished = True
- game.won = True
- origin = game.origin_room_id
- _record_game_result(sender, game)
- del _active_games[sender]
-
- num = len(game.guesses)
- congrats = _CONGRATS.get(num, "Nice!")
- grid_plain = render_grid_plain(game)
- plain = (
- f"{congrats} Wordle {game.daily_number} {num}/6"
- f"{'*' if game.hard_mode else ''}\n\n"
- f"{grid_plain}"
- )
- grid_html = render_grid_html(game)
- mode = "*" if game.hard_mode else ""
- html = (
- f''
- f"{congrats} "
- f"Wordle {game.daily_number} {num}/6{mode}
"
- f"{grid_html}"
- )
- await send_html(client, room_id, plain, html)
- if origin and origin != room_id:
- await wordle_share(client, origin, sender)
- return
-
- # Check loss (6 guesses used)
- if len(game.guesses) >= 6:
- game.finished = True
- game.won = False
- origin = game.origin_room_id
- _record_game_result(sender, game)
- del _active_games[sender]
-
- grid_plain = render_grid_plain(game)
- plain = (
- f"Wordle {game.daily_number} X/6"
- f"{'*' if game.hard_mode else ''}\n\n"
- f"{grid_plain}\n\n"
- f"The word was: {game.target}\n"
- f"Better luck tomorrow!"
- )
- grid_html = render_grid_html(game)
- mode = "*" if game.hard_mode else ""
- html = (
- f''
- f"Wordle {game.daily_number} X/6{mode}
"
- f"{grid_html}
"
- f'The word was: '
- f"{game.target}
"
- f"Better luck tomorrow!"
- )
- await send_html(client, room_id, plain, html)
- if origin and origin != room_id:
- await wordle_share(client, origin, sender)
- return
-
- # Still playing — show grid + keyboard
- guesses_left = 6 - len(game.guesses)
- grid_plain = render_grid_plain(game)
- kb_plain = render_keyboard_plain(game)
- plain = (
- f"Wordle {game.daily_number} — "
- f"Guess {len(game.guesses) + 1}/6\n\n"
- f"{grid_plain}\n\n{kb_plain}"
- )
- grid_html = render_grid_html(game)
- kb_html = render_keyboard_html(game)
- mode = " (Hard Mode)" if game.hard_mode else ""
- html = (
- f''
- f"Wordle {game.daily_number}{mode} — "
- f"Guess {len(game.guesses) + 1}/6
"
- f"{grid_html}{kb_html}"
- )
- await send_html(client, room_id, plain, html)
-
-
-async def wordle_stats(client: AsyncClient, room_id: str, sender: str):
- """Show player statistics."""
- stats = _get_player_stats(sender)
-
- if stats["games_played"] == 0:
- await send_text(
- client, room_id,
- f"No Wordle stats yet! Start a game with {BOT_PREFIX}wordle"
- )
- return
-
- plain = render_stats_plain(stats)
- html = render_stats_html(stats)
- await send_html(client, room_id, plain, html)
-
-
-async def wordle_toggle_hard(client: AsyncClient, room_id: str, sender: str):
- """Toggle hard mode for the player."""
- stats = _get_player_stats(sender)
- new_mode = not stats.get("hard_mode", False)
- stats["hard_mode"] = new_mode
- _save_stats()
-
- # Also update active game if one exists
- if sender in _active_games:
- game = _active_games[sender]
- if not game.guesses:
- # Only allow toggling before first guess
- game.hard_mode = new_mode
- elif new_mode:
- await send_text(
- client, room_id,
- "Hard mode enabled for future games. "
- "Cannot enable mid-game after guessing."
- )
- return
- else:
- game.hard_mode = False
-
- status = "enabled" if new_mode else "disabled"
- plain = (
- f"Hard mode {status}. "
- + ("You must use all revealed hints in subsequent guesses."
- if new_mode else "Standard rules apply.")
- )
- await send_text(client, room_id, plain)
-
-
-async def wordle_share(client: AsyncClient, room_id: str, sender: str):
- """Share the last completed daily result."""
- stats = _get_player_stats(sender)
- share_text = generate_share(stats)
-
- if not share_text:
- await send_text(
- client, room_id,
- f"No completed daily puzzle to share. Play one with {BOT_PREFIX}wordle"
- )
- return
-
- await send_text(client, room_id, share_text)
-
-
-async def wordle_give_up(client: AsyncClient, room_id: str, sender: str):
- """Forfeit the current game."""
- if sender not in _active_games:
- await send_text(
- client, room_id,
- f"No active game to give up. Start one with {BOT_PREFIX}wordle"
- )
- return
-
- game = _active_games[sender]
- if game.finished:
- del _active_games[sender]
- return
-
- game.finished = True
- game.won = False
- origin = game.origin_room_id
- _record_game_result(sender, game)
- del _active_games[sender]
-
- grid_plain = render_grid_plain(game)
- plain = (
- f"Game over! The word was: {game.target}\n\n"
- f"{grid_plain}\n\n"
- f"Better luck tomorrow!"
- )
- grid_html = render_grid_html(game)
- html = (
- f''
- f"Game Over
"
- f"{grid_html}
"
- f'The word was: '
- f"{game.target}
"
- f"Better luck tomorrow!"
- )
- await send_html(client, room_id, plain, html)
- if origin and origin != room_id:
- await wordle_share(client, origin, sender)
-
-
-# ---------------------------------------------------------------------------
-# Main router
-# ---------------------------------------------------------------------------
-
-async def _get_dm_room(client: AsyncClient, room_id: str, sender: str) -> tuple[str, str]:
- """Get or create DM room for the sender. Returns (dm_room_id, origin_room_id).
-
- If already in a DM, returns (room_id, stored_origin or room_id).
- If in a public room, creates/finds DM and returns (dm_room_id, room_id).
- """
- # Check if this is already a DM (2 members)
- room = client.rooms.get(room_id)
- if room and room.member_count == 2:
- # Already in DM — use origin from active game if available
- game = _active_games.get(sender)
- origin = game.origin_room_id if game and game.origin_room_id else room_id
- return room_id, origin
-
- # Public room — find/create DM
- dm_room = await get_or_create_dm(client, sender)
- if dm_room:
- return dm_room, room_id
-
- # Fallback to public room if DM creation fails
- logger.warning("Could not create DM with %s, falling back to public room", sender)
- return room_id, room_id
-
-
-async def handle_wordle(
- client: AsyncClient, room_id: str, sender: str, args: str
-):
- """Main entry point — dispatches to subcommands."""
- parts = args.strip().split(None, 1)
- subcmd = parts[0].lower() if parts else ""
- sub_args = parts[1] if len(parts) > 1 else ""
-
- # Share always goes to the public room
- if subcmd == "share":
- await wordle_share(client, room_id, sender)
- return
-
- # All other commands route through DM
- dm_room, origin = await _get_dm_room(client, room_id, sender)
-
- # Silently redirect to DM — origin room will get an auto-share when the game ends
-
- if subcmd == "help":
- await wordle_help(client, dm_room)
- elif subcmd == "stats":
- await wordle_stats(client, dm_room, sender)
- elif subcmd == "hard":
- await wordle_toggle_hard(client, dm_room, sender)
- elif subcmd == "give" and sub_args.lower().startswith("up"):
- await wordle_give_up(client, dm_room, sender)
- elif subcmd == "":
- await wordle_start_or_status(client, dm_room, sender, origin)
- elif len(subcmd) == 5 and subcmd.isalpha():
- await wordle_guess(client, dm_room, sender, subcmd.upper())
- else:
- await send_text(
- client, room_id,
- f"Invalid wordle command or guess. "
- f"Guesses must be exactly 5 letters. "
- f"Try {BOT_PREFIX}wordle help"
- )
-
-
-# ---------------------------------------------------------------------------
-# Load stats on module import
-# ---------------------------------------------------------------------------
-_load_stats()
diff --git a/wordlist_answers.py b/wordlist_answers.py
deleted file mode 100644
index c1c089b..0000000
--- a/wordlist_answers.py
+++ /dev/null
@@ -1,411 +0,0 @@
-# Curated list of 2,309 five-letter words used as Wordle daily answers.
-# Common, well-known English words only.
-ANSWERS = [
- "aback", "abase", "abate", "abbey", "abbot",
- "abhor", "abide", "abled", "abode", "abort",
- "about", "above", "abuse", "abyss", "acidy",
- "acorn", "acrid", "actor", "acute", "adage",
- "adapt", "adept", "admin", "admit", "adobe",
- "adopt", "adore", "adorn", "adult", "aegis",
- "afoot", "afoul", "after", "again", "agent",
- "agile", "aging", "aglow", "agony", "agree",
- "ahead", "aider", "aisle", "alarm", "album",
- "alert", "algae", "alibi", "alien", "align",
- "alike", "alive", "allay", "alley", "allot",
- "allow", "alloy", "aloft", "alone", "along",
- "aloof", "aloud", "alpha", "altar", "alter",
- "amass", "amaze", "amber", "amble", "amend",
- "amine", "amino", "amiss", "amity", "among",
- "ample", "amply", "amuse", "angel", "anger",
- "angle", "angry", "angst", "anime", "ankle",
- "annex", "annoy", "annul", "anode", "antic",
- "anvil", "aorta", "apart", "aphid", "aping",
- "apnea", "apple", "apply", "apron", "aptly",
- "arbor", "ardor", "arena", "argue", "arise",
- "armor", "aroma", "arose", "array", "arrow",
- "arson", "artsy", "ascot", "ashen", "aside",
- "askew", "assay", "asset", "atoll", "atone",
- "attic", "audio", "audit", "augur", "aunty",
- "avian", "avoid", "await", "awake", "award",
- "aware", "awash", "awful", "awoke", "axial",
- "axiom", "azure", "bacon", "badge", "badly",
- "bagel", "baggy", "baker", "balmy", "banal",
- "banjo", "barge", "baron", "basal", "basic",
- "basil", "basin", "basis", "baste", "batch",
- "bathe", "baton", "batty", "bawdy", "bayou",
- "beach", "beady", "beard", "beast", "beech",
- "beefy", "befit", "began", "begat", "beget",
- "begin", "begun", "being", "belch", "belly",
- "below", "bench", "beret", "berry", "berth",
- "beset", "betel", "bevel", "bible", "bicep",
- "biddy", "bigot", "bilge", "billy", "binge",
- "bingo", "biome", "birch", "birth", "black",
- "blade", "blame", "bland", "blank", "blare",
- "blast", "blaze", "bleak", "bleat", "bleed",
- "blend", "bless", "blimp", "blind", "blini",
- "bliss", "blitz", "bloat", "block", "bloke",
- "blond", "blood", "bloom", "blown", "blues",
- "bluff", "blunt", "blurb", "blurt", "blush",
- "board", "boast", "bobby", "boney", "bonus",
- "booby", "boost", "booth", "booty", "booze",
- "boozy", "borax", "borne", "bosom", "bossy",
- "botch", "bound", "bowel", "boxer", "brace",
- "braid", "brain", "brake", "brand", "brash",
- "brass", "brave", "bravo", "brawl", "brawn",
- "bread", "break", "breed", "briar", "bribe",
- "brick", "bride", "brief", "brine", "bring",
- "brink", "briny", "brisk", "broad", "broil",
- "broke", "brood", "brook", "broth", "brown",
- "brush", "brunt", "brute", "buddy", "budge",
- "buggy", "bugle", "build", "built", "bulge",
- "bulky", "bully", "bunch", "bunny", "burly",
- "burnt", "burst", "bushy", "butch", "butte",
- "buyer", "bylaw", "cabal", "cabin", "cable",
- "cadet", "camel", "cameo", "canal", "candy",
- "canny", "canoe", "caper", "caput", "carat",
- "cargo", "carol", "carry", "carve", "caste",
- "catch", "cater", "cause", "cavil", "cease",
- "cedar", "chain", "chair", "chalk", "champ",
- "chant", "chaos", "chard", "charm", "chart",
- "chase", "chasm", "cheap", "cheat", "check",
- "cheek", "cheer", "chess", "chest", "chick",
- "chide", "chief", "child", "chili", "chill",
- "chime", "china", "chirp", "chock", "choir",
- "choke", "chord", "chore", "chose", "chuck",
- "chump", "chunk", "churn", "chute", "cider",
- "cigar", "cinch", "civic", "civil", "claim",
- "clamp", "clang", "clank", "clash", "clasp",
- "class", "clean", "clear", "clerk", "click",
- "cliff", "climb", "cling", "cloak", "clock",
- "clone", "close", "cloth", "cloud", "clout",
- "clown", "cluck", "clued", "clump", "clung",
- "coach", "coast", "cobra", "cocoa", "colon",
- "color", "comet", "comic", "comma", "conch",
- "condo", "coney", "coral", "corny", "couch",
- "could", "count", "coupe", "court", "cover",
- "covet", "crack", "craft", "cramp", "crane",
- "crank", "crash", "crass", "crate", "crave",
- "crawl", "craze", "crazy", "creak", "cream",
- "credo", "creed", "creek", "creep", "crest",
- "crick", "cried", "crime", "crimp", "crisp",
- "croak", "crock", "crone", "crony", "crook",
- "cross", "crowd", "crown", "crude", "cruel",
- "crush", "crust", "crypt", "cubic", "cumin",
- "cupid", "curly", "curry", "curse", "curve",
- "curvy", "cutie", "cycle", "cynic", "daddy",
- "daily", "dairy", "daisy", "dally", "dance",
- "dandy", "datum", "daunt", "dealt", "death",
- "debut", "decay", "decal", "decor", "decoy",
- "decry", "defer", "deign", "deity", "delay",
- "delta", "delve", "demon", "demur", "denim",
- "dense", "depot", "depth", "derby", "deter",
- "detox", "deuce", "devil", "diary", "dicey",
- "digit", "dilly", "dimly", "diner", "dingo",
- "dingy", "diode", "dirge", "dirty", "disco",
- "ditch", "ditto", "ditty", "diver", "dizzy",
- "dodge", "dodgy", "dogma", "doing", "dolly",
- "donor", "donut", "dopey", "doubt", "dough",
- "dowdy", "dowel", "draft", "drain", "drake",
- "drama", "drank", "drape", "drawl", "drawn",
- "dread", "dream", "dress", "dried", "drift",
- "drill", "drink", "drive", "droit", "droll",
- "drone", "drool", "droop", "dross", "drove",
- "drown", "drugs", "drunk", "dryer", "dryly",
- "duchy", "dully", "dummy", "dunce", "dusty",
- "duvet", "dwarf", "dwell", "dwelt", "dying",
- "eager", "eagle", "early", "earth", "easel",
- "eaten", "eater", "ebony", "eclat", "edict",
- "edify", "eerie", "egret", "eight", "elder",
- "elect", "elite", "elope", "elude", "email",
- "ember", "emcee", "empty", "enact", "endow",
- "enemy", "enjoy", "ennui", "ensue", "enter",
- "entry", "envoy", "epoch", "equal", "equip",
- "erase", "erode", "error", "erupt", "essay",
- "ester", "ether", "ethic", "ethos", "evade",
- "event", "every", "evict", "evoke", "exact",
- "exalt", "excel", "exert", "exile", "exist",
- "expat", "expel", "extol", "extra", "exude",
- "exult", "fable", "facet", "fairy", "faith",
- "false", "fancy", "fanny", "farce", "fatal",
- "fatty", "fault", "fauna", "feast", "feign",
- "feint", "fella", "felon", "femur", "fence",
- "feral", "ferry", "fetal", "fetch", "fetid",
- "fetus", "fever", "fiber", "fibre", "field",
- "fiend", "fiery", "fifth", "fifty", "fight",
- "filly", "filmy", "filth", "final", "finch",
- "fishy", "fixer", "fizzy", "fjord", "flack",
- "flail", "flair", "flake", "flaky", "flame",
- "flank", "flare", "flash", "flask", "fleet",
- "flesh", "flick", "flier", "fling", "flint",
- "flirt", "float", "flock", "flood", "floor",
- "flora", "floss", "flour", "flout", "flown",
- "fluid", "fluke", "flung", "flunk", "flush",
- "flute", "foamy", "focal", "focus", "foggy",
- "folly", "foray", "force", "forge", "forgo",
- "forte", "forth", "forty", "forum", "found",
- "foyer", "frail", "frame", "frank", "fraud",
- "freak", "freed", "fresh", "friar", "fried",
- "frill", "frisk", "fritz", "frock", "frond",
- "front", "frost", "frown", "froze", "fruit",
- "frump", "fully", "fungi", "funky", "funny",
- "furry", "fussy", "fuzzy", "gamma", "gamut",
- "gassy", "gaudy", "gauge", "gaunt", "gauze",
- "gavel", "gawky", "geeky", "genie", "genre",
- "ghost", "giant", "giddy", "girth", "given",
- "giver", "gland", "glare", "glass", "glaze",
- "gleam", "glean", "glide", "glint", "gloat",
- "globe", "gloom", "glory", "gloss", "glove",
- "glyph", "gnash", "gnome", "godly", "going",
- "golem", "golly", "gonad", "goner", "goody",
- "gooey", "goofy", "goose", "gorge", "gouge",
- "gourd", "grace", "grade", "graft", "grail",
- "grain", "grand", "grant", "grape", "graph",
- "grasp", "grass", "grate", "grave", "gravy",
- "graze", "great", "greed", "green", "greet",
- "grief", "grill", "grime", "grimy", "grind",
- "gripe", "groan", "groat", "groin", "groom",
- "grope", "gross", "group", "grout", "grove",
- "growl", "grown", "gruel", "gruff", "grump",
- "grunt", "guard", "guava", "guess", "guest",
- "guide", "guild", "guilt", "guise", "gulch",
- "gully", "gumbo", "gummy", "guppy", "gusto",
- "gusty", "habit", "hairy", "halve", "handy",
- "happy", "hardy", "harem", "harpy", "harry",
- "harsh", "haste", "hasty", "hatch", "haunt",
- "haven", "hazel", "heady", "heart", "heath",
- "heavy", "hedge", "hefty", "heist", "helix",
- "hello", "hence", "heron", "hilly", "hinge",
- "hippo", "hippy", "hitch", "hoard", "hobby",
- "hoist", "holly", "homer", "honey", "honor",
- "hooey", "horde", "horny", "horse", "hotel",
- "hotly", "hound", "house", "hover", "howdy",
- "human", "humid", "humor", "humus", "hunch",
- "hunky", "hurry", "husky", "hussy", "hutch",
- "hyena", "hymen", "hyper", "icily", "icing",
- "ideal", "idiom", "idiot", "idyll", "igloo",
- "image", "imbue", "impel", "imply", "inane",
- "inbox", "incur", "index", "inept", "inert",
- "infer", "ingot", "inlay", "inlet", "inner",
- "input", "inter", "intro", "ionic", "irate",
- "irony", "islet", "issue", "itchy", "ivory",
- "jazzy", "jelly", "jenny", "jerky", "jewel",
- "jiffy", "jimmy", "joker", "jolly", "joust",
- "judge", "juice", "juicy", "jumbo", "jumpy",
- "juror", "karma", "kayak", "kebab", "khaki",
- "kinky", "kiosk", "kitty", "knack", "knead",
- "kneel", "knelt", "knife", "knock", "knoll",
- "known", "koala", "kudos", "label", "labor",
- "laden", "ladle", "lager", "lance", "lanky",
- "lapel", "lapse", "large", "larva", "latch",
- "later", "lathe", "latte", "laugh", "layer",
- "leach", "leafy", "leaky", "leapt", "learn",
- "lease", "leash", "least", "leave", "ledge",
- "leech", "legal", "leggy", "lemon", "lemur",
- "level", "lever", "libel", "light", "liken",
- "lilac", "limbo", "linen", "liner", "lingo",
- "lipid", "liter", "lithe", "liver", "livid",
- "llama", "lobby", "local", "locus", "lodge",
- "lofty", "logic", "login", "loopy", "loose",
- "lorry", "loser", "lousy", "lover", "lower",
- "lowly", "loyal", "lucid", "lucky", "lumen",
- "lumpy", "lunar", "lunch", "lunge", "lupus",
- "lusty", "lying", "lynch", "lyric", "macaw",
- "macho", "macro", "madam", "madly", "magic",
- "magma", "maize", "major", "maker", "mambo",
- "mamma", "mango", "mangy", "mania", "manic",
- "manly", "manor", "maple", "march", "marry",
- "marsh", "mason", "match", "matey", "maxim",
- "maybe", "mayor", "mealy", "meant", "meaty",
- "media", "medic", "melee", "melon", "mercy",
- "merge", "merit", "merry", "metal", "meter",
- "midst", "might", "milky", "mimic", "mince",
- "minor", "minus", "mirth", "miser", "missy",
- "mocha", "modal", "model", "modem", "mogul",
- "moist", "molar", "moldy", "money", "month",
- "moody", "moose", "moral", "morph", "mossy",
- "motel", "motif", "motor", "motto", "moult",
- "mound", "mount", "mourn", "mouse", "mousy",
- "mouth", "mover", "movie", "mower", "mucus",
- "muddy", "mulch", "mummy", "mural", "murky",
- "mushy", "music", "musty", "myrrh", "nadir",
- "naive", "nanny", "nasal", "nasty", "natal",
- "naval", "navel", "needy", "nerve", "never",
- "newer", "newly", "nexus", "nicer", "niche",
- "night", "ninja", "ninny", "ninth", "noble",
- "nobly", "noise", "noisy", "nomad", "noose",
- "north", "notch", "noted", "novel", "nudge",
- "nurse", "nutty", "nylon", "nymph", "oaken",
- "oasis", "occur", "ocean", "octet", "oddly",
- "offal", "offer", "often", "olive", "omega",
- "onset", "opera", "opium", "optic", "orbit",
- "order", "organ", "other", "otter", "ought",
- "ounce", "outdo", "outer", "ovary", "ovate",
- "overt", "ovoid", "owing", "owner", "oxide",
- "ozone", "paddy", "pagan", "paint", "paler",
- "palsy", "panel", "panic", "pansy", "papal",
- "paper", "parch", "parka", "parry", "parse",
- "party", "pasta", "paste", "pasty", "patch",
- "patio", "patsy", "patty", "pause", "payee",
- "peace", "peach", "pearl", "pecan", "pedal",
- "penal", "pence", "penny", "peppy", "perch",
- "peril", "perky", "pesky", "petal", "petty",
- "phase", "phone", "photo", "piano", "picky",
- "piece", "piety", "piggy", "pilot", "pinch",
- "piney", "pious", "piper", "pipit", "pixel",
- "pixie", "pizza", "place", "plaid", "plain",
- "plait", "plane", "plank", "plant", "plate",
- "plaza", "plead", "pleat", "plied", "plier",
- "pluck", "plumb", "plume", "plump", "plunk",
- "plush", "poesy", "point", "poise", "poker",
- "polar", "polka", "polyp", "pooch", "poppy",
- "porch", "poser", "posit", "posse", "pouch",
- "poult", "pound", "pouty", "power", "prank",
- "prawn", "preen", "press", "price", "prick",
- "pride", "pried", "prime", "primo", "print",
- "prior", "prism", "privy", "prize", "probe",
- "prone", "prong", "proof", "prose", "proud",
- "prove", "prowl", "prude", "prune", "psalm",
- "pubic", "pudgy", "pulse", "punch", "pupil",
- "puppy", "puree", "purge", "purse", "pushy",
- "putty", "pygmy", "quack", "quaff", "quail",
- "quake", "qualm", "quart", "quasi", "queen",
- "queer", "query", "quest", "queue", "quick",
- "quiet", "quill", "quirk", "quite", "quota",
- "quote", "quoth", "rabbi", "rabid", "racer",
- "radar", "radii", "radio", "radon", "rally",
- "ramen", "ranch", "randy", "range", "rapid",
- "rarer", "raspy", "ratio", "raven", "rayon",
- "razor", "reach", "react", "ready", "realm",
- "rebel", "rebus", "rebut", "recap", "recur",
- "reedy", "refer", "regal", "rehab", "reign",
- "relax", "relay", "relic", "remit", "renal",
- "renew", "repay", "repel", "reply", "rerun",
- "reset", "resin", "retch", "retro", "retry",
- "reuse", "revel", "rider", "ridge", "rifle",
- "right", "rigid", "rigor", "rinse", "ripen",
- "riper", "risen", "risky", "rival", "river",
- "rivet", "roach", "roast", "robin", "robot",
- "rocky", "rogue", "roomy", "roost", "rouge",
- "rough", "round", "rouse", "route", "rover",
- "rowdy", "rower", "royal", "ruddy", "rugby",
- "ruler", "rumba", "rumor", "rupee", "rural",
- "rusty", "sadly", "safer", "saint", "salad",
- "sally", "salon", "salsa", "salty", "salve",
- "salvo", "sandy", "saner", "sappy", "sassy",
- "sauce", "saucy", "sauna", "saute", "savor",
- "savoy", "savvy", "scald", "scale", "scalp",
- "scaly", "scamp", "scant", "scare", "scarf",
- "scary", "scene", "scent", "scion", "scoff",
- "scold", "scone", "scoop", "scope", "score",
- "scorn", "scout", "scowl", "scram", "scrap",
- "scrub", "scrum", "sedan", "seedy", "segue",
- "seize", "sense", "sepia", "serve", "setup",
- "seven", "sever", "sewer", "shack", "shade",
- "shady", "shaft", "shake", "shaky", "shall",
- "shame", "shank", "shape", "shard", "share",
- "shark", "sharp", "shave", "shawl", "shear",
- "sheen", "sheep", "sheer", "sheet", "shelf",
- "shell", "shift", "shine", "shiny", "shire",
- "shirk", "shirt", "shoal", "shock", "shone",
- "shook", "shoot", "shore", "shorn", "short",
- "shout", "shove", "shown", "showy", "shrub",
- "shrug", "shuck", "shunt", "siege", "sieve",
- "sight", "sigma", "silky", "silly", "since",
- "sinew", "siren", "sissy", "sixth", "sixty",
- "sized", "skate", "skier", "skimp", "skirt",
- "skull", "skunk", "slack", "slain", "slang",
- "slant", "slash", "slate", "slave", "sleek",
- "sleep", "sleet", "slept", "slice", "slide",
- "slime", "slimy", "sling", "slink", "slope",
- "sloth", "slump", "slung", "slunk", "slurp",
- "smack", "small", "smart", "smash", "smear",
- "smell", "smelt", "smile", "smirk", "smite",
- "smith", "smock", "smoke", "smoky", "snack",
- "snail", "snake", "snaky", "snare", "snarl",
- "sneak", "sneer", "snide", "sniff", "snipe",
- "snoop", "snore", "snort", "snout", "snowy",
- "snuck", "snuff", "soapy", "sober", "solar",
- "solid", "solve", "sonic", "sooth", "sooty",
- "sorry", "sound", "south", "space", "spade",
- "spank", "spare", "spark", "spasm", "spawn",
- "speak", "spear", "speck", "speed", "spell",
- "spend", "spent", "spice", "spicy", "spied",
- "spike", "spiky", "spill", "spine", "spite",
- "splat", "split", "spoil", "spoke", "spoof",
- "spook", "spool", "spoon", "spore", "sport",
- "spout", "spray", "spree", "sprig", "spunk",
- "spurn", "squad", "squat", "squid", "stack",
- "staff", "stage", "staid", "stain", "stair",
- "stake", "stale", "stalk", "stall", "stamp",
- "stand", "stank", "staph", "stare", "stark",
- "start", "stash", "state", "stave", "stead",
- "steak", "steal", "steam", "steel", "steep",
- "steer", "stern", "stick", "stiff", "still",
- "stilt", "sting", "stink", "stint", "stock",
- "stoic", "stoke", "stole", "stomp", "stone",
- "stony", "stood", "stool", "stoop", "store",
- "stork", "storm", "story", "stout", "stove",
- "strap", "straw", "stray", "strip", "strew",
- "stuck", "study", "stuff", "stump", "stung",
- "stunk", "stunt", "style", "suave", "sugar",
- "suing", "suite", "sulky", "sunny", "super",
- "surge", "surly", "sushi", "swamp", "swarm",
- "swath", "swear", "sweat", "sweep", "sweet",
- "swell", "swept", "swift", "swill", "swine",
- "swing", "swipe", "swirl", "swish", "swoon",
- "swoop", "sword", "swore", "sworn", "swung",
- "synod", "syrup", "tabby", "table", "taboo",
- "tacit", "tacky", "taffy", "taint", "taken",
- "taker", "talon", "tamer", "tango", "tangy",
- "taper", "tapir", "tardy", "tarot", "taste",
- "tasty", "tatty", "taunt", "tawny", "teach",
- "teary", "tease", "teddy", "teeth", "tempo",
- "tenet", "tenor", "tense", "tenth", "tepee",
- "tepid", "terra", "terse", "theft", "their",
- "theme", "there", "thick", "thief", "thigh",
- "thing", "think", "third", "thorn", "those",
- "three", "threw", "throb", "throw", "thrum",
- "thumb", "thump", "thyme", "tiara", "tidal",
- "tiger", "tight", "timer", "timid", "tipsy",
- "titan", "title", "toast", "today", "token",
- "tonal", "tongs", "tonic", "tooth", "topaz",
- "topic", "torch", "torso", "total", "totem",
- "touch", "tough", "towel", "tower", "toxic",
- "trace", "track", "trade", "trail", "train",
- "trait", "tramp", "trash", "trawl", "tread",
- "treat", "trend", "triad", "trial", "tribe",
- "trick", "tried", "trill", "trite", "troll",
- "troop", "trope", "troth", "trout", "truce",
- "truck", "truly", "trump", "trunk", "truss",
- "trust", "truth", "tryst", "tulip", "tumor",
- "tuner", "tunic", "turbo", "tutor", "twain",
- "twang", "tweak", "tweed", "tweet", "twice",
- "twill", "twine", "twist", "tying", "udder",
- "ulcer", "ultra", "umbra", "uncle", "uncut",
- "under", "undid", "undue", "unfed", "unfit",
- "unify", "union", "unite", "unity", "unlit",
- "unmet", "unset", "untie", "until", "unwed",
- "unzip", "upper", "upset", "urban", "usage",
- "usher", "using", "usual", "usurp", "utero",
- "utter", "vague", "valid", "valor", "valve",
- "vapid", "vault", "vaunt", "vegan", "venue",
- "verge", "verse", "vigor", "villa", "vinyl",
- "viola", "viper", "viral", "virus", "visor",
- "vista", "vital", "vivid", "vixen", "vocal",
- "vodka", "vogue", "voice", "voila", "voter",
- "vouch", "vowel", "vulva", "wacky", "wafer",
- "wager", "wagon", "waist", "waltz", "watch",
- "water", "waver", "waxen", "weary", "weave",
- "wedge", "weedy", "weigh", "weird", "welch",
- "whale", "wheat", "wheel", "where", "which",
- "while", "whiff", "whine", "whiny", "whirl",
- "whisk", "white", "whole", "whose", "widen",
- "wider", "widow", "width", "wield", "wince",
- "winch", "windy", "wiper", "wiser", "witch",
- "witty", "woken", "woman", "women", "world",
- "worry", "worse", "worst", "worth", "would",
- "wound", "wrack", "wrath", "wreak", "wreck",
- "wrest", "wring", "wrist", "write", "wrong",
- "wrote", "yacht", "yearn", "yeast", "yield",
- "young", "youth", "zebra", "zesty", "zonal",
-]
diff --git a/wordlist_valid.py b/wordlist_valid.py
deleted file mode 100644
index 52366cf..0000000
--- a/wordlist_valid.py
+++ /dev/null
@@ -1,2568 +0,0 @@
-# Extended list of valid five-letter words accepted as Wordle guesses.
-# These words are never selected as the daily answer.
-VALID_GUESSES = [
- "aahed", "aalii", "aapas", "aargh", "aarti",
- "abaca", "abaci", "abacs", "abaft", "abaht",
- "abaka", "abamp", "aband", "abash", "abask",
- "abaya", "abbas", "abbed", "abbes", "abcee",
- "abeam", "abear", "abeat", "abeer", "abele",
- "abeng", "abers", "abets", "abeys", "abies",
- "abius", "abjad", "abjud", "abler", "ables",
- "ablet", "ablow", "abmho", "abnet", "abohm",
- "aboil", "aboma", "aboon", "abord", "abore",
- "aborn", "abram", "abray", "abrim", "abrin",
- "abris", "absey", "absit", "abuna", "abune",
- "abura", "aburn", "abuts", "abuzz", "abyes",
- "abysm", "acais", "acara", "acari", "accas",
- "accha", "accoy", "accra", "acedy", "acene",
- "acerb", "acers", "aceta", "achar", "ached",
- "acher", "aches", "achey", "achoo", "acids",
- "acies", "acing", "acini", "ackee", "acker",
- "acmes", "acmic", "acned", "acnes", "acock",
- "acoel", "acold", "acone", "acral", "acred",
- "acres", "acron", "acros", "acryl", "actas",
- "acted", "actin", "acton", "actus", "acyls",
- "adats", "adawn", "adaws", "adays", "adbot",
- "addas", "addax", "added", "adder", "addin",
- "addio", "addle", "addra", "adead", "adeem",
- "adhan", "adhoc", "adieu", "adios", "adits",
- "adlib", "adman", "admen", "admix", "adnex",
- "adobo", "adoon", "adorb", "adown", "adoze",
- "adrad", "adraw", "adred", "adret", "adrip",
- "adsum", "aduki", "adunc", "adust", "advew",
- "advts", "adyta", "adyts", "adzed", "adzes",
- "aecia", "aedes", "aeger", "aeons", "aerie",
- "aeros", "aesir", "aevum", "afald", "afanc",
- "afara", "afars", "afear", "affix", "affly",
- "afion", "afire", "afizz", "aflaj", "aflap",
- "aflow", "afoam", "afore", "afret", "afrit",
- "afros", "aftos", "agals", "agama", "agami",
- "agamy", "agape", "agars", "agasp", "agast",
- "agate", "agaty", "agave", "agaze", "agbas",
- "agene", "agers", "aggag", "agger", "aggie",
- "aggri", "aggro", "aggry", "aghas", "agidi",
- "agila", "agios", "agism", "agist", "agita",
- "aglee", "aglet", "agley", "agloo", "aglus",
- "agmas", "agoge", "agogo", "agone", "agons",
- "agood", "agora", "agria", "agrin", "agros",
- "agrum", "agued", "agues", "aguey", "aguna",
- "agush", "aguti", "aheap", "ahent", "ahigh",
- "ahind", "ahing", "ahint", "ahold", "ahole",
- "ahull", "ahuru", "aidas", "aided", "aides",
- "aidoi", "aidos", "aiery", "aigas", "aight",
- "ailed", "aimag", "aimak", "aimed", "aimer",
- "ainee", "ainga", "aioli", "aired", "airer",
- "airns", "airth", "airts", "aitch", "aitus",
- "aiver", "aixes", "aiyah", "aiyee", "aiyoh",
- "aiyoo", "aizle", "ajies", "ajiva", "ajuga",
- "ajupa", "ajwan", "akara", "akees", "akela",
- "akene", "aking", "akita", "akkas", "akker",
- "akoia", "akoja", "akoya", "aksed", "akses",
- "alaap", "alack", "alala", "alamo", "aland",
- "alane", "alang", "alans", "alant", "alapa",
- "alaps", "alary", "alata", "alate", "alays",
- "albas", "albee", "albid", "alcea", "alces",
- "alcid", "alcos", "aldea", "alder", "aldol",
- "aleak", "aleck", "alecs", "aleem", "alefs",
- "aleft", "aleph", "alews", "aleye", "alfas",
- "algal", "algas", "algid", "algin", "algor",
- "algos", "algum", "alias", "alick", "alifs",
- "alims", "aline", "alios", "alist", "aliya",
- "alkie", "alkin", "alkos", "alkyd", "alkyl",
- "allan", "allee", "allel", "allen", "aller",
- "allin", "allis", "allod", "allus", "allyl",
- "almah", "almas", "almeh", "almes", "almud",
- "almug", "alods", "aloed", "aloes", "aloha",
- "aloin", "aloos", "alose", "alowe", "altho",
- "altos", "alula", "alums", "alumy", "alure",
- "alurk", "alvar", "alway", "amahs", "amain",
- "amari", "amaro", "amate", "amaut", "amban",
- "ambit", "ambos", "ambry", "ameba", "ameer",
- "amene", "amens", "ament", "amias", "amice",
- "amici", "amide", "amido", "amids", "amies",
- "amiga", "amigo", "amins", "amirs", "amlas",
- "amman", "ammas", "ammon", "ammos", "amnia",
- "amnic", "amnio", "amoks", "amole", "amore",
- "amort", "amour", "amove", "amowt", "amped",
- "ampul", "amrit", "amuck", "amyls", "anana",
- "anata", "ancho", "ancle", "ancon", "andic",
- "andro", "anear", "anele", "anent", "angas",
- "anglo", "anigh", "anile", "anils", "anima",
- "animi", "anion", "anise", "anker", "ankhs",
- "ankus", "anlas", "annal", "annan", "annas",
- "annat", "annum", "annus", "anoas", "anole",
- "anomy", "ansae", "ansas", "antae", "antar",
- "antas", "anted", "antes", "antis", "antra",
- "antre", "antsy", "anura", "anyon", "apace",
- "apage", "apaid", "apayd", "apays", "apeak",
- "apeek", "apers", "apert", "apery", "apgar",
- "aphis", "apian", "apiol", "apish", "apism",
- "apode", "apods", "apols", "apoop", "aport",
- "appal", "appam", "appay", "appel", "appro",
- "appts", "appui", "appuy", "apres", "apses",
- "apsis", "apsos", "apted", "apter", "aquae",
- "aquas", "araba", "araks", "arame", "arars",
- "arbah", "arbas", "arced", "archi", "arcos",
- "arcus", "ardeb", "ardri", "aread", "areae",
- "areal", "arear", "areas", "areca", "aredd",
- "arede", "arefy", "areic", "arene", "arepa",
- "arere", "arete", "arets", "arett", "argal",
- "argan", "argil", "argle", "argol", "argon",
- "argot", "argus", "arhat", "arias", "ariel",
- "ariki", "arils", "ariot", "arish", "arith",
- "arked", "arled", "arles", "armed", "armer",
- "armet", "armil", "arnas", "arnis", "arnut",
- "aroba", "aroha", "aroid", "arpas", "arpen",
- "arrah", "arras", "arret", "arris", "arroz",
- "arsed", "arses", "arsey", "arsis", "artal",
- "artel", "arter", "artic", "artis", "artly",
- "aruhe", "arums", "arval", "arvee", "arvos",
- "aryls", "asada", "asana", "ascon", "ascus",
- "asdic", "ashed", "ashes", "ashet", "asity",
- "askar", "asked", "asker", "askoi", "askos",
- "aspen", "asper", "aspic", "aspie", "aspis",
- "aspro", "assai", "assam", "assed", "asses",
- "assez", "assot", "aster", "astir", "astun",
- "asura", "asway", "aswim", "asyla", "ataps",
- "ataxy", "atigi", "atilt", "atimy", "atlas",
- "atman", "atmas", "atmos", "atocs", "atoke",
- "atoks", "atoms", "atomy", "atony", "atopy",
- "atria", "atrip", "attap", "attar", "attas",
- "atter", "atuas", "aucht", "audad", "audax",
- "augen", "auger", "auges", "aught", "aulas",
- "aulic", "auloi", "aulos", "aumil", "aunes",
- "aunts", "aurae", "aural", "aurar", "auras",
- "aurei", "aures", "auric", "auris", "aurum",
- "autos", "auxin", "avail", "avale", "avant",
- "avast", "avels", "avens", "avers", "avert",
- "avgas", "avine", "avion", "avise", "aviso",
- "avize", "avows", "avyze", "awari", "awarn",
- "awato", "awave", "aways", "awdls", "aweel",
- "aweto", "awing", "awkin", "awmry", "awned",
- "awner", "awols", "awork", "axels", "axile",
- "axils", "axing", "axion", "axite", "axled",
- "axles", "axman", "axmen", "axoid", "axone",
- "axons", "ayahs", "ayaya", "ayelp", "aygre",
- "ayins", "aymag", "ayont", "ayres", "ayrie",
- "azans", "azide", "azido", "azine", "azlon",
- "azoic", "azole", "azons", "azote", "azoth",
- "azuki", "azurn", "azury", "azygy", "azyme",
- "azyms", "baaed", "baals", "baaps", "babas",
- "babby", "babel", "babes", "babka", "baboo",
- "babul", "babus", "bacca", "bacco", "baccy",
- "bacha", "bachs", "backs", "backy", "bacne",
- "badam", "baddy", "baels", "baffs", "baffy",
- "bafta", "bafts", "baghs", "bagie", "bagsy",
- "bagua", "bahts", "bahus", "bahut", "baiks",
- "baile", "bails", "bairn", "baisa", "baith",
- "baits", "baiza", "baize", "bajan", "bajra",
- "bajri", "bajus", "baked", "baken", "bakes",
- "bakra", "balas", "balds", "baldy", "baled",
- "baler", "bales", "balks", "balky", "ballo",
- "balls", "bally", "balms", "baloi", "balon",
- "baloo", "balot", "balsa", "balti", "balun",
- "balus", "balut", "bamas", "bambi", "bamma",
- "bammy", "banak", "banco", "bancs", "banda",
- "bandh", "bands", "bandy", "baned", "banes",
- "bangs", "bania", "banks", "banky", "banns",
- "bants", "bantu", "banty", "bantz", "banya",
- "baons", "baozi", "bappu", "bapus", "barbe",
- "barbs", "barby", "barca", "barde", "bardo",
- "bards", "bardy", "bared", "barer", "bares",
- "barfi", "barfs", "barfy", "baric", "barks",
- "barky", "barms", "barmy", "barns", "barny",
- "barps", "barra", "barre", "barro", "barry",
- "barye", "basan", "basas", "based", "basen",
- "baser", "bases", "basha", "basho", "basij",
- "basks", "bason", "basse", "bassi", "basso",
- "bassy", "basta", "basti", "basto", "basts",
- "bated", "bates", "baths", "batik", "batos",
- "batta", "batts", "battu", "bauds", "bauks",
- "baulk", "baurs", "bavin", "bawds", "bawks",
- "bawls", "bawns", "bawrs", "bawty", "bayas",
- "bayed", "bayer", "bayes", "bayle", "bayts",
- "bazar", "bazas", "bazoo", "bball", "bdays",
- "beads", "beaks", "beaky", "beals", "beams",
- "beamy", "beano", "beans", "beany", "beare",
- "bears", "beath", "beats", "beaty", "beaus",
- "beaut", "beaux", "bebop", "becap", "becke",
- "becks", "bedad", "bedel", "bedes", "bedew",
- "bedim", "bedye", "beedi", "beefs", "beeps",
- "beers", "beery", "beets", "befog", "begad",
- "begar", "begem", "begob", "begot", "begum",
- "beige", "beigy", "beins", "beira", "beisa",
- "bekah", "belah", "belar", "belay", "belee",
- "belga", "belie", "belit", "belle", "belli",
- "bello", "bells", "belon", "belts", "belve",
- "bemad", "bemas", "bemix", "bemud", "bends",
- "bendy", "benes", "benet", "benga", "benis",
- "benji", "benne", "benni", "benny", "bento",
- "bents", "benty", "bepat", "beray", "beres",
- "bergs", "berko", "berks", "berme", "berms",
- "berob", "beryl", "besat", "besaw", "besee",
- "beses", "besit", "besom", "besot", "besti",
- "bests", "betas", "beted", "betes", "beths",
- "betid", "beton", "betta", "betty", "bevan",
- "bever", "bevor", "bevue", "bevvy", "bewdy",
- "bewet", "bewig", "bezel", "bezes", "bezil",
- "bezzy", "bhais", "bhaji", "bhang", "bhats",
- "bhava", "bhels", "bhoot", "bhuna", "bhuts",
- "biach", "biali", "bialy", "bibbs", "bibes",
- "bibis", "biccy", "bices", "bicky", "bided",
- "bider", "bides", "bidet", "bidis", "bidon",
- "bidri", "bield", "biers", "biffo", "biffs",
- "biffy", "bifid", "bigae", "biggs", "biggy",
- "bigha", "bight", "bigly", "bigos", "bihon",
- "bijou", "biked", "biker", "bikes", "bikie",
- "bikky", "bilal", "bilat", "bilbo", "bilby",
- "biled", "biles", "bilgy", "bilks", "bills",
- "bimah", "bimas", "bimbo", "binal", "bindi",
- "binds", "biner", "bines", "bings", "bingy",
- "binit", "binks", "binky", "bints", "biogs",
- "bions", "biont", "biose", "biota", "biped",
- "bipod", "bippy", "birdo", "birds", "biris",
- "birks", "birle", "birls", "biros", "birrs",
- "birse", "birsy", "birze", "birzz", "bises",
- "bisks", "bisom", "bison", "bitch", "biter",
- "bites", "bitey", "bitos", "bitou", "bitsy",
- "bitte", "bitts", "bitty", "bivia", "bivvy",
- "bizes", "bizzo", "bizzy", "blabs", "blads",
- "blady", "blaer", "blaes", "blaff", "blags",
- "blahs", "blain", "blams", "blanc", "blart",
- "blase", "blash", "blate", "blats", "blatt",
- "blaud", "blawn", "blaws", "blays", "bleah",
- "blear", "blebs", "blech", "bleep", "blees",
- "blent", "blert", "blest", "blets", "bleys",
- "blimy", "bling", "blink", "blins", "bliny",
- "blips", "blist", "blite", "blits", "blive",
- "blobs", "blocs", "blogs", "blonx", "blook",
- "bloop", "blore", "blots", "blows", "blowy",
- "blubs", "blude", "bluds", "bludy", "blued",
- "bluer", "bluet", "bluey", "bluid", "blume",
- "blunk", "blurs", "blype", "boabs", "boaks",
- "boars", "boart", "boats", "boaty", "bobac",
- "bobak", "bobas", "bobol", "bobos", "bocca",
- "bocce", "bocci", "boche", "bocks", "boded",
- "bodes", "bodge", "bodgy", "bodhi", "bodle",
- "bodoh", "boeps", "boers", "boeti", "boets",
- "boeuf", "boffo", "boffs", "bogan", "bogey",
- "boggy", "bogie", "bogle", "bogue", "bogus",
- "bohea", "bohos", "boils", "boing", "boink",
- "boite", "boked", "bokeh", "bokes", "bokos",
- "bolar", "bolas", "boldo", "bolds", "boles",
- "bolet", "bolix", "bolks", "bolls", "bolos",
- "bolts", "bolus", "bomas", "bombe", "bombo",
- "bombs", "bomoh", "bomor", "bonce", "bonds",
- "boned", "boner", "bones", "bongo", "bongs",
- "bonie", "bonks", "bonne", "bonny", "bonum",
- "bonza", "bonze", "booai", "booay", "boobs",
- "boody", "booed", "boofy", "boogy", "boohs",
- "books", "booky", "bools", "booms", "boomy",
- "boong", "boons", "boord", "boors", "boose",
- "boots", "boppy", "borak", "boral", "boras",
- "borde", "bords", "bored", "boree", "borek",
- "borel", "borer", "bores", "borgo", "boric",
- "borks", "borms", "borna", "boron", "borts",
- "borty", "bortz", "bosey", "bosie", "bosks",
- "bosky", "boson", "bossa", "bosun", "botas",
- "boteh", "botel", "botes", "botew", "bothy",
- "botos", "botte", "botts", "botty", "bouge",
- "bough", "bouks", "boule", "boult", "bouns",
- "bourd", "bourg", "bourn", "bouse", "bousy",
- "bouts", "boutu", "bovid", "bowat", "bowed",
- "bower", "bowes", "bowet", "bowie", "bowls",
- "bowne", "bowrs", "bowse", "boxed", "boxen",
- "boxes", "boxla", "boxty", "boyar", "boyau",
- "boyed", "boyey", "boyfs", "boygs", "boyla",
- "boyly", "boyos", "boysy", "bozos", "braai",
- "brach", "brack", "bract", "brads", "braes",
- "brags", "brahs", "brail", "braks", "braky",
- "brame", "brane", "brank", "brans", "brant",
- "brast", "brats", "brava", "bravi", "braws",
- "braxy", "brays", "braza", "braze", "bream",
- "brede", "breds", "breem", "breer", "brees",
- "breid", "breis", "breme", "brens", "brent",
- "brere", "brers", "breve", "brews", "breys",
- "brier", "bries", "brigs", "briki", "briks",
- "brill", "brims", "brins", "brios", "brise",
- "briss", "brith", "brits", "britt", "brize",
- "broch", "brock", "brods", "brogh", "brogs",
- "brome", "bromo", "bronc", "brond", "brool",
- "broom", "broos", "brose", "brosy", "brows",
- "bruck", "brugh", "bruhs", "bruin", "bruit",
- "bruja", "brujo", "brule", "brume", "brung",
- "brusk", "brust", "bruts", "bruvs", "buats",
- "buaze", "bubal", "bubas", "bubba", "bubbe",
- "bubby", "bubus", "buchu", "bucko", "bucks",
- "bucku", "budas", "buded", "budes", "budis",
- "budos", "buena", "buffa", "buffe", "buffi",
- "buffo", "buffs", "buffy", "bufos", "bufty",
- "bugan", "buhls", "buhrs", "buiks", "buist",
- "bukes", "bukos", "bulbs", "bulgy", "bulks",
- "bulla", "bulls", "bulse", "bumbo", "bumfs",
- "bumph", "bumps", "bumpy", "bunas", "bunce",
- "bunco", "bunde", "bundh", "bunds", "bundt",
- "bundu", "bundy", "bungs", "bungy", "bunia",
- "bunje", "bunjy", "bunko", "bunks", "bunns",
- "bunts", "bunty", "bunya", "buoys", "buppy",
- "buran", "buras", "burbs", "burds", "buret",
- "burfi", "burgh", "burgs", "burin", "burka",
- "burke", "burks", "burls", "burns", "buroo",
- "burps", "burqa", "burra", "burro", "burrs",
- "burry", "bursa", "burse", "busby", "bused",
- "buses", "busks", "busky", "bussu", "busti",
- "busts", "busty", "buteo", "butes", "butle",
- "butoh", "butts", "butty", "butut", "butyl",
- "buxom", "buyin", "buzzy", "bwana", "bwazi",
- "byded", "bydes", "byked", "bykes", "byres",
- "byrls", "byssi", "bytes", "byway", "caaed",
- "cabas", "cabby", "caber", "cabob", "caboc",
- "cabre", "cacao", "cacas", "cache", "cacks",
- "cacky", "cacti", "caddy", "cadee", "cades",
- "cadge", "cadgy", "cadie", "cadis", "cadre",
- "caeca", "caese", "cafes", "caffe", "caffs",
- "caged", "cager", "cages", "cagey", "cagot",
- "cahow", "caids", "cains", "caird", "cairn",
- "cajon", "cajun", "caked", "cakes", "cakey",
- "calfs", "calid", "calif", "calix", "calks",
- "calla", "calle", "calls", "calms", "calmy",
- "calos", "calpa", "calps", "calve", "calyx",
- "caman", "camas", "cames", "camis", "camos",
- "campi", "campo", "camps", "campy", "camus",
- "cando", "caned", "caneh", "caner", "canes",
- "cangs", "canid", "canna", "canns", "canon",
- "canso", "canst", "canti", "canto", "cants",
- "canty", "capas", "capax", "caped", "capes",
- "capex", "caphs", "capiz", "caple", "capon",
- "capos", "capot", "capri", "capul", "carap",
- "carbo", "carbs", "carby", "cardi", "cards",
- "cardy", "cared", "carer", "cares", "caret",
- "carex", "carks", "carle", "carls", "carne",
- "carns", "carny", "carob", "carom", "caron",
- "carpe", "carpi", "carps", "carrs", "carse",
- "carta", "carte", "carts", "carvy", "casas",
- "casco", "cased", "caser", "cases", "casks",
- "casky", "casts", "casus", "cates", "catty",
- "cauda", "cauks", "cauld", "caulk", "cauls",
- "caums", "caups", "cauri", "causa", "cavas",
- "caved", "cavel", "caver", "caves", "cavie",
- "cavus", "cawed", "cawks", "caxon", "ceaze",
- "cebid", "cecal", "cecum", "ceded", "ceder",
- "cedes", "cedis", "ceiba", "ceili", "ceils",
- "celeb", "cella", "celli", "cello", "cells",
- "celly", "celom", "celts", "cense", "cento",
- "cents", "centu", "ceorl", "cepes", "cerci",
- "cered", "ceres", "cerge", "ceria", "ceric",
- "cerne", "ceroc", "ceros", "certs", "certy",
- "cesse", "cesta", "cesti", "cetes", "cetyl",
- "cezve", "chaap", "chaat", "chace", "chack",
- "chaco", "chado", "chads", "chafe", "chaff",
- "chaft", "chais", "chals", "chams", "chana",
- "chang", "chank", "chape", "chaps", "chapt",
- "chara", "chare", "chark", "charr", "chars",
- "chary", "chats", "chava", "chave", "chavs",
- "chawk", "chawl", "chaws", "chaya", "chays",
- "cheba", "chedi", "cheeb", "cheep", "cheet",
- "chefs", "cheka", "chela", "chelp", "chemo",
- "chems", "chere", "chert", "cheth", "chevy",
- "chews", "chewy", "chiao", "chias", "chiba",
- "chibs", "chica", "chich", "chico", "chics",
- "chiel", "chiko", "chiks", "chile", "chimb",
- "chimo", "chimp", "chine", "ching", "chink",
- "chino", "chins", "chips", "chirk", "chirl",
- "chirm", "chiro", "chirr", "chirt", "chiru",
- "chiti", "chits", "chiva", "chive", "chivs",
- "chivy", "chizz", "choco", "chocs", "chode",
- "chogs", "choil", "choko", "choky", "chola",
- "choli", "cholo", "chomp", "chons", "choof",
- "chook", "choom", "choon", "chops", "choss",
- "chota", "chott", "chout", "choux", "chowk",
- "chows", "chubs", "chufa", "chuff", "chugs",
- "chums", "churl", "churr", "chuse", "chuts",
- "chyle", "chyme", "chynd", "cibol", "cided",
- "cides", "ciels", "ciggy", "cilia", "cills",
- "cimar", "cimex", "cinct", "cines", "cinqs",
- "cions", "cippi", "circa", "circs", "cires",
- "cirls", "cirri", "cisco", "cissy", "cists",
- "cital", "cited", "citee", "citer", "cites",
- "cives", "civet", "civie", "civvy", "clach",
- "clack", "clade", "clads", "claes", "clags",
- "clair", "clame", "clams", "clans", "claps",
- "clapt", "claro", "clart", "clary", "clast",
- "clats", "claut", "clave", "clavi", "claws",
- "clays", "cleat", "cleck", "cleek", "cleep",
- "clefs", "cleft", "clegs", "cleik", "clems",
- "clepe", "clept", "cleve", "clews", "clied",
- "clies", "clift", "clime", "cline", "clink",
- "clint", "clipe", "clips", "clipt", "clits",
- "cloam", "clods", "cloff", "clogs", "cloke",
- "clomb", "clomp", "clonk", "clons", "cloop",
- "cloot", "clops", "clote", "clots", "clour",
- "clous", "clove", "clows", "cloye", "cloys",
- "cloze", "clubs", "clues", "cluey", "clunk",
- "clype", "cnida", "coact", "coady", "coala",
- "coals", "coaly", "coapt", "coarb", "coate",
- "coati", "coats", "cobbs", "cobby", "cobia",
- "coble", "cobot", "cobza", "cocas", "cocci",
- "cocco", "cocks", "cocky", "cocos", "cocus",
- "codas", "codec", "coded", "coden", "coder",
- "codes", "codex", "codon", "coeds", "coffs",
- "cogie", "cogon", "cogue", "cohab", "cohen",
- "cohoe", "cohog", "cohos", "coifs", "coign",
- "coils", "coins", "coirs", "coits", "coked",
- "cokes", "cokey", "colas", "colby", "colds",
- "coled", "coles", "coley", "colic", "colin",
- "colle", "colls", "colly", "colog", "colts",
- "colza", "comae", "comal", "comas", "combe",
- "combi", "combo", "combs", "comby", "comer",
- "comes", "comfy", "comix", "comme", "commo",
- "comms", "commy", "compo", "comps", "compt",
- "comte", "comus", "coned", "cones", "conex",
- "confs", "conga", "conge", "congo", "conia",
- "conic", "conin", "conks", "conky", "conne",
- "conns", "conte", "conto", "conus", "convo",
- "cooch", "cooed", "cooee", "cooer", "cooey",
- "coofs", "cooks", "cooky", "cools", "cooly",
- "coomb", "cooms", "coomy", "coons", "coops",
- "coopt", "coost", "coots", "cooty", "cooze",
- "copal", "copay", "coped", "copen", "coper",
- "copes", "copha", "coppy", "copra", "copse",
- "copsy", "coqui", "coram", "corbe", "corby",
- "corda", "cords", "cored", "corer", "cores",
- "corey", "corgi", "coria", "corks", "corky",
- "corms", "corni", "corno", "corns", "cornu",
- "corps", "corse", "corso", "cosec", "cosed",
- "coses", "coset", "cosey", "cosie", "costa",
- "coste", "costs", "cotan", "cotch", "coted",
- "cotes", "coths", "cotta", "cotts", "coude",
- "cough", "coups", "courb", "courd", "coure",
- "cours", "couta", "couth", "coved", "coven",
- "coves", "covey", "covin", "cowal", "cowan",
- "cowed", "cower", "cowks", "cowls", "cowps",
- "cowry", "coxae", "coxal", "coxed", "coxes",
- "coxib", "coyau", "coyed", "coyer", "coyly",
- "coypu", "cozed", "cozen", "cozes", "cozey",
- "cozie", "craal", "crabs", "crags", "craic",
- "craig", "crake", "crame", "crams", "crans",
- "crape", "craps", "crapy", "crare", "craws",
- "crays", "creds", "creel", "crees", "crein",
- "crema", "creme", "crems", "crena", "crepe",
- "creps", "crept", "crepy", "cress", "crewe",
- "crews", "crias", "cribo", "cribs", "crier",
- "cries", "crims", "crine", "crink", "crins",
- "crios", "cripe", "crips", "crise", "criss",
- "crith", "crits", "croci", "crocs", "croft",
- "crogs", "cromb", "crome", "cronk", "crons",
- "crool", "croon", "crops", "crore", "crost",
- "croup", "crout", "crowl", "crows", "croze",
- "cruck", "crudo", "cruds", "crudy", "crues",
- "cruet", "cruft", "crumb", "crump", "crunk",
- "cruor", "crura", "cruse", "crusy", "cruve",
- "crwth", "cryer", "cryne", "ctene", "cubby",
- "cubeb", "cubed", "cuber", "cubes", "cubit",
- "cucks", "cudda", "cuddy", "cueca", "cuffo",
- "cuffs", "cuifs", "cuing", "cuish", "cuits",
- "cukes", "culch", "culet", "culex", "culls",
- "cully", "culms", "culpa", "culti", "cults",
- "culty", "cumec", "cundy", "cunei", "cunit",
- "cunny", "cunts", "cupel", "cuppa", "cuppy",
- "cupro", "curat", "curbs", "curch", "curds",
- "curdy", "cured", "curer", "cures", "curet",
- "curfs", "curia", "curie", "curio", "curli",
- "curls", "curns", "curny", "currs", "cursi",
- "curst", "cusec", "cushy", "cusks", "cusps",
- "cuspy", "cusso", "cusum", "cutch", "cuter",
- "cutes", "cutey", "cutin", "cutis", "cutto",
- "cutty", "cutup", "cuvee", "cuzes", "cwtch",
- "cyano", "cyans", "cyber", "cycad", "cycas",
- "cyclo", "cyder", "cylix", "cymae", "cymar",
- "cymas", "cymes", "cymol", "cysts", "cytes",
- "cyton", "czars", "daals", "dabba", "daces",
- "dacha", "dacks", "dadah", "dadas", "dadis",
- "dadla", "dados", "daffs", "daffy", "dagga",
- "daggy", "dagos", "dahis", "dahls", "daiko",
- "daine", "daint", "daker", "daled", "dalek",
- "dales", "dalis", "dalle", "dalts", "daman",
- "damar", "dames", "damme", "damna", "damns",
- "damps", "dampy", "dancy", "danda", "dangs",
- "danio", "danks", "danny", "danse", "dants",
- "dappy", "daraf", "darbs", "darcy", "dared",
- "darer", "dares", "darga", "dargs", "daric",
- "daris", "darks", "darky", "darls", "darns",
- "darre", "darts", "darzi", "dashi", "dashy",
- "datal", "dated", "dater", "dates", "datil",
- "datos", "datto", "daube", "daubs", "dauby",
- "dauds", "dault", "daurs", "dauts", "daven",
- "davit", "dawah", "dawds", "dawed", "dawen",
- "dawgs", "dawks", "dawns", "dawts", "dayal",
- "dayan", "daych", "daynt", "dazed", "dazer",
- "dazes", "dbags", "deads", "deair", "deals",
- "deans", "deare", "dearn", "dears", "deary",
- "deash", "deave", "deaws", "deawy", "debag",
- "debar", "debby", "debel", "debes", "debit",
- "debts", "debud", "debug", "debur", "debus",
- "debye", "decad", "decaf", "decan", "decim",
- "decko", "decks", "decos", "decyl", "dedal",
- "deeds", "deedy", "deely", "deems", "deens",
- "deeps", "deere", "deers", "deets", "deeve",
- "deevs", "defat", "deffo", "defis", "defog",
- "degas", "degum", "degus", "deice", "deids",
- "deify", "deils", "deink", "deism", "deist",
- "deked", "dekes", "dekko", "deled", "deles",
- "delfs", "delft", "delis", "della", "dells",
- "delly", "delos", "delph", "delts", "deman",
- "demes", "demic", "demit", "demob", "demoi",
- "demos", "demot", "dempt", "denar", "denay",
- "dench", "denes", "denet", "denis", "dente",
- "dents", "deoch", "deoxy", "derat", "deray",
- "dered", "deres", "derig", "derma", "derms",
- "derns", "derny", "deros", "derpy", "derro",
- "derry", "derth", "dervs", "desex", "deshi",
- "desis", "desks", "desse", "detag", "devas",
- "devel", "devis", "devon", "devos", "devot",
- "dewan", "dewar", "dewax", "dewed", "dexes",
- "dexie", "dexys", "dhaba", "dhaks", "dhals",
- "dhikr", "dhobi", "dhole", "dholl", "dhols",
- "dhoni", "dhoti", "dhows", "dhuti", "diact",
- "dials", "diana", "diane", "diazo", "dibbs",
- "diced", "dicer", "dices", "dicht", "dicks",
- "dicky", "dicot", "dicta", "dicto", "dicts",
- "dictu", "dicty", "diddy", "didie", "didis",
- "didos", "didst", "diebs", "diels", "diene",
- "diets", "diffs", "dight", "dikas", "diked",
- "diker", "dikes", "dikey", "dildo", "dilli",
- "dills", "dimbo", "dimer", "dimes", "dimps",
- "dinar", "dined", "dines", "dinge", "dings",
- "dinic", "dinks", "dinky", "dinlo", "dinna",
- "dinos", "dints", "dioch", "diols", "diota",
- "dippy", "dipso", "diram", "direr", "dirke",
- "dirks", "dirls", "dirts", "disas", "disci",
- "discs", "dishy", "disks", "disme", "dital",
- "ditas", "dited", "dites", "ditsy", "ditts",
- "ditzy", "divan", "divas", "dived", "dives",
- "divey", "divis", "divna", "divos", "divot",
- "divvy", "diwan", "dixie", "dixit", "diyas",
- "dizen", "djinn", "djins", "doabs", "doats",
- "dobby", "dobes", "dobie", "dobla", "doble",
- "dobra", "dobro", "docht", "docks", "docos",
- "docus", "doddy", "dodos", "doeks", "doers",
- "doest", "doeth", "doffs", "dogal", "dogan",
- "doges", "dogey", "doggo", "doggy", "dogie",
- "dogly", "dohyo", "doilt", "doily", "doits",
- "dojos", "dolce", "dolci", "doled", "dolee",
- "doles", "doley", "dolia", "dolie", "dolls",
- "dolma", "dolor", "dolos", "dolts", "domal",
- "domed", "domes", "domic", "donah", "donas",
- "donee", "doner", "donga", "dongs", "donko",
- "donna", "donne", "donny", "donsy", "doobs",
- "dooce", "doody", "doofs", "dooks", "dooky",
- "doole", "dools", "dooly", "dooms", "doomy",
- "doona", "doorn", "doors", "doozy", "dopas",
- "doped", "doper", "dopes", "doppe", "dorad",
- "dorba", "dorbs", "doree", "dores", "doric",
- "doris", "dorje", "dorks", "dorky", "dorms",
- "dormy", "dorps", "dorrs", "dorsa", "dorse",
- "dorts", "dorty", "dosai", "dosas", "dosed",
- "doseh", "doser", "doses", "dosha", "dotal",
- "doted", "doter", "dotes", "dotty", "douar",
- "douce", "doucs", "douks", "doula", "douma",
- "doums", "doups", "doura", "douse", "douts",
- "doved", "doven", "dover", "doves", "dovie",
- "dowak", "dowar", "dowds", "dowed", "dower",
- "dowfs", "dowie", "dowle", "dowls", "dowly",
- "downa", "downs", "downy", "dowps", "dowry",
- "dowse", "dowts", "doxed", "doxes", "doxie",
- "doyen", "doyly", "dozed", "dozen", "dozer",
- "dozes", "drabs", "drack", "draco", "draff",
- "drags", "drail", "drams", "drant", "draps",
- "drapy", "drats", "drave", "draws", "drays",
- "drear", "dreck", "dreed", "dreer", "drees",
- "dregs", "dreks", "drent", "drere", "drest",
- "dreys", "dribs", "drice", "drier", "dries",
- "drily", "drips", "dript", "drock", "droid",
- "droil", "droke", "drole", "drome", "drony",
- "droob", "droog", "drook", "drops", "dropt",
- "drouk", "drows", "drubs", "druid", "drums",
- "drupe", "druse", "drusy", "druxy", "dryad",
- "dryas", "dsobo", "dsomo", "duads", "duals",
- "duans", "duars", "dubbo", "dubby", "ducal",
- "ducat", "duces", "ducks", "ducky", "ducti",
- "ducts", "duddy", "duded", "dudes", "duels",
- "duets", "duett", "duffs", "dufus", "duing",
- "duits", "dukas", "duked", "dukes", "dukka",
- "dukun", "dulce", "dules", "dulia", "dulls",
- "dulse", "dumas", "dumbo", "dumbs", "dumka",
- "dumky", "dumps", "dumpy", "dunam", "dunch",
- "dunes", "dungs", "dungy", "dunks", "dunno",
- "dunny", "dunsh", "dunts", "duomi", "duomo",
- "duped", "duper", "dupes", "duple", "duply",
- "duppy", "dural", "duras", "dured", "dures",
- "durgy", "durns", "duroc", "duros", "duroy",
- "durra", "durrs", "durry", "durst", "durum",
- "durzi", "dusks", "dusky", "dusts", "dutch",
- "duxes", "dwaal", "dwale", "dwalm", "dwams",
- "dwamy", "dwang", "dwaum", "dweeb", "dwile",
- "dwine", "dyads", "dyers", "dyked", "dykes",
- "dykey", "dykon", "dynel", "dynes", "dynos",
- "dzhos", "eagly", "eagre", "ealed", "eales",
- "eaned", "eards", "eared", "earls", "earns",
- "earnt", "earst", "eased", "easer", "eases",
- "easle", "easts", "eathe", "eatin", "eaved",
- "eaver", "eaves", "ebank", "ebbed", "ebbet",
- "ebena", "ebene", "ebike", "ebons", "ebook",
- "ecads", "ecard", "ecash", "eched", "eches",
- "echos", "ecigs", "ecole", "ecrus", "edema",
- "edged", "edger", "edges", "edile", "edits",
- "educe", "educt", "eejit", "eensy", "eeven",
- "eever", "eevns", "effed", "effer", "efits",
- "egads", "egers", "egest", "eggar", "egged",
- "egger", "egmas", "ehing", "eider", "eidos",
- "eigne", "eiked", "eikon", "eilds", "eiron",
- "eisel", "eject", "ejido", "ekdam", "eking",
- "ekkas", "elain", "eland", "elans", "elate",
- "elbow", "elchi", "eldin", "eleet", "elegy",
- "elemi", "elfed", "elfin", "eliad", "elide",
- "elint", "elmen", "eloge", "elogy", "eloin",
- "elops", "elpee", "elsin", "elute", "elvan",
- "elven", "elver", "elves", "emacs", "embar",
- "embay", "embed", "embog", "embow", "embox",
- "embus", "emeer", "emend", "emerg", "emery",
- "emeus", "emics", "emirs", "emits", "emmas",
- "emmer", "emmet", "emmew", "emmys", "emoji",
- "emong", "emote", "emove", "empts", "emule",
- "emure", "emyde", "emyds", "enarm", "enate",
- "ended", "ender", "endew", "endue", "enema",
- "enews", "enfix", "eniac", "enlit", "enmew",
- "ennog", "enoki", "enols", "enorm", "enows",
- "enrol", "ensew", "ensky", "entia", "entre",
- "enure", "enurn", "envoi", "enzym", "eolid",
- "eorls", "eosin", "epact", "epees", "epena",
- "epene", "ephah", "ephas", "ephod", "ephor",
- "epics", "epode", "epopt", "epoxy", "eppie",
- "epris", "eques", "equid", "erbia", "erect",
- "erevs", "ergon", "ergos", "ergot", "erhus",
- "erica", "erick", "erics", "ering", "erned",
- "ernes", "erose", "erred", "erses", "eruct",
- "erugo", "eruvs", "erven", "ervil", "escar",
- "escot", "esile", "eskar", "esker", "esnes",
- "esrog", "esses", "estoc", "estop", "estro",
- "etage", "etape", "etats", "etens", "ethal",
- "ethne", "ethyl", "etics", "etnas", "etrog",
- "ettin", "ettle", "etude", "etuis", "etwee",
- "etyma", "eughs", "euked", "eupad", "euros",
- "eusol", "evegs", "evens", "evert", "evets",
- "evhoe", "evils", "evite", "evohe", "ewers",
- "ewest", "ewhow", "ewked", "exams", "exeat",
- "execs", "exeem", "exeme", "exfil", "exier",
- "exies", "exine", "exing", "exite", "exits",
- "exode", "exome", "exons", "expos", "exuls",
- "exurb", "eyass", "eyers", "eying", "eyots",
- "eyras", "eyres", "eyrie", "eyrir", "ezine",
- "fabbo", "fabby", "faced", "facer", "faces",
- "facey", "facia", "facie", "facta", "facto",
- "facts", "facty", "faddy", "faded", "fader",
- "fades", "fadge", "fados", "faena", "faery",
- "faffs", "faffy", "faggy", "fagin", "fagot",
- "faiks", "fails", "faine", "fains", "faint",
- "faire", "fairs", "faked", "faker", "fakes",
- "fakey", "fakie", "fakir", "falaj", "fales",
- "falls", "falsy", "famed", "fames", "fanal",
- "fands", "fanes", "fanga", "fango", "fangs",
- "fanks", "fanon", "fanos", "fanum", "faqir",
- "farad", "farci", "farcy", "fards", "fared",
- "farer", "fares", "farle", "farls", "farms",
- "faros", "farro", "farse", "farts", "fasci",
- "fasti", "fasts", "fated", "fates", "fatly",
- "fatso", "fatwa", "fauch", "faugh", "fauld",
- "fauns", "faurd", "faute", "fauts", "fauve",
- "favas", "favel", "faver", "faves", "favor",
- "favus", "fawns", "fawny", "faxed", "faxes",
- "fayed", "fayer", "fayne", "fayre", "fazed",
- "fazes", "feals", "feard", "feare", "fears",
- "feart", "fease", "feats", "feaze", "fecal",
- "feces", "fecht", "fecit", "fecks", "fedai",
- "fedex", "feebs", "feeds", "feels", "feely",
- "feens", "feers", "feese", "feeze", "fehme",
- "feist", "felch", "felid", "felix", "fells",
- "felly", "felts", "felty", "femal", "femes",
- "femic", "femme", "femmy", "fends", "fendy",
- "fenis", "fenks", "fenny", "fents", "feods",
- "feoff", "ferer", "feres", "feria", "ferly",
- "fermi", "ferms", "ferns", "ferny", "ferox",
- "fesse", "festa", "fests", "festy", "fetas",
- "feted", "fetes", "fetor", "fetta", "fetts",
- "fetwa", "feuar", "feuds", "feued", "fewer",
- "feyed", "feyer", "feyly", "fezes", "fezzy",
- "fiars", "fiats", "fibro", "fices", "fiche",
- "fichu", "ficin", "ficos", "ficta", "ficus",
- "fides", "fidge", "fidos", "fidus", "fiefs",
- "fient", "fiere", "fieri", "fiers", "fiest",
- "fifed", "fifer", "fifes", "fifis", "figgy",
- "figos", "fiked", "fikes", "filar", "filch",
- "filed", "filer", "files", "filet", "filii",
- "filks", "fille", "fillo", "fills", "filmi",
- "films", "filon", "filos", "filum", "finca",
- "finds", "fined", "finer", "fines", "finis",
- "finks", "finny", "finos", "fiord", "fiqhs",
- "fique", "fired", "firer", "fires", "firie",
- "firks", "firma", "firms", "firni", "firns",
- "firry", "first", "firth", "fiscs", "fisho",
- "fisks", "fists", "fisty", "fitch", "fitly",
- "fitna", "fitte", "fitts", "fiver", "fives",
- "fixed", "fixes", "fixie", "fixit", "fjeld",
- "flabs", "flaff", "flags", "flaks", "flamm",
- "flams", "flamy", "flane", "flans", "flaps",
- "flary", "flats", "flava", "flawn", "flaws",
- "flawy", "flaxy", "flays", "fleam", "fleas",
- "fleck", "fleek", "fleer", "flees", "flegs",
- "fleme", "fleur", "flews", "flexi", "flexo",
- "fleys", "flics", "flied", "flies", "flimp",
- "flims", "flips", "flirs", "flisk", "flite",
- "flits", "flitt", "flobs", "flocs", "floes",
- "flogs", "flong", "flops", "flore", "flors",
- "flory", "flosh", "flota", "flote", "flows",
- "flowy", "flubs", "flued", "flues", "fluey",
- "fluff", "fluky", "flume", "flump", "fluor",
- "flurr", "fluty", "fluyt", "flyby", "flyer",
- "flyin", "flype", "flyte", "fnarr", "foals",
- "foams", "foehn", "fogey", "fogie", "fogle",
- "fogos", "fogou", "fohns", "foids", "foils",
- "foins", "foist", "folds", "foley", "folia",
- "folic", "folie", "folio", "folks", "folky",
- "fomes", "fonda", "fonds", "fondu", "fones",
- "fonio", "fonly", "fonts", "foods", "foody",
- "fools", "foots", "footy", "foram", "forbs",
- "forby", "fordo", "fords", "forel", "fores",
- "forex", "forks", "forky", "forma", "forme",
- "forms", "forts", "forza", "forze", "fossa",
- "fosse", "fouat", "fouds", "fouer", "fouet",
- "foule", "fouls", "fount", "fours", "fouth",
- "fovea", "fowls", "fowth", "foxed", "foxes",
- "foxie", "foyle", "foyne", "frabs", "frack",
- "fract", "frags", "fraim", "frais", "franc",
- "frape", "fraps", "frass", "frate", "frati",
- "frats", "fraus", "frays", "freer", "frees",
- "freet", "freit", "fremd", "frena", "freon",
- "frere", "frets", "fribs", "frier", "fries",
- "frigs", "frise", "frist", "frita", "frite",
- "frith", "frits", "fritt", "frize", "frizz",
- "froes", "frogs", "fromm", "frons", "froom",
- "frore", "frorn", "frory", "frosh", "froth",
- "frows", "frowy", "froyo", "frugs", "frush",
- "frust", "fryer", "fubar", "fubby", "fubsy",
- "fucks", "fucus", "fuddy", "fudge", "fudgy",
- "fuels", "fuero", "fuffs", "fuffy", "fugal",
- "fuggy", "fugie", "fugio", "fugis", "fugle",
- "fugly", "fugue", "fugus", "fujis", "fulla",
- "fulls", "fulth", "fulwa", "fumed", "fumer",
- "fumes", "fumet", "funda", "fundi", "fundo",
- "funds", "fundy", "fungo", "fungs", "funic",
- "funis", "funks", "funsy", "funts", "fural",
- "furan", "furca", "furls", "furol", "furor",
- "furos", "furrs", "furth", "furze", "furzy",
- "fused", "fusee", "fusel", "fuses", "fusil",
- "fusks", "fusts", "fusty", "futon", "fuzed",
- "fuzee", "fuzes", "fuzil", "fyces", "fyked",
- "fykes", "fyles", "fyrds", "fytte", "gabba",
- "gabby", "gable", "gaddi", "gades", "gadge",
- "gadgy", "gadid", "gadis", "gadje", "gadjo",
- "gadso", "gaffe", "gaffs", "gaged", "gager",
- "gages", "gaids", "gaily", "gains", "gairs",
- "gaita", "gaits", "gaitt", "gajos", "galah",
- "galas", "galax", "galea", "galed", "gales",
- "galia", "galis", "galls", "gally", "galop",
- "galut", "galvo", "gamas", "gamay", "gamba",
- "gambe", "gambo", "gambs", "gamed", "gamer",
- "games", "gamey", "gamic", "gamin", "gamme",
- "gammy", "gamps", "ganch", "gandy", "ganef",
- "ganev", "gangs", "ganja", "ganks", "ganof",
- "gants", "gaols", "gaped", "gaper", "gapes",
- "gapos", "gappy", "garam", "garba", "garbe",
- "garbo", "garbs", "garda", "garde", "gares",
- "garis", "garms", "garni", "garre", "garri",
- "garth", "garum", "gases", "gashy", "gasps",
- "gaspy", "gasts", "gatch", "gated", "gater",
- "gates", "gaths", "gator", "gauch", "gaucy",
- "gauds", "gauje", "gault", "gaums", "gaumy",
- "gaups", "gaurs", "gauss", "gauzy", "gavot",
- "gawcy", "gawds", "gawks", "gawps", "gawsy",
- "gayal", "gayer", "gayly", "gazal", "gazar",
- "gazed", "gazer", "gazes", "gazon", "gazoo",
- "geals", "geans", "geare", "gears", "geasa",
- "geats", "gebur", "gecko", "gecks", "geeks",
- "geeps", "geese", "geest", "geist", "geits",
- "gelds", "gelee", "gelid", "gelly", "gelts",
- "gemel", "gemma", "gemmy", "gemot", "genae",
- "genal", "genas", "genes", "genet", "genic",
- "genii", "genin", "genio", "genip", "genny",
- "genoa", "genom", "genro", "gents", "genty",
- "genua", "genus", "geode", "geoid", "gerah",
- "gerbe", "geres", "gerle", "germs", "germy",
- "gerne", "gesse", "gesso", "geste", "gests",
- "getas", "getup", "geums", "geyan", "geyer",
- "ghast", "ghats", "ghaut", "ghazi", "ghees",
- "ghest", "ghoul", "ghusl", "ghyll", "gibed",
- "gibel", "giber", "gibes", "gibli", "gibus",
- "gifts", "gigas", "gighe", "gigot", "gigue",
- "gilas", "gilds", "gilet", "gilia", "gills",
- "gilly", "gilpy", "gilts", "gimel", "gimme",
- "gimps", "gimpy", "ginch", "ginga", "ginge",
- "gings", "ginks", "ginny", "ginzo", "gipon",
- "gippo", "gippy", "gipsy", "girds", "girlf",
- "girls", "girly", "girns", "giron", "giros",
- "girrs", "girsh", "girts", "gismo", "gisms",
- "gists", "gitch", "gites", "giust", "gived",
- "gives", "gizmo", "glace", "glade", "glads",
- "glady", "glaik", "glair", "glamp", "glams",
- "glans", "glary", "glatt", "glaum", "glaur",
- "glazy", "gleba", "glebe", "gleby", "glede",
- "gleds", "gleed", "gleek", "glees", "gleet",
- "gleis", "glens", "glent", "gleys", "glial",
- "glias", "glibs", "gliff", "glift", "glike",
- "glime", "glims", "glisk", "glits", "glitz",
- "gloam", "globi", "globs", "globy", "glode",
- "glogg", "gloms", "gloop", "glops", "glost",
- "glout", "glows", "glowy", "gloze", "glued",
- "gluer", "glues", "gluey", "glugg", "glugs",
- "glume", "glums", "gluon", "glute", "gluts",
- "gnapi", "gnarl", "gnarr", "gnars", "gnats",
- "gnawn", "gnaws", "gnows", "goads", "goafs",
- "goaft", "goals", "goary", "goats", "goaty",
- "goave", "goban", "gobar", "gobbe", "gobbi",
- "gobbo", "gobby", "gobis", "gobos", "godet",
- "godso", "goels", "goers", "goest", "goeth",
- "goety", "gofer", "goffs", "gogga", "gogos",
- "goier", "gojis", "gokes", "golds", "goldy",
- "goles", "golfs", "golpe", "golps", "gombo",
- "gomer", "gompa", "gonch", "gonef", "gongs",
- "gonia", "gonif", "gonks", "gonna", "gonof",
- "gonys", "gonzo", "gooby", "goodo", "goods",
- "goofs", "googs", "gooks", "gooky", "goold",
- "gools", "gooly", "goomy", "goons", "goony",
- "goops", "goopy", "goors", "goory", "goosy",
- "gopak", "gopik", "goral", "goras", "goray",
- "gorbs", "gordo", "gored", "gores", "goris",
- "gorms", "gormy", "gorps", "gorse", "gorsy",
- "gosht", "gosse", "gotch", "goths", "gothy",
- "gotta", "gouch", "gouks", "goura", "gouts",
- "gouty", "goved", "goves", "gowan", "gowds",
- "gowfs", "gowks", "gowls", "gowns", "goxes",
- "goyim", "goyle", "graal", "grabs", "grads",
- "graff", "graip", "grama", "grame", "gramp",
- "grams", "grana", "grano", "grans", "grapy",
- "grata", "grats", "gravs", "grays", "grebe",
- "grebo", "grece", "greek", "grees", "grege",
- "grego", "grein", "grens", "greps", "grese",
- "greve", "grews", "greys", "grice", "gride",
- "grids", "griff", "grift", "grigs", "grike",
- "grins", "griot", "grips", "gript", "gripy",
- "grise", "grist", "grisy", "grith", "grits",
- "grize", "grody", "grogs", "groks", "groma",
- "groms", "grone", "groof", "grosz", "grots",
- "grouf", "grovy", "grows", "grrls", "grrrl",
- "grubs", "grued", "grues", "grufe", "grume",
- "grund", "gryce", "gryde", "gryke", "grype",
- "grypt", "guaco", "guana", "guano", "guans",
- "guars", "gubba", "gucks", "gucky", "gudes",
- "guffs", "gugas", "guggl", "guido", "guids",
- "guile", "guimp", "guiro", "gulab", "gulag",
- "gular", "gulas", "gules", "gulet", "gulfs",
- "gulfy", "gulls", "gulph", "gulps", "gulpy",
- "gumma", "gummi", "gumps", "gunas", "gundi",
- "gundy", "gunge", "gungy", "gunks", "gunky",
- "gunny", "guqin", "gurdy", "gurge", "gurks",
- "gurls", "gurly", "gurns", "gurry", "gursh",
- "gurus", "gushy", "gusla", "gusle", "gusli",
- "gussy", "gusts", "gutsy", "gutta", "gutty",
- "guyed", "guyle", "guyot", "guyse", "gwine",
- "gyals", "gyans", "gybed", "gybes", "gyeld",
- "gymps", "gynae", "gynie", "gynny", "gynos",
- "gyoza", "gypes", "gypos", "gyppo", "gyppy",
- "gypsy", "gyral", "gyred", "gyres", "gyron",
- "gyros", "gyrus", "gytes", "gyved", "gyver",
- "gyves", "haafs", "haars", "haats", "hable",
- "habus", "hacek", "hacks", "hacky", "hadal",
- "haded", "hades", "hadji", "hadst", "haems",
- "haere", "haets", "haffs", "hafiz", "hafta",
- "hafts", "haggs", "haham", "hahas", "haick",
- "haika", "haiks", "haiku", "hails", "haily",
- "hains", "haint", "hairs", "haith", "hajes",
- "hajis", "hajji", "hakam", "hakas", "hakea",
- "hakes", "hakim", "hakus", "halal", "haldi",
- "haled", "haler", "hales", "halfa", "halfs",
- "halid", "hallo", "halls", "halma", "halms",
- "halon", "halos", "halse", "halsh", "halts",
- "halva", "halwa", "hamal", "hamba", "hamed",
- "hamel", "hames", "hammy", "hamza", "hanap",
- "hance", "hanch", "handi", "hands", "hangi",
- "hangs", "hanks", "hanky", "hansa", "hanse",
- "hants", "haole", "haoma", "hapas", "hapax",
- "haply", "happi", "hapus", "haram", "hards",
- "hared", "hares", "harim", "harks", "harls",
- "harms", "harns", "haros", "harps", "harts",
- "hashy", "hasks", "hasps", "hasta", "hated",
- "hater", "hates", "hatha", "hathi", "hatty",
- "hauds", "haufs", "haugh", "haugo", "hauld",
- "haulm", "hauls", "hault", "hauns", "hause",
- "haute", "havan", "havel", "haver", "haves",
- "havoc", "hawed", "hawks", "hawms", "hawse",
- "hayed", "hayer", "hayey", "hayle", "hazan",
- "hazed", "hazer", "hazes", "hazle", "heads",
- "heald", "heals", "heame", "heaps", "heapy",
- "heard", "heare", "hears", "heast", "heats",
- "heaty", "heave", "heben", "hebes", "hecht",
- "hecks", "heder", "hedgy", "heeds", "heedy",
- "heels", "heeze", "hefte", "hefts", "heiau",
- "heids", "heigh", "heils", "heirs", "hejab",
- "hejra", "heled", "heles", "helio", "hella",
- "hells", "helly", "helms", "helos", "helot",
- "helps", "helve", "hemal", "hemes", "hemic",
- "hemin", "hemps", "hempy", "hench", "hends",
- "henge", "henna", "henny", "henry", "hents",
- "hepar", "herbs", "herby", "herds", "heres",
- "herls", "herma", "herms", "herns", "heros",
- "herps", "herry", "herse", "hertz", "herye",
- "hesps", "hests", "hetes", "heths", "heuch",
- "heugh", "hevea", "hevel", "hewed", "hewer",
- "hewgh", "hexad", "hexed", "hexer", "hexes",
- "hexyl", "heyed", "hiant", "hibas", "hicks",
- "hided", "hider", "hides", "hiems", "hifis",
- "highs", "hight", "hijab", "hijra", "hiked",
- "hiker", "hikes", "hikoi", "hilar", "hilch",
- "hillo", "hills", "hilsa", "hilts", "hilum",
- "hilus", "himbo", "hinau", "hinds", "hings",
- "hinky", "hinny", "hints", "hiois", "hiped",
- "hiper", "hipes", "hiply", "hired", "hiree",
- "hirer", "hires", "hissy", "hists", "hithe",
- "hived", "hiver", "hives", "hizen", "hoach",
- "hoaed", "hoagy", "hoars", "hoary", "hoast",
- "hobos", "hocks", "hocus", "hodad", "hodja",
- "hoers", "hogan", "hogen", "hoggs", "hoghs",
- "hogoh", "hogos", "hohed", "hoick", "hoied",
- "hoiks", "hoing", "hoise", "hokas", "hoked",
- "hokes", "hokey", "hokis", "hokku", "hokum",
- "holds", "holed", "holes", "holey", "holks",
- "holla", "hollo", "holme", "holms", "holon",
- "holos", "holts", "homas", "homed", "homes",
- "homey", "homie", "homme", "homos", "honan",
- "honda", "honds", "honed", "honer", "hones",
- "hongi", "hongs", "honks", "honky", "hooch",
- "hoods", "hoody", "hoofs", "hoogo", "hooha",
- "hooka", "hooks", "hooky", "hooly", "hoons",
- "hoops", "hoord", "hoors", "hoosh", "hoots",
- "hooty", "hoove", "hopak", "hoped", "hoper",
- "hopes", "hoppy", "horah", "horal", "horas",
- "horis", "horks", "horme", "horns", "horst",
- "horsy", "hosed", "hosel", "hosen", "hoser",
- "hoses", "hosey", "hosta", "hosts", "hotch",
- "hoten", "hotis", "hotte", "hotty", "houff",
- "houfs", "hough", "houri", "hours", "houts",
- "hovea", "hoved", "hovel", "hoven", "hoves",
- "howay", "howbe", "howes", "howff", "howfs",
- "howks", "howls", "howre", "howso", "howto",
- "hoxed", "hoxes", "hoyas", "hoyed", "hoyle",
- "hubba", "hubby", "hucks", "hudna", "hudud",
- "huers", "huffs", "huffy", "huger", "huggy",
- "huhus", "huias", "huies", "hukou", "hulas",
- "hules", "hulks", "hulky", "hullo", "hulls",
- "hully", "humas", "humfs", "humic", "humph",
- "humps", "humpy", "hundo", "hunks", "hunts",
- "hurds", "hurls", "hurly", "hurra", "hurst",
- "hurts", "hurty", "hushy", "husks", "husos",
- "hutia", "huzza", "huzzy", "hwyls", "hydel",
- "hydra", "hydro", "hyens", "hygge", "hying",
- "hykes", "hylas", "hyleg", "hyles", "hylic",
- "hymns", "hynde", "hyoid", "hyped", "hypes",
- "hypha", "hyphy", "hypos", "hyrax", "hyson",
- "hythe", "iambi", "iambs", "ibrik", "icers",
- "iched", "iches", "ichor", "icier", "icker",
- "ickle", "icons", "ictal", "ictic", "ictus",
- "idant", "iddah", "iddat", "iddut", "ideas",
- "idees", "ident", "idled", "idler", "idles",
- "idlis", "idola", "idols", "idyls", "iftar",
- "igapo", "igged", "iglus", "ignis", "ihram",
- "iiwis", "ikans", "ikats", "ikons", "ileac",
- "ileal", "ileum", "ileus", "iliac", "iliad",
- "ilial", "ilium", "iller", "illth", "imago",
- "imagy", "imams", "imari", "imaum", "imbar",
- "imbed", "imbos", "imide", "imido", "imids",
- "imine", "imino", "imlis", "immew", "immit",
- "immix", "imped", "impis", "impot", "impro",
- "imshi", "imshy", "inapt", "inarm", "inbye",
- "incas", "incel", "incle", "incog", "incus",
- "incut", "indew", "india", "indie", "indol",
- "indow", "indri", "indue", "inerm", "infix",
- "infos", "infra", "ingan", "ingle", "inion",
- "inked", "inker", "inkle", "inned", "innie",
- "innit", "inorb", "inros", "inrun", "insee",
- "inset", "inspo", "intel", "intil", "intis",
- "intra", "inula", "inure", "inurn", "inust",
- "invar", "inver", "inwit", "iodic", "iodid",
- "iodin", "ioras", "iotas", "ippon", "irade",
- "irids", "iring", "irked", "iroko", "irone",
- "irons", "isbas", "ishes", "isled", "isles",
- "isnae", "issei", "istle", "items", "ither",
- "ivied", "ivies", "ixias", "ixnay", "ixora",
- "ixtle", "izard", "izars", "izzat", "jaaps",
- "jabot", "jacal", "jacet", "jacks", "jacky",
- "jaded", "jades", "jafas", "jaffa", "jagas",
- "jager", "jaggs", "jaggy", "jagir", "jagra",
- "jails", "jaker", "jakes", "jakey", "jakie",
- "jalap", "jaleo", "jalop", "jambe", "jambo",
- "jambs", "jambu", "james", "jammy", "jamon",
- "jamun", "janes", "janky", "janns", "janny",
- "janty", "japan", "japed", "japer", "japes",
- "jarks", "jarls", "jarps", "jarta", "jarul",
- "jasey", "jaspe", "jasps", "jatha", "jatis",
- "jatos", "jauks", "jaune", "jaunt", "jaups",
- "javas", "javel", "jawan", "jawed", "jawns",
- "jaxie", "jeans", "jeats", "jebel", "jedis",
- "jeels", "jeely", "jeeps", "jeera", "jeers",
- "jeeze", "jefes", "jeffs", "jehad", "jehus",
- "jelab", "jello", "jells", "jembe", "jemmy",
- "jeons", "jerid", "jerks", "jerry", "jesse",
- "jessy", "jests", "jesus", "jetee", "jetes",
- "jeton", "jetty", "jeune", "jewed", "jewie",
- "jhala", "jheel", "jhils", "jiaos", "jibba",
- "jibbs", "jibed", "jiber", "jibes", "jiffs",
- "jiggy", "jigot", "jihad", "jills", "jilts",
- "jimpy", "jingo", "jings", "jinks", "jinne",
- "jinni", "jinns", "jirds", "jirga", "jirre",
- "jisms", "jitis", "jitty", "jived", "jiver",
- "jives", "jivey", "jnana", "jobed", "jobes",
- "jocko", "jocks", "jocky", "jocos", "jodel",
- "joeys", "johns", "joins", "joint", "joist",
- "joked", "jokes", "jokey", "jokol", "joled",
- "joles", "jolie", "jollo", "jolls", "jolts",
- "jolty", "jomon", "jomos", "jones", "jongs",
- "jonty", "jooks", "joram", "jorts", "jorum",
- "jotas", "jotty", "jotun", "joual", "jougs",
- "jouks", "joule", "jours", "jowar", "jowed",
- "jowls", "jowly", "joyed", "jubas", "jubes",
- "jucos", "judas", "judgy", "judos", "jugal",
- "jugum", "jujus", "juked", "jukes", "jukus",
- "julep", "julia", "jumar", "jumby", "jumps",
- "junco", "junks", "junky", "junta", "junto",
- "jupes", "jupon", "jural", "jurat", "jurel",
- "jures", "juris", "juste", "justs", "jutes",
- "jutty", "juves", "juvie", "kaama", "kabab",
- "kabar", "kabob", "kacha", "kacks", "kadai",
- "kades", "kadis", "kafir", "kagos", "kagus",
- "kahal", "kaiak", "kaids", "kaies", "kaifs",
- "kaika", "kaiks", "kails", "kaims", "kaing",
- "kains", "kajal", "kakas", "kakis", "kalam",
- "kalas", "kales", "kalif", "kalis", "kalpa",
- "kalua", "kamas", "kames", "kamik", "kamis",
- "kamme", "kanae", "kanal", "kanas", "kanat",
- "kandy", "kaneh", "kanes", "kanga", "kangs",
- "kanji", "kants", "kanzu", "kaons", "kapai",
- "kapas", "kapha", "kaphs", "kapok", "kapow",
- "kappa", "kapur", "kapus", "kaput", "karai",
- "karas", "karat", "karee", "karez", "karks",
- "karns", "karoo", "karos", "karri", "karst",
- "karsy", "karts", "karzy", "kasha", "kasme",
- "katal", "katas", "katis", "katti", "kaugh",
- "kauri", "kauru", "kaury", "kaval", "kavas",
- "kawas", "kawau", "kawed", "kayle", "kayos",
- "kazis", "kazoo", "kbars", "kcals", "keaki",
- "kebar", "kebob", "kecks", "kedge", "kedgy",
- "keech", "keefs", "keeks", "keels", "keema",
- "keeno", "keens", "keeps", "keets", "keeve",
- "kefir", "kehua", "keirs", "kelep", "kelim",
- "kells", "kelly", "kelps", "kelpy", "kelts",
- "kelty", "kembo", "kembs", "kemps", "kempt",
- "kempy", "kenaf", "kench", "kendo", "kenos",
- "kente", "kents", "kepis", "kerbs", "kerel",
- "kerfs", "kerky", "kerma", "kerne", "kerns",
- "keros", "kerry", "kerve", "kesar", "kests",
- "ketas", "ketch", "ketes", "ketol", "kevel",
- "kevil", "kexes", "keyed", "keyer", "khadi",
- "khads", "khafs", "khana", "khans", "khaph",
- "khats", "khaya", "khazi", "kheda", "kheer",
- "kheth", "khets", "khirs", "khoja", "khors",
- "khoum", "khuds", "khula", "khyal", "kiaat",
- "kiack", "kiaki", "kiang", "kiasu", "kibbe",
- "kibbi", "kibei", "kibes", "kibla", "kicks",
- "kicky", "kiddo", "kiddy", "kidel", "kideo",
- "kidge", "kiefs", "kiers", "kieve", "kievs",
- "kight", "kikay", "kikes", "kikoi", "kiley",
- "kilig", "kilim", "kills", "kilns", "kilos",
- "kilps", "kilts", "kilty", "kimbo", "kimet",
- "kinas", "kinda", "kinds", "kindy", "kines",
- "kings", "kingy", "kinin", "kinks", "kinos",
- "kiore", "kipah", "kipas", "kipes", "kippa",
- "kipps", "kipsy", "kirby", "kirks", "kirns",
- "kirri", "kisan", "kissy", "kists", "kitab",
- "kited", "kiter", "kites", "kithe", "kiths",
- "kitke", "kitul", "kivas", "kiwis", "klang",
- "klaps", "klett", "klick", "klieg", "kliks",
- "klong", "kloof", "kluge", "klutz", "knags",
- "knaps", "knarl", "knars", "knaur", "knave",
- "knawe", "kneed", "knees", "knell", "knick",
- "knish", "knits", "knive", "knobs", "knoop",
- "knops", "knosp", "knots", "knoud", "knout",
- "knowd", "knowe", "knows", "knubs", "knule",
- "knurl", "knurr", "knurs", "knuts", "koans",
- "koaps", "koban", "kobos", "koels", "koffs",
- "kofta", "kogal", "kohas", "kohen", "kohls",
- "koine", "koiwi", "kojis", "kokam", "kokas",
- "koker", "kokra", "kokum", "kolas", "kolos",
- "kombi", "kombu", "konbu", "kondo", "konks",
- "kooks", "kooky", "koori", "kopek", "kophs",
- "kopje", "koppa", "korai", "koran", "koras",
- "korat", "kores", "koris", "korma", "koros",
- "korun", "korus", "koses", "kotch", "kotos",
- "kotow", "koura", "kraal", "krabs", "kraft",
- "krais", "krait", "krang", "krans", "kranz",
- "kraut", "krays", "kreef", "kreen", "kreep",
- "kreng", "krewe", "krill", "kriol", "krona",
- "krone", "kroon", "krubi", "krump", "krunk",
- "ksars", "kubie", "kudus", "kudzu", "kufis",
- "kugel", "kuias", "kukri", "kukus", "kulak",
- "kulan", "kulas", "kulfi", "kumis", "kumys",
- "kunas", "kunds", "kuris", "kurre", "kurta",
- "kurus", "kusso", "kusti", "kutai", "kutas",
- "kutch", "kutis", "kutus", "kuyas", "kuzus",
- "kvass", "kvell", "kwaai", "kwela", "kwink",
- "kwirl", "kyack", "kyaks", "kyang", "kyars",
- "kyats", "kybos", "kydst", "kyles", "kylie",
- "kylin", "kylix", "kyloe", "kynde", "kynds",
- "kypes", "kyrie", "kytes", "kythe", "kyudo",
- "laarf", "laari", "labda", "labia", "labis",
- "labne", "labra", "laccy", "laced", "lacer",
- "laces", "lacet", "lacey", "lacis", "lacka",
- "lacks", "lacky", "laddu", "laddy", "laded",
- "ladee", "lader", "lades", "ladoo", "laers",
- "laevo", "lagan", "lagar", "laggy", "lahal",
- "lahar", "laich", "laics", "laide", "laids",
- "laigh", "laika", "laiks", "laird", "lairs",
- "lairy", "laith", "laity", "laked", "laker",
- "lakes", "lakhs", "lakin", "laksa", "laldy",
- "lalls", "lamas", "lambs", "lamby", "lamed",
- "lamer", "lames", "lamia", "lammy", "lamps",
- "lanai", "lanas", "lanch", "lande", "lands",
- "laned", "lanes", "lanks", "lants", "lapas",
- "lapin", "lapis", "lapje", "lappa", "lappy",
- "larch", "lards", "lardy", "laree", "lares",
- "larfs", "larga", "largo", "laris", "larks",
- "larky", "larns", "larnt", "larum", "lased",
- "laser", "lases", "lassi", "lasso", "lassu",
- "lassy", "lasts", "latah", "lated", "laten",
- "latex", "lathi", "laths", "lathy", "latke",
- "latus", "lauan", "lauch", "laude", "lauds",
- "laufs", "laund", "laura", "laval", "lavas",
- "laved", "laver", "laves", "lavra", "lavvy",
- "lawed", "lawer", "lawin", "lawks", "lawns",
- "lawny", "lawsy", "laxed", "laxer", "laxes",
- "laxly", "layby", "layed", "layin", "layup",
- "lazar", "lazed", "lazes", "lazos", "lazzi",
- "lazzo", "leads", "leady", "leafs", "leaks",
- "leams", "leans", "leant", "leany", "leaps",
- "leare", "lears", "leary", "leats", "leavy",
- "leaze", "leben", "leccy", "leche", "ledes",
- "ledgy", "ledum", "leear", "leeks", "leeps",
- "leers", "leery", "leese", "leets", "leeze",
- "lefte", "lefts", "lefty", "leger", "leges",
- "legge", "leggo", "legit", "legno", "lehrs",
- "lehua", "leirs", "leish", "leman", "lemed",
- "lemel", "lemes", "lemma", "lemme", "lends",
- "lenes", "lengs", "lenis", "lenos", "lense",
- "lenti", "lento", "leone", "lepak", "leper",
- "lepid", "lepra", "lepta", "lered", "leres",
- "lerps", "lesbo", "leses", "lesos", "lests",
- "letch", "lethe", "letty", "letup", "leuch",
- "leuco", "leuds", "leugh", "levas", "levee",
- "leves", "levin", "levis", "lewis", "lexes",
- "lexis", "lezes", "lezza", "lezzo", "lezzy",
- "liana", "liane", "liang", "liard", "liars",
- "liart", "liber", "libor", "libra", "libre",
- "libri", "licet", "lichi", "licht", "licit",
- "licks", "lidar", "lidos", "liefs", "liege",
- "liens", "liers", "lieus", "lieve", "lifer",
- "lifes", "lifey", "lifts", "ligan", "liger",
- "ligge", "ligne", "liked", "liker", "likes",
- "likin", "lills", "lilos", "lilts", "lilty",
- "liman", "limas", "limax", "limba", "limbi",
- "limbs", "limby", "limed", "limen", "limes",
- "limey", "limit", "limma", "limns", "limos",
- "limpa", "limps", "linac", "linch", "linds",
- "lindy", "lined", "lines", "liney", "linga",
- "lings", "lingy", "linin", "links", "linky",
- "linns", "linny", "linos", "lints", "linty",
- "linum", "linux", "lions", "lipas", "lipes",
- "lipin", "lipos", "lippy", "liras", "lirks",
- "lirot", "lises", "lisks", "lisle", "lisps",
- "lists", "litai", "litas", "lited", "litem",
- "lites", "litho", "liths", "litie", "litre",
- "lived", "liven", "lives", "livor", "livre",
- "liwaa", "liwas", "llano", "loach", "loads",
- "loafs", "loams", "loamy", "loans", "loast",
- "loath", "loave", "lobar", "lobed", "lobes",
- "lobos", "lobus", "loche", "lochs", "lochy",
- "locie", "locis", "locks", "locky", "locos",
- "locum", "loden", "lodes", "loess", "lofts",
- "logan", "loges", "loggy", "logia", "logie",
- "logoi", "logon", "logos", "lohan", "loids",
- "loins", "loipe", "loirs", "lokes", "lokey",
- "lokum", "lolas", "loled", "lollo", "lolls",
- "lolly", "lolog", "lolos", "lomas", "lomed",
- "lomes", "loner", "longa", "longe", "longs",
- "looby", "looed", "looey", "loofa", "loofs",
- "looie", "looks", "looky", "looms", "loons",
- "loony", "loops", "loord", "loots", "loped",
- "loper", "lopes", "loppy", "loral", "loran",
- "lords", "lordy", "lorel", "lores", "loric",
- "loris", "losed", "losel", "losen", "loses",
- "lossy", "lotah", "lotas", "lotes", "lotic",
- "lotos", "lotsa", "lotta", "lotte", "lotto",
- "lotus", "loued", "lough", "louie", "louis",
- "louma", "lound", "louns", "loupe", "loups",
- "loure", "lours", "loury", "louse", "louts",
- "lovat", "loved", "lovee", "loves", "lovey",
- "lovie", "lowan", "lowed", "lowen", "lowes",
- "lownd", "lowne", "lowns", "lowps", "lowry",
- "lowse", "lowth", "lowts", "loxed", "loxes",
- "lozen", "luach", "luaus", "lubed", "lubes",
- "lubra", "luces", "lucks", "lucre", "ludes",
- "ludic", "ludos", "luffa", "luffs", "luged",
- "luger", "luges", "lulls", "lulus", "lumas",
- "lumbi", "lumme", "lummy", "lumps", "lunas",
- "lunes", "lunet", "lungi", "lungs", "lunks",
- "lunts", "lupin", "lurch", "lured", "lurer",
- "lures", "lurex", "lurgi", "lurgy", "lurid",
- "lurks", "lurry", "lurve", "luser", "lushy",
- "lusks", "lusts", "lusus", "lutea", "luted",
- "luter", "lutes", "luvvy", "luxed", "luxer",
- "luxes", "lweis", "lyams", "lyard", "lyart",
- "lyase", "lycea", "lycee", "lycra", "lymes",
- "lymph", "lynes", "lyres", "lysed", "lyses",
- "lysin", "lysis", "lysol", "lyssa", "lyted",
- "lytes", "lythe", "lytic", "lytta", "maaed",
- "maare", "maars", "maban", "mabes", "macas",
- "macca", "maced", "macer", "maces", "mache",
- "machi", "machs", "macka", "macks", "macle",
- "macon", "macte", "madal", "madar", "maddy",
- "madge", "madid", "mados", "madre", "maedi",
- "maerl", "mafia", "mafic", "mafts", "magas",
- "mages", "maggs", "magna", "magot", "magus",
- "mahal", "mahem", "mahis", "mahoe", "mahrs",
- "mahua", "mahwa", "maids", "maiko", "maiks",
- "maile", "maill", "mailo", "mails", "maims",
- "mains", "maire", "mairs", "maise", "maist",
- "majas", "majat", "majoe", "majos", "makaf",
- "makai", "makan", "makar", "makee", "makes",
- "makie", "makis", "makos", "malae", "malai",
- "malam", "malar", "malas", "malax", "maleo",
- "males", "malic", "malik", "malis", "malky",
- "malls", "malms", "malmy", "malts", "malty",
- "malus", "malva", "malwa", "mamak", "mamas",
- "mamba", "mambu", "mamee", "mamey", "mamie",
- "mamil", "mammy", "manas", "manat", "mandi",
- "mands", "mandy", "maneb", "maned", "maneh",
- "manes", "manet", "manga", "mange", "mangi",
- "mangs", "manie", "manis", "manks", "manky",
- "manna", "manny", "manoa", "manos", "manse",
- "manso", "manta", "mante", "manto", "mants",
- "manty", "manul", "manus", "manzo", "mapau",
- "mapes", "mapou", "mappy", "maqam", "maqui",
- "marae", "marah", "maral", "maran", "maras",
- "maray", "marcs", "mards", "mardy", "mares",
- "marga", "marge", "margo", "margs", "maria",
- "marid", "maril", "marka", "marks", "marle",
- "marls", "marly", "marma", "marms", "maron",
- "maror", "marra", "marri", "marse", "marts",
- "marua", "marvy", "masas", "mased", "maser",
- "mases", "masha", "mashy", "masks", "massa",
- "masse", "massy", "masts", "masty", "masur",
- "masus", "masut", "matai", "mated", "mater",
- "mates", "mathe", "maths", "matin", "matlo",
- "matra", "matsu", "matte", "matts", "matty",
- "matza", "matzo", "mauby", "mauds", "mauka",
- "maula", "mauls", "maums", "maumy", "maund",
- "maunt", "mauri", "mausy", "mauts", "mauve",
- "mauvy", "mauzy", "maven", "mavie", "mavin",
- "mavis", "mawed", "mawks", "mawky", "mawla",
- "mawns", "mawps", "mawrs", "maxed", "maxes",
- "maxis", "mayan", "mayas", "mayed", "mayos",
- "mayst", "mazac", "mazak", "mazar", "mazas",
- "mazed", "mazel", "mazer", "mazes", "mazet",
- "mazey", "mazut", "mbari", "mbars", "mbila",
- "mbira", "mbret", "mbube", "mbuga", "meads",
- "meake", "meaks", "meals", "meane", "means",
- "meany", "meare", "mease", "meath", "meats",
- "mebbe", "mebos", "mecca", "mecha", "mechs",
- "mecks", "mecum", "medal", "medii", "medin",
- "medle", "meech", "meeds", "meeja", "meeps",
- "meers", "meets", "meffs", "meids", "meiko",
- "meils", "meins", "meint", "meiny", "meism",
- "meith", "mekka", "melam", "melas", "melba",
- "melch", "melds", "meles", "melic", "melik",
- "mells", "meloe", "melos", "melts", "melty",
- "memes", "memic", "memos", "menad", "mence",
- "mends", "mened", "menes", "menge", "mengs",
- "menil", "mensa", "mense", "mensh", "menta",
- "mento", "ments", "menus", "meous", "meows",
- "merch", "mercs", "merde", "merds", "mered",
- "merel", "merer", "meres", "meril", "meris",
- "merks", "merle", "merls", "merse", "mersk",
- "mesad", "mesal", "mesas", "mesca", "mesel",
- "mesem", "meses", "meshy", "mesia", "mesic",
- "mesne", "meson", "messy", "mesto", "mesyl",
- "metas", "meted", "meteg", "metel", "metes",
- "methi", "metho", "meths", "methy", "metic",
- "metif", "metis", "metol", "metre", "metro",
- "metta", "meums", "meuse", "meved", "meves",
- "mewed", "mewls", "meynt", "mezes", "mezza",
- "mezze", "mezzo", "mgals", "mhorr", "miais",
- "miaou", "miaow", "miasm", "miaul", "micas",
- "miche", "michi", "micht", "micks", "micky",
- "micos", "micra", "micro", "middy", "midge",
- "midgy", "midis", "miens", "mieux", "mieve",
- "miffs", "miffy", "mifty", "miggs", "migma",
- "migod", "mihas", "mihis", "mikan", "miked",
- "mikes", "mikos", "mikra", "mikva", "milch",
- "milds", "miler", "miles", "milfs", "milia",
- "milko", "milks", "mille", "mills", "milly",
- "milor", "milos", "milpa", "milts", "milty",
- "miltz", "mimed", "mimeo", "mimer", "mimes",
- "mimis", "mimsy", "minae", "minar", "minas",
- "mincy", "mindi", "minds", "mined", "miner",
- "mines", "minge", "mingi", "mings", "mingy",
- "minim", "minis", "minke", "minks", "minny",
- "minos", "minse", "mints", "minty", "minxy",
- "miraa", "mirah", "mirch", "mired", "mires",
- "mirex", "mirid", "mirin", "mirkn", "mirks",
- "mirky", "mirls", "mirly", "miros", "mirrl",
- "mirrs", "mirvs", "mirza", "misal", "misch",
- "misdo", "mises", "misgo", "misky", "misls",
- "misos", "missa", "misto", "mists", "misty",
- "mitas", "mitch", "miter", "mites", "mitey",
- "mitie", "mitis", "mitre", "mitry", "mitta",
- "mitts", "mivey", "mivvy", "mixed", "mixen",
- "mixer", "mixes", "mixie", "mixis", "mixte",
- "mixup", "miyas", "mizen", "mizes", "mizzy",
- "mmkay", "mneme", "moais", "moaky", "moals",
- "moana", "moans", "moany", "moars", "moats",
- "mobby", "mobed", "mobee", "mobes", "mobey",
- "mobie", "moble", "mobos", "mocap", "mochi",
- "mochs", "mochy", "mocks", "mocky", "mocos",
- "mocus", "moder", "modes", "modge", "modii",
- "modin", "modoc", "modom", "modus", "moeni",
- "moers", "mofos", "mogar", "mogas", "moggy",
- "mogos", "mogra", "mogue", "mohar", "mohel",
- "mohos", "mohrs", "mohua", "mohur", "moile",
- "moils", "moira", "moire", "moits", "moity",
- "mojos", "moker", "mokes", "mokey", "mokis",
- "mokky", "mokos", "mokus", "molal", "molas",
- "molds", "moled", "moler", "moles", "moley",
- "molie", "molla", "molle", "mollo", "molls",
- "molly", "moloi", "molos", "molto", "molts",
- "molue", "molvi", "molys", "momes", "momie",
- "momma", "momme", "mommy", "momos", "mompe",
- "momus", "monad", "monal", "monas", "monde",
- "mondo", "moner", "mongo", "mongs", "monic",
- "monie", "monks", "monos", "monpe", "monte",
- "monty", "moobs", "mooch", "moods", "mooed",
- "mooey", "mooks", "moola", "mooli", "mools",
- "mooly", "moong", "mooni", "moons", "moony",
- "moops", "moors", "moory", "mooth", "moots",
- "moove", "moped", "moper", "mopes", "mopey",
- "moppy", "mopsy", "mopus", "morae", "morah",
- "moran", "moras", "morat", "moray", "moree",
- "morel", "mores", "morgy", "moria", "morin",
- "mormo", "morna", "morne", "morns", "moron",
- "moror", "morra", "morro", "morse", "morts",
- "moruk", "mosed", "moses", "mosey", "mosks",
- "mosso", "moste", "mosto", "mosts", "moted",
- "moten", "motes", "motet", "motey", "moths",
- "mothy", "motis", "moton", "motte", "motts",
- "motty", "motus", "motza", "mouch", "moues",
- "moufs", "mould", "moule", "mouls", "mouly",
- "moups", "moust", "moved", "moves", "mowas",
- "mowed", "mowie", "mowra", "moxas", "moxie",
- "moyas", "moyle", "moyls", "mozed", "mozes",
- "mozos", "mpret", "mrads", "msasa", "mtepe",
- "mucho", "mucic", "mucid", "mucin", "mucko",
- "mucks", "mucky", "mucor", "mucro", "mudar",
- "mudge", "mudif", "mudim", "mudir", "mudra",
- "muffs", "muffy", "mufti", "mugga", "muggs",
- "muggy", "mugho", "mugil", "mugos", "muhly",
- "muids", "muils", "muirs", "muiry", "muist",
- "mujik", "mukim", "mukti", "mulai", "mulct",
- "muled", "mules", "muley", "mulga", "mulie",
- "mulla", "mulls", "mulse", "mulsh", "mumbo",
- "mumms", "mumph", "mumps", "mumsy", "mumus",
- "munch", "munds", "mundu", "munga", "munge",
- "mungi", "mungo", "mungs", "mungy", "munia",
- "munis", "munja", "munjs", "munts", "muntu",
- "muons", "muras", "mured", "mures", "murex",
- "murgh", "murgi", "murid", "murks", "murls",
- "murly", "murra", "murre", "murri", "murrs",
- "murry", "murth", "murti", "muruk", "murva",
- "musar", "musca", "mused", "musee", "muser",
- "muses", "muset", "musha", "musit", "musks",
- "musky", "musos", "musse", "mussy", "musta",
- "musth", "musts", "mutas", "mutch", "muted",
- "muter", "mutes", "mutha", "mutic", "mutis",
- "muton", "mutti", "mutts", "mutum", "muvva",
- "muxed", "muxes", "muzak", "muzzy", "mvula",
- "mvule", "mvuli", "myall", "myals", "mylar",
- "mynah", "mynas", "myoid", "myoma", "myons",
- "myope", "myops", "myopy", "mysid", "mysie",
- "mythi", "myths", "mythy", "myxos", "mzees",
- "naams", "naans", "naats", "nabam", "nabby",
- "nabes", "nabis", "nabks", "nabla", "nabob",
- "nache", "nacho", "nacre", "nadas", "naeve",
- "naevi", "naffs", "nagar", "nagas", "nages",
- "naggy", "nagor", "nahal", "naiad", "naibs",
- "naice", "naids", "naieo", "naifs", "naiks",
- "nails", "naily", "nains", "naios", "naira",
- "nairu", "najib", "nakas", "naked", "naker",
- "nakfa", "nalas", "naled", "nalla", "namad",
- "namak", "namaz", "named", "namer", "names",
- "namma", "namus", "nanas", "nance", "nancy",
- "nandu", "nanna", "nanos", "nante", "nanti",
- "nanto", "nants", "nanty", "nanua", "napas",
- "naped", "napes", "napoh", "napoo", "nappa",
- "nappe", "nappy", "naras", "narco", "narcs",
- "nards", "nares", "naric", "naris", "narks",
- "narky", "narod", "narra", "narre", "nashi",
- "nasho", "nasis", "nason", "nasus", "natak",
- "natch", "nates", "natis", "natto", "natty",
- "natya", "nauch", "naunt", "navar", "naved",
- "naves", "navew", "navvy", "nawab", "nawal",
- "nazar", "nazes", "nazir", "nazis", "nazzy",
- "nduja", "neafe", "neals", "neant", "neaps",
- "nears", "neath", "neato", "neats", "nebby",
- "nebek", "nebel", "neche", "necks", "neddy",
- "neebs", "needs", "neefs", "neeld", "neele",
- "neemb", "neems", "neeps", "neese", "neeze",
- "nefie", "negri", "negro", "negus", "neifs",
- "neigh", "neist", "neive", "nelia", "nelis",
- "nelly", "nemas", "nemic", "nemns", "nempt",
- "nenes", "nenta", "neons", "neosa", "neoza",
- "neper", "nepit", "neral", "neram", "nerds",
- "nerdy", "nerfs", "nerka", "nerks", "nerol",
- "nerts", "nertz", "nervy", "neski", "nests",
- "nesty", "netas", "netes", "netop", "netta",
- "netts", "netty", "neuks", "neume", "neums",
- "nevel", "neves", "nevis", "nevus", "nevvy",
- "newbs", "newed", "newel", "newie", "newsy",
- "newts", "nexal", "nexin", "nexts", "nexum",
- "ngaio", "ngaka", "ngana", "ngapi", "ngati",
- "ngege", "ngoma", "ngoni", "ngram", "ngwee",
- "nibby", "nicad", "niced", "nicey", "nicht",
- "nicks", "nicky", "nicol", "nidal", "nided",
- "nides", "nidor", "nidus", "niece", "niefs",
- "niess", "nieve", "nifes", "niffs", "niffy",
- "nifle", "nifty", "niger", "nigga", "nighs",
- "nigre", "nigua", "nihil", "nikab", "nikah",
- "nikau", "nilas", "nills", "nimbi", "nimbs",
- "nimby", "nimps", "niner", "nines", "ninon",
- "ninta", "niopo", "nioza", "nipas", "nipet",
- "nippy", "niqab", "nirls", "nirly", "nisei",
- "nisin", "nisse", "nisus", "nital", "niter",
- "nites", "nitid", "niton", "nitre", "nitro",
- "nitry", "nitta", "nitto", "nitty", "nival",
- "nivas", "nivel", "nixed", "nixer", "nixes",
- "nixie", "nizam", "njirl", "nkosi", "nmoli",
- "nmols", "noahs", "nobby", "nocks", "nodal",
- "noddy", "noded", "nodes", "nodum", "nodus",
- "noels", "noema", "noeme", "nogal", "noggs",
- "noggy", "nohow", "noias", "noils", "noily",
- "noint", "noire", "noirs", "nokes", "noles",
- "nolle", "nolls", "nolos", "nomas", "nomen",
- "nomes", "nomic", "nomoi", "nomos", "nonan",
- "nonas", "nonce", "noncy", "nonda", "nondo",
- "nones", "nonet", "nongs", "nonic", "nonis",
- "nonna", "nonno", "nonny", "nonyl", "noobs",
- "noois", "nooit", "nooks", "nooky", "noone",
- "noons", "noops", "noove", "nopal", "noria",
- "norie", "noris", "norks", "norma", "norms",
- "nosed", "noser", "noses", "nosey", "noshi",
- "nosir", "notal", "notam", "noter", "notes",
- "notum", "nougs", "nouja", "nould", "noule",
- "nouls", "nouns", "nouny", "noups", "noust",
- "novae", "novas", "novia", "novio", "novum",
- "noway", "nowds", "nowed", "nowls", "nowts",
- "nowty", "noxal", "noxas", "noxes", "noyau",
- "noyed", "noyes", "nrtta", "nrtya", "nsima",
- "nubby", "nubia", "nucha", "nucin", "nuddy",
- "nuder", "nudes", "nudgy", "nudie", "nudzh",
- "nuevo", "nuffs", "nugae", "nujol", "nuked",
- "nukes", "nulla", "nullo", "nulls", "nully",
- "numbs", "numen", "nummy", "numps", "nunks",
- "nunky", "nunny", "nunus", "nuque", "nurds",
- "nurdy", "nurls", "nurrs", "nurts", "nurtz",
- "nused", "nuses", "nutso", "nutsy", "nyaff",
- "nyala", "nyams", "nying", "nyong", "nyssa",
- "nyung", "nyuse", "nyuze", "oafos", "oaked",
- "oaker", "oakum", "oared", "oarer", "oasal",
- "oases", "oasts", "oaten", "oater", "oaths",
- "oaves", "obang", "obbos", "obeah", "obeli",
- "obese", "obeys", "obias", "obied", "obiit",
- "obits", "objet", "oboes", "obole", "oboli",
- "obols", "occam", "ocher", "oches", "ochre",
- "ochry", "ocker", "ocote", "ocrea", "octad",
- "octal", "octan", "octas", "octic", "octli",
- "octyl", "oculi", "odahs", "odals", "odder",
- "odeon", "odeum", "odism", "odist", "odium",
- "odoom", "odors", "odour", "odums", "odyle",
- "odyls", "ofays", "offed", "offie", "oflag",
- "ofter", "ofuro", "ogams", "ogeed", "ogees",
- "oggin", "ogham", "ogive", "ogled", "ogler",
- "ogles", "ogmic", "ogres", "ohelo", "ohias",
- "ohing", "ohmic", "ohone", "oicks", "oidia",
- "oiled", "oiler", "oilet", "oinks", "oints",
- "oiran", "ojime", "okapi", "okays", "okehs",
- "okies", "oking", "okole", "okras", "okrug",
- "oktas", "olate", "olden", "older", "oldie",
- "oldly", "olehs", "oleic", "olein", "olent",
- "oleos", "oleum", "oleyl", "oligo", "olios",
- "oliva", "ollas", "ollav", "oller", "ollie",
- "ology", "olona", "olpae", "olpes", "omasa",
- "omber", "ombre", "ombus", "omdah", "omdas",
- "omdda", "omdeh", "omees", "omens", "omers",
- "omiai", "omits", "omlah", "ommel", "ommin",
- "omnes", "omovs", "omrah", "omuls", "oncer",
- "onces", "oncet", "oncus", "ondes", "ondol",
- "onely", "oners", "onery", "ongon", "onion",
- "onium", "onkus", "onlap", "onlay", "onmun",
- "onned", "onsen", "ontal", "ontic", "ooaas",
- "oobit", "oohed", "ooids", "oojah", "oomph",
- "oonts", "oopak", "ooped", "oopsy", "oorie",
- "ooses", "ootid", "ooyah", "oozed", "oozes",
- "oozie", "oozle", "opahs", "opals", "opens",
- "opepe", "opery", "opgaf", "opihi", "opine",
- "oping", "oppos", "opsat", "opsin", "opsit",
- "opted", "opter", "opzit", "orach", "oracy",
- "orals", "orang", "orans", "orant", "orate",
- "orbat", "orbed", "orbic", "orcas", "orcin",
- "ordie", "ordos", "oread", "orfes", "orful",
- "orgia", "orgic", "orgue", "oribi", "oriel",
- "origo", "orixa", "orles", "orlon", "orlop",
- "ormer", "ornee", "ornis", "orped", "orpin",
- "orris", "ortet", "ortho", "orval", "orzos",
- "osars", "oscar", "osetr", "oseys", "oshac",
- "osier", "oskin", "oslin", "osmic", "osmol",
- "osone", "ossia", "ostia", "otaku", "otary",
- "othyl", "otium", "ottar", "ottos", "oubit",
- "ouche", "oucht", "oueds", "ouens", "ouija",
- "oulks", "oumas", "oundy", "oupas", "ouped",
- "ouphe", "ouphs", "ourey", "ourie", "ousel",
- "ousia", "ousts", "outby", "outed", "outen",
- "outgo", "outie", "outre", "outro", "outta",
- "ouzel", "ouzos", "ovals", "ovels", "ovens",
- "overs", "ovine", "ovism", "ovist", "ovoli",
- "ovolo", "ovule", "oware", "owari", "owche",
- "owers", "owies", "owled", "owler", "owlet",
- "owned", "ownio", "owres", "owrie", "owsen",
- "oxbow", "oxeas", "oxers", "oxeye", "oxids",
- "oxies", "oxime", "oxims", "oxine", "oxlip",
- "oxman", "oxmen", "oxter", "oyama", "oyers",
- "ozeki", "ozena", "ozzie", "paaho", "paals",
- "paans", "pacai", "pacas", "pacay", "paced",
- "pacer", "paces", "pacey", "pacha", "packs",
- "packy", "pacos", "pacta", "pacts", "padam",
- "padas", "paddo", "padis", "padle", "padma",
- "padou", "padre", "padri", "paean", "paedo",
- "paeon", "paged", "pager", "pages", "pagle",
- "pagne", "pagod", "pagri", "pahit", "pahos",
- "pahus", "paiks", "pails", "pains", "paipe",
- "paips", "paire", "pairs", "paisa", "paise",
- "pakay", "pakka", "pakki", "pakua", "pakul",
- "palak", "palar", "palas", "palay", "palea",
- "paled", "pales", "palet", "palis", "palki",
- "palla", "palls", "pallu", "pally", "palms",
- "palmy", "palpi", "palps", "palsa", "palus",
- "pamby", "pampa", "panax", "pance", "panch",
- "panda", "pands", "pandy", "paned", "panes",
- "panga", "pangs", "panim", "panir", "panko",
- "panks", "panna", "panne", "panni", "panny",
- "panto", "pants", "panty", "paoli", "paolo",
- "papad", "papas", "papaw", "papes", "papey",
- "pappi", "pappy", "papri", "parae", "paras",
- "parcs", "pardi", "pards", "pardy", "pared",
- "paren", "pareo", "parer", "pares", "pareu",
- "parev", "parge", "pargo", "parid", "paris",
- "parki", "parks", "parky", "parle", "parly",
- "parma", "parmo", "parms", "parol", "parps",
- "parra", "parrs", "parte", "parti", "parts",
- "parve", "parvo", "pasag", "pasar", "pasch",
- "paseo", "pases", "pasha", "pashm", "paska",
- "pasmo", "paspy", "passe", "passu", "pasts",
- "patas", "pated", "patee", "patel", "paten",
- "pater", "pates", "paths", "patia", "patin",
- "patka", "patly", "patta", "patte", "pattu",
- "patus", "pauas", "pauls", "pauxi", "pavan",
- "pavas", "paved", "paven", "paver", "paves",
- "pavid", "pavie", "pavin", "pavis", "pavon",
- "pavvy", "pawas", "pawaw", "pawed", "pawer",
- "pawks", "pawky", "pawls", "pawns", "paxes",
- "payed", "payer", "payor", "paysd", "peage",
- "peags", "peake", "peaks", "peaky", "peals",
- "peans", "peare", "pears", "peart", "pease",
- "peasy", "peats", "peaty", "peavy", "peaze",
- "pebas", "pechs", "pecia", "pecke", "pecks",
- "pecky", "pects", "pedes", "pedis", "pedon",
- "pedos", "pedro", "peece", "peeks", "peeky",
- "peels", "peely", "peens", "peent", "peeoy",
- "peepe", "peeps", "peepy", "peers", "peery",
- "peeve", "peevo", "peggy", "peghs", "pegma",
- "pegos", "peine", "peins", "peise", "peisy",
- "peize", "pekan", "pekau", "pekea", "pekes",
- "pekid", "pekin", "pekoe", "pelas", "pelau",
- "pelch", "peles", "pelfs", "pells", "pelma",
- "pelog", "pelon", "pelsh", "pelta", "pelts",
- "pelus", "pends", "pendu", "pened", "penes",
- "pengo", "penie", "penis", "penks", "penna",
- "penne", "penni", "pense", "pensy", "pents",
- "peola", "peons", "peony", "pepla", "peple",
- "pepon", "pepos", "pepsi", "pequi", "perae",
- "perai", "perce", "percs", "perdu", "perdy",
- "perea", "peres", "perfs", "peris", "perks",
- "perle", "perls", "perms", "permy", "perne",
- "perns", "perog", "perps", "perry", "perse",
- "persp", "perst", "perts", "perve", "pervo",
- "pervs", "pervy", "pesch", "pesos", "pesta",
- "pesto", "pests", "pesty", "petar", "peter",
- "petit", "petos", "petre", "petri", "petti",
- "petto", "pewed", "pewee", "pewit", "peyse",
- "pfftt", "phage", "phang", "phare", "pharm",
- "phasm", "pheer", "pheme", "phene", "pheon",
- "phese", "phial", "phies", "phish", "phizz",
- "phlox", "phobe", "phoca", "phono", "phons",
- "phony", "phooh", "phooo", "phota", "phots",
- "photy", "phpht", "phubs", "phuts", "phutu",
- "phwat", "phyla", "phyle", "phyma", "phynx",
- "physa", "piais", "piani", "pians", "pibal",
- "pical", "picas", "piccy", "picey", "pichi",
- "picks", "picon", "picot", "picra", "picul",
- "pieds", "piend", "piers", "piert", "pieta",
- "piets", "piezo", "pight", "pigly", "pigmy",
- "piing", "pikas", "pikau", "piked", "pikel",
- "piker", "pikes", "pikey", "pikis", "pikul",
- "pilae", "pilaf", "pilao", "pilar", "pilau",
- "pilaw", "pilch", "pilea", "piled", "pilei",
- "piler", "piles", "piley", "pilin", "pilis",
- "pills", "pilon", "pilow", "pilum", "pilus",
- "pimas", "pimps", "pinas", "pinax", "pince",
- "pinda", "pinds", "pined", "piner", "pines",
- "pinga", "pinge", "pingo", "pings", "pinko",
- "pinks", "pinky", "pinna", "pinny", "pinol",
- "pinon", "pinot", "pinta", "pinto", "pints",
- "pinup", "pions", "piony", "pioye", "pioys",
- "pipal", "pipas", "piped", "pipes", "pipet",
- "pipid", "pipis", "pippy", "pipul", "pique",
- "piqui", "pirai", "pirks", "pirls", "pirns",
- "pirog", "pirre", "pirri", "pirrs", "pisco",
- "pises", "pisky", "pisos", "pissy", "piste",
- "pitas", "pitch", "piths", "pithy", "piton",
- "pitot", "pitso", "pitsu", "pitta", "pittu",
- "piuma", "piums", "pivos", "pivot", "pixes",
- "piyut", "pized", "pizer", "pizes", "plaas",
- "plack", "plaga", "plage", "plaig", "planc",
- "planh", "plans", "plaps", "plash", "plasm",
- "plast", "plats", "platt", "platy", "plaud",
- "plaur", "plavs", "playa", "plays", "pleas",
- "plebe", "plebs", "pleck", "pleep", "plein",
- "plena", "plene", "pleno", "pleon", "plesh",
- "plets", "plews", "plexi", "plica", "plies",
- "pligs", "plims", "pling", "plink", "plips",
- "plish", "ploat", "ploce", "plock", "plods",
- "ploit", "plomb", "plong", "plonk", "plook",
- "ploot", "plops", "plore", "plots", "plotz",
- "plouk", "plout", "plows", "plowt", "ploye",
- "ploys", "pluds", "plues", "pluff", "plugs",
- "pluke", "plums", "plumy", "plung", "pluot",
- "plups", "plute", "pluto", "pluty", "plyer",
- "pneus", "poach", "poaka", "poake", "poalo",
- "pobby", "poboy", "pocan", "poche", "pocho",
- "pocks", "pocky", "podal", "poddy", "podex",
- "podge", "podgy", "podia", "podos", "podus",
- "poems", "poena", "poeps", "poete", "poets",
- "pogey", "pogge", "poggy", "pogos", "pogue",
- "pohed", "poilu", "poind", "poire", "pokal",
- "poked", "pokes", "pokey", "pokie", "pokit",
- "poled", "poler", "poles", "poley", "polio",
- "polis", "polje", "polks", "pollo", "polls",
- "polly", "polos", "polts", "polys", "pomas",
- "pombe", "pomes", "pomme", "pommy", "pomos",
- "pompa", "pomps", "ponce", "poncy", "ponds",
- "pondy", "pones", "poney", "ponga", "pongo",
- "pongs", "pongy", "ponks", "ponor", "ponto",
- "ponts", "ponty", "ponzu", "pooay", "poods",
- "pooed", "pooey", "poofs", "poofy", "poohs",
- "poohy", "pooja", "pooka", "pooks", "pools",
- "pooly", "poons", "poopa", "poops", "poopy",
- "poori", "poort", "poots", "pooty", "poove",
- "poovy", "popes", "popia", "popos", "poppa",
- "popsy", "popup", "porae", "poral", "pored",
- "porer", "pores", "porey", "porge", "porgy",
- "porin", "porks", "porky", "porno", "porns",
- "porny", "porta", "porte", "porth", "ports",
- "porty", "porus", "posca", "posed", "poses",
- "poset", "posey", "posho", "posol", "poste",
- "posts", "potae", "potai", "potch", "poted",
- "potes", "potin", "potoo", "potro", "potsy",
- "potto", "potts", "potty", "pouce", "pouff",
- "poufs", "poufy", "pouis", "pouke", "pouks",
- "poule", "poulp", "poupe", "poupt", "pours",
- "pousy", "pouts", "povos", "powan", "powie",
- "powin", "powis", "powlt", "pownd", "powns",
- "powny", "powre", "powsy", "poxed", "poxes",
- "poyas", "poynt", "poyou", "poyse", "pozzy",
- "praam", "prads", "prags", "prahu", "prams",
- "prana", "prang", "praos", "praps", "prase",
- "prate", "prats", "pratt", "praty", "praus",
- "prays", "preak", "predy", "preed", "preem",
- "prees", "preif", "preke", "prems", "premy",
- "prent", "preon", "preop", "preps", "presa",
- "prese", "prest", "preta", "preux", "preve",
- "prexy", "preys", "prial", "prian", "pricy",
- "pridy", "prief", "prier", "pries", "prigs",
- "prill", "prima", "primi", "primp", "prims",
- "primy", "pring", "prink", "prion", "prise",
- "priss", "prius", "proal", "proas", "probs",
- "proby", "prodd", "prods", "proem", "profs",
- "progs", "proin", "proke", "prole", "proll",
- "promo", "proms", "pronk", "prook", "proot",
- "props", "prora", "prore", "proso", "pross",
- "prost", "prosy", "proto", "proul", "prowk",
- "prows", "proxy", "proyn", "pruno", "prunt",
- "pruny", "pruta", "pryan", "pryer", "pryse",
- "pseud", "pshaw", "pshut", "psias", "psion",
- "psoae", "psoai", "psoas", "psora", "psych",
- "psyop", "ptish", "ptype", "pubby", "pubco",
- "pubes", "pubis", "pubsy", "pucan", "pucer",
- "puces", "pucka", "pucks", "puddy", "pudge",
- "pudic", "pudor", "pudsy", "pudus", "puers",
- "puffa", "puffs", "puffy", "puggy", "pugil",
- "puhas", "pujah", "pujas", "pukas", "puked",
- "puker", "pukes", "pukey", "pukka", "pukus",
- "pulao", "pulas", "puled", "puler", "pules",
- "pulik", "pulis", "pulka", "pulks", "pulli",
- "pulls", "pully", "pulmo", "pulps", "pulpy",
- "pulus", "pulut", "pumas", "pumie", "pumps",
- "pumpy", "punas", "punce", "punga", "pungi",
- "pungo", "pungs", "pungy", "punim", "punji",
- "punka", "punks", "punky", "punny", "punto",
- "punts", "punty", "pupae", "pupal", "pupas",
- "puppa", "pupus", "purao", "purau", "purda",
- "purdy", "pured", "purer", "pures", "purga",
- "purin", "puris", "purls", "puros", "purps",
- "purpy", "purre", "purrs", "purry", "pursy",
- "purty", "puses", "pusle", "pussy", "putas",
- "puter", "putid", "putin", "puton", "putos",
- "putti", "putto", "putts", "puttu", "putza",
- "puuko", "puyas", "puzel", "puzta", "pwned",
- "pyats", "pyets", "pygal", "pyins", "pylon",
- "pyned", "pynes", "pyoid", "pyots", "pyral",
- "pyran", "pyres", "pyrex", "pyric", "pyros",
- "pyrus", "pyuff", "pyxed", "pyxes", "pyxie",
- "pyxis", "pzazz", "qadis", "qaids", "qajaq",
- "qanat", "qapik", "qibla", "qilas", "qipao",
- "qophs", "qorma", "quabs", "quads", "quags",
- "quair", "quais", "quaky", "quale", "qualy",
- "quank", "quant", "quare", "quark", "quarl",
- "quash", "quass", "quate", "quats", "quawk",
- "quaws", "quayd", "quays", "qubit", "quean",
- "queck", "queek", "queem", "quell", "queme",
- "quena", "quern", "queso", "quete", "queyn",
- "queys", "queyu", "quibs", "quich", "quids",
- "quies", "quiff", "quila", "quilt", "quims",
- "quina", "quine", "quink", "quino", "quins",
- "quint", "quipo", "quips", "quipu", "quire",
- "quirl", "quirt", "quist", "quits", "quoad",
- "quods", "quoif", "quoin", "quois", "quoit",
- "quoll", "quonk", "quops", "quork", "quorl",
- "quouk", "quoys", "quran", "qursh", "quyte",
- "raads", "raake", "rabat", "rabic", "rabis",
- "raced", "races", "rache", "racks", "racon",
- "raddi", "raddy", "radge", "radgy", "radif",
- "radix", "rafee", "raffs", "raffy", "rafik",
- "rafiq", "rafts", "rafty", "ragas", "ragde",
- "raged", "ragee", "rager", "rages", "ragga",
- "raggs", "raggy", "ragis", "ragus", "rahed",
- "rahui", "raiah", "raias", "raids", "raike",
- "raiks", "raile", "rails", "raine", "rains",
- "rainy", "raird", "raise", "raita", "raith",
- "raits", "rajah", "rajas", "rajes", "raked",
- "rakee", "raker", "rakes", "rakhi", "rakia",
- "rakis", "rakki", "raksi", "rakus", "rales",
- "ralli", "ralph", "ramal", "ramee", "rames",
- "ramet", "ramie", "ramin", "ramis", "rammy",
- "ramon", "ramps", "ramse", "ramsh", "ramus",
- "ranas", "rance", "rando", "rands", "raned",
- "ranee", "ranes", "ranga", "rangi", "rangs",
- "rangy", "ranid", "ranis", "ranke", "ranks",
- "ranns", "ranny", "ranse", "rants", "ranty",
- "raped", "rapee", "raper", "rapes", "raphe",
- "rapin", "rappe", "rapso", "rared", "raree",
- "rares", "rarks", "rasam", "rasas", "rased",
- "raser", "rases", "rasps", "rasse", "rasta",
- "ratal", "ratan", "ratas", "ratch", "rated",
- "ratel", "rater", "rates", "ratha", "rathe",
- "raths", "ratoo", "ratos", "ratti", "ratty",
- "ratus", "rauli", "rauns", "raupo", "raved",
- "ravel", "raver", "raves", "ravey", "ravin",
- "rawdy", "rawer", "rawin", "rawks", "rawly",
- "rawns", "raxed", "raxes", "rayah", "rayas",
- "rayed", "rayle", "rayls", "rayne", "razai",
- "razed", "razee", "razer", "razes", "razet",
- "razoo", "readd", "reads", "reais", "reaks",
- "realo", "reals", "reame", "reams", "reamy",
- "reans", "reaps", "reard", "rearm", "rears",
- "reast", "reata", "reate", "reave", "rebab",
- "rebar", "rebbe", "rebec", "rebid", "rebit",
- "rebop", "rebud", "rebuy", "recal", "recce",
- "recco", "reccy", "recep", "recit", "recks",
- "recon", "recta", "recte", "recti", "recto",
- "recue", "recut", "redan", "redds", "reddy",
- "reded", "redes", "redia", "redid", "redif",
- "redig", "redip", "redly", "redon", "redos",
- "redox", "redry", "redub", "redug", "redux",
- "redye", "reeaf", "reech", "reede", "reeds",
- "reefs", "reefy", "reeks", "reeky", "reels",
- "reely", "reems", "reens", "reerd", "reest",
- "reeve", "reeze", "refan", "refed", "refel",
- "reffo", "refis", "refit", "refix", "refly",
- "refry", "regar", "reges", "reget", "regex",
- "reggo", "regia", "regie", "regle", "regma",
- "regna", "regos", "regot", "regur", "rehem",
- "reifs", "reify", "reiki", "reiks", "reine",
- "reing", "reink", "reins", "reird", "reist",
- "reive", "rejas", "rejig", "rejon", "reked",
- "rekes", "rekey", "relet", "relie", "relit",
- "rello", "relos", "reman", "remap", "remen",
- "remet", "remex", "remix", "remou", "renay",
- "rends", "rendu", "reney", "renga", "rengs",
- "renig", "renin", "renks", "renne", "renos",
- "rente", "rents", "reoil", "reorg", "repas",
- "repat", "repeg", "repen", "repin", "repla",
- "repos", "repot", "repps", "repro", "repun",
- "reput", "reran", "rerig", "resam", "resat",
- "resaw", "resay", "resee", "reses", "resew",
- "resid", "resit", "resod", "resol", "resow",
- "resto", "rests", "resty", "resue", "resus",
- "retag", "retam", "retax", "retem", "retia",
- "retie", "retin", "retip", "retox", "reune",
- "reups", "revet", "revie", "revow", "revue",
- "rewan", "rewax", "rewed", "rewet", "rewin",
- "rewon", "rewth", "rexes", "rezes", "rhabd",
- "rheas", "rheid", "rheme", "rheum", "rhies",
- "rhime", "rhine", "rhino", "rhody", "rhomb",
- "rhone", "rhumb", "rhyme", "rhymy", "rhyne",
- "rhyta", "riads", "rials", "riant", "riata",
- "riato", "ribas", "ribby", "ribes", "riced",
- "ricer", "rices", "ricey", "riche", "richt",
- "ricin", "ricks", "rides", "ridgy", "ridic",
- "riels", "riems", "rieve", "rifer", "riffs",
- "riffy", "rifte", "rifts", "rifty", "riggs",
- "rigmo", "rigol", "rikka", "rikwa", "riled",
- "riles", "riley", "rille", "rills", "rilly",
- "rimae", "rimed", "rimer", "rimes", "rimon",
- "rimus", "rince", "rinds", "rindy", "rines",
- "ringe", "rings", "ringy", "rinks", "rioja",
- "rione", "riots", "rioty", "riped", "ripes",
- "ripps", "riqqs", "riser", "rises", "rishi",
- "risks", "risps", "rists", "risus", "rites",
- "rithe", "ritts", "ritzy", "rivas", "rived",
- "rivel", "riven", "rives", "riyal", "rizas",
- "roads", "roady", "roake", "roaky", "roams",
- "roans", "roany", "roars", "roary", "roate",
- "robbo", "robed", "rober", "robes", "roble",
- "robug", "robur", "roche", "rocks", "roded",
- "rodeo", "rodes", "rodny", "roers", "rogan",
- "roger", "roguy", "rohan", "rohes", "rohun",
- "rohus", "roids", "roils", "roily", "roins",
- "roist", "rojak", "rojis", "roked", "roker",
- "rokes", "rokey", "rokos", "rolag", "roleo",
- "roles", "rolfs", "rolls", "rolly", "romal",
- "roman", "romeo", "romer", "romps", "rompu",
- "rompy", "ronde", "rondo", "roneo", "rones",
- "ronin", "ronne", "ronte", "ronts", "ronuk",
- "roods", "roofs", "roofy", "rooks", "rooky",
- "rooms", "roons", "roops", "roopy", "roosa",
- "roose", "roots", "rooty", "roped", "roper",
- "ropes", "ropey", "roque", "roral", "rores",
- "roric", "rorid", "rorie", "rorts", "rorty",
- "rosal", "rosco", "rosed", "roses", "roset",
- "rosha", "roshi", "rosin", "rosit", "rosps",
- "rossa", "rosso", "rosti", "rosts", "rotal",
- "rotan", "rotas", "rotch", "roted", "rotes",
- "rotis", "rotls", "roton", "rotor", "rotos",
- "rotta", "rotte", "rotto", "rotty", "rouen",
- "roues", "rouet", "roufs", "rougy", "rouks",
- "rouky", "roule", "rouls", "roums", "roups",
- "roupy", "roust", "routh", "routs", "roved",
- "roven", "roves", "rowan", "rowed", "rowel",
- "rowen", "rowet", "rowie", "rowme", "rownd",
- "rowns", "rowth", "rowts", "royet", "royne",
- "royst", "rozes", "rozet", "rozit", "ruach",
- "ruana", "rubai", "ruban", "rubby", "rubel",
- "rubes", "rubin", "rubio", "ruble", "rubli",
- "rubor", "rubus", "ruche", "ruchy", "rucks",
- "rudas", "rudds", "ruder", "rudes", "rudie",
- "rudis", "rueda", "ruers", "ruffe", "ruffs",
- "ruffy", "rufus", "rugae", "rugal", "rugas",
- "ruggy", "ruice", "ruing", "ruins", "rukhs",
- "ruled", "rules", "rully", "rumal", "rumbo",
- "rumen", "rumes", "rumly", "rummy", "rumpo",
- "rumps", "rumpy", "runce", "runch", "runds",
- "runed", "runer", "runes", "rungs", "runic",
- "runny", "runos", "runts", "runty", "runup",
- "ruote", "rupia", "rurps", "rurus", "rusas",
- "ruses", "rushy", "rusks", "rusky", "rusma",
- "russe", "rusts", "ruths", "rutin", "rutty",
- "ruvid", "ryals", "rybat", "ryiji", "ryijy",
- "ryked", "rykes", "rymer", "rymme", "rynds",
- "ryoti", "ryots", "ryper", "rypin", "rythe",
- "ryugi", "saags", "sabal", "sabed", "saber",
- "sabes", "sabha", "sabin", "sabir", "sabji",
- "sable", "sabos", "sabot", "sabra", "sabre",
- "sabzi", "sacks", "sacra", "sacre", "saddo",
- "saddy", "sades", "sadhe", "sadhu", "sadic",
- "sadis", "sados", "sadza", "saeta", "safed",
- "safes", "sagar", "sagas", "sager", "sages",
- "saggy", "sagos", "sagum", "sahab", "saheb",
- "sahib", "saice", "saick", "saics", "saids",
- "saiga", "sails", "saims", "saine", "sains",
- "sairs", "saist", "saith", "sajou", "sakai",
- "saker", "sakes", "sakia", "sakis", "sakti",
- "salal", "salas", "salat", "salep", "sales",
- "salet", "salic", "salis", "salix", "salle",
- "salmi", "salol", "salop", "salpa", "salps",
- "salse", "salto", "salts", "salud", "salue",
- "salut", "saman", "samas", "samba", "sambo",
- "samek", "samel", "samen", "sames", "samey",
- "samfi", "samfu", "sammy", "sampi", "samps",
- "sanad", "sands", "saned", "sanes", "sanga",
- "sangh", "sango", "sangs", "sanko", "sansa",
- "santo", "sants", "saola", "sapan", "sapid",
- "sapor", "saran", "sards", "sared", "saree",
- "sarge", "sargo", "sarin", "sarir", "saris",
- "sarks", "sarky", "sarod", "saros", "sarus",
- "sarvo", "saser", "sasin", "sasse", "satai",
- "satay", "sated", "satem", "sater", "sates",
- "satin", "satis", "satyr", "sauba", "sauch",
- "saugh", "sauls", "sault", "saunf", "saunt",
- "saury", "sauts", "sauve", "saved", "saver",
- "saves", "savey", "savin", "sawah", "sawed",
- "sawer", "saxes", "sayas", "sayed", "sayee",
- "sayer", "sayid", "sayne", "sayon", "sayst",
- "sazes", "scabs", "scads", "scaff", "scags",
- "scail", "scala", "scall", "scams", "scand",
- "scans", "scapa", "scape", "scapi", "scarp",
- "scars", "scart", "scath", "scats", "scatt",
- "scaud", "scaup", "scaur", "scaws", "sceat",
- "scena", "scend", "schav", "schif", "schmo",
- "schul", "schwa", "scifi", "scind", "scire",
- "sclim", "scobe", "scody", "scogs", "scoog",
- "scoot", "scopa", "scops", "scorp", "scote",
- "scots", "scoug", "scoup", "scour", "scowp",
- "scows", "scrab", "scrae", "scrag", "scran",
- "scrat", "scraw", "scray", "scree", "screw",
- "scrim", "scrip", "scrob", "scrod", "scrog",
- "scroo", "scrow", "scuba", "scudi", "scudo",
- "scuds", "scuff", "scuft", "scugs", "sculk",
- "scull", "sculp", "sculs", "scums", "scups",
- "scurf", "scurs", "scuse", "scuta", "scute",
- "scuts", "scuzz", "scyes", "sdayn", "sdein",
- "seals", "seame", "seams", "seamy", "seans",
- "seare", "sears", "sease", "seats", "seaze",
- "sebum", "secco", "sechs", "sects", "seder",
- "sedes", "sedge", "sedgy", "sedum", "seeds",
- "seeks", "seeld", "seels", "seely", "seems",
- "seeps", "seepy", "seers", "sefer", "segar",
- "segas", "segni", "segno", "segol", "segos",
- "sehri", "seifs", "seils", "seine", "seirs",
- "seise", "seism", "seity", "seiza", "sekos",
- "sekts", "selah", "seles", "selfs", "selfy",
- "selky", "sella", "selle", "sells", "selva",
- "semas", "semee", "semen", "semes", "semie",
- "semis", "senas", "sends", "senes", "senex",
- "sengi", "senna", "senor", "sensa", "sensi",
- "sensu", "sente", "senti", "sents", "senvy",
- "senza", "sepad", "sepal", "sepic", "sepoy",
- "seppo", "septa", "septs", "serac", "serai",
- "seral", "sered", "serer", "seres", "serfs",
- "serge", "seria", "seric", "serif", "serin",
- "serir", "serks", "seron", "serow", "serra",
- "serre", "serrs", "serry", "serum", "servo",
- "sesey", "sessa", "setae", "setal", "seter",
- "seths", "seton", "setts", "sevak", "sevir",
- "sewan", "sewar", "sewed", "sewel", "sewen",
- "sewin", "sexed", "sexer", "sexes", "sexor",
- "sexto", "sexts", "seyen", "sezes", "shads",
- "shags", "shahs", "shaka", "shako", "shakt",
- "shale", "shalm", "shalt", "shaly", "shama",
- "shams", "shand", "shans", "shaps", "sharn",
- "shart", "shash", "shaul", "shawm", "shawn",
- "shaws", "shaya", "shays", "shchi", "sheaf",
- "sheal", "sheas", "sheds", "sheel", "sheik",
- "shend", "sheng", "shent", "sheol", "sherd",
- "shere", "shero", "shets", "sheva", "shewn",
- "shews", "shiai", "shied", "shiel", "shier",
- "shies", "shill", "shily", "shims", "shins",
- "shiok", "ships", "shirr", "shirs", "shish",
- "shiso", "shist", "shite", "shits", "shiur",
- "shiva", "shive", "shivs", "shlep", "shlub",
- "shmek", "shmoe", "shoat", "shoed", "shoer",
- "shoes", "shogi", "shogs", "shoji", "shojo",
- "shola", "shonk", "shool", "shoon", "shoos",
- "shope", "shops", "shorl", "shote", "shots",
- "shott", "shoud", "showd", "shows", "shoyu",
- "shred", "shrew", "shris", "shrow", "shtar",
- "shtik", "shtum", "shtup", "shuba", "shule",
- "shuln", "shuls", "shuns", "shura", "shush",
- "shute", "shuts", "shwas", "shyer", "shyly",
- "sials", "sibbs", "sibia", "sibyl", "sices",
- "sicht", "sicko", "sicks", "sicky", "sidas",
- "sided", "sider", "sides", "sidey", "sidha",
- "sidhe", "sidle", "sield", "siens", "sient",
- "sieth", "sieur", "sifts", "sighs", "sigil",
- "sigla", "signa", "signs", "sigri", "sijos",
- "sikas", "siker", "sikes", "silds", "siled",
- "silen", "siler", "siles", "silex", "silks",
- "sills", "silos", "silts", "silty", "silva",
- "simar", "simas", "simba", "simis", "simps",
- "simul", "sinds", "sined", "sines", "singe",
- "sings", "sinhs", "sinks", "sinky", "sinsi",
- "sinus", "siped", "sipes", "sippy", "sired",
- "siree", "sires", "sirih", "siris", "siroc",
- "sirra", "sirup", "sisal", "sises", "sista",
- "sists", "sitar", "sitch", "sited", "sites",
- "sithe", "sitka", "situp", "situs", "siver",
- "sixer", "sixes", "sixmo", "sixte", "sizar",
- "sizel", "sizer", "sizes", "skags", "skail",
- "skald", "skank", "skarn", "skart", "skats",
- "skatt", "skaws", "skean", "skear", "skeds",
- "skeed", "skeef", "skeen", "skeer", "skees",
- "skeet", "skeev", "skeez", "skegg", "skegs",
- "skein", "skelf", "skell", "skelm", "skelp",
- "skene", "skens", "skeos", "skeps", "skerm",
- "skers", "skets", "skews", "skids", "skied",
- "skies", "skiey", "skiff", "skill", "skimo",
- "skims", "skink", "skins", "skint", "skios",
- "skips", "skirl", "skirr", "skite", "skits",
- "skive", "skivy", "sklim", "skoal", "skobe",
- "skody", "skoff", "skofs", "skogs", "skols",
- "skool", "skort", "skosh", "skran", "skrik",
- "skroo", "skuas", "skugs", "skulk", "skyed",
- "skyer", "skyey", "skyfs", "skyre", "skyrs",
- "skyte", "slabs", "slade", "slaes", "slags",
- "slaid", "slake", "slams", "slane", "slank",
- "slaps", "slart", "slats", "slaty", "slaws",
- "slays", "slebs", "sleds", "sleer", "slews",
- "sleys", "slick", "slier", "slily", "slims",
- "slipe", "slips", "slipt", "slish", "slits",
- "slive", "sloan", "slobs", "sloes", "slogs",
- "sloid", "slojd", "sloka", "slomo", "sloom",
- "sloop", "sloot", "slops", "slopy", "slorm",
- "slosh", "slots", "slove", "slows", "sloyd",
- "slubb", "slubs", "slued", "slues", "sluff",
- "slugs", "sluit", "slums", "slurb", "slurs",
- "sluse", "slush", "sluts", "slyer", "slyly",
- "slype", "smaak", "smaik", "smalm", "smalt",
- "smarm", "smaze", "smeek", "smees", "smeik",
- "smeke", "smerk", "smews", "smick", "smily",
- "smirr", "smirs", "smits", "smize", "smogs",
- "smoko", "smolt", "smoor", "smoot", "smore",
- "smorg", "smote", "smout", "smowt", "smugs",
- "smurs", "smush", "smuts", "snabs", "snafu",
- "snags", "snaps", "snarf", "snark", "snars",
- "snary", "snash", "snath", "snaws", "snead",
- "sneap", "snebs", "sneck", "sneds", "sneed",
- "snees", "snell", "snibs", "snick", "snied",
- "snies", "snift", "snigs", "snips", "snipy",
- "snirt", "snits", "snive", "snobs", "snods",
- "snoek", "snoep", "snogs", "snoke", "snood",
- "snook", "snool", "snoot", "snots", "snowk",
- "snows", "snubs", "snugs", "snush", "snyes",
- "soaks", "soaps", "soare", "soars", "soave",
- "sobas", "socas", "soces", "socia", "socko",
- "socks", "socle", "sodas", "soddy", "sodic",
- "sodom", "sofar", "sofas", "softa", "softs",
- "softy", "soger", "soggy", "sohur", "soils",
- "soily", "sojas", "sojus", "sokah", "soken",
- "sokes", "sokol", "solah", "solan", "solas",
- "solde", "soldi", "soldo", "solds", "soled",
- "solei", "soler", "soles", "solon", "solos",
- "solum", "solus", "soman", "somas", "sonar",
- "sonce", "sonde", "sones", "songo", "songs",
- "songy", "sonly", "sonne", "sonny", "sonse",
- "sonsy", "sooey", "sooks", "sooky", "soole",
- "sools", "sooms", "soops", "soote", "soots",
- "sophs", "sophy", "sopor", "soppy", "sopra",
- "soral", "soras", "sorbi", "sorbo", "sorbs",
- "sorda", "sordo", "sords", "sored", "soree",
- "sorel", "sorer", "sores", "sorex", "sorgo",
- "sorns", "sorra", "sorta", "sorts", "sorus",
- "soths", "sotol", "sotto", "souce", "souct",
- "sough", "souks", "souls", "souly", "soums",
- "soups", "soupy", "sours", "souse", "souts",
- "sowar", "sowce", "sowed", "sower", "sowff",
- "sowfs", "sowle", "sowls", "sowms", "sownd",
- "sowne", "sowps", "sowse", "sowth", "soxes",
- "soyas", "soyle", "soyuz", "sozin", "spack",
- "spacy", "spado", "spads", "spaed", "spaer",
- "spaes", "spags", "spahi", "spail", "spain",
- "spait", "spake", "spald", "spale", "spall",
- "spalt", "spams", "spane", "spang", "spans",
- "spard", "spars", "spart", "spate", "spats",
- "spaul", "spawl", "spaws", "spayd", "spays",
- "spaza", "spazz", "speal", "spean", "speat",
- "specs", "spect", "speel", "speer", "speil",
- "speir", "speks", "speld", "spelk", "spelt",
- "speos", "sperm", "spesh", "spets", "speug",
- "spews", "spewy", "spial", "spica", "spick",
- "spics", "spide", "spiel", "spier", "spies",
- "spiff", "spifs", "spiks", "spile", "spilt",
- "spims", "spina", "spink", "spins", "spiny",
- "spire", "spirt", "spiry", "spits", "spitz",
- "spivs", "splay", "splog", "spode", "spods",
- "spoom", "spoor", "spoot", "spork", "sposa",
- "sposh", "sposo", "spots", "sprad", "sprag",
- "sprat", "spred", "sprew", "sprit", "sprod",
- "sprog", "sprue", "sprug", "spuds", "spued",
- "spuer", "spues", "spugs", "spule", "spume",
- "spumy", "spurs", "spurt", "sputa", "spyal",
- "spyre", "squab", "squaw", "squee", "squeg",
- "squib", "squit", "squiz", "srsly", "stabs",
- "stade", "stags", "stagy", "staig", "stane",
- "stang", "stans", "staps", "starn", "starr",
- "stars", "stary", "stats", "statu", "staun",
- "staws", "stays", "stean", "stear", "stedd",
- "stede", "steds", "steed", "steek", "steem",
- "steen", "steez", "steik", "steil", "stein",
- "stela", "stele", "stell", "steme", "stems",
- "stend", "steno", "stens", "stent", "steps",
- "stept", "stere", "stets", "stews", "stewy",
- "steys", "stich", "stied", "sties", "stilb",
- "stile", "stime", "stims", "stimy", "stipa",
- "stipe", "stire", "stirk", "stirp", "stirs",
- "stive", "stivy", "stoae", "stoai", "stoas",
- "stoat", "stobs", "stoep", "stogs", "stogy",
- "stoit", "stoln", "stoma", "stond", "stong",
- "stonk", "stonn", "stook", "stoor", "stope",
- "stops", "stopt", "stoss", "stots", "stott",
- "stoun", "stoup", "stour", "stown", "stowp",
- "stows", "strad", "strae", "strag", "strak",
- "strep", "stria", "strig", "strim", "strop",
- "strow", "stroy", "strum", "strut", "stubs",
- "stucs", "stude", "studs", "stull", "stulm",
- "stumm", "stums", "stuns", "stupa", "stupe",
- "sture", "sturt", "stush", "styed", "styes",
- "styli", "stylo", "styme", "stymy", "styre",
- "styte", "subah", "subak", "subas", "subby",
- "suber", "subha", "succi", "sucks", "sucky",
- "sucre", "sudan", "sudds", "sudor", "sudsy",
- "suede", "suent", "suers", "suete", "suets",
- "suety", "sugan", "sughs", "sugos", "suhur",
- "suids", "suint", "suits", "sujee", "sukhs",
- "sukis", "sukuk", "sulci", "sulfa", "sulfo",
- "sulks", "sulls", "sully", "sulph", "sulus",
- "sumac", "sumis", "summa", "sumos", "sumph",
- "sumps", "sunis", "sunks", "sunna", "sunns",
- "sunts", "sunup", "suona", "suped", "supes",
- "supra", "surah", "sural", "suras", "surat",
- "surds", "sured", "surer", "sures", "surfs",
- "surfy", "surgy", "surra", "sused", "suses",
- "susus", "sutor", "sutra", "sutta", "swabs",
- "swack", "swads", "swage", "swags", "swail",
- "swain", "swale", "swaly", "swami", "swamy",
- "swang", "swank", "swans", "swaps", "swapt",
- "sward", "sware", "swarf", "swart", "swash",
- "swats", "swayl", "sways", "sweal", "swede",
- "sweed", "sweel", "sweer", "swees", "sweir",
- "swelt", "swerf", "sweys", "swies", "swigs",
- "swile", "swims", "swink", "swire", "swiss",
- "swith", "swits", "swive", "swizz", "swobs",
- "swole", "swoll", "swoln", "swops", "swopt",
- "swots", "swoun", "sybbe", "sybil", "syboe",
- "sybow", "sycee", "syces", "sycon", "syeds",
- "syens", "syker", "sykes", "sylis", "sylph",
- "sylva", "symar", "synch", "syncs", "synds",
- "syned", "synes", "synth", "syped", "sypes",
- "syphs", "syrah", "syren", "sysop", "sythe",
- "syver", "taals", "taata", "tabac", "taber",
- "tabes", "tabid", "tabis", "tabla", "tabls",
- "tabor", "tabos", "tabun", "tabus", "tacan",
- "taces", "tacet", "tache", "tachi", "tacho",
- "tachs", "tacks", "tacos", "tacts", "tadah",
- "taels", "tafia", "taggy", "tagma", "tagua",
- "tahas", "tahrs", "taiga", "taigs", "taiko",
- "tails", "tains", "taira", "taish", "taits",
- "tajes", "takas", "takes", "takhi", "takht",
- "takin", "takis", "takky", "talak", "talaq",
- "talar", "talas", "talcs", "talcy", "talea",
- "taler", "tales", "talik", "talks", "talky",
- "talls", "tally", "talma", "talpa", "taluk",
- "talus", "tamal", "tamas", "tamed", "tames",
- "tamin", "tamis", "tammy", "tamps", "tanas",
- "tanga", "tangi", "tangs", "tanhs", "tania",
- "tanka", "tanks", "tanky", "tanna", "tansu",
- "tansy", "tante", "tanti", "tanto", "tanty",
- "tapas", "taped", "tapen", "tapes", "tapet",
- "tapis", "tappa", "tapus", "taras", "tardo",
- "tards", "tared", "tares", "targa", "targe",
- "tarka", "tarns", "taroc", "tarok", "taros",
- "tarps", "tarre", "tarry", "tarse", "tarsi",
- "tarte", "tarts", "tarty", "tarzy", "tasar",
- "tasca", "tased", "taser", "tases", "tasks",
- "tassa", "tasse", "tasso", "tasto", "tatar",
- "tater", "tates", "taths", "tatie", "tatou",
- "tatts", "tatus", "taube", "tauld", "tauon",
- "taupe", "tauts", "tauty", "tavah", "tavas",
- "taver", "tawaf", "tawai", "tawas", "tawed",
- "tawer", "tawie", "tawse", "tawts", "taxed",
- "taxer", "taxes", "taxis", "taxol", "taxon",
- "taxor", "taxus", "tayra", "tazza", "tazze",
- "teade", "teads", "teaed", "teaks", "teals",
- "teams", "tears", "teats", "teaze", "techs",
- "techy", "tecta", "tecum", "teels", "teems",
- "teend", "teene", "teens", "teeny", "teers",
- "teets", "teffs", "teggs", "tegua", "tegus",
- "tehee", "tehrs", "teiid", "teils", "teind",
- "teins", "tekke", "telae", "telco", "teles",
- "telex", "telia", "telic", "tells", "telly",
- "teloi", "telos", "temed", "temes", "tempi",
- "temps", "tempt", "temse", "tench", "tends",
- "tendu", "tenes", "tenge", "tenia", "tenne",
- "tenno", "tenny", "tenon", "tents", "tenty",
- "tenue", "tepal", "tepas", "tepoy", "terai",
- "teras", "terce", "terek", "teres", "terfe",
- "terfs", "terga", "terms", "terne", "terns",
- "terre", "terry", "terts", "terza", "tesla",
- "testa", "teste", "tests", "testy", "tetes",
- "teths", "tetra", "tetri", "teuch", "teugh",
- "tewed", "tewel", "tewit", "texas", "texes",
- "texta", "texts", "thack", "thagi", "thaim",
- "thale", "thali", "thana", "thane", "thang",
- "thank", "thans", "thanx", "tharm", "thars",
- "thaws", "thawt", "thawy", "thebe", "theca",
- "theed", "theek", "thees", "thegn", "theic",
- "thein", "thelf", "thema", "thens", "theor",
- "theow", "therm", "these", "thesp", "theta",
- "thete", "thews", "thewy", "thigs", "thilk",
- "thill", "thine", "thins", "thiol", "thirl",
- "thoft", "thole", "tholi", "thong", "thoro",
- "thorp", "thots", "thous", "thowl", "thrae",
- "thraw", "thrid", "thrip", "throe", "thuds",
- "thugs", "thuja", "thunk", "thurl", "thuya",
- "thymi", "thymy", "tians", "tiare", "tiars",
- "tibia", "tical", "ticca", "ticed", "tices",
- "tichy", "ticks", "ticky", "tiddy", "tided",
- "tides", "tiefs", "tiers", "tiffs", "tifos",
- "tifts", "tiges", "tigon", "tikas", "tikes",
- "tikia", "tikis", "tikka", "tilak", "tilde",
- "tiled", "tiler", "tiles", "tills", "tilly",
- "tilth", "tilts", "timbo", "timed", "times",
- "timon", "timps", "tinas", "tinct", "tinds",
- "tinea", "tined", "tines", "tinge", "tings",
- "tinks", "tinny", "tinto", "tints", "tinty",
- "tipis", "tippy", "tipup", "tired", "tires",
- "tirls", "tiros", "tirrs", "tirth", "titar",
- "titas", "titch", "titer", "tithe", "tithi",
- "titin", "titir", "titis", "titre", "titty",
- "titup", "tiyin", "tiyns", "tizes", "tizzy",
- "toads", "toady", "toaze", "tocks", "tocky",
- "tocos", "todde", "toddy", "todea", "todos",
- "toeas", "toffs", "toffy", "tofts", "tofus",
- "togae", "togas", "toged", "toges", "togue",
- "tohos", "toidy", "toile", "toils", "toing",
- "toise", "toits", "toity", "tokay", "toked",
- "toker", "tokes", "tokos", "tolan", "tolar",
- "tolas", "toled", "toles", "tolls", "tolly",
- "tolts", "tolus", "tolyl", "toman", "tombo",
- "tombs", "tomen", "tomes", "tomia", "tomin",
- "tomme", "tommy", "tomos", "tomoz", "tondi",
- "tondo", "toned", "toner", "tones", "toney",
- "tonga", "tonka", "tonks", "tonne", "tonus",
- "tools", "tooms", "toons", "toots", "toped",
- "topee", "topek", "toper", "topes", "tophe",
- "tophi", "tophs", "topis", "topoi", "topos",
- "toppy", "toque", "torah", "toran", "toras",
- "torcs", "tores", "toric", "torii", "toros",
- "torot", "torrs", "torse", "torsi", "torsk",
- "torta", "torte", "torts", "torus", "tosas",
- "tosed", "toses", "toshy", "tossy", "tosyl",
- "toted", "toter", "totes", "totty", "touks",
- "touns", "tours", "touse", "tousy", "touts",
- "touze", "touzy", "towai", "towed", "towie",
- "towno", "towns", "towny", "towse", "towsy",
- "towts", "towze", "towzy", "toxin", "toyed",
- "toyer", "toyon", "toyos", "tozed", "tozes",
- "tozie", "trabs", "tract", "trads", "trady",
- "traga", "tragi", "trags", "tragu", "traik",
- "trams", "trank", "tranq", "trans", "trant",
- "trape", "trapo", "traps", "trapt", "trass",
- "trats", "tratt", "trave", "trayf", "trays",
- "treck", "treed", "treen", "trees", "trefa",
- "treif", "treks", "trema", "trems", "tress",
- "trest", "trets", "trews", "treyf", "treys",
- "triac", "trice", "tride", "trier", "tries",
- "trifa", "triff", "trigo", "trigs", "trike",
- "trild", "trims", "trine", "trins", "triol",
- "trior", "trios", "tripe", "trips", "tripy",
- "trist", "troad", "troak", "troat", "trock",
- "trode", "trods", "trogs", "trois", "troke",
- "tromp", "trona", "tronc", "trone", "tronk",
- "trons", "trooz", "tropo", "trots", "trove",
- "trows", "troys", "trued", "truer", "trues",
- "trugo", "trugs", "trull", "tryer", "tryke",
- "tryma", "tryps", "tsade", "tsadi", "tsars",
- "tsked", "tsuba", "tsubo", "tuans", "tuart",
- "tuath", "tubae", "tubal", "tubar", "tubas",
- "tubby", "tubed", "tuber", "tubes", "tucks",
- "tufas", "tuffe", "tuffs", "tufts", "tufty",
- "tugra", "tuile", "tuina", "tuism", "tuktu",
- "tules", "tulle", "tulpa", "tulps", "tulsi",
- "tumid", "tummy", "tumps", "tumpy", "tunas",
- "tunds", "tuned", "tunes", "tungs", "tunny",
- "tupek", "tupik", "tuple", "tuque", "turds",
- "turfs", "turfy", "turks", "turme", "turms",
- "turns", "turnt", "turon", "turps", "turrs",
- "tushy", "tusks", "tusky", "tutee", "tutes",
- "tutti", "tutty", "tutus", "tuxes", "tuyer",
- "twaes", "twals", "twank", "twats", "tways",
- "tweel", "tween", "tweep", "tweer", "twerk",
- "twerp", "twier", "twigs", "twilt", "twink",
- "twins", "twiny", "twire", "twirk", "twirl",
- "twirp", "twite", "twits", "twixt", "twocs",
- "twoer", "twonk", "twyer", "tyees", "tyers",
- "tyiyn", "tykes", "tyler", "tymps", "tynde",
- "tyned", "tynes", "typal", "typed", "types",
- "typey", "typic", "typos", "typps", "typto",
- "tyran", "tyred", "tyres", "tyros", "tythe",
- "tzars", "ubacs", "ubity", "udals", "udons",
- "udyog", "ugali", "ugged", "uhlan", "uhuru",
- "ukase", "ulama", "ulans", "ulema", "ulmin",
- "ulmos", "ulnad", "ulnae", "ulnar", "ulnas",
- "ulpan", "ulvas", "ulyie", "ulzie", "umami",
- "umbel", "umber", "umble", "umbos", "umbre",
- "umiac", "umiak", "umiaq", "ummah", "ummas",
- "ummed", "umped", "umphs", "umpie", "umpty",
- "umrah", "umras", "unagi", "unais", "unapt",
- "unarm", "unary", "unaus", "unbag", "unban",
- "unbar", "unbed", "unbid", "unbox", "uncap",
- "unces", "uncia", "uncos", "uncoy", "uncus",
- "undam", "undee", "undos", "undug", "uneth",
- "unfix", "ungag", "unget", "ungod", "ungot",
- "ungum", "unhat", "unhip", "unica", "unios",
- "units", "unjam", "unked", "unket", "unkey",
- "unkid", "unkut", "unlap", "unlaw", "unlay",
- "unled", "unleg", "unlet", "unlid", "unmad",
- "unman", "unmew", "unmix", "unode", "unold",
- "unown", "unpay", "unpeg", "unpen", "unpin",
- "unply", "unpot", "unput", "unred", "unrid",
- "unrig", "unrip", "unsaw", "unsay", "unsee",
- "unsew", "unsex", "unsod", "unsub", "untag",
- "untax", "untin", "unwet", "unwit", "unwon",
- "upbow", "upbye", "updos", "updry", "upend",
- "upful", "upjet", "uplay", "upled", "uplit",
- "upped", "upran", "uprun", "upsee", "upsey",
- "uptak", "upter", "uptie", "uraei", "urali",
- "uraos", "urare", "urari", "urase", "urate",
- "urbex", "urbia", "urdee", "ureal", "ureas",
- "uredo", "ureic", "ureid", "urena", "urent",
- "urged", "urger", "urges", "urial", "urine",
- "urite", "urman", "urnal", "urned", "urped",
- "ursae", "ursid", "urson", "urubu", "urupa",
- "urvas", "usens", "users", "useta", "usnea",
- "usnic", "usque", "ustad", "uster", "usure",
- "usury", "uteri", "utile", "uveal", "uveas",
- "uvula", "vacas", "vacay", "vacua", "vacui",
- "vacuo", "vadas", "vaded", "vades", "vadge",
- "vagal", "vagus", "vaids", "vails", "vaire",
- "vairs", "vairy", "vajra", "vakas", "vakil",
- "vales", "valet", "valis", "valli", "valse",
- "value", "vamps", "vampy", "vanda", "vaned",
- "vanes", "vanga", "vangs", "vants", "vaped",
- "vaper", "vapes", "vapor", "varan", "varas",
- "varda", "vardo", "vardy", "varec", "vares",
- "varia", "varix", "varna", "varus", "varve",
- "vasal", "vases", "vasts", "vasty", "vatas",
- "vatha", "vatic", "vatje", "vatos", "vatus",
- "vauch", "vaute", "vauts", "vawte", "vaxes",
- "veale", "veals", "vealy", "veena", "veeps",
- "veers", "veery", "vegas", "veges", "veggo",
- "vegie", "vegos", "vehme", "veils", "veily",
- "veins", "veiny", "velar", "velds", "veldt",
- "veles", "vells", "velum", "venae", "venal",
- "venas", "vends", "vendu", "veney", "venge",
- "venin", "venom", "venti", "vents", "venus",
- "verba", "verbs", "verde", "verra", "verre",
- "verry", "versa", "verso", "verst", "verte",
- "verts", "vertu", "verve", "vespa", "vesta",
- "vests", "vetch", "veuve", "veves", "vexed",
- "vexer", "vexes", "vexil", "vezir", "vials",
- "viand", "vibed", "vibes", "vibex", "vibey",
- "vicar", "viced", "vices", "vichy", "vicus",
- "video", "viers", "vieux", "views", "viewy",
- "vifda", "viffs", "vigas", "vigia", "vigil",
- "vilde", "viler", "ville", "villi", "vills",
- "vimen", "vinal", "vinas", "vinca", "vined",
- "viner", "vines", "vinew", "vinho", "vinic",
- "vinny", "vinos", "vints", "viold", "viols",
- "vired", "vireo", "vires", "virga", "virge",
- "virgo", "virid", "virls", "virtu", "visas",
- "vised", "vises", "visie", "visit", "visna",
- "visne", "vison", "visto", "vitae", "vitas",
- "vitex", "vitro", "vitta", "vivas", "vivat",
- "vivda", "viver", "vives", "vivos", "vivre",
- "vizir", "vizor", "vlast", "vleis", "vlies",
- "vlogs", "voars", "vobla", "vocab", "voces",
- "voddy", "vodou", "vodun", "voema", "vogie",
- "voici", "voids", "voile", "voips", "volae",
- "volar", "voled", "voles", "volet", "volke",
- "volks", "volta", "volte", "volti", "volts",
- "volva", "volve", "vomer", "vomit", "voted",
- "votes", "vouge", "voulu", "vowed", "vower",
- "voxel", "voxes", "vozhd", "vraic", "vrils",
- "vroom", "vrous", "vrouw", "vrows", "vuggs",
- "vuggy", "vughs", "vughy", "vulgo", "vulns",
- "vutty", "vygie", "vying", "waacs", "wacke",
- "wacko", "wacks", "wadas", "wadds", "waddy",
- "waded", "wader", "wades", "wadge", "wadis",
- "wadts", "waffs", "wafts", "waged", "wages",
- "wagga", "wagyu", "wahay", "wahey", "wahoo",
- "waide", "waifs", "waift", "wails", "wains",
- "wairs", "waite", "waits", "waive", "wakas",
- "waked", "waken", "waker", "wakes", "wakfs",
- "waldo", "walds", "waled", "waler", "wales",
- "walie", "walis", "walks", "walla", "walls",
- "wally", "walty", "wamed", "wames", "wamus",
- "wands", "waned", "wanes", "waney", "wangs",
- "wanks", "wanky", "wanle", "wanly", "wanna",
- "wanta", "wants", "wanty", "wanze", "waqfs",
- "warbs", "warby", "wards", "wared", "wares",
- "warez", "warks", "warms", "warns", "warps",
- "warre", "warst", "warts", "warty", "wases",
- "washi", "washy", "wasms", "wasps", "waspy",
- "waste", "wasts", "watap", "watts", "wauff",
- "waugh", "wauks", "waulk", "wauls", "waurs",
- "waved", "waves", "wavey", "wawas", "wawes",
- "wawls", "waxed", "waxer", "waxes", "wayed",
- "wazir", "wazoo", "weald", "weals", "weamb",
- "weans", "wears", "webby", "weber", "wecht",
- "wedel", "wedgy", "weeds", "weeis", "weeke",
- "weeks", "weels", "weems", "weens", "weeny",
- "weeps", "weepy", "weest", "weete", "weets",
- "wefte", "wefts", "weids", "weils", "weirs",
- "weise", "weize", "wekas", "welds", "welke",
- "welks", "welkt", "wells", "welly", "welsh",
- "welts", "wembs", "wench", "wends", "wenge",
- "wenny", "wents", "werfs", "weros", "wersh",
- "wests", "wetas", "wetly", "wexed", "wexes",
- "whack", "whamo", "whams", "whang", "whaps",
- "whare", "wharf", "whata", "whats", "whaup",
- "whaur", "wheal", "whear", "wheek", "wheen",
- "wheep", "wheft", "whelk", "whelm", "whelp",
- "whens", "whets", "whews", "wheys", "whids",
- "whies", "whift", "whigs", "whilk", "whims",
- "whins", "whios", "whips", "whipt", "whirr",
- "whirs", "whish", "whiss", "whist", "whits",
- "whity", "whizz", "whomp", "whoof", "whoop",
- "whoot", "whops", "whore", "whorl", "whort",
- "whoso", "whows", "whump", "whups", "whyda",
- "wicca", "wicks", "wicky", "widdy", "wides",
- "wiels", "wifed", "wifes", "wifey", "wifie",
- "wifts", "wifty", "wigan", "wigga", "wiggy",
- "wight", "wikis", "wilco", "wilds", "wiled",
- "wiles", "wilga", "wilis", "wilja", "wills",
- "willy", "wilts", "wimps", "wimpy", "winds",
- "wined", "wines", "winey", "winge", "wings",
- "wingy", "winks", "winky", "winna", "winns",
- "winos", "winze", "wiped", "wipes", "wired",
- "wirer", "wires", "wirra", "wirri", "wised",
- "wises", "wisha", "wisht", "wisps", "wispy",
- "wists", "witan", "wited", "wites", "withe",
- "withs", "withy", "wived", "wiver", "wives",
- "wizen", "wizes", "wizzo", "woads", "woady",
- "woald", "wocks", "wodge", "wodgy", "woful",
- "wojus", "woker", "wokka", "wolds", "wolfs",
- "wolly", "wolve", "womas", "wombs", "womby",
- "womyn", "wonga", "wongi", "wonks", "wonky",
- "wonts", "woods", "woody", "wooed", "wooer",
- "woofs", "woofy", "woold", "wools", "wooly",
- "woons", "woops", "woopy", "woose", "woosh",
- "wootz", "woozy", "words", "wordy", "works",
- "worky", "worms", "wormy", "worts", "woven",
- "wowed", "wowee", "wowse", "woxen", "wrang",
- "wraps", "wrapt", "wrast", "wrate", "wrawl",
- "wrens", "wrick", "wried", "wrier", "wries",
- "writs", "wroke", "wroot", "wroth", "wrung",
- "wryer", "wryly", "wuddy", "wudus", "wuffs",
- "wulls", "wunga", "wurst", "wuses", "wushu",
- "wussy", "wuxia", "wyled", "wyles", "wynds",
- "wynns", "wyted", "wytes", "wythe", "xebec",
- "xenia", "xenic", "xenon", "xeric", "xerox",
- "xerus", "xoana", "xolos", "xrays", "xviii",
- "xylan", "xylem", "xylic", "xylol", "xylyl",
- "xysti", "xysts", "yaars", "yaass", "yabas",
- "yabba", "yabby", "yacca", "yacka", "yacks",
- "yadda", "yaffs", "yager", "yages", "yagis",
- "yagna", "yahoo", "yaird", "yajna", "yakka",
- "yakow", "yales", "yamen", "yampa", "yampy",
- "yamun", "yandy", "yangs", "yanks", "yapok",
- "yapon", "yapps", "yappy", "yarak", "yarco",
- "yards", "yarer", "yarfa", "yarks", "yarns",
- "yarra", "yarrs", "yarta", "yarto", "yates",
- "yatra", "yauds", "yauld", "yaups", "yawed",
- "yawey", "yawls", "yawns", "yawny", "yawps",
- "yayas", "ybore", "yclad", "ycled", "ycond",
- "ydrad", "ydred", "yeads", "yeahs", "yealm",
- "yeans", "yeard", "years", "yecch", "yechs",
- "yechy", "yedes", "yeeds", "yeeek", "yeesh",
- "yeggs", "yelks", "yells", "yelms", "yelps",
- "yelts", "yenta", "yente", "yerba", "yerds",
- "yerks", "yeses", "yesks", "yests", "yesty",
- "yetis", "yetts", "yeuch", "yeuks", "yeuky",
- "yeven", "yeves", "yewen", "yexed", "yexes",
- "yfere", "yiked", "yikes", "yills", "yince",
- "yipes", "yippy", "yirds", "yirks", "yirrs",
- "yirth", "yites", "yitie", "ylems", "ylide",
- "ylids", "ylike", "ylkes", "ymolt", "ympes",
- "yobbo", "yobby", "yocks", "yodel", "yodhs",
- "yodle", "yogas", "yogee", "yoghs", "yogic",
- "yogin", "yogis", "yohah", "yohay", "yoick",
- "yojan", "yokan", "yoked", "yokeg", "yokel",
- "yoker", "yokes", "yokul", "yolks", "yolky",
- "yolps", "yomim", "yomps", "yonic", "yonis",
- "yonks", "yonny", "yoofs", "yoops", "yopos",
- "yoppo", "yores", "yorga", "yorks", "yorps",
- "youks", "yourn", "yours", "yourt", "youse",
- "yowed", "yowes", "yowie", "yowls", "yowsa",
- "yowza", "yoyos", "yrapt", "yrent", "yrivd",
- "yrneh", "ysame", "ytost", "yuans", "yucas",
- "yucca", "yucch", "yucko", "yucks", "yucky",
- "yufts", "yugas", "yuked", "yukes", "yukky",
- "yukos", "yulan", "yules", "yummo", "yummy",
- "yumps", "yupon", "yuppy", "yurta", "yurts",
- "yuzus", "zabra", "zacks", "zaida", "zaide",
- "zaidy", "zaire", "zakat", "zamac", "zamak",
- "zaman", "zambo", "zamia", "zamis", "zanja",
- "zante", "zanza", "zanze", "zappy", "zarda",
- "zarfs", "zaris", "zatis", "zawns", "zaxes",
- "zayde", "zayin", "zazen", "zeals", "zebec",
- "zebub", "zebus", "zedas", "zeera", "zeins",
- "zendo", "zerda", "zerks", "zeros", "zests",
- "zetas", "zexes", "zezes", "zhomo", "zhush",
- "zhuzh", "zibet", "ziffs", "zigan", "zikrs",
- "zilas", "zilch", "zilla", "zills", "zimbi",
- "zimbs", "zinco", "zincs", "zincy", "zineb",
- "zines", "zings", "zingy", "zinke", "zinky",
- "zinos", "zippo", "zippy", "ziram", "zitis",
- "zitty", "zizel", "zizit", "zlote", "zloty",
- "zoaea", "zobos", "zobus", "zocco", "zoeae",
- "zoeal", "zoeas", "zoism", "zoist", "zokor",
- "zolle", "zombi", "zonae", "zonda", "zoned",
- "zoner", "zones", "zonks", "zooea", "zooey",
- "zooid", "zooks", "zooms", "zoomy", "zoons",
- "zooty", "zoppa", "zoppo", "zoril", "zoris",
- "zorro", "zorse", "zouks", "zowee", "zowie",
- "zulus", "zupan", "zupas", "zuppa", "zurfs",
- "zuzim", "zygal", "zygon", "zymes", "zymic",
-]