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

") - 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"
      {options_html}
    " - 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. + 1 + Open chat.lotusguild.org — your homeserver is pre-configured +
    2. +
    3. + 2 + Register with a token from Jared@jared:matrix.lotusguild.org +
    4. +
    5. + 3 + Join the Lotus Guild Space to access all rooms +
    6. +
    + +
    or join from another server
    +
    + Already have a Matrix account? Sign up free at mozilla.org or matrix.org, then join our space. +
    + +
    + +
    +

    Recommended Client

    + + + +
    +

    Already signed in? Jump straight into the community:

    + Join Lotus Guild Space → +
    + +
    + +
    +

    Other Clients

    + +
    +

    Mobile — iOS & Android

    + +
    + +
    +

    Desktop alternatives

    + +
    + +

    View all Matrix clients →

    +
    +
    +
    + +
    +

    Server Details

    +
    +
    +
    Access
    +
    Invite-only
    +
    +
    +
    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 +
    +
    + + + +
    +

    Questions or need a registration token?

    +

    Reach out to @jared:matrix.lotusguild.org

    +
    + + +
    + + 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", -]