87 Commits

Author SHA1 Message Date
Chris Ham
2e6cfe21d8 remove unused imports 2025-08-17 09:57:04 -07:00
Chris Ham
5d72159cb2 Fix scheduled events not executing due to property name mismatch
The scheduler was correctly scheduling events but they weren't executing because the code was checking for camelCase property names (channelId, pingRoleId) while the database returns snake_case names (channel_id, ping_role_id). This caused channel and role validation to be skipped, resulting in silent failures.

Also added sqlite3 CLI to Docker container for debugging database issues.

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-17 09:53:38 -07:00
Chris Ham
8823eac094 Convert to database-first configuration with token storage
- Rename config.json → seed.json to clarify seeding purpose
- Update ConfigManager to be database-first with minimal file fallbacks
- Store Discord token in database instead of environment variables
- Remove allowedSfxChannels functionality completely
- Update seeding script to import token from seed.json to database
- Add token field to bot_config table in database schema
- Update Docker volume mount to use seed.json
- Update gitignore to protect seed.json while allowing seed.example.json

Configuration Flow:
1. First run: Import from seed.json to database (one-time seeding)
2. Runtime: All configuration from SQLite database
3. Fallback: Environment variables if database unavailable

Security: All sensitive data now stored in encrypted SQLite database

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 23:37:06 -07:00
Chris Ham
6d93f3dcad /soundboard -> /sfxboard 2025-08-16 22:32:37 -07:00
Chris Ham
7dc7a92dd1 ephemeral 2025-08-16 22:22:40 -07:00
Chris Ham
b2821d412c SFX -> Sfx 2025-08-16 22:21:10 -07:00
Chris Ham
46b78dd6b3 Add reaction-based status feedback for prefix SFX commands
- Implement reaction progression: 🔊 (playing) →  (finished)
- Remove permissions-heavy removeAll() call that was causing errors
- Add error reaction () for failed sound playback
- Fallback to text reply if reactions fail (permission handling)
- Maintain clean chat experience with minimal visual feedback

Status Flow:
- !sfx command starts: 🔊 reaction added
- Sound finishes playing:  reaction added (both visible)
- Error occurs:  reaction added

Now all SFX interfaces provide consistent status feedback:
- Prefix: Reaction-based progression
- Slash commands: Ephemeral message updates

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 22:05:20 -07:00
Chris Ham
98ed84aa2b Fix reboot command configuration reference
- Update reboot command to use ConfigManager instead of raw config
- Fix broken adminUserId reference that was causing command errors
- Use getBotConfig() method to properly access admin configuration
- Ensure reboot command works with hybrid database/file config system

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 21:50:35 -07:00
Chris Ham
bcefe03c50 Remove allowedSfxChannels functionality - allow SFX in all channels
- Remove allowedSfxChannels from database schema and all code
- Remove channel checking logic from all SFX commands (!sfx, /sfx, /soundboard)
- Remove /config sfxchannels subcommand
- Update config.json and example to remove channel restrictions
- Simplify SFX system to work in any channel with bot access

Benefits:
- Better user experience - no confusing channel restrictions
- Simpler configuration - fewer settings to manage
- Cleaner codebase - reduced complexity
- Universal access - SFX works anywhere the bot can send messages

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 21:45:42 -07:00
Chris Ham
e9b3b630b6 Add smart contextual autocomplete for admin role management
- Add autocomplete to /config roles add/remove commands
- /config roles add only shows roles not currently self-assignable
- /config roles remove only shows roles currently self-assignable
- Filter out bot roles, @everyone, and unmanageable roles
- Convert role parameter to string with autocomplete for better UX
- Add role name validation and lookup in config command

Admin Experience Improvements:
- Smart role suggestions prevent duplicate/invalid selections
- No more seeing roles already in the self-assignable list
- Only see roles the bot can actually manage
- Clean, focused autocomplete interface

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 21:05:46 -07:00
Chris Ham
efd5472eed Add smart contextual autocomplete for role management
- Add context-aware autocomplete for /role add and /role remove commands
- /role add only shows roles user doesn't have that are self-assignable
- /role remove only shows roles user currently has that are self-manageable
- Filter autocomplete based on user input with 25 result limit
- Convert role options from RoleOption to StringOption with autocomplete
- Prevent users from seeing irrelevant role choices

User Experience Improvements:
- Smart role suggestions based on current user state
- No more trying to add roles you already have
- No more seeing roles you can't manage
- Clean, focused autocomplete interface

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 20:49:01 -07:00
Chris Ham
3c8e005405 Fix role configuration database persistence issue
- Fix /config roles add/remove commands not saving to database
- Prevent upsertGuildConfig from overwriting direct database role updates
- Add early return for role management to skip general config update
- Ensure role IDs are properly persisted when using /config roles commands
- Add proper success feedback and logging for role configuration changes

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 20:33:43 -07:00
Chris Ham
61a376cfbb Modernize role management system with slash commands and role IDs
- Convert role management from prefix to slash commands (/role add/remove/list)
- Update database schema to store role IDs as JSON arrays instead of regex patterns
- Add /config roles command for administrators to manage allowed roles
- Simplify database schema by reusing allowed_roles_for_request field as JSON
- Add database reset script (pnpm reset-db) for easy testing and migration
- Update config format to only support array format (no backward compatibility)

Role Management Features:
- /role add <role> - Self-assign roles with dropdown selection
- /role remove <role> - Remove roles with dropdown selection
- /role list - Show available self-assignable roles
- /config roles add/remove/list/clear - Administrator role management

Technical Improvements:
- Role ID based matching (more reliable than name-based regex)
- Type-safe role selection with Discord's native role picker
- Permission hierarchy validation
- Rich embed responses with proper error handling
- Ephemeral responses for clean chat experience

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 18:07:07 -07:00
Chris Ham
18350ee878 Consolidate soundboard categories and improve layout
- Merge all people categories (JOSH, LANX, MUTT, etc.) into NERDS section
- Consolidate TV & movie categories into single TV & MOVIES section
- Maintain alphabetical order within all sections
- Update soundboard to show 4 categories per row for cleaner layout
- Remove verbose category list from soundboard interface
- Add emojis to category headers for better visual appeal

Soundboard now shows:
- 🔉 GENERAL - Common/utility sounds
- 🤓 NERDS - Gaming, streaming, and personality sounds
- 🎥 TV & MOVIES - All show/movie clips combined
- Clean 4-button layout per row

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 16:47:59 -07:00
Chris Ham
aaf33d55db CHECKPOINT: Interactive soundboard and refactored SFX system
Major Features Implemented:
- Complete Discord.js v14 modernization from v12 with hybrid command system
- SQLite database for dynamic guild configuration management
- Interactive soundboard with categorized button interface (/soundboard)
- Three-tier SFX interface: prefix (!sfx), autocomplete (/sfx), and visual soundboard
- Auto-registration system for public bot distribution
- Soft delete guild management preserving configurations

Technical Improvements:
- Refactored SFX playing into reusable service methods (playSFXInteraction/playSFXMessage)
- Smart markdown chunking that respects code block boundaries
- High-performance caching for 275+ sound effects with autocomplete optimization
- Modern Discord.js v14 patterns (MessageFlags.Ephemeral, proper intents)
- Fixed security vulnerability in @discordjs/opus with pnpm overrides
- Docker deployment with Node 20 and npm for reliable SQLite compilation

Interactive Soundboard Features:
- Category-based navigation with buttons (GENERAL, NERDS, TWIN PEAKS, etc.)
- Pagination support for large categories (16 sounds per page, 4 per row)
- Real-time status updates (Playing → Finished playing)
- Navigation buttons (Previous/Next/Back to Categories)
- Ephemeral responses for clean chat experience

Database System:
- Auto-migration from config.json to SQLite on first run
- /config slash commands for live server configuration
- Scheduled events with timezone support (object and cron formats)
- Guild auto-registration with welcome messages for new servers

Current State: Fully functional modern Discord bot ready for public distribution
2025-08-16 16:20:02 -07:00
Chris Ham
0b167aaa35 Add high-performance caching for SFX autocomplete
- Pre-sort SFX names at startup for efficient autocomplete
- Add search result caching with Map for instant repeated queries
- Respect Discord's 25-choice autocomplete limit
- Cache invalidation when SFX directory changes
- Optimize for 275+ sound effects with minimal latency

Performance improvements:
- Autocomplete responses now cached and instant
- No file system access during user interactions
- Memory-efficient search result caching

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 15:21:50 -07:00
Chris Ham
c3adb66de8 Improve SFX list formatting and add smart markdown chunking
- Add smart chunking logic that respects markdown block boundaries
- Prevent code blocks from being split across Discord messages
- Update SFX list display to use improved README.md formatting
- Ensure proper markdown rendering in Discord chat

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 15:13:43 -07:00
Chris Ham
f8147ffb14 formatting 2025-08-16 15:02:06 -07:00
Chris Ham
bc74978a79 Update to modern Discord.js ephemeral message pattern
- Replace ephemeral: true with flags: [MessageFlags.Ephemeral]
- Add MessageFlags import to Discord.js imports
- Update all slash command error responses to use modern flag syntax
- Maintains same functionality with Discord.js v14 best practices

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 15:01:49 -07:00
Chris Ham
437206851b Update SFX list and improve display functionality
- Update sfx/README.md to include all 275 sound files with proper categorization
- Organize missing sounds into appropriate categories (SILICON VALLEY, KING OF THE HILL, etc.)
- Fix SFX list display to use local README.md instead of external URL
- Add fallback to auto-generated list if README is missing
- Improve chunking to prevent Discord character limit errors

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 14:54:40 -07:00
Chris Ham
80b21e5073 Fix security vulnerability with pnpm override
- Add pnpm override to replace vulnerable @discordjs/opus with opusscript
- Eliminates CVE-2024-21521 Denial of Service vulnerability (CVSS 8.7)
- Maintains API compatibility while using secure implementation
- Security audit now shows: No known vulnerabilities found

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 14:26:49 -07:00
Chris Ham
fd68a02503 Update CLAUDE.md to reflect current SQLite database architecture
- Document SQLite database system with table structures
- Update commands to match package.json scripts (start, stop, boom, etc.)
- Add comprehensive database configuration flow documentation
- Document auto-registration system for public bot distribution
- Update Docker setup details (Node 20, npm usage, persistence)
- Add guild management and soft delete system documentation
- Document hybrid configuration system (database + file fallback)
- Update architecture with all new services and components

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 14:21:24 -07:00
Chris Ham
c2962bac16 Update README.md with SQLite database documentation
- Document database-driven configuration system
- Add three configuration approaches (auto-registration, live config, seeding)
- Update Docker commands to match package.json scripts
- Add comprehensive scheduled events documentation with timezone support
- Update architecture diagram to include database components
- Clarify Node.js version requirements (20 for Docker, 22+ local)
- Document migration process from config.json to database

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 14:12:21 -07:00
Chris Ham
d74aebfda7 Add SQLite database for dynamic guild management
Features:
- SQLite database with better-sqlite3 for guild configurations
- Auto-registration when bot joins new guilds with welcome messages
- Soft delete system preserves settings when bot is removed
- Dynamic configuration via /config slash command with subcommands
- Automatic migration from config.json to database on first run
- Support for scheduled events with timezone preservation

Technical Implementation:
- Node.js 20 for better SQLite compatibility in Docker
- Full Debian base image with npm for reliable native module compilation
- Database persistence via Docker volume (./data)
- Hybrid configuration system (database primary, file fallback)
- JSON storage for complex schedule objects with timezone support

Database Schema:
- guilds table with soft delete (is_active flag)
- scheduled_events table with JSON schedule storage
- bot_config table for global settings
- Auto-initialization and seeding from existing config

Admin Features:
- /config show - View current server settings
- /config subcommands - Update prefix, volume, features, etc.
- Administrator permissions required for configuration changes
- Graceful handling of missing or malformed data

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 14:02:27 -07:00
Chris Ham
9661ba92d5 Add Docker Compose setup and clean up legacy code
- Add docker-compose.yml with volume mounts for config/sfx
- Simplify npm scripts (up/down/build/restart/logs)
- Update README.md and CLAUDE.md with new commands
- Remove unused lib/ directory (migrated to src/)
- Update package.json scripts to cleaner naming

Benefits:
- Update configs and sound effects without rebuilding image
- Simplified Docker workflow management
- Cleaner project structure

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 12:06:40 -07:00
Chris Ham
e53360e887 gen readme 2025-08-16 11:43:46 -07:00
Chris Ham
0ad4265bed Modernize Discord bot to v14 and Node.js 22
Major upgrades and architectural improvements:
- Upgrade Discord.js from v12 to v14.21.0
- Upgrade Node.js from 14 to 22 LTS
- Switch to pnpm package manager
- Complete rewrite with modern Discord API patterns

New Features:
- Hybrid command system: prefix commands + slash commands
- /sfx slash command with autocomplete for sound discovery
- Modern @discordjs/voice integration for audio
- Improved voice connection management
- Enhanced logging for SFX commands
- Multi-stage Docker build for optimized images

Technical Improvements:
- Modular architecture with services and command handlers
- Proper intent management for Discord gateway
- Better error handling and logging
- Hot-reload capability maintained
- Environment variable support
- Optimized Docker container with Alpine Linux

Breaking Changes:
- Moved main entry from index.js to src/index.js
- Updated configuration structure for v14 compatibility
- Replaced deprecated voice APIs with @discordjs/voice
- Updated audio dependencies (opus, ffmpeg)

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-08-16 11:37:37 -07:00
Chris Ham
19c8f4fa85 output next invocation time for scheduled jobs 2024-02-28 16:43:29 -08:00
Chris Ham
abdc894359 dockerize 2024-02-27 18:27:31 -08:00
Chris Ham
30f8307551 new sfx 2023-12-26 13:03:30 -08:00
Chris Ham
194d74f4e7 Merge branch 'main' of github.com:greenham/ghbot
* 'main' of github.com:greenham/ghbot:
  Bump node-fetch from 2.6.1 to 2.6.7
  Bump follow-redirects from 1.14.7 to 1.14.8
  Bump axios from 0.21.1 to 0.21.2
  Bump follow-redirects from 1.13.3 to 1.14.7
2023-12-26 13:02:22 -08:00
Chris Ham
0528e71ad8 new sfx 2023-12-26 13:00:09 -08:00
greenham
fa70efaa98 Merge pull request #8 from greenham/dependabot/npm_and_yarn/node-fetch-2.6.7
Bump node-fetch from 2.6.1 to 2.6.7
2022-02-16 07:47:24 -08:00
dependabot[bot]
313ca7c677 Bump node-fetch from 2.6.1 to 2.6.7
Bumps [node-fetch](https://github.com/node-fetch/node-fetch) from 2.6.1 to 2.6.7.
- [Release notes](https://github.com/node-fetch/node-fetch/releases)
- [Commits](https://github.com/node-fetch/node-fetch/compare/v2.6.1...v2.6.7)

---
updated-dependencies:
- dependency-name: node-fetch
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-16 15:46:04 +00:00
greenham
2ac5050284 Merge pull request #7 from greenham/dependabot/npm_and_yarn/follow-redirects-1.14.8
Bump follow-redirects from 1.14.7 to 1.14.8
2022-02-16 07:45:30 -08:00
dependabot[bot]
30d660ad7a Bump follow-redirects from 1.14.7 to 1.14.8
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.7 to 1.14.8.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.7...v1.14.8)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-02-13 12:58:19 +00:00
greenham
b7a72f6792 Merge pull request #6 from greenham/dependabot/npm_and_yarn/axios-0.21.2
Bump axios from 0.21.1 to 0.21.2
2022-01-14 16:22:02 -08:00
dependabot[bot]
8730b90039 Bump axios from 0.21.1 to 0.21.2
Bumps [axios](https://github.com/axios/axios) from 0.21.1 to 0.21.2.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/master/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.21.1...v0.21.2)

---
updated-dependencies:
- dependency-name: axios
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-15 00:21:34 +00:00
greenham
76403d3821 Merge pull request #5 from greenham/dependabot/npm_and_yarn/follow-redirects-1.14.7
Bump follow-redirects from 1.13.3 to 1.14.7
2022-01-14 16:20:41 -08:00
dependabot[bot]
67fc7edbb5 Bump follow-redirects from 1.13.3 to 1.14.7
Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.13.3 to 1.14.7.
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.13.3...v1.14.7)

---
updated-dependencies:
- dependency-name: follow-redirects
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-01-14 09:26:03 +00:00
greenham
ca00e52377 use correct argument when reporting invalid group 2021-08-10 20:03:31 -07:00
greenham
01cc04f0b2 better error handling 2021-08-06 09:04:05 -07:00
greenham
aa83901435 new sfx 2021-08-06 08:57:30 -07:00
greenham
60db93dd2d remove dead clip 2021-08-06 08:57:23 -07:00
greenham
2b3027c496 handle role requests 2021-08-06 08:57:16 -07:00
greenham
7b92e139a7 - support event scheduling
- upgrade discordjs and associated packages
- add/edit some sfx
2021-04-02 22:06:23 -07:00
greenham
6bf997e3be - new location for sfx list due to pastebin restrictions
- new sfx
2021-04-02 19:49:00 -07:00
Chris Ham
48be847d08 new sfx 2021-01-21 07:45:30 -08:00
greenham
75809a310a Merge pull request #4 from greenham/dependabot/npm_and_yarn/socket.io-2.4.1
Bump socket.io from 2.3.0 to 2.4.1
2021-01-21 07:43:43 -08:00
greenham
f87d40948f Merge pull request #3 from greenham/dependabot/npm_and_yarn/axios-0.21.1
Bump axios from 0.19.2 to 0.21.1
2021-01-21 07:43:32 -08:00
greenham
21d9ed8602 Merge pull request #1 from greenham/dependabot/npm_and_yarn/node-fetch-2.6.1
Bump node-fetch from 2.6.0 to 2.6.1
2021-01-21 07:43:22 -08:00
greenham
3f3da329b4 Merge pull request #2 from greenham/dependabot/npm_and_yarn/ini-1.3.8
Bump ini from 1.3.5 to 1.3.8
2021-01-21 07:43:09 -08:00
dependabot[bot]
416f258f00 Bump socket.io from 2.3.0 to 2.4.1
Bumps [socket.io](https://github.com/socketio/socket.io) from 2.3.0 to 2.4.1.
- [Release notes](https://github.com/socketio/socket.io/releases)
- [Changelog](https://github.com/socketio/socket.io/blob/2.4.1/CHANGELOG.md)
- [Commits](https://github.com/socketio/socket.io/compare/2.3.0...2.4.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-21 15:10:41 +00:00
dependabot[bot]
c2a0d955c3 Bump axios from 0.19.2 to 0.21.1
Bumps [axios](https://github.com/axios/axios) from 0.19.2 to 0.21.1.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v0.21.1/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v0.19.2...v0.21.1)

Signed-off-by: dependabot[bot] <support@github.com>
2021-01-06 02:19:16 +00:00
dependabot[bot]
680ece01a4 Bump ini from 1.3.5 to 1.3.8
Bumps [ini](https://github.com/isaacs/ini) from 1.3.5 to 1.3.8.
- [Release notes](https://github.com/isaacs/ini/releases)
- [Commits](https://github.com/isaacs/ini/compare/v1.3.5...v1.3.8)

Signed-off-by: dependabot[bot] <support@github.com>
2020-12-12 17:10:51 +00:00
dependabot[bot]
39d8230355 Bump node-fetch from 2.6.0 to 2.6.1
Bumps [node-fetch](https://github.com/bitinn/node-fetch) from 2.6.0 to 2.6.1.
- [Release notes](https://github.com/bitinn/node-fetch/releases)
- [Changelog](https://github.com/node-fetch/node-fetch/blob/master/docs/CHANGELOG.md)
- [Commits](https://github.com/bitinn/node-fetch/compare/v2.6.0...v2.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2020-09-12 21:19:24 +00:00
greenham
34b86fff09 update sfx code to match new discord API 2020-05-10 17:25:06 -07:00
greenham
fb7aaa5c84 new sfx 2020-05-10 17:24:35 -07:00
greenham
1ac1cc4278 upgrade discord packages 2020-05-10 17:24:27 -07:00
greenham
b1d3114921 upgrade discord packages 2020-05-10 17:24:18 -07:00
greenham
b5da012a34 ignore todo files 2020-05-10 17:24:03 -07:00
Chris Ham
5c2b2a6ef3 remove unused packages 2020-05-01 17:52:21 -07:00
Chris Ham
acc5630e42 Merge branch 'master' of github.com:greenham/ghbot
* 'master' of github.com:greenham/ghbot:
  support blacklisting users
2020-05-01 17:49:21 -07:00
greenham
663fa2b628 support blacklisting users 2020-05-01 17:49:00 -07:00
Chris Ham
080d4fa06f support blacklisting users 2020-05-01 17:48:03 -07:00
Chris Ham
5d2595020a switch to yarn 2020-05-01 17:40:41 -07:00
Chris Ham
42013800da move config item 2020-05-01 17:38:38 -07:00
Chris Ham
f2bf3a7ad5 support guild-level enabling/disabling of some commands 2020-05-01 17:38:09 -07:00
Chris Ham
68d8d67fc5 remove old fgfm code, general cleanup 2020-05-01 17:27:06 -07:00
Chris Ham
de193c1099 multi-guild support, new sfx 2020-05-01 16:01:38 -07:00
Chris Ham
11ccf684b5 normalize and compress all sfx, read sfx list from pastebin 2020-02-29 06:59:24 -08:00
Chris Ham
0f6271c507 new sfx 2019-10-13 10:08:15 -07:00
Chris Ham
13594f7e3a update packages, add knob 2019-05-15 08:18:01 -07:00
Chris Ham
1ab648903e new sfx 2019-05-07 15:10:23 -07:00
Chris Ham
24a1451b45 new sfx 2019-04-03 10:02:36 -07:00
greenham
bf3ccca868 remove vods.json from repo 2018-12-11 11:39:38 -08:00
greenham
5f036227f2 Merge branch 'master' of https://github.com/greenham/ghbot 2018-12-11 11:38:30 -08:00
greenham
1fe5a35044 Delete vods.json 2018-12-11 11:36:48 -08:00
greenham
baa35f2b7e local changes 2018-12-11 11:34:11 -08:00
Chris Ham
22eba12c0c ignore vod config 2018-12-11 11:30:24 -08:00
Chris Ham
fd6ea2f926 remove vod config from repo, local only now 2018-12-11 11:29:36 -08:00
Chris Ham
4b6bb38192 support playlist changing, shuffle, and repeat mode control 2018-12-11 11:25:15 -08:00
Chris Ham
11655fac86 spotify control 2018-12-04 11:55:33 -08:00
Chris Ham
67c677f61c Merge branch 'master' into spotify-integration
* master:
  todo updates
  todo
2018-12-04 11:24:08 -08:00
Chris Ham
1603f26aeb todo updates 2018-12-04 09:57:33 -08:00
Chris Ham
6683d7f68a todo 2018-11-16 08:51:18 -08:00
Chris Ham
51f417b916 wip 2018-11-14 09:05:45 -08:00
349 changed files with 6257 additions and 17306 deletions

13
.dockerignore Normal file
View File

@@ -0,0 +1,13 @@
node_modules
npm-debug.log
.git
.gitignore
README.md
.env
.env.*
*.log
.DS_Store
Dockerfile
.dockerignore
CLAUDE.md
todo.todo

11
.gitignore vendored
View File

@@ -47,10 +47,15 @@ Temporary Items
.apdisk
# Ignores
node_modules/*
logs/*
node_modules
logs
data/
start.bat
tokens.json
config.json
seed.json
!seed.example.json
.env
*.todo

1
.nvmrc Normal file
View File

@@ -0,0 +1 @@
20

139
CLAUDE.md Normal file
View File

@@ -0,0 +1,139 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a modern Discord bot built with Discord.js v14 that provides sound effects (both prefix and slash commands), text commands, fun facts, and scheduled events functionality for Discord servers. The bot uses SQLite database for dynamic guild configuration management and is designed for public distribution.
## Development Commands
### Running the Bot
```bash
# Install dependencies
pnpm install
# Local Node.js execution (requires Node 20+ LTS)
pnpm dev # Development mode with auto-reload
pnpm start:prod # Production mode (local Node.js)
# Docker Compose (recommended for production)
pnpm start # Start bot with Docker Compose
pnpm stop # Stop bot
pnpm restart # Restart bot (useful after config changes)
pnpm logs # View logs
pnpm build # Rebuild image
pnpm boom # Quick rebuild and restart
# Direct Docker (alternative)
pnpm image:build # Build Docker image
pnpm image:run # Run Docker container with auto-restart
```
### Docker Management
```bash
# Docker Compose approach (recommended)
pnpm start && pnpm logs # Start and follow logs
pnpm restart # Restart after config changes
pnpm boom # Quick rebuild and restart after code changes
# Direct Docker approach
docker stop discord-bot && docker rm discord-bot
docker logs discord-bot -f
pnpm image:build && pnpm image:run
```
## Architecture & Key Components
### Core Structure
- **src/index.js**: Main bot entry point with Discord.js v14 client and event handlers
- **src/config/**: Configuration management
- `config.js`: Hybrid config manager (SQLite database + file fallback)
- `intents.js`: Discord gateway intents
- **src/commands/**:
- `prefix/`: Traditional prefix commands (!sfx, !funfact, !hamfact, !role, !dance, !join, !leave, !reboot)
- `slash/`: Modern slash commands (/sfx with autocomplete, /config with subcommands)
- **src/services/**:
- `databaseService.js`: SQLite database operations and guild management
- `voiceService.js`: Voice connections using @discordjs/voice
- `commandLoader.js`: Static/Ankhbot command loader with hot-reload
- `sfxManager.js`: Sound effect file management and caching
- `schedulerService.js`: Scheduled events handler with timezone support
- **src/utils/**: Helper functions (randElement, chunkSubstr, etc.)
- **data/**: SQLite database directory (auto-created, mounted in Docker)
### Database System
**SQLite Database (data/ghbot.db):**
- **guilds table**: Per-server configurations with soft delete support
- **scheduled_events table**: Cron jobs with JSON schedule storage
- **bot_config table**: Global bot settings (activities, admin, blacklist)
**Configuration Flow:**
1. **Database primary**: Live configurations stored in SQLite
2. **File fallback**: config.json used for initial seeding and token storage
3. **Auto-migration**: Existing config.json guilds imported on first run
4. **Live updates**: `/config` slash commands update database directly
### Command System
Commands are handled in priority order:
1. **Prefix commands**: Modular commands in src/commands/prefix/
2. **Static text commands**: From conf/text_commands (pipe-delimited with aliases)
3. **Ankhbot commands**: From conf/ghbot.abcomg (legacy format support)
### Sound Effects System
- **Dual interfaces**: Both !sfx prefix command and /sfx slash command with autocomplete
- **Auto-discovery**: Sound files in sfx/ directory (.mp3/.wav) automatically available
- **Voice integration**: @discordjs/voice with connection pooling and cleanup
- **Configuration**: Volume and allowed channels configurable per guild via database
### Guild Management
**Auto-Registration System:**
- **GuildCreate event**: New guilds get default config + welcome message
- **GuildDelete event**: Soft delete preserves settings for re-invite
- **Welcome messages**: Different messages for new vs returning guilds
- **Slash command registration**: Automatic per-guild registration
### Configuration Files
- **conf/text_commands**: Pipe-delimited text commands with alias support
- **conf/funfacts & conf/hamfacts**: Line-separated fact collections with hot-reload
- **conf/ghbot.abcomg**: Ankhbot command database (JSON format, legacy eval support)
- **config.json**: Initial configuration and bot token (optional after seeding)
### Scheduled Events
**Storage**: Database with JSON schedule format support
**Formats**:
- Object format: `{"hour": 7, "minute": 30, "tz": "America/Los_Angeles"}`
- Cron format: `"0 9 * * *"`
**Features**: Timezone support, role pings, channel targeting
### Docker Setup
- **Base image**: Node 20 full Debian (for better SQLite compatibility)
- **Package manager**: npm for Docker builds (bypasses pnpm security restrictions)
- **Multi-stage**: Single-stage build for simplicity and reliability
- **Persistence**: data/ volume for SQLite database
- **Dependencies**: All audio libraries and native modules properly compiled
## Important Implementation Details
- **Discord.js v14** with modern API patterns and explicit intents
- **@discordjs/voice** for audio playback with connection pooling
- **SQLite database** with better-sqlite3 for persistent configuration
- **Hybrid command system**: Traditional prefix commands + modern slash commands
- **Auto-registration**: Public bot ready - automatically configures new guilds
- **Soft delete system**: Guild settings preserved when bot is removed/re-added
- **Live configuration**: `/config` slash commands for real-time settings updates
- **Hot-reload**: Static commands and facts reload without restart
- **Voice system**: Modern @discordjs/voice with proper cleanup and error handling
- **Admin permissions**: Configuration changes require Administrator Discord permission
- **Logging**: Comprehensive logging for commands, SFX usage, and system events
- **Error handling**: Graceful handling of missing files, permissions, and malformed data

18
Dockerfile Normal file
View File

@@ -0,0 +1,18 @@
# Use Node 20 LTS with full Debian for better compatibility
FROM node:20
RUN apt update && apt install -y sqlite3
WORKDIR /app
# Copy package files (npm will work better for native modules in Docker)
COPY package*.json ./
# Install dependencies using npm (no security restrictions like pnpm)
RUN npm install --production
# Copy application code
COPY . .
# Start the bot
CMD [ "node", "src/index.js" ]

473
README.md Normal file
View File

@@ -0,0 +1,473 @@
# GHBot - Discord Sound Effects Bot
A modern Discord bot built with Discord.js v14 that provides sound effects, text commands, fun facts, and scheduled events for Discord servers.
## ✨ Features
### 🔊 Sound Effects
- **Prefix commands**: `!sfx <sound>` - Classic text-based commands
- **Slash commands**: `/sfx` with autocomplete - Modern Discord UI with searchable sound effects
- Automatic sound discovery from the `sfx/` directory
- Configurable volume and channel restrictions per guild
### 💬 Text Commands
- **Fun Facts**: Random or specific fact retrieval (`!funfact [number]`)
- **Ham Facts**: Ham-related facts (`!hamfact [number]`)
- **Static Commands**: Custom text responses loaded from configuration files
- **Ankhbot Import**: Support for imported Ankhbot command databases
### 🎭 Interactive Features
- **Role Management**: Self-service role assignment (`!role add/remove <role>`)
- **Dance Command**: ASCII art dance animation
- **Voice Controls**: Join/leave voice channels (`!join`, `!leave`)
### ⏰ Scheduling
- **Scheduled Events**: Cron-based message scheduling with role pings
- **Activity Rotation**: Automatic bot status updates
### 🛠️ Admin Features
- **Dynamic Configuration**: SQLite database for persistent, per-server settings
- **Auto-Registration**: New guilds automatically configured when bot is added
- **Live Configuration**: `/config` slash commands for real-time settings updates
- **Soft Delete**: Guild settings preserved when bot is temporarily removed
- **Hot Reload**: Configuration files update without restart
- **Admin Commands**: Bot management and restart capabilities
- **Blacklist System**: Block specific users from using commands
## 🚀 Quick Start
### Prerequisites
- Node.js 20 LTS (for Docker) or 22+ LTS (for local development)
- pnpm package manager
- Discord Bot Token (see Discord Setup section below)
### Installation
1. **Clone the repository**
```bash
git clone https://github.com/greenham/ghbot.git
cd ghbot
```
2. **Install dependencies**
```bash
pnpm install
```
3. **Configure the bot**
```bash
cp config.example.json config.json
# Edit config.json with your bot token and existing guild settings (optional)
```
**Note**: The bot uses a SQLite database for guild configurations. If you have existing guilds in `config.json`, they will be automatically imported to the database on first run. For new deployments, guilds are auto-registered when the bot is added to servers.
4. **Set up Discord Bot** (see Discord Setup section below)
5. **Run the bot**
```bash
# Development mode with auto-reload (local Node.js)
pnpm dev
# Production mode with Docker Compose
pnpm start
# Production mode with local Node.js
pnpm start:prod
```
## 🐳 Docker Deployment
### Recommended: Docker Compose
```bash
# Start the bot with Docker Compose
pnpm start
# View logs
pnpm logs
# Restart the bot (useful after config changes)
pnpm restart
# Stop the bot
pnpm stop
# Quick rebuild and restart (after code changes)
pnpm boom
# Manual build and start
pnpm build && pnpm start
```
**Benefits of Docker Compose:**
- Update `config.json`, `sfx/`, and `conf/` files without rebuilding the image
- SQLite database persistence via mounted `./data` volume
- Automatic restart on failure
- Easy log management
- Resource limits and health checks
### Alternative: Direct Docker
```bash
# Build the Docker image
pnpm image:build
# Run the container
pnpm image:run
```
## 📖 Usage
### Sound Effects
**Prefix Command:**
```
!sfx albert # Play 'albert' sound effect
!sfx # List all available sounds
```
**Slash Command:**
```
/sfx sound: albert # Play with autocomplete suggestions
```
### Text Commands
```
!funfact # Random fun fact
!funfact 42 # Specific fun fact #42
!hamfact # Random ham fact
!dance # ASCII dance animation
```
### Role Management
```
!role add streamer # Add the 'streamer' role
!role remove vip # Remove the 'vip' role
```
### Voice Commands
```
!join # Join your voice channel
!leave # Leave current voice channel
```
### Configuration Management
**Dynamic Configuration (Administrator only):**
```
/config show # View current server settings
/config prefix ! # Set command prefix
/config sfx true # Enable/disable sound effects
/config volume 0.8 # Set SFX volume (0.1-1.0)
/config funfacts true # Enable/disable fun facts
/config hamfacts true # Enable/disable ham facts
/config sfxchannels general|music # Set allowed SFX channels (regex)
/config roles streamer|vip|member # Set self-assignable roles
```
## 🔧 Discord Setup
### Creating a Discord Bot
1. **Go to Discord Developer Portal**
- Visit https://discord.com/developers/applications
- Click "New Application" and give it a name
2. **Create Bot User**
- Go to the "Bot" section
- Click "Add Bot"
- Copy the bot token for your config.json
3. **Enable Required Intents**
Under "Privileged Gateway Intents", enable:
- **SERVER MEMBERS INTENT** (Required for role management)
- **MESSAGE CONTENT INTENT** (Required for prefix commands)
4. **Bot Permissions**
When inviting the bot, ensure it has these permissions:
- Send Messages
- Embed Links
- Read Message History
- Connect (for voice channels)
- Speak (for voice channels)
- Use Voice Activity
- Manage Roles (if using role commands)
5. **Invite Bot to Server**
- Go to "OAuth2 > URL Generator"
- Select "bot" and "applications.commands" scopes
- Select the permissions listed above
- Use the generated URL to invite your bot
## ⚙️ Configuration
### Database-Driven Configuration
The bot uses **SQLite database** for persistent guild configurations. Configuration can be managed in three ways:
#### 1. Automatic Registration (Recommended for Public Bot)
When the bot is added to a new server, it automatically:
- Creates default configuration with sensible settings
- Sends a welcome message explaining features
- Registers slash commands for the server
#### 2. Live Configuration via Slash Commands
Administrators can use `/config` commands to modify settings in real-time:
- `/config show` - View current server settings
- `/config prefix <prefix>` - Change command prefix
- `/config sfx <true/false>` - Enable/disable sound effects
- And more (see Configuration Management section above)
#### 3. Seed Data from config.json (Optional)
For initial deployment or migrating existing servers, create `config.json`:
```json
{
"botName": "YourBot",
"debug": false,
"discord": {
"token": "YOUR_BOT_TOKEN",
"adminUserId": "YOUR_DISCORD_USER_ID",
"guilds": [
{
"id": "GUILD_ID",
"internalName": "My Server",
"prefix": "!",
"enableSfx": true,
"sfxVolume": 0.5,
"enableFunFacts": true,
"enableHamFacts": true,
"scheduledEvents": [
{
"id": "daily-reminder",
"schedule": {
"hour": 7,
"minute": 30,
"tz": "America/Los_Angeles"
},
"channelId": "CHANNEL_ID",
"message": "Good morning!",
"pingRoleId": "ROLE_ID"
}
]
}
],
"activities": ["Playing sounds", "Serving facts"],
"blacklistedUsers": []
}
}
```
**Migration Process**: On first startup with an empty database, the bot will automatically import all guilds and settings from `config.json` into the database. After migration, the database becomes the primary configuration source.
### Sound Effects Setup
1. Add `.mp3` or `.wav` files to the `sfx/` directory
2. Files are automatically discovered (filename becomes command name)
3. Sounds appear in slash command autocomplete
### Static Commands
Edit `conf/text_commands` with pipe-separated commands:
```
hello|Hello there!
wiki,wikipedia|https://wikipedia.org
help,commands|Available commands: !sfx, !funfact, !hamfact
```
### Scheduled Events (Advanced)
**For config.json seeding only** - Scheduled events are stored in the database:
```json
"scheduledEvents": [
{
"id": "daily-greeting",
"schedule": {
"hour": 9,
"minute": 0,
"tz": "America/New_York"
},
"channelId": "CHANNEL_ID",
"message": "Good morning everyone!",
"pingRoleId": "ROLE_ID"
},
{
"id": "weekly-reminder",
"schedule": "0 10 * * 1",
"channelId": "CHANNEL_ID",
"message": "Happy Monday!"
}
]
```
**Schedule Formats Supported:**
- **Object format**: `{"hour": 9, "minute": 30, "tz": "America/Los_Angeles"}` (with timezone)
- **Cron format**: `"0 9 * * *"` (standard cron expression)
## 🏗️ Architecture
```
src/
├── index.js # Main bot entry point
├── config/
│ ├── config.js # Hybrid configuration manager (database + file)
│ └── intents.js # Discord gateway intents
├── commands/
│ ├── prefix/ # Traditional prefix commands (!sfx, !funfact, etc.)
│ └── slash/ # Modern slash commands (/sfx, /config)
├── services/
│ ├── databaseService.js # SQLite database operations
│ ├── voiceService.js # Voice connection management
│ ├── commandLoader.js # Static/Ankhbot command loader
│ ├── sfxManager.js # Sound effect file management
│ └── schedulerService.js # Scheduled events handler
└── utils/
└── helpers.js # Utility functions
data/
└── ghbot.db # SQLite database (auto-created)
```
## 🧪 Development
### Running in Development Mode
```bash
pnpm dev # Uses nodemon for auto-reload
```
### Adding New Commands
**Prefix Command Example:**
```javascript
// src/commands/prefix/hello.js
module.exports = {
name: "hello",
description: "Say hello",
async execute(message, args, guildConfig) {
await message.channel.send("Hello there!");
},
};
```
**Slash Command Example:**
```javascript
// src/commands/slash/ping.js
const { SlashCommandBuilder } = require("discord.js");
module.exports = {
data: new SlashCommandBuilder()
.setName("ping")
.setDescription("Replies with Pong!"),
async execute(interaction, guildConfig) {
await interaction.reply("Pong!");
},
};
```
## 📊 System Requirements
- **Node.js**: 20 LTS (Docker) or 22+ LTS (local development)
- **Memory**: 256MB+ RAM
- **Storage**: 500MB+ (depending on sound effects and database)
- **Network**: Stable internet connection for Discord API
- **Database**: SQLite (auto-created, no external database required)
## 🔧 Troubleshooting
### Common Issues
**"Used disallowed intents" Error**
- Enable required intents in Discord Developer Portal (see Discord Setup section above)
- Ensure SERVER MEMBERS INTENT and MESSAGE CONTENT INTENT are enabled
**Voice/Audio Issues**
- Ensure ffmpeg is installed (handled automatically in Docker)
- Check bot has Connect and Speak permissions
- Verify voice channel isn't full or restricted
**Slash Commands Not Appearing**
- Commands register on bot startup
- May take up to 1 hour to appear globally
- Try restarting the bot
**Permission Errors**
- Ensure bot has necessary permissions in channels
- Check role hierarchy (bot role should be above managed roles)
### Debug Mode
Enable debug logging in `config.json`:
```json
{
"debug": true
}
```
## 🤝 Contributing
1. Fork the repository
2. Create a feature branch: `git checkout -b feature/amazing-feature`
3. Commit changes: `git commit -m 'Add amazing feature'`
4. Push to branch: `git push origin feature/amazing-feature`
5. Open a Pull Request
## 📄 License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
## 🙏 Acknowledgments
- Built with [Discord.js v14](https://discord.js.org/)
- Audio processing via [@discordjs/voice](https://github.com/discordjs/voice)
- Inspired by the original AnkhBot command system
- Special thanks to the Discord.js community
## 📞 Support
- Create an [Issue](https://github.com/greenham/ghbot/issues) for bug reports
- Check the Discord Setup section above for configuration help
- Review [CLAUDE.md](CLAUDE.md) for development guidance
---
Made with ❤️ for Discord communities

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,3 @@
f|is for :frog:
ladeda|https://www.youtube.com/watch?v=V0HCZ4YGqbw
fine,ez,steve|https://www.youtube.com/watch?v=_c1NJQ0UP_Q
popsicle,popsicles|https://clips.twitch.tv/FrigidTardyNoodleKappaRoss
imelly|Didn't know this was a political channel. WTF, you don't have sites where you can vomit that garbage out for the peanut gallery? Not everyone holds your same position. Why do that? Don't assume everyone is a socialist. Knock it off. I didn't join here to listen to your political BS. I am not paying a subscription to listen to you people pontificate about your political hangups with normal people. Unsubscribed, unfollowed. -iMellyGurl
dance|*┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛*

View File

@@ -1,7 +0,0 @@
[
{
"name": "vr",
"interval": 1800,
"value": "Video and room requests are on! Use $vr <video-id> to request a video from this list [https://pastebin.com/qv0wDkvB] or $room <room-id> to request a specific room (looped for a few minutes) from this list [https://goo.gl/qoNmuH]"
}
]

View File

@@ -1,720 +0,0 @@
{
"alttp": [
{
"id": "ttas",
"category": "Optimal TTAS",
"label": "Any% NMG Optimal Theory TAS [1:20:38.72]",
"name": "OTTAS Full",
"chatName": "OTTAS Full",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\full-risky-raw.mp4",
"sceneItem": "16x9ph",
"length": 4843,
"includeInShuffle": true
},
{
"id": "ot-seg-escape",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Escape (OTTAS Seg)",
"chatName": "Escape (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\01-escape.mp4",
"sceneItem": "4x3ph",
"length": 352,
"includeInShuffle": true
},
{
"id": "ot-seg-eastern",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Eastern (OTTAS Seg)",
"chatName": "Eastern (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\02-eastern.mp4",
"sceneItem": "4x3ph",
"length": 277,
"includeInShuffle": true
},
{
"id": "ot-seg-desert",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Desert (OTTAS Seg)",
"chatName": "Desert (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\03-desert.mp4",
"sceneItem": "4x3ph",
"length": 345,
"includeInShuffle": true
},
{
"id": "ot-seg-hera",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Hera (OTTAS Seg)",
"chatName": "Hera (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\04-hera.mp4",
"sceneItem": "4x3ph",
"length": 299,
"includeInShuffle": true
},
{
"id": "ot-seg-atower",
"category": "Optimal TTAS Segment",
"label": false,
"name": "ATower (OTTAS Seg)",
"chatName": "ATower (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\05-atower.mp4",
"sceneItem": "4x3ph",
"length": 352,
"includeInShuffle": true
},
{
"id": "ot-seg-pod",
"category": "Optimal TTAS Segment",
"label": false,
"name": "PoD (OTTAS Seg)",
"chatName": "PoD (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\06-pod.mp4",
"sceneItem": "4x3ph",
"length": 309,
"includeInShuffle": true
},
{
"id": "ot-seg-thieves",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Thieves (OTTAS Seg)",
"chatName": "Thieves (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\07-thieves.mp4",
"sceneItem": "4x3ph",
"length": 377,
"includeInShuffle": true
},
{
"id": "ot-seg-skull",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Skull (OTTAS Seg)",
"chatName": "Skull (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\08-skull.mp4",
"sceneItem": "4x3ph",
"length": 267,
"includeInShuffle": true
},
{
"id": "ot-seg-ice",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Ice (OTTAS Seg)",
"chatName": "Ice (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\09-ice.mp4",
"sceneItem": "4x3ph",
"length": 318,
"includeInShuffle": true
},
{
"id": "ot-seg-swamp",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Swamp (OTTAS Seg)",
"chatName": "Swamp (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\10-swamp.mp4",
"sceneItem": "4x3ph",
"length": 345,
"includeInShuffle": true
},
{
"id": "ot-seg-mire",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Mire (OTTAS Seg)",
"chatName": "Mire (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\11-mire.mp4",
"sceneItem": "4x3ph",
"length": 365,
"includeInShuffle": true
},
{
"id": "ot-seg-trock",
"category": "Optimal TTAS Segment",
"label": false,
"name": "TRock (OTTAS Seg)",
"chatName": "TRock (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\12-trock.mp4",
"sceneItem": "4x3ph",
"length": 361,
"includeInShuffle": true
},
{
"id": "ot-seg-gtower",
"category": "Optimal TTAS Segment",
"label": false,
"name": "GTower (OTTAS Seg)",
"chatName": "GTower (OTTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\13-gtower.mp4",
"sceneItem": "4x3ph",
"length": 396,
"includeInShuffle": true
},
{
"id": "ot-seg-ganon",
"category": "Optimal TTAS Segment",
"label": false,
"name": "Ganon (TTAS Seg)",
"chatName": "Ganon (TTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\Risky\\final-versions-timed\\14-ganon.mp4",
"sceneItem": "4x3ph",
"length": 108,
"includeInShuffle": true
},
{
"id": "rta-ttas",
"category": "Safe TTAS",
"label": "Any% NMG Safe/RTA Theory TAS [1:21:26.52]",
"name": "STTAS Full",
"chatName": "STTAS Full",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\full-rta-raw.mp4",
"sceneItem": "16x9ph",
"length": 4892,
"includeInShuffle": true
},
{
"id": "st-seg-escape",
"category": "Safe TTAS Segment",
"label": false,
"name": "Escape (STTAS Seg)",
"chatName": "Escape (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\01-escape.mp4",
"sceneItem": "4x3ph",
"length": 354,
"includeInShuffle": true
},
{
"id": "st-seg-eastern",
"category": "Safe TTAS Segment",
"label": false,
"name": "Eastern (STTAS Seg)",
"chatName": "Eastern (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\02-eastern.mp4",
"sceneItem": "4x3ph",
"length": 281,
"includeInShuffle": true
},
{
"id": "st-seg-desert",
"category": "Safe TTAS Segment",
"label": false,
"name": "Desert (STTAS Seg)",
"chatName": "Desert (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\03-desert.mp4",
"sceneItem": "4x3ph",
"length": 347,
"includeInShuffle": true
},
{
"id": "st-seg-hera",
"category": "Safe TTAS Segment",
"label": false,
"name": "Hera (STTAS Seg)",
"chatName": "Hera (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\04-hera.mp4",
"sceneItem": "4x3ph",
"length": 303,
"includeInShuffle": true
},
{
"id": "st-seg-atower",
"category": "Safe TTAS Segment",
"label": false,
"name": "Agah Tower (STTAS Seg)",
"chatName": "Agah Tower (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\05-atower.mp4",
"sceneItem": "4x3ph",
"length": 354,
"includeInShuffle": true
},
{
"id": "st-seg-pod",
"category": "Safe TTAS Segment",
"label": false,
"name": "PoD (STTAS Seg)",
"chatName": "PoD (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\06-pod.mp4",
"sceneItem": "4x3ph",
"length": 310,
"includeInShuffle": true
},
{
"id": "st-seg-thieves",
"category": "Safe TTAS Segment",
"label": false,
"name": "Thieves (STTAS Seg)",
"chatName": "Thieves (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\07-thieves.mp4",
"sceneItem": "4x3ph",
"length": 379,
"includeInShuffle": true
},
{
"id": "st-seg-skull",
"category": "Safe TTAS Segment",
"label": false,
"name": "Skull (STTAS Seg)",
"chatName": "Skull (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\08-skull.mp4",
"sceneItem": "4x3ph",
"length": 270,
"includeInShuffle": true
},
{
"id": "st-seg-ice",
"category": "Safe TTAS Segment",
"label": false,
"name": "Ice (STTAS Seg)",
"chatName": "Ice (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\09-ice.mp4",
"sceneItem": "4x3ph",
"length": 321,
"includeInShuffle": true
},
{
"id": "st-seg-swamp",
"category": "Safe TTAS Segment",
"label": false,
"name": "Swamp (STTAS Seg)",
"chatName": "Swamp (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\10-swamp.mp4",
"sceneItem": "4x3ph",
"length": 351,
"includeInShuffle": true
},
{
"id": "st-seg-mire",
"category": "Safe TTAS Segment",
"label": false,
"name": "Mire (STTAS Seg)",
"chatName": "Mire (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\11-mire.mp4",
"sceneItem": "4x3ph",
"length": 370,
"includeInShuffle": true
},
{
"id": "st-seg-trock",
"category": "Safe TTAS Segment",
"label": false,
"name": "TRock (STTAS Seg)",
"chatName": "TRock (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\12-trock.mp4",
"sceneItem": "4x3ph",
"length": 364,
"includeInShuffle": true
},
{
"id": "st-seg-gtower",
"category": "Safe TTAS Segment",
"label": false,
"name": "GTower (STTAS Seg)",
"chatName": "GTower (STTAS Seg)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\ttas\\RTA\\final-versions-timed\\13-gtower.mp4",
"sceneItem": "4x3ph",
"length": 408,
"includeInShuffle": true
},
{
"id": "nmg-gold-escape",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Escape (5:52.43) [2018-09-19]",
"name": "Escape (NMG Gold)",
"chatName": "Escape (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\01-[552.43]-2018-09-19-escape.mp4",
"sceneItem": "4x3ph",
"length": 360,
"includeInShuffle": true
},
{
"id": "nmg-gold-eastern",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Eastern (5:00.12) [2018-06-25]",
"name": "Eastern (NMG Gold)",
"chatName": "Eastern (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\02-[500.12]-2018-06-25-eastern.mp4",
"sceneItem": "4x3ph",
"length": 302,
"includeInShuffle": true
},
{
"id": "nmg-gold-desert",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Desert (6:10.32) [2018-06-02]",
"name": "Desert (NMG Gold)",
"chatName": "Desert (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\03-[610.32]-2018-06-02-desert.mp4",
"sceneItem": "4x3ph",
"length": 374,
"includeInShuffle": true
},
{
"id": "nmg-gold-hera",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Hera (5:28.83) [2018-06-01]",
"name": "Hera (NMG Gold)",
"chatName": "Hera (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\04-[528.83]-2018-06-01-hera.mp4",
"sceneItem": "4x3ph",
"length": 332,
"includeInShuffle": true
},
{
"id": "nmg-gold-atower",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Agah Tower (5:13.45) [2018-09-07]",
"name": "Agah Tower (NMG Gold)",
"chatName": "Agah Tower (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\05a-[513.45]-2018-09-07-atower.mp4",
"sceneItem": "4x3ph",
"length": 314,
"includeInShuffle": true
},
{
"id": "nmg-gold-pod",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Palace of Darkness (6:11.15) [2018-05-27]",
"name": "PoD (NMG Gold)",
"chatName": "PoD (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\06-[611.15]-2018-05-27-pod.mp4",
"sceneItem": "4x3ph",
"length": 376,
"includeInShuffle": true
},
{
"id": "nmg-gold-thieves",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Thieves Town (7:08.37) [2018-07-01]",
"name": "Thieves (NMG Gold)",
"chatName": "Thieves (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\07-[708.37]-2018-07-01-thieves.mp4",
"sceneItem": "4x3ph",
"length": 432,
"includeInShuffle": true
},
{
"id": "nmg-gold-skull",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Skull Woods (5:24.28) [2018-06-02]",
"name": "Skull (NMG Gold)",
"chatName": "Skull (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\08-[524.28]-2018-06-02-skull.mp4",
"sceneItem": "4x3ph",
"length": 328,
"includeInShuffle": true
},
{
"id": "nmg-gold-ice",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Ice Palace (6:18.13) [2018-09-08]",
"name": "Ice (NMG Gold)",
"chatName": "Ice (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\09-[618.13]-2018-09-08-ice.mp4",
"sceneItem": "4x3ph",
"length": 379,
"includeInShuffle": true
},
{
"id": "nmg-gold-swamp",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Swamp Palace (6:51.87) [2018-06-10]",
"name": "Swamp (NMG Gold)",
"chatName": "Swamp (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\10-[651.87]-2018-06-10-swamp.mp4",
"sceneItem": "4x3ph",
"length": 418,
"includeInShuffle": true
},
{
"id": "nmg-gold-mire",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Misery Mire (7:06.58) [2018-10-21]",
"name": "Mire (NMG Gold)",
"chatName": "Mire (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\11-[706.58]-2018-10-21-mire.mp4",
"sceneItem": "4x3ph",
"length": 430,
"includeInShuffle": true
},
{
"id": "nmg-gold-trock",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Turtle Rock (7:07.34) [2018-06-25]",
"name": "TRock (NMG Gold)",
"chatName": "TRock (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\12-[707.34]-2018-06-25-trock.mp4",
"sceneItem": "4x3ph",
"length": 434,
"includeInShuffle": true
},
{
"id": "nmg-gold-gtower",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Ganon's Tower (7:09.85) [2018-08-19]",
"name": "GTower (NMG Gold)",
"chatName": "GTower (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\13-[709.85]-2018-08-19-gtower.mp4",
"sceneItem": "4x3ph",
"length": 433,
"includeInShuffle": true
},
{
"id": "nmg-gold-ganon",
"category": "Any% NMG Gold Segment",
"label": "Any% NMG Gold Segment: Ganon (1:43.06) [2018-05-30]",
"name": "Ganon (NMG Gold)",
"chatName": "Ganon (NMG Gold)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\gold-segments\\any%-nmg-nsq\\720p\\timed\\14-[143.06]-2018-05-30-ganon.mp4",
"sceneItem": "4x3ph",
"length": 117,
"includeInShuffle": true
},
{
"id": "pb-100-ahp",
"category": "Personal Best",
"label": "Personal Best: 100% All Heart Pieces (1:19:12) [2017-12-22]",
"name": "100% MG AHP (PB)",
"chatName": "100% MG AHP (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%-mg-ahp\\2017-12-22-100mg-11912.mp4",
"sceneItem": "16x9ph",
"length": 4786,
"includeInShuffle": true
},
{
"id": "pb-ab",
"category": "Personal Best",
"label": "Personal Best: All Bosses No EG (1:09:23) [2017-11-20]",
"name": "All Bosses (PB)",
"chatName": "All Bosses (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-bosses\\2017-11-20-ab-10923.mp4",
"sceneItem": "16x9ph",
"length": 4200,
"includeInShuffle": true
},
{
"id": "pb-ad",
"category": "Personal Best",
"label": "Personal Best: All Dungeons No EG/DG/WW (1:14:59) [2017-11-19]",
"name": "All Dungeons (PB)",
"chatName": "All Dungeons (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\all-dungeons\\2017-11-19-ad-11459.mp4",
"sceneItem": "16x9ph",
"length": 4555,
"includeInShuffle": true
},
{
"id": "pb-any-nmg",
"category": "Personal Best",
"label": "Personal Best: Any% NMG No S+Q (1:26:24) [2018-05-27]",
"name": "Any% NMG (PB)",
"chatName": "Any% NMG (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-nmg-nsq\\2018-05-27-nmg-12624.mp4",
"sceneItem": "16x9ph",
"length": 5190,
"includeInShuffle": true
},
{
"id": "pb-any-no-eg",
"category": "Personal Best",
"label": "Personal Best: Any% No EG (29:05) [2018-10-14]",
"name": "Any% No EG (PB)",
"chatName": "Any% No EG (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\any%-no-eg\\2018-10-14-no-eg-2905.mp4",
"sceneItem": "16x9ph",
"length": 1777,
"includeInShuffle": true
},
{
"id": "pb-master-sword",
"category": "Personal Best",
"label": "Personal Best: Master Sword NMG (22:23) [2018-08-23]",
"name": "Master Sword (PB)",
"chatName": "Master Sword (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\master-sword\\2018-08-23-master-sword-2223.mp4",
"sceneItem": "16x9ph",
"length": 1409,
"includeInShuffle": true
},
{
"id": "pb-ms",
"category": "Personal Best",
"label": "Personal Best: Mirror Shield NMG (50:32) [2017-06-20]",
"name": "Mirror Shield (PB)",
"chatName": "Mirror Shield (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield\\2017-06-20-mirror-shield-5032.mp4",
"sceneItem": "16x9ph",
"length": 3068,
"includeInShuffle": true
},
{
"id": "pb-ms-no-eg",
"category": "Personal Best",
"label": "Personal Best: Mirror Shield No EG (11:38) [2017-07-02]",
"name": "Mirror Shield No EG (PB)",
"chatName": "Mirror Shield No EG (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\mirror-shield-no-eg\\2017-07-02-mirror-shield-no-eg-1138-resized.mp4",
"sceneItem": "4x3ph",
"length": 738,
"includeInShuffle": true
},
{
"id": "pb-rbo",
"category": "Personal Best",
"label": "Personal Best: Reverse Boss Order (1:18:13) [2017-12-01]",
"name": "Reverse Boss Order (PB)",
"chatName": "Reverse Boss Order (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\rbo\\2017-12-01-rbo-11813.mp4",
"sceneItem": "16x9ph",
"length": 4725,
"includeInShuffle": true
},
{
"id": "pb-100-nmg",
"category": "Personal Best",
"label": "Personal Best: 100% NMG (1:49:30) [2017-01-31]",
"name": "100% NMG (PB)",
"chatName": "100% NMG (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\100%\\2017-01-31-hundo-14930.mp4",
"sceneItem": "legacyph",
"length": 6598,
"includeInShuffle": false
},
{
"id": "pb-dg",
"category": "Personal Best",
"label": "Personal Best: Defeat Ganon (13:41) [2017-02-20]",
"name": "Defeat Ganon (PB)",
"chatName": "Defeat Ganon (PB)",
"filePath": "Y:\\media\\videos\\ALttP\\my-vids\\personal-bests\\defeat-ganon\\2017-02-20_12-55-40-defeat-ganon-1341.mp4",
"sceneItem": "legacyph",
"length": 868,
"includeInShuffle": true
}
],
"memes": [
{
"id": "auw",
"name": "auw",
"filePath": "Y:\\media\\videos\\black-box-everybody-wow.mp4",
"sceneItem": "everybody-wow",
"length": 247,
"includeInShuffle": true
},
{
"id": "archery",
"name": "archery",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\archery-contest.mp4",
"sceneItem": "meme1",
"length": 27,
"includeInShuffle": true
},
{
"id": "69blazeit",
"name": "69blazeit",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\69BlazeIt.mp4",
"sceneItem": "meme1",
"length": 92,
"includeInShuffle": true
},
{
"id": "rpgfarm",
"name": "rpgfarm",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\2016-07-24-1424-06-rpg-race-sniped.mp4",
"sceneItem": "meme1",
"length": 144,
"includeInShuffle": true
},
{
"id": "emmapeg",
"name": "emmapeg",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\emma-pegging.mp4",
"sceneItem": "meme1",
"length": 8,
"includeInShuffle": true
},
{
"id": "handy",
"name": "handy",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\handy-in-the-mothhole.mp4",
"sceneItem": "meme1",
"length": 39,
"includeInShuffle": true
},
{
"id": "bodyguard",
"name": "bodyguard",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\heroic-popo.mp4",
"sceneItem": "meme1",
"length": 14,
"includeInShuffle": true
},
{
"id": "whowillitbe",
"name": "whowillitbe",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\its-gonna-be-may.mp4",
"sceneItem": "meme1",
"length": 30,
"includeInShuffle": true
},
{
"id": "mindblown",
"name": "mindblown",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\mindblowing-and-lifechanging.mp4",
"sceneItem": "meme1",
"length": 55,
"includeInShuffle": true
},
{
"id": "nerd-nookie",
"name": "nerd-nookie",
"filePath": "Y:\\media\\videos\\ALttP\\memes\\nerd-bizkit.mp4",
"sceneItem": "meme1",
"length": 27,
"includeInShuffle": true
},
{
"id": "curling",
"name": "curling",
"filePath": "Y:\\media\\videos\\ALttP\\curling-bored-janitors.mp4",
"sceneItem": "meme1",
"length": 16,
"includeInShuffle": true
},
{
"id": "airplane",
"name": "airplane",
"filePath": "Y:\\media\\videos\\ALttP\\emetaPlane.mp4",
"sceneItem": "meme1",
"length": 5,
"includeInShuffle": true
},
{
"id": "hard-things",
"name": "hard-things",
"filePath": "Y:\\media\\videos\\ALttP\\questions-about-hard-things.mp4",
"sceneItem": "meme1",
"length": 39,
"includeInShuffle": true
},
{
"id": "18arrows",
"name": "18arrows",
"filePath": "Y:\\media\\videos\\ALttP\\screevo-18-arrows-fine.mp4",
"sceneItem": "meme1",
"length": 41,
"includeInShuffle": true
},
{
"id": "quake",
"name": "quake",
"filePath": "Y:\\media\\videos\\ALttP\\trock-indoor-quake.mp4",
"sceneItem": "meme1",
"length": 17,
"includeInShuffle": true
}
]
}

View File

@@ -1,11 +0,0 @@
{
"botName": "greenhambot",
"d_token": "YOUR DISCORD APP TOKEN",
"adminID": "YOUR DISCORD USER ID",
"prefix": "!", // prefix that must precede all commands
"botChannel": "bot" // default channel where the bot will post things
"allowedSfxChannels": "bot", // channels where sfx can be used (separated by pipes)
"sfxVolume": 0.3,
"passes": 2, // can be increased to reduce packetloss at the expense of upload bandwidth, 4-5 should be lossless at the expense of 4-5x upload
"textCmdCooldown": 5 // default cooldown in seconds for all text commands
}

View File

@@ -1,283 +0,0 @@
// Import modules
const { Client } = require('discord.js'),
fs = require('fs'),
path = require('path'),
moment = require('moment'),
timers = require('./lib/timers.js'),
staticCommands = require('./lib/static-commands.js'),
//cooldowns = require('./lib/cooldowns.js'),
ankhbotCommands = require('./lib/ankhbot-commands.js'),
config = require('./config.json');
// Set up Discord client
const client = new Client();
// Set up SFX
const sfxFilePath = path.join(__dirname, 'sfx');
const allowedSfxChannels = new RegExp(config.allowedSfxChannels);
let playOptions = {volume: config.sfxVolume, passes: config.passes};
let playing = false;
// Read in sfx directory, filenames are the commands
let sfxList = readSfxDirectory(sfxFilePath);
// Watch directory for changes and update the list
fs.watch(sfxFilePath, (eventType, filename) => {
if (eventType === 'rename') {
sfxList = readSfxDirectory(sfxFilePath);
}
});
// @todo DRY this shit up
// Read in fun facts
const funFactsFilePath = path.join(__dirname, 'conf', 'funfacts');
let funFacts = parseLines(funFactsFilePath);
fs.watchFile(funFactsFilePath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
funFacts = parseLines(funFactsFilePath);
}
});
// Read in ham facts
const hamFactsFilePath = path.join(__dirname, 'conf', 'hamfacts');
let hamFacts = parseLines(hamFactsFilePath);
fs.watchFile(hamFactsFilePath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
hamFacts = parseLines(hamFactsFilePath);
}
});
// Set up the native commands to handle
const commands = {
'sfx': (msg, disconnectAfter) => {
if (!allowedSfxChannels.test(msg.channel.name)) return;
let sfx = msg.content.split(' ')[1];
if (sfx == '' || sfx === undefined) return msg.channel.send('```'+sfxList.join(', ')+'```');
if (playing === true) return msg.channel.send('Already playing, please wait.');
// make sure this file exists either as an mp3 or wav
let sfxPath;
if (fs.existsSync(path.join(sfxFilePath, sfx + '.mp3'))) {
sfxPath = path.join(sfxFilePath, sfx + '.mp3');
} else if (fs.existsSync(path.join(sfxFilePath, sfx + '.wav'))) {
sfxPath = path.join(sfxFilePath, sfx + '.wav');
} else {
return msg.reply('This sound effect does not exist!');
}
if (!msg.guild.voiceConnection) return joinVoiceChannel(msg).then(() => commands.sfx(msg, disconnectAfter));
disconnectAfter = (typeof disconnectAfter !== "undefined") ? disconnectAfter : true;
playing = true;
(function play(sfxFile) {
const dispatcher = msg.guild.voiceConnection.playFile(sfxFile, playOptions);
dispatcher.on('end', reason => {
playing = false;
if (disconnectAfter) msg.guild.voiceConnection.disconnect();
})
.on('error', error => {
playing = false;
if (disconnectAfter) msg.guild.voiceConnection.disconnect();
})
.on('start', () => {});
})(sfxPath.toString());
},
'funfact': (msg) => {
if (funFacts.length > 0) {
// return random element from funFacts, unless one is specifically requested
let el;
let req = parseInt(msg.content.split(' ')[1]);
if (Number.isNaN(req) || typeof funFacts[req-1] === 'undefined') {
el = Math.floor(Math.random() * funFacts.length);
} else {
el = req - 1;
}
let displayNum = (el+1).toString();
let funFact = funFacts[el]
msg.channel.send({embed: {
"title": "FunFact #"+displayNum,
"color": 0x21c629,
"description": funFact
}}).catch(console.error);
} else {
msg.channel.send("No fun facts found!");
}
},
'hamfact': (msg) => {
if (hamFacts.length > 0) {
// return random element from hamFacts, unless one is specifically requested
let el;
let req = parseInt(msg.content.split(' ')[1]);
if (Number.isNaN(req) || typeof hamFacts[req-1] === 'undefined') {
el = Math.floor(Math.random() * hamFacts.length);
} else {
el = req - 1;
}
let displayNum = (el+1).toString();
let hamFact = hamFacts[el]
msg.channel.send({embed: {
"title": "HamFact #"+displayNum,
"color": 0x21c629,
"description": hamFact
}}).catch(console.error);
} else {
msg.channel.send("No ham facts found!");
}
},
'dance': (msg) => {
msg.channel.send("*┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛*");
},
'join': (msg) => {
if (!msg.guild.voiceConnection) {
joinVoiceChannel(msg).then(() => {
//
}).catch(console.error);
} else {
return msg.reply(`I'm already in a voice channel!`);
}
},
'leave': (msg) => {
if (msg.guild.voiceConnection) {
msg.content = '!sfx bye';
commands.sfx(msg);
//msg.guild.voiceConnection.disconnect();
} else {
return msg.reply(`If ya don't eat your meat, ya can't have any pudding!`);
}
},
'listen': (msg) => {
// listen for a particular member to speak and respond appropriately
if (msg.guild.voiceConnection) {
// get the guild member
//let guildMemberId = "88301001169207296"; // me
let guildMemberId = "153563292265086977"; // Screevo
let guildMember = msg.guild.members.get(guildMemberId);
if (guildMember) {
let listenInterval = 1000;
setInterval(() => {
if (guildMember.speaking === true) {
msg.content = '!sfx stfu';
commands.sfx(msg, false);
}
}, listenInterval);
} else {
console.error(`Could not find specified guild member: ${guildMemberId}!`);
msg.guild.voiceConnection.disconnect();
}
} else {
// join the voice channel then call this command again
joinVoiceChannel(msg).then(() => {
commands.listen(msg);
}).catch(console.error);
}
},
'reboot': (msg) => {
if (msg.author.id == config.adminID) process.exit(); //Requires a node module like Forever to work.
}
};
// Wait for discord to be ready, handle messages
client.on('ready', () => {
console.log(`${config.botName} is connected and ready`);
let botChannel = client.channels.find('name', config.botChannel);
// Listen for commands for the bot to respond to across all channels
}).on('message', msg => {
msg.originalContent = msg.content;
msg.content = msg.content.toLowerCase();
// Make sure it starts with the configured prefix
if (!msg.content.startsWith(config.prefix)) return;
// And that it's not on cooldown
/*let cooldownKey = config.botName + msg.content + msg.channel.id;
cooldowns.get(cooldownKey, config.textCmdCooldown)
.then(onCooldown => {
if (onCooldown === false) {*/
// Not on CD, check for native or static command
let commandNoPrefix = msg.content.slice(config.prefix.length).split(' ')[0];
console.log(`'${commandNoPrefix}' received in #${msg.channel.name} from @${msg.author.username}`);
// check for native command first
if (commands.hasOwnProperty(commandNoPrefix)) {
commands[commandNoPrefix](msg);
// then a static command we've manually added
} else if (staticCommands.exists(commandNoPrefix)) {
let result = staticCommands.get(commandNoPrefix);
msg.channel.send({embed: {
"title": commandNoPrefix,
"color": 0x21c629,
"description": result
}}).then(sentMessage => {}/*cooldowns.set(cooldownKey, config.textCmdCooldown)*/)
.catch(console.error);
// then a command exported from ankhbot
} else if (ankhbotCommands.exists(commandNoPrefix)) {
let result = ankhbotCommands.get(commandNoPrefix);
msg.channel.send({embed: {
"title": commandNoPrefix,
"color": 0x21c629,
"description": result
}}).then(sentMessage => {}/*cooldowns.set(cooldownKey, config.textCmdCooldown)*/)
.catch(console.error);
} else {
// Not a command we recognize, ignore
}
/*} else {
// DM the user that it's on CD
dmUser(msg, `**${msg.content}** is currently on cooldown for another *${onCooldown} seconds!*`);
}
})
.catch(console.error);*/
}).login(config.d_token);
function readSfxDirectory(path)
{
let sfxList = fs.readdirSync(sfxFilePath);
sfxList.forEach(function(el, index, a) {
a[index] = el.split('.')[0];
});
return sfxList;
}
function joinVoiceChannel(msg)
{
return new Promise((resolve, reject) => {
const voiceChannel = msg.member.voiceChannel;
if (!voiceChannel || voiceChannel.type !== 'voice') return msg.reply('I couldn\'t connect to your voice channel...');
voiceChannel.join().then(connection => resolve(connection)).catch(err => reject(err));
});
}
// Read/parse text lines from a file
function parseLines(filePath)
{
let lines = [];
let data = fs.readFileSync(filePath, 'utf-8');
let splitLines = data.toString().split('\n');
splitLines.forEach(function(line) {
if (line.length > 0) {
lines.push(line);
}
});
return lines;
}
function dmUser(originalMessage, newMessage)
{
// check that this isn't already a DM before sending
if (originalMessage.channel.type === 'dm') {
originalMessage.channel.send(newMessage);
} else {
originalMessage.member.createDM()
.then(channel => {
channel.send(newMessage);
})
.catch(console.log);
}
}
// catch Promise errors
process.on('unhandledRejection', console.error);

View File

@@ -0,0 +1,6 @@
version: '3.8'
services:
discord-bot:
volumes:
- ./src:/app/src

65
docker-compose.yml Normal file
View File

@@ -0,0 +1,65 @@
version: '3.8'
services:
discord-bot:
build:
context: .
dockerfile: Dockerfile
container_name: discord-bot
restart: unless-stopped
volumes:
# Seed file for initial database population (read-only)
- ./seed.json:/app/seed.json:ro
- ./conf:/app/conf:ro
# Sound effects directory (read-only)
- ./sfx:/app/sfx:ro
# Database persistence
- ./data:/app/data
# Optional: Mount logs directory if you want persistent logs
# - ./logs:/app/logs
# Optional: Environment variables
environment:
- NODE_ENV=production
# - DEBUG=true # Uncomment for debug mode
# Optional: Resource limits
deploy:
resources:
limits:
memory: 512M
reservations:
memory: 256M
# Optional: Health check
healthcheck:
test: ["CMD", "node", "-e", "process.exit(0)"]
interval: 30s
timeout: 10s
retries: 3
start_period: 40s
# Optional: Add a volume backup service
# backup:
# image: alpine:latest
# container_name: ghbot-backup
# volumes:
# - ./config.json:/backup/config.json:ro
# - ./conf:/backup/conf:ro
# - ./sfx:/backup/sfx:ro
# command: |
# sh -c "
# echo 'Creating backup...'
# tar czf /backup/ghbot-backup-$(date +%Y%m%d_%H%M%S).tar.gz -C /backup config.json conf sfx
# echo 'Backup complete'
# "
# profiles:
# - backup
# Optional: Create named volumes for persistent data
# volumes:
# ghbot_logs:
# driver: local

View File

@@ -1,95 +0,0 @@
TODO:
☐ Handle socket disconnect
☐ Start/stop stream automation
☐ Support scheduled start/stop
- instead of countdown for X seconds, use datetime argument
☐ Start
- Countdown for X minutes is triggered and shown
☐ Decouple twitch chat from GHOBS
☐ Move anything that calls director.state from app into fgfm lib
☐ Restrict # of requests a user can have in the queue at once
☐ Room vid requests / import
☐ Improved interface for viewer requests
☐ Web interface? Twitch extension?
☐ Improvements
☐ When playing a room back, loop it at slower speeds for a few iterations
☐ Support adding sets of videos to the queue at once (like the entire ttas or all gold segments)
☐ Tool to output list of video ID's / descriptions
☐ Stream alerts for chat
☐ Rotating background images (leftside)
☐ Support gif's via command through gifph
- !slowdance
- !bender
- !carlton
- !weeb
☐ Fix commercial playing issue (switches back to scene early)
☐ Change $auw and $meme to queue up the videos just like the others (don't switch scenes)
Ideas:
☐ Web interface for viewers to issue commands -- twitch extension?!
☐ Support songrequests -- play through discord?
StreamWebRemote:
- OBS Websocket Connection Mgmt
- Stream Start/Stop/Restart
- Video Queue Mgmt
- Video DB Mgmt
Users:
- use twitch login
- video requests
- room requests
___________________
Archive:
✔ Stop @done (18-11-06 21:54) @project(TODO)
✔ video starting/ending/skipped @done (18-11-06 21:53) @project(TODO)
✔ show status changing @done (18-11-06 21:53) @project(TODO)
✔ Have director emit events for bots to listen to @done (18-11-06 21:53) @project(TODO)
✔ Auto-enable vrmode timer when show starts (listen for event) @done (18-11-06 21:53) @project(TODO)
✔ hide sources before switching to credits @done (18-11-06 21:41) @project(TODO)
✔ Support a delay for Start Streaming @done (18-11-06 21:41) @project(TODO)
✔ Add a $rooms alias for $room @done (18-11-06 15:05) @project(TODO)
✔ Remove currently playing video from vote choices @done (18-10-30 11:51) @project(TODO)
✔ track votes for the current playing video @done (18-10-30 11:48) @project(TODO)
✔ if threshold is met, skip @done (18-10-30 11:48) @project(TODO)
✔ config item for how many votes are required to skip @done (18-10-30 11:48) @project(TODO)
✔ clear votes after video finishes @done (18-10-30 11:48) @project(TODO)
✔ Support viewer $skip voting @done (18-10-30 11:48) @project(TODO)
✔ update director showStatus to 'PAUSED' @done (18-10-30 11:33) @project(TODO)
✔ have nextVideo check for PAUSED @done (18-10-30 11:33) @project(TODO)
✔ Support for $pause (pauses queue after current video finishes) @done (18-10-30 11:33) @project(TODO)
✔ Move vrmode timer to this bot, delete from SLCB @done (18-10-30 11:20) @project(TODO)
✔ Don't auto-init GHOBS or FGFM, make them on-demand @done (18-10-30 08:26) @project(TODO)
✔ Switch to credits with 1 minute remaining @done (18-10-30 08:15) @project(TODO)
✔ Stop Stream @done (18-10-30 08:15) @project(TODO)
✔ Stop @done (18-10-30 08:15) @project(TODO)
✔ Parameter for how long until the stream should end @done (18-10-30 08:15) @project(TODO)
✔ Fade out audio sources with 5 seconds left @done (18-10-30 08:15) @project(TODO)
✔ Add parameter for countdown @done (18-10-30 08:15) @project(TODO)
✔ Set up the queue upon init so it can be managed during startup @done (18-10-30 08:14) @project(TODO)
✔ Starting Soon is shown until countdown is triggered @done (18-10-26 09:44) @project(TODO)
✔ Start stream @done (18-10-26 09:44) @project(TODO)
✔ Add cooldowns @done (18-10-02 10:16) @project(TODO)
✔ video length @done (18-09-28 09:33) @project(TODO)
✔ root folder name @done (18-09-28 09:33) @project(TODO)
✔ room ID @done (18-09-28 09:33) @project(TODO)
✔ original file name @done (18-09-28 09:33) @project(TODO)
✔ keywords @done (18-09-28 09:33) @project(TODO)
✔ dungeon (parse from root folder name) @done (18-09-28 09:33) @project(TODO)
✔ Store the following as metadata: @done (18-09-28 09:33) @project(TODO)
✔ Read the Y:\media\videos\ALttP\my-vids\room-vids directory @done (18-09-28 09:33) @project(TODO)
✔ Go through each folder and get all the .mp4 files @done (18-09-28 09:33) @project(TODO)
✔ Look into just making the video loop instead of hiding at the end @done (18-09-26 18:29) @project(TODO)
✔ specify # of loops in video object @done (18-09-26 18:28) @project(TODO)
✔ change source to loop? calculate time? @done (18-09-26 18:28) @project(TODO)
✔ Update the queue to support looping @done (18-09-26 18:28) @project(TODO)
✔ Fix queue to only return first 20 or so @done (18-09-26 12:04) @project(TODO)
✔ Importing @done (18-09-26 08:25) @project(TODO)
✔ Move vods to their own config @done (18-09-25 15:40) @project(TODO)
✔ Modularize OBS and Twitch code @done (18-09-25 15:39) @project(TODO)
✔ Ability to include/exclude vods from shuffle in config @done (18-09-25 15:39) @project(TODO)
✔ Add random chance for room grind playlist to show for certain amount of time @done (18-09-21 12:28) @project(TODO)
✔ show commercials after a video length cap is hit -- show at conclusion of video @done (18-09-19 11:11) @project(TODO)
✔ add memes to commercial scene @done (18-09-19 11:11) @project(TODO)
✔ add $setcurrent support (to update text label through obs websocket instead of chat) @done (18-09-17 18:06) @project(TODO)
✔ remember the last X vids played, remove these from shuffle choices @done (18-09-17 14:34) @project(TODO)

617
fgfm.js
View File

@@ -1,617 +0,0 @@
/**
* FG.fm Automation
*/
// Import 3rd party packages
const irc = require('irc');
const schedule = require('node-schedule');
const md5 = require('md5');
const moment = require('moment');
// Import local packages
const GHOBS = require('./lib/ghobs');
const FGFM = require('./lib/fgfm');
const cooldowns = require('./lib/cooldowns');
const util = require('./lib/util');
// Read internal configuration
let config = require('./config.json');
config.vods = require(config.vodConfigFile);
config.rooms = require(config.roomConfigFile);
let snesGames = require('./conf/snesgames.json');
let timersList = require('./conf/timers.json');
let activeTimers = [];
let skipVote = {target: null, count: 0};
// Main screen turn on
const obs = new GHOBS(config);
obs.init()
.then(() => twitchInit(config.twitch))
.then(twitch => streamInit(config, twitch))
.catch(console.error);
// Connect to twitch, set up basic event listeners
const twitchInit = (config) => {
return new Promise((resolve, reject) => {
let controlRoom = `#chatrooms:${config.channelId}:${config.controlRoomId}`;
console.log(`Connecting to Twitch / ${config.channel} / ${controlRoom}`);
let defaultTwitchConfig = {
autoRejoin: true,
retryCount: 10,
channels: [config.channel, controlRoom],
debug: config.debug
};
// Connect to Twitch with the bot account
let botChat = new irc.Client(
config.ircServer,
config.botLogin.username,
Object.assign({password: config.botLogin.oauth}, defaultTwitchConfig)
);
// Connect to Twitch with an editor account
let editorChat = new irc.Client(
config.ircServer,
config.editorLogin.username,
Object.assign({password: config.editorLogin.oauth}, defaultTwitchConfig)
);
let twitchErrorHandler = message => {
if (message.command != 'err_unknowncommand') {
console.error('Error from Twitch IRC Server: ', message);
}
};
// Set up bare minimum event listeners for Twitch
botChat.addListener('error', twitchErrorHandler);
editorChat.addListener('error', twitchErrorHandler);
resolve({"botChat": botChat, "editorChat": editorChat, "controlRoom": controlRoom});
});
};
// Initialize Stream automation
const streamInit = (config, twitch) => {
// All your comfy are belong to us
const director = new FGFM({config: config, obs: obs});
// Handle show events from the director
director.on('SHOW_STARTED', () => {
// Enable vrmode timer
manageTimer('vr', 'on');
});
director.on('SHOW_ENDING', (secondsUntilCredits) => {
// Disable vrmode timer
manageTimer('vr', 'off');
// Let the chat know the stream is ending soon
twitch.botChat.say(config.twitch.channel, `The stream will be ending in ${parseFloat(secondsUntilCredits/60).toFixed(0)} minutes!`);
});
director.on('CREDITS_SHOWN', (secondsUntilEnd) => {
twitch.editorChat.say(config.twitch.channel, `Thanks to everyone for watching and lurking! Have a wonderful night and stay comfy. greenhComfy`);
});
// Chat commands
const commands = {
admin: {
init: (cmd) => {
let streamStartDelaySeconds = cmd.args[1] || 1;
let showStartDelaySeconds = cmd.args[2] || 300;
director.startingSoon(streamStartDelaySeconds, showStartDelaySeconds);
},
start: (cmd) => {
director.startTheShow();
},
end: (cmd) => {
let creditsDelay = cmd.args[1] || 1;
let endDelay = cmd.args[2] || 60;
director.endTheShow(creditsDelay, endDelay);
},
changevis: (cmd, newVisibility) => {
let sceneItem = command.args[1] || false;
if (!sceneItem) {
twitch.botChat.say(cmd.to, `A scene item name is required!`);
return;
}
let sceneOrGroup = command.args[2] || obs.currentScene;
obs.setVisible(sceneItem, sceneOrGroup, newVisibility).catch(console.error);
},
show: (cmd) => {
commands.admin.changevis(cmd, true);
},
hide: (cmd) => {
commands.admin.changevis(cmd, false);
},
t: (cmd) => {
let sceneItem = cmd.args[1] || false;
if (!sceneItem) {
twitch.botChat.say(cmd.to, `A scene item name is required!`);
return;
}
obs.toggleVisible(sceneItem).catch(console.error);
},
timer: (cmd) => {
let timerName = cmd.args[1] || false;
if (!timerName) {
twitch.botChat.say(cmd.to, `A timer name is required!`);
return;
}
let timerStatus = cmd.args[2] || false;
try {
manageTimer(timerName, timerStatus);
} catch (e) {
twitch.botChat.say(cmd.to, e);
}
},
auw: (cmd) => {
director.showMeme('auw');
},
meme: (cmd) => {
let memeId = cmd.args[1] || false;
if (memeId) {
console.log(`${memeId} meme requested by ${cmd.from}`);
if ( config.vods.memes.findIndex(e => e.id === memeId) === -1) {
twitch.botChat.say(cmd.to, `No meme with that ID exists!`);
return;
}
} else {
memeId = config.vods.memes.sort(util.randSort)[0].id;
console.log(`${memeId} meme randomly selected`);
}
director.showMeme(memeId);
},
switch: (cmd) => {
let newScene = cmd.args[1] || false;
if (!newScene) {
twitch.botChat.say(cmd.to, `A scene name is required!`);
return;
}
obs.switchToScene(newScene).catch(console.error);
},
setact: (cmd) => {
let newActivity = cmd.args.slice(1).join(' ');
if (!newActivity) {
twitch.botChat.say(cmd.to, `Please provide a new activity`);
return;
}
obs.showActivity(newActivity).catch(console.error);
},
showact: (cmd) => {
obs.showActivity().catch(console.error);
},
hideact: (cmd) => {
obs.hideActivity().catch(console.error);
},
add: (cmd) => {
// @TODO: DRY this out with the checks in vr
let requestedVideoId = cmd.args[1] || false;
if (requestedVideoId === false) {
twitch.botChat.say(cmd.to, `Missing video ID`);
return;
}
// make sure request vid isn't in the queue already
// @TODO: Move into FGFM
if (director.state.videoQueue.findIndex(e => e.id == requestedVideoId) !== -1) {
twitch.botChat.say(cmd.to, `That video is in the queue already!`);
return;
}
// search for req'd vid by id in config.vods.alttp
let vodIndex = config.vods.alttp.findIndex(e => e.id == requestedVideoId);
if (vodIndex === -1) {
twitch.botChat.say(cmd.to, `A video with that ID does not exist!`);
return;
}
// add to queue if it exists
// @TODO: Move into FGFM
if (director.addVideo(config.vods.alttp[vodIndex])) {
twitch.botChat.say(cmd.to, `${config.vods.alttp[vodIndex].chatName} has been added to the queue [${director.state.videoQueue.length}]`);
} else {
twitch.botChat.say(cmd.to, `Video could not be added to queue!`);
}
},
skip: (cmd) => {
director.skip();
},
pause: (cmd) => {
director.pause();
},
resume: (cmd) => {
director.resume();
},
clear: (cmd) => {
director.clearQueue();
},
startvote: (cmd) => {
videoVoteJob.reschedule(`*/${config.videoPollIntervalMinutes} * * * *`);
twitch.botChat.say(cmd.to, `Video Queue Voting will start in ${config.videoPollIntervalMinutes} minutes!`);
},
pausevote: (cmd) => {
clearInterval(rtvInterval);
videoVoteJob.cancel();
twitch.botChat.say(cmd.to, `Video Queue Voting has been paused.`);
},
reboot: (cmd) => {
console.log('Received request from admin to reboot...');
twitch.botChat.say(cmd.to, 'Rebooting...');
process.exit(0); // requires process manager with autoreboot to work
}
},
user: {
vote: (cmd) => {
let userVote = cmd.args[1] || false;
if (userVote === false) {
rockTheVote();
return;
}
userVote = Number.parseInt(userVote);
if (!Number.isInteger(userVote) || userVote < 1 || userVote > currentChoices.length) {
return twitch.botChat.say(cmd.to, `@${from}, please choose an option from 1 - ${currentChoices.length}!`);
}
// Check for uniqueness of vote
// if it's not unique, update the vote
let prevVote = userVotes.findIndex(e => e.from === from);
if (prevVote !== -1) {
if (userVotes[prevVote].vote !== userVote) {
// update vote and inform the user
userVotes[prevVote].vote = userVote;
twitch.botChat.say(cmd.to, `@${from}, your vote has been updated!`);
} else {
twitch.botChat.say(cmd.to, `@${from}, your vote is already in!`);
}
} else {
// log user vote
userVotes.push({"from": from, "vote": userVote});
twitch.botChat.say(cmd.to, `@${from}, your vote has been logged!`);
}
},
queue: (cmd) => {
// @TODO: Move into FGFM
if (director.state.videoQueue.length > 0) {
let chatQueue = director.state.videoQueue.slice(0, 10).map((c, i) => {
return `[${i+1}] ${c.chatName}`;
});
twitch.botChat.say(cmd.to, chatQueue.join(' | '));
} else {
twitch.botChat.say(cmd.to, `No videos currently in queue!`);
}
},
current: (cmd) => {
// @TODO: Move retrieval of currentVideo into FGFM
twitch.botChat.say(cmd.to, `Now Playing: ${director.state.currentVideo.chatName}`);
},
next: (cmd) => {
// @TODO: Move retrieval of videoQueue into FGFM
if (director.state.videoQueue.length > 0) {
twitch.botChat.say(cmd.to, `Next Video: ${director.state.videoQueue[0].chatName}`);
} else {
twitch.botChat.say(cmd.to, `No videos currently in queue!`);
}
},
vr: (cmd) => {
let requestedVideoId = cmd.args[1] || false;
if (requestedVideoId === false) {
twitch.botChat.say(cmd.to, `Useage: ${config.twitch.cmdPrefix}vr <video-id> | Videos: https://pastebin.com/qv0wDkvB`);
return;
}
// make sure request vid isn't in the queue already
// @TODO: Move check into FGFM
if (director.state.videoQueue.findIndex(e => e.id === requestedVideoId) !== -1) {
twitch.botChat.say(cmd.to, `That video is in the queue already!`);
return;
}
// search for req'd vid by id in config.vods.alttp
let vodIndex = config.vods.alttp.findIndex(e => e.id === requestedVideoId);
if (vodIndex === -1) {
twitch.botChat.say(cmd.to, `A video with that ID does not exist!`);
return;
}
// @TODO: Make sure user hasn't met the request limit
config.vods.alttp[vodIndex].requestedBy = cmd.from;
// add to queue if it exists
// @TODO: Return queue position from addVideo
if (director.addVideo(config.vods.alttp[vodIndex])) {
twitch.botChat.say(cmd.to, `${config.vods.alttp[vodIndex].chatName} has been added to the queue [${director.state.videoQueue.length}]`);
} else {
twitch.botChat.say(cmd.to, `${config.vods.alttp[vodIndex].chatName} could not be added to the queue!`);
}
},
room: (cmd) => {
let roomId = cmd.args[1] || false;
let room;
if (roomId !== false) {
let roomIndex = config.rooms.findIndex(e => e.id === parseInt(roomId));
if (roomIndex === -1) {
twitch.botChat.say(cmd.to, `No room found matching that ID!`);
return;
}
room = config.rooms[roomIndex];
} else {
twitch.botChat.say(cmd.to, `Useage: ${config.twitch.cmdPrefix}room <room-id> | Rooms: https://goo.gl/qoNmuH`);
return;
}
// @TODO: Make sure user hasn't met the request limit
room.requestedBy = cmd.from;
director.addRoomVideo(room);
// @TODO: Return new queue position from addRoomVideo and use below
twitch.botChat.say(cmd.to, `Added ${room.dungeonName||'?'} - ${room.roomName||'?'} to the queue [${director.state.videoQueue.length}]!`);
},
rngames: (cmd) => {
twitch.botChat.say(cmd.to, snesGames.sort(util.randSort).slice(0, 10).join(' | '));
},
// voting to skip current video
skip: (cmd) => {
// check if there is an existing vote to skip for the director.state.currentVideo
if (skipVote.target === director.state.currentVideo.id) {
// if yes, add the vote, check if threshold is met, skip if necessary
skipVote.count++;
} else {
skipVote.target = director.state.currentVideo.id;
skipVote.count = 1;
}
if (skipVote.count >= config.skipVoteThreshold) {
director.skip();
skipVote.target = null;
}
},
}
};
// Aliases for chat commands
const aliases = {
"rooms": "room"
};
// Listen for the above commands
twitch.botChat.addListener('message', (from, to, message) => {
// Ignore everything from blacklisted users
if (config.twitch.blacklistedUsers.includes(from)) return;
// Ignore commands that don't start with the designated prefix
if (!message.startsWith(config.twitch.cmdPrefix)) return;
// Remove command prefix for parsing
let noPrefix = message.slice(config.twitch.cmdPrefix.length);
// Ignore blank commands
if (noPrefix.length === 0) return;
// Parse command arguments
let args = noPrefix.split(' ');
let key = args[0] || '';
// Ignore messages without a command
if (!key || key.length === 0) return;
// Case-insensitive
key.toLowerCase();
// Check for aliased commands
if (aliases.hasOwnProperty(key)) key = aliases[key];
// Ignore unrecognized commands
if (!commands.admin.hasOwnProperty(key) && !commands.user.hasOwnProperty(key)) return;
// Check if the command is on cooldown for this user in this channel (admins bypass this)
let cooldownKey = md5(from+to+key);
cooldowns.get(cooldownKey, config.twitch.defaultUserCooldown)
.then(onCooldown => {
if (onCooldown === false || config.twitch.admins.includes(from)) {
let command = {message: message, from: from, to: to, key: key, args: args};
// Handle admin commands
if (commands.admin.hasOwnProperty(command.key) && config.twitch.admins.includes(from)) {
return commands.admin[command.key](command);
}
// Handle all other user commands
if (commands.user.hasOwnProperty(command.key)) {
// Place this command on cooldown for the user
cooldowns.set(cooldownKey, config.twitch.defaultUserCooldown);
return commands.user[command.key](command);
}
}
})
.catch(console.error);
});
const manageTimer = (timerName, timerStatus) => {
// search timers for matching name
let theTimerIndex = timersList.findIndex(e => e.name === timerName);
if (theTimerIndex === -1) {
throw("Invalid timer name!");
}
let theTimer = timersList[theTimerIndex];
// look in activeTimers for current status
let currentTimerIndex = activeTimers.findIndex(e => e.name === timerName);
if (!timerStatus || timerStatus !== 'on' || timerStatus !== 'off') {
// toggle by default
if (currentTimerIndex === -1) {
timerStatus = 'on';
} else {
timerStatus = 'off';
}
}
if (currentTimerIndex === -1 && timerStatus === 'on') {
let timerFunc = () => {
twitch.botChat.say(config.twitch.channel, theTimer.value);
};
let timerInterval = setInterval(timerFunc, theTimer.interval*1000);
activeTimers.push({name: theTimer.name, timer: timerInterval});
timerFunc();
} else if (timerStatus === 'off') {
clearInterval(activeTimers[currentTimerIndex].timer);
activeTimers.splice(currentTimerIndex, 1);
}
return;
}
// @TODO: Modularize timed events
//console.log(`Initializing stream timers...`);
let userVotes = currentChoices = [];
let rockTheVote = () => {};
// @TODO: Move this interval to config
let rtvInterval = setInterval(() => {rockTheVote()}, 300000);
let videoVoteJob = new schedule.Job(async () => {
// Tally votes from previous election (if there was one), add the winner to the queue
let winner;
if (currentChoices.length > 0) {
if (userVotes.length === 0) {
// choose a random element from currentChoices
winner = util.randElement(currentChoices);
console.log(`VIDEO CHOSEN RANDOMLY: ${winner.chatName}`);
twitch.botChat.say(config.twitch.channel, `No Votes Logged -- Next Video Chosen at Random: ${winner.chatName}`);
} else {
// tally and sort votes
let voteTallies = [];
await util.asyncForEach(userVotes, async (vote) => {
tallyIndex = voteTallies.findIndex(e => e.id === vote.vote);
if (tallyIndex !== -1) {
voteTallies[tallyIndex].count++;
} else {
voteTallies.push({id: vote.vote, count: 1});
}
});
voteTallies.sort((a, b) => {
if (a.count < b.count) {
return -1;
}
if (a.count > b.count) {
return 1;
}
// a must be equal to b
return 0;
});
console.log(`Voting Results: ${JSON.stringify(voteTallies)}`);
winner = currentChoices[voteTallies[0].id-1];
console.log(`WINNER OF THE VOTE: ${winner.chatName}`);
twitch.botChat.say(config.twitch.channel, `Winner of the Video Vote: ${winner.chatName}`);
// clear user votes
userVotes = [];
}
director.addVideo(winner);
}
// choose more random videos from config.vods.alttp (that aren't already in the queue)
// @TODO: Move into FGFM
let vodsNotInQueue = config.vods.alttp.filter(e => {
let inQueue = (director.state.videoQueue.findIndex(q => q.id === e.id) !== -1) && (director.state.currentVideo.id !== e.id);
return !inQueue;
});
currentChoices = vodsNotInQueue.sort(util.randSort).slice(0, config.videoPollSize);
// Poll the chat
let chatChoices = currentChoices.map((c, i) => {
return `[${i+1}] ${c.chatName}`;
});
rockTheVote = () => {
twitch.botChat.say(config.twitch.channel, `Vote for which video you'd like to add to the queue using ${config.twitch.cmdPrefix}vote #: ${chatChoices.join(' | ')}`)
};
clearInterval(rtvInterval);
rockTheVote();
rtvInterval = setInterval(() => {rockTheVote()}, 300000);
});
};
// catches Promise errors
process.on('unhandledRejection', console.error);

547
legacy.js Normal file
View File

@@ -0,0 +1,547 @@
// Import modules
const Discord = require("discord.js"),
fs = require("fs"),
path = require("path"),
axios = require("axios"),
schedule = require("node-schedule"),
staticCommands = require("./lib/static-commands.js"),
ankhbotCommands = require("./lib/ankhbot-commands.js"),
{ randElement, chunkSubstr } = require("./lib/utils.js"),
config = require("./config.json");
function init(config) {
// Set up Discord client
const client = new Discord.Client();
// Set up SFX
const sfxFilePath = path.join(__dirname, "sfx");
// Read in sfx directory, filenames are the commands
let sfxList = readSfxDirectory(sfxFilePath);
// Watch directory for changes and update the list
fs.watch(sfxFilePath, (eventType, filename) => {
if (eventType === "rename") {
sfxList = readSfxDirectory(sfxFilePath);
}
});
// @todo DRY this shit up
// Read in fun facts
const funFactsFilePath = path.join(__dirname, "conf", "funfacts");
let funFacts = parseLines(funFactsFilePath);
fs.watchFile(funFactsFilePath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
funFacts = parseLines(funFactsFilePath);
}
});
// Read in ham facts
const hamFactsFilePath = path.join(__dirname, "conf", "hamfacts");
let hamFacts = parseLines(hamFactsFilePath);
fs.watchFile(hamFactsFilePath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
hamFacts = parseLines(hamFactsFilePath);
}
});
// Set up the native commands to handle
const commands = {
sfx: async (msg, guildConfig) => {
let allowedSfxChannels = new RegExp(guildConfig.allowedSfxChannels);
if (!allowedSfxChannels.test(msg.channel.name)) return;
let sfx = msg.content.split(" ")[1];
// retrieve sfx list from pastebin
if (sfx == "" || sfx === undefined) {
axios
.get("https://rentry.co/ghbotsfx/raw")
.then((res) => {
// break the result into half chunks if it exceeds the message limit size
// (discord limit is 2k)
let chunks = [res.data];
if (res.data.length > 2000) {
chunks = chunkSubstr(res.data, res.data.length / 2);
}
chunks.forEach((chunk) => {
return msg.channel.send(chunk);
});
})
.catch(console.error);
return true;
}
// make sure this file exists either as an mp3 or wav
let sfxPath;
if (fs.existsSync(path.join(sfxFilePath, sfx + ".mp3"))) {
sfxPath = path.join(sfxFilePath, sfx + ".mp3");
} else if (fs.existsSync(path.join(sfxFilePath, sfx + ".wav"))) {
sfxPath = path.join(sfxFilePath, sfx + ".wav");
} else {
return msg.reply("This sound effect does not exist!");
}
// Join the same voice channel of the author of the message
const connection = await joinVoiceChannel(msg);
if (connection === false) {
return msg.reply("I couldn't connect to your voice channel...");
}
(function play(sfxFile) {
const dispatcher = connection.play(sfxFile, {
volume: guildConfig.sfxVolume,
passes: guildConfig.passes,
});
dispatcher
.on("finish", (reason) => {
connection.disconnect();
})
.on("error", (error) => {
connection.disconnect();
console.error("Error playing sfx: " + error);
})
.on("start", () => {});
})(sfxPath.toString());
},
funfact: (msg, guildConfig) => {
if (guildConfig.enableFunFacts === false) {
return;
}
if (funFacts.length > 0) {
// return random element from funFacts, unless one is specifically requested
let el;
let req = parseInt(msg.content.split(" ")[1]);
if (Number.isNaN(req) || typeof funFacts[req - 1] === "undefined") {
el = Math.floor(Math.random() * funFacts.length);
} else {
el = req - 1;
}
let displayNum = (el + 1).toString();
let funFact = funFacts[el];
msg.channel
.send({
embed: {
title: "FunFact #" + displayNum,
color: 0x21c629,
description: funFact,
},
})
.catch(console.error);
} else {
msg.channel.send("No fun facts found!");
}
},
hamfact: (msg, guildConfig) => {
if (guildConfig.enableHamFacts === false) {
return;
}
if (hamFacts.length > 0) {
// return random element from hamFacts, unless one is specifically requested
let el;
let req = parseInt(msg.content.split(" ")[1]);
if (Number.isNaN(req) || typeof hamFacts[req - 1] === "undefined") {
el = Math.floor(Math.random() * hamFacts.length);
} else {
el = req - 1;
}
let displayNum = (el + 1).toString();
let hamFact = hamFacts[el];
msg.channel
.send({
embed: {
title: "HamFact #" + displayNum,
color: 0x21c629,
description: hamFact,
},
})
.catch(console.error);
} else {
msg.channel.send("No ham facts found!");
}
},
dance: (msg, guildConfig) => {
msg.channel.send(
"*┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛ ┏(-_-)┓┏(-_-)┛┗(-_- )┓┗(-_-)┛┏(-_-)┛*"
);
},
// Allow members to request role additions/removals for allowed roles
role: (msg, guildConfig) => {
// make sure there are allowed roles defined
if (
typeof guildConfig.allowedRolesForRequest === undefined ||
guildConfig.allowedRolesForRequest.length === 0
) {
return msg.reply(
"No roles are currently allowed to be added/removed by members."
);
}
let validRoles = guildConfig.allowedRolesForRequest.split("|");
if (msg.content === guildConfig.prefix + "role") {
return msg.reply(
`Useage: ${guildConfig.prefix}role {add|remove} {${guildConfig.allowedRolesForRequest}}`
);
}
// parse+validate action+role (use original case from message because roles are case-sensitive)
let roleName = msg.originalContent.match(
/role\s(add|remove)\s([a-z0-9\-]+)/i
);
if (!roleName) {
return msg.reply(
`Useage: ${guildConfig.prefix}role {add|remove} {${guildConfig.allowedRolesForRequest}}`
);
} else {
let tester = new RegExp(guildConfig.allowedRolesForRequest, "i");
if (tester.test(roleName[2])) {
// make sure this message is in a guild channel they're a member of
if (!msg.guild) return;
// find the role in the member's guild
let role = msg.guild.roles.cache.find((x) => x.name === roleName[2]);
if (!role) {
return msg.reply(`${roleName[2]} is not a role on this server!`);
}
// add/remove the role and react to the message with the results
if (roleName[1] === "add") {
msg.member.roles
.add(role, "User requested")
.then((requestingMember) => {
msg
.react("👍")
.then(() => {
console.log("Reaction sent");
})
.catch(console.error);
})
.catch((err) => {
console.error(`Error during role addition: ${err}`);
msg
.react("⚠")
.then(() => {
console.log("Reaction sent");
})
.catch(console.error);
});
} else if (roleName[1] === "remove") {
msg.member.roles
.remove(role, "User requested")
.then((requestingMember) => {
msg
.react("👍")
.then(() => {
console.log("Reaction sent");
})
.catch(console.error);
})
.catch((err) => {
console.error(`Error during role addition: ${err}`);
msg
.react("⚠")
.then(() => {
console.log("Reaction sent");
})
.catch(console.error);
});
} else {
msg.reply(
`You must use add/remove after the role command! *e.g. ${guildConfig.prefix}role add ${validRoles[0]}*`
);
}
} else {
msg.reply(
`**${
roleName[2]
}** is not a valid role name! The roles allowed for request are: ${validRoles.join(
","
)}`
);
}
}
},
join: (msg, guildConfig) => {
if (!msg.guild.voiceConnection) {
joinVoiceChannel(msg)
.then(() => {
//
})
.catch(console.error);
} else {
return msg.reply(`I'm already in a voice channel!`);
}
},
leave: (msg, guildConfig) => {
if (msg.guild.voiceConnection) {
msg.guild.voiceConnection.disconnect();
} else {
return msg.reply(
`If ya don't eat your meat, ya can't have any pudding!`
);
}
},
listen: (msg, guildConfig) => {
// listen for a particular member to speak and respond appropriately
if (msg.guild.voiceConnection) {
// get the guild member
//let guildMemberId = "88301001169207296"; // me
let guildMemberId = "153563292265086977"; // Screevo
let guildMember = msg.guild.members.get(guildMemberId);
if (guildMember) {
let listenInterval = 1000;
setInterval(() => {
if (guildMember.speaking === true) {
msg.content = "!sfx stfu";
commands.sfx(msg, false);
}
}, listenInterval);
} else {
console.error(
`Could not find specified guild member: ${guildMemberId}!`
);
msg.guild.voiceConnection.disconnect();
}
} else {
// join the voice channel then call this command again
joinVoiceChannel(msg)
.then(() => {
commands.listen(msg, guildConfig);
})
.catch(console.error);
}
},
reboot: (msg, guildConfig) => {
if (msg.author.id == config.discord.adminUserId) process.exit(); // Requires a node module like Forever to work.
},
};
client
// Wait for discord to be ready, handle messages
.on("ready", () => {
console.log(`${config.botName} is connected and ready`);
client.setRandomActivity();
setInterval(() => {
client.setRandomActivity();
}, 3600 * 1000);
// Set up scheduled events for each guild
config.discord.guilds.forEach(async (guild) => {
let discordGuild = false;
try {
discordGuild = await client.guilds.fetch(guild.id);
} catch (err) {
console.error(err);
}
if (!discordGuild) return;
if (
guild.hasOwnProperty("scheduledEvents") &&
guild.scheduledEvents.length > 0
) {
guild.scheduledEvents.forEach(async (event) => {
let channel = false;
if (
event.hasOwnProperty("channelId") &&
event.channelId.length > 0
) {
channel = await discordGuild.channels.resolve(event.channelId);
}
if (!channel) {
console.log(
`Invalid channel configured for event ${event.id}, guild ${guild.name}`
);
return;
}
let pingRole = false;
if (
event.hasOwnProperty("pingRoleId") &&
event.pingRoleId.length > 0
) {
pingRole = await discordGuild.roles.fetch(event.pingRoleId);
}
console.log(
`Scheduling event ${event.id} for ${discordGuild.name}...`
);
const job = schedule.scheduleJob(event.schedule, () => {
let payload = [];
if (pingRole !== false) {
payload.push(pingRole);
}
if (event.hasOwnProperty("message") && event.message.length > 0) {
payload.push(event.message);
}
channel.send(payload);
});
console.log(`Next invocation: ${job.nextInvocation()}`);
});
}
});
})
// Listen for commands for the bot to respond to across all channels
.on("message", (msg) => {
// Ignore DMs and messages from unconfigured guilds
if (msg.guild) {
if (!config.discord.guilds.find((g) => g.id === msg.guild.id)) {
return;
}
} else {
return;
}
// Ignore anything from blacklisted users
if (config.discord.blacklistedUsers.includes(msg.author.id)) {
return;
}
// Find the guild config for this msg, use default if no guild (DM)
let guildConfig = config.discord.guilds.find(
(g) => g.id === msg.guild.id
);
// Parse message content
msg.originalContent = msg.content;
msg.content = msg.content.toLowerCase();
// Make sure the command starts with the configured prefix
if (!msg.content.startsWith(guildConfig.prefix)) return;
let commandNoPrefix = msg.content
.slice(guildConfig.prefix.length)
.split(" ")[0];
console.log(
`'${commandNoPrefix}' received in ${guildConfig.internalName}#${msg.channel.name} from @${msg.author.username}`
);
// check for native command first
if (commands.hasOwnProperty(commandNoPrefix)) {
commands[commandNoPrefix](msg, guildConfig);
// then a static command we've manually added
} else if (staticCommands.exists(commandNoPrefix)) {
let result = staticCommands.get(commandNoPrefix);
msg.channel
.send({
embed: {
title: commandNoPrefix,
color: 0x21c629,
description: result,
},
})
.catch(console.error);
// then a command exported from ankhbot
} else if (ankhbotCommands.exists(commandNoPrefix)) {
let result = ankhbotCommands.get(commandNoPrefix);
msg.channel
.send({
embed: {
title: commandNoPrefix,
color: 0x21c629,
description: result,
},
})
.catch(console.error);
} else {
// Not a command we recognize, ignore
}
})
// Handle new members joining one of our guilds
.on("guildMemberAdd", (member) => {
// Ignore events from unconfigured guilds
if (member.guild) {
if (!config.discord.guilds.find((g) => g.id === msg.guild.id)) {
return;
}
} else {
return;
}
console.log(
`A new member has joined '${member.guild.name}': ${member.displayName}`
);
})
// Log guild becoming unavailable (usually due to server outage)
.on("guildUnavailable", (guild) => {
console.log(
`Guild '${guild.name}' is no longer available! Most likely due to server outage.`
);
})
// Log debug messages if enabled
.on("debug", (info) => {
if (config.debug === true) {
console.log(`[${new Date()}] DEBUG: ${info}`);
}
})
// Log disconnect event
.on("disconnect", (event) => {
console.log(
`Web Socket disconnected with code ${event.code} and reason '${event.reason}'`
);
})
// Log errors
.on("error", console.error)
// Log the bot in
.login(config.discord.token);
}
function readSfxDirectory(path) {
let thePath = path || sfxFilePath;
let sfxList = fs.readdirSync(thePath);
sfxList.forEach(function (el, index, a) {
a[index] = el.split(".")[0];
});
return sfxList;
}
async function joinVoiceChannel(message) {
// Join the same voice channel of the author of the message
if (message.member.voice.channel) {
return await message.member.voice.channel.join();
} else {
return false;
}
}
// Read/parse text lines from a file
function parseLines(filePath) {
let lines = [];
let data = fs.readFileSync(filePath, "utf-8");
let splitLines = data.toString().split("\n");
splitLines.forEach(function (line) {
if (line.length > 0) {
lines.push(line);
}
});
return lines;
}
// catch Promise errors
process.on("unhandledRejection", console.error);
// Fire it up
init(config);
Discord.Client.prototype.setRandomActivity = function () {
let activity =
config.discord.activities.length > 0
? randElement(config.discord.activities)
: "DESTROY ALL HUMANS";
console.log(`Setting Discord activity to: ${activity}`);
this.user.setActivity(activity, {
url: `https://twitch.tv/fgfm`,
type: "STREAMING",
});
};

View File

@@ -1,49 +0,0 @@
module.exports = {
exists: exists,
get: get
};
const fs = require('fs'),
path = require('path');
const cmdPrefix = '!';
const commandsFilePath = path.join(__dirname, '..', 'conf', 'ghbot.abcomg');
let commands = parseCommands(commandsFilePath);
fs.watchFile(commandsFilePath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
commands = require(commandsFilePath);
}
});
function exists(command)
{
let matches = commands.filter(obj => { return obj.Command === cmdPrefix+command; });
if (matches.length > 0) {
return true;
} else {
return false;
}
}
function get(command)
{
let matches = commands.filter(obj => { return obj.Command === cmdPrefix+command; });
if (matches.length > 0) {
let match = matches[0];
if (match.Enabled === true) {
return matches[0].Response;
} else {
return undefined;
}
} else {
return undefined;
}
}
// Read in the array of objects exported from AnkhBot
function parseCommands(filePath)
{
let data = fs.readFileSync(filePath, 'utf-8');
let commands = eval(data);
return commands;
}

View File

@@ -1,45 +0,0 @@
module.exports = {
get: isOnCooldown,
set: placeOnCooldown
};
const NodeCache = require('node-cache');
const cache = new NodeCache({checkperiod: 1});
const md5 = require('md5');
const keyPrefix = 'cd';
// Given a cooldownTime in seconds and a command, returns false if the command is not on cooldown
// returns the time in seconds until the command will be ready again otherwise
function isOnCooldown(command, cooldownTime, callback)
{
return new Promise((resolve, reject) => {
let now = Date.now();
let onCooldown = false;
let key = keyPrefix + md5(command);
cache.get(key, function(err, timeUsed) {
if (err) reject(err);
if (timeUsed !== null) {
// Command was recently used, check timestamp to see if it's on cooldown
if ((now - timeUsed) <= (cooldownTime*1000)) {
// Calculate how much longer it's on cooldown
onCooldown = ((cooldownTime*1000) - (now - timeUsed))/1000;
}
}
resolve(onCooldown);
});
});
}
// Places a command on cooldown for cooldownTime (in seconds)
function placeOnCooldown(command, cooldownTime)
{
let key = keyPrefix + md5(command);
return cache.set(key, Date.now(), cooldownTime, handleCacheSet);
}
function handleCacheSet(error, result) {}
process.on('exit', (code) => {cache.close()});

View File

@@ -1,324 +0,0 @@
const util = require('./util'),
emitter = require('events').EventEmitter,
sysutil = require('util');
function FGFM(config) {
// Set up initial state
this.config = config.config;
this.obs = config.obs;
this.state = {
showStatus: 'IDLE',
videoQueue: [],
recentlyPlayed: [],
currentVideo: null,
videoTimer: null,
lastCommercialShownAt: Date.now(),
commercialPlaying: false
};
emitter.call(this);
this.startingSoon = (streamStartDelaySeconds, showStartDelaySeconds) => {
// @TODO: Move these defaults to config
if (typeof streamStartDelaySeconds === 'undefined') {
streamStartDelaySeconds = 1;
} else {
streamStartDelaySeconds = parseInt(streamStartDelaySeconds);
}
if (typeof showStartDelaySeconds === 'undefined') {
showStartDelaySeconds = 300;
} else {
showStartDelaySeconds = parseInt(showStartDelaySeconds);
showStartDelaySeconds += streamStartDelaySeconds;
}
this.state.showStatus = 'STARTING_SOON';
// Set up the initial queue by randomly choosing the configured amount of vods included in shuffling
this.state.videoQueue = this.config.vods.alttp.filter(e => e.includeInShuffle === true).sort(util.randSort).slice(0, this.config.initialQueueSize);
// Show the starting-soon scene
this.obs.switchToScene('starting-soon');
// Restore volume
this.obs.setVolume('headphones', 1.0);
// Start the stream after delay
console.log(`The stream will start in ${streamStartDelaySeconds} seconds!`);
setTimeout(() => {this.obs.startStream().then(() => {this.emit('STREAM_STARTED')})}, streamStartDelaySeconds*1000);
// Start the "show" after stream+show delay
// @TODO: Actually show the countdown in the scene
console.log(`The show will start in ${showStartDelaySeconds} seconds!`);
setTimeout(this.startTheShow, showStartDelaySeconds*1000);
};
// Set up initial queue + start playback
this.startTheShow = () => {
// Set up the initial queue by randomly choosing the configured amount of vods included in shuffling
this.state.videoQueue = this.config.vods.alttp.filter(e => e.includeInShuffle === true).sort(util.randSort).slice(0, this.config.initialQueueSize);
// Start queue playback
this.state.currentVideo = this.state.videoQueue.shift();
this.showVideo(this.state.currentVideo);
// restore volume
this.obs.setVolume('headphones', 1.0);
this.state.showStatus = 'RUNNING';
this.emit('SHOW_STARTED');
};
this.endTheShow = (creditsDelaySeconds, endDelaySeconds) => {
if (typeof creditsDelaySeconds === 'undefined' || creditsDelaySeconds === false) {
creditsDelaySeconds = 0;
}
if (typeof endDelaySeconds === 'undefined' || endDelaySeconds === false) {
endDelaySeconds = 60;
}
console.log(`Credits will be shown in ${creditsDelaySeconds} seconds!`);
this.emit('SHOW_ENDING', creditsDelaySeconds);
let end = () => {
this.state.showStatus = 'ENDING';
// Hide current video, don't play next video
this.obs.hide(this.state.currentVideo.sceneItem, this.config.defaultSceneName)
clearTimeout(this.state.videoTimer);
this.obs.switchToScene('credits')
.then(() => {
this.emit('CREDITS_SHOWN', endDelaySeconds);
if (endDelaySeconds < 5) endDelaySeconds = 5;
console.log(`Stream will stop in ${endDelaySeconds} seconds`);
let fadeOutDelay = endDelaySeconds - 5;
// Fade out volume with 5 seconds left
setTimeout(() => {
this.obs.getVolume('headphones')
.then(currentVolume => {
console.log(`current volume of headphones: ${currentVolume}`);
let step = 0.1;
while (currentVolume > 0.1) {
currentVolume -= step;
console.log(`setting volume to: ${currentVolume}`);
this.obs.setVolume('headphones', currentVolume);
util.sleep(250);
}
})
.catch(console.error);
}, fadeOutDelay*1000);
setTimeout(() => {
this.obs.stopStream();
this.state.showStatus = 'ENDED';
this.emit('SHOW_ENDED');
}, endDelaySeconds*1000);
})
.catch(console.error);
};
if (creditsDelaySeconds > 0) {
setTimeout(end, creditsDelaySeconds*1000);
} else {
end();
}
};
// Shows.. a... video
this.showVideo = video => {
console.log(`Showing video: ${video.chatName}`);
this.obs.playVideoInScene(video, this.config.defaultSceneName, this.nextVideo)
.then(timer => {
// track timer so we can cancel callback later on if necessary
this.state.videoTimer = timer;
// update activity label and show/hide appropriately
if (video.hasOwnProperty('label') && video.label !== false) {
this.obs.showActivity(video.label);
} else {
this.obs.hideActivity();
}
})
.catch(console.error);
};
// Adds a gameplay vod to the queue
this.addVideo = video => {
return this.state.videoQueue.push(video);
};
// Adds a room to the queue and handles looping setup
this.addRoomVideo = (room, loop) => {
let loops = 1;
if (typeof loop === 'undefined' || loop === true) {
loops = Math.floor(this.config.roomVidPlaytime / room.videoData.length);
}
console.log(`Adding room video for ${room.dungeonName} - ${room.roomName} to the queue (${loops} loops)`);
let video = {
filePath: `${this.config.roomVidsBasePath}${room.winPath}`,
sceneItem: (room.videoData.width === 960) ? "4x3ph" : "16x9ph",
length: room.videoData.length,
label: room.roomName,
chatName: room.roomName,
loops: loops,
requestedBy: room.requestedBy
};
this.state.videoQueue.push(video);
};
// Picks the next video in the queue (shuffles if empty)
// Also handles "commercial breaks" if enabled
this.nextVideo = () => {
// @TODO: Validate this.state.showStatus -- make sure the "show" hasn't been paused or stopped
let ignoreStates = ['ENDING', 'ENDED', 'PAUSED'];
if (ignoreStates.includes(this.state.showStatus)) {
return;
}
// Show a "commercial break" if it's been long enough since the last one
let secondsSinceLastCommercial = (Date.now() - this.state.lastCommercialShownAt) / 1000;
if (this.config.commercialsEnabled === true && secondsSinceLastCommercial >= this.config.commercialInterval) {
console.log(`It has been ${secondsSinceLastCommercial} seconds since the last commercial break!`);
// Random chance for it to be "everybody wow"
let memeId = false;
if ((Math.floor(Math.random() * 100) + 1) <= this.config.auwChance) {
console.log(`Showing AUW!`);
memeId = 'auw';
}
this.showMeme(memeId)
.then(() => {
this.state.lastCommercialShownAt = Date.now();
this.nextVideo();
})
.catch(console.error);
return;
}
// Keep track of recently played videos
if (this.state.recentlyPlayed.length === this.config.recentlyPlayedMemory) {
this.state.recentlyPlayed.shift();
}
this.state.recentlyPlayed.push(this.state.currentVideo.id);
// If a commercial is playing, wait until it's done to switch
while (this.state.commercialPlaying === true) {}
// play the next video in the queue, or pick one at random if the queue is empty
if (this.state.videoQueue.length > 0) {
this.state.currentVideo = this.state.videoQueue.shift();
} else {
// Random chance for room grind to be played for an amount of time instead of another video be shuffled to
if ((Math.floor(Math.random() * 100) + 1) <= this.config.roomGrindChance) {
console.log(`Room grind selected!`);
// show room-grind source
this.obs.showRoomGrind(this.config.roomGrindPlaytime, () => {this.nextVideo()})
.then(timer => {
this.state.videoTimer = timer;
})
.catch(console.error);
return;
}
// Random chance for room videos to be added
if ((Math.floor(Math.random() * 100) + 1) <= this.config.roomShuffleChance) {
console.log(`Room vids selected!`);
this.addRoomVideo(this.config.rooms.sort(util.randSort).slice(0, 1).shift());
// play the first one
this.state.currentVideo = this.state.videoQueue.shift();
} else {
// filter recently played from shuffle
let freshVods = this.config.vods.alttp.filter(e => {
return e.includeInShuffle === true && !this.state.recentlyPlayed.includes(e.id);
});
this.state.currentVideo = freshVods.sort(util.randSort).slice(0, 1).shift();
}
}
this.showVideo(this.state.currentVideo);
};
// "Commercials"
this.showCommercial = (video, callback) => {
return new Promise((resolve, reject) => {
let handleFinish = () => {
console.log('commercial is finished playing...');
this.state.commercialPlaying = false;
if (typeof callback !== 'undefined') callback();
}
this.obs.playVideoInScene(video, this.config.commercialSceneName, handleFinish)
.then(timer => {
this.state.commercialPlaying = true;
resolve(timer);
})
.catch(reject);
});
};
// Memes-By-Id
this.showMeme = id => {
return new Promise((resolve, reject) => {
// find the vod in memes
let video = this.config.vods.memes.find(e => e.id === id);
if (!video) {
reject(`No meme found matching ID ${id}`);
}
let handleFinish = () => {
if (id === 'auw') {
this.obs.hide("owen", this.config.commercialSceneName);
}
resolve();
};
this.showCommercial(video, handleFinish)
.then(videoHasStarted => {
// in the case of 'auw', show owen
if (id === 'auw') {
this.obs.show("owen", this.config.commercialSceneName);
}
})
.catch(console.error);
});
};
// Skip the current video and play the next
this.skip = () => {
clearTimeout(this.state.videoTimer);
this.obs.hide(this.state.currentVideo.sceneItem, this.config.defaultSceneName).then(this.nextVideo).catch(console.error);
};
// Clears.. the... queue
this.clearQueue = () => {
this.state.videoQueue = [];
};
this.pause = () => {
this.state.showStatus = 'PAUSED';
this.emit('SHOW_PAUSED');
};
this.resume = () => {
this.state.showStatus = 'RUNNING';
this.nextVideo();
this.emit('SHOW_RESUMED');
};
}
sysutil.inherits(FGFM, emitter);
module.exports = FGFM;

View File

@@ -1,178 +0,0 @@
const OBSWebSocket = require('obs-websocket-js');
function GHOBS(config) {
this.config = config;
this.websocket = new OBSWebSocket();
this.init = () => {
return new Promise((resolve, reject) => {
console.log(`Connecting to OBS Websocket...`);
this.websocket.connect({ address: this.config.obs.websocket.address, password: this.config.obs.websocket.password })
.then(() => {
console.log(`Success! We're connected to OBS!`);
this.websocket.getCurrentScene().then(currentScene => this.currentScene = currentScene.name);
this.websocket.onSwitchScenes(newScene => this.currentScene = newScene.sceneName);
resolve();
})
.catch(reject);
// Listen for errors from OBS
// @TODO: Handle socket disconnect gracefully
/** { status: 'error',
description: 'There is no Socket connection available.',
code: 'NOT_CONNECTED',
error: 'There is no Socket connection available.' }*/
this.websocket.on('error', err => {
console.error(`OBS websocket error: ${JSON.stringify(err)}`);
});
});
};
this.startStream = () => {
return this.websocket.startStreaming();
};
this.stopStream = () => {
return this.websocket.stopStreaming();
};
this.setVolume = (source, volume) => {
return this.websocket.setVolume({source: source, volume: volume});
}
this.getVolume = (source) => {
return new Promise((resolve, reject) => {
this.websocket.getVolume({source: source})
.then(res => {
resolve(res.volume);
})
.catch(reject);
});
}
// Plays a video in the current scene and hides when finished
this.playVideo = (video, callback) => {
return new Promise((resolve, reject) => {
// @TODO Validation of video
// set the file path on the source
let sourceSettings = {
local_file: video.filePath,
looping: (typeof video.loops !== 'undefined' && video.loops > 1)
};
sourceSettings.loop = sourceSettings.looping;
this.websocket.setSourceSettings({"sourceName": video.sceneItem, "sourceSettings": sourceSettings})
// show the video scene item
.then(() => this.websocket.setSceneItemProperties({"item": video.sceneItem, "visible": true}))
// when the video is over, hide it and trigger the user callback, but resolve promise immediately with the timer
.then(() => {
// if this video is being looped, adjust timeout length to allow the requested number of loops to complete
if (sourceSettings.loop === true) {
video.length *= video.loops;
}
// resolve Promise with a timer of when the video will finish playback
// trigger user callback when the video finishes
let timer = setTimeout(() => {
this.websocket.setSceneItemProperties({"item": video.sceneItem, "visible": false});
if (typeof callback !== 'undefined') {
callback();
}
}, parseInt(video.length*1000));
resolve(timer);
})
.catch(reject);
});
}
// Shows a video in the given scene/item and then hides it and switches back to the original scene when finished
this.playVideoInScene = (video, scene, callback) => {
return new Promise((resolve, reject) => {
video.scene = scene;
let originalScene = this.currentScene || false;
let handleVideoEnd = () => {
if (originalScene !== false) {
this.websocket.setCurrentScene({"scene-name": originalScene});
}
if (typeof callback !== 'undefined') {
callback();
}
};
this.websocket.setCurrentScene({"scene-name": scene})
.then(() => this.playVideo(video, handleVideoEnd))
.then(timer => { resolve(timer) })
.catch(reject);
});
};
this.showActivity = (newActivity) => {
let update = {
"source": this.config.currentActivitySceneItemName,
"scene-name": this.config.defaultSceneName,
"render": true
};
if (typeof newActivity !== 'undefined' && newActivity.length > 0) {
update.text = newActivity;
}
return this.websocket.setTextGDIPlusProperties(update);
};
this.hideActivity = () => {
return this.websocket.setSceneItemProperties({"item": this.config.currentActivitySceneItemName, "scene-name": this.config.defaultSceneName, "visible": false});
};
this.showRoomGrind = (playTime, callback) => {
return new Promise((resolve, reject) => {
this.websocket.setSceneItemProperties({"item": "room-grind", "scene-name": this.config.defaultSceneName, "visible": true})
.then(res => {
this.showActivity("NOW SHOWING: TTAS Room Grind !ttas");
resolve(setTimeout(() => {
// after timeout, hide room-grind and call user callback
this.websocket.setSceneItemProperties({"item": "room-grind", "scene-name": this.config.defaultSceneName, "visible": false});
if (typeof callback !== 'undefined') callback();
}, playTime*1000));
})
.catch(reject);
});
};
this.setVisible = (item, scene, visible) => {
return this.websocket.setSceneItemProperties({"item": item, "scene-name": scene, "visible": visible});
};
this.toggleVisible = (item) => {
return new Promise((resolve, reject) => {
this.websocket.getSceneItemProperties({"item": item})
.then(data => {
let newVisibility = !data.visible;
this.websocket.setSceneItemProperties({"item": item, "visible": newVisibility}).then(resolve);
})
.catch(reject);
});
}
this.show = (item, scene) => {
return this.setVisible(item, scene, true);
};
this.hide = (item, scene) => {
return this.setVisible(item, scene, false);
};
this.switchToScene = (scene) => {
return new Promise((resolve, reject) => {
if (this.currentScene === scene) {
resolve(true);
}
this.websocket.setCurrentScene({"scene-name": scene}).then(resolve).catch(reject);
});
};
};
module.exports = GHOBS;

View File

@@ -1,53 +0,0 @@
module.exports = {
exists: exists,
get: get
};
const fs = require('fs'),
path = require('path');
const commandsFilePath = path.join(__dirname, '..', 'conf', 'text_commands');
// Read in basic text commands / definitions and watch for changes
let commands = parseCommands(commandsFilePath);
fs.watchFile(commandsFilePath, (curr, prev) => {
if (curr.mtime !== prev.mtime) {
commands = parseCommands(commandsFilePath);
}
});
// Read/parse text commands from the "database"
function parseCommands(filePath)
{
let commands = {};
let data = fs.readFileSync(filePath, 'utf-8');
let commandLines = data.toString().split('\n');
let commandParts;
let aliases;
commandLines.forEach(function(line) {
if (line.length > 0 && line.indexOf('|') !== -1) {
commandParts = line.split('|');
// check for aliases
aliases = commandParts[0].split(',');
aliases.forEach(function(cmd) {
commands[cmd] = commandParts[1];
//console.log(`!${cmd},`);
});
}
});
return commands;
}
function exists(command)
{
return commands.hasOwnProperty(command);
}
function get(command)
{
if (exists(command)) {
return commands[command];
} else {
return undefined;
}
}

View File

@@ -1,37 +0,0 @@
const util = require('util'),
emitter = require('events').EventEmitter;
function Timers()
{
let self = this;
emitter.call(self);
self.once = (forTimestamp, eventName) => {
// figure out ms between now and scheduled time
// setTimeout for event to be fired at that time
let diff = forTimestamp - Date.now();
if (diff < 0) return;
setTimeout(() => {self.emit(eventName)}, diff);
return self;
};
self.repeat = (intervalSeconds, eventName) => {
setInterval(() => {self.emit(eventName)}, intervalSeconds*1000);
return self;
};
self.onceAndRepeat = (forTimestamp, intervalSeconds, eventName) => {
let diff = forTimestamp - Date.now();
if (diff < 0) return self;
setTimeout(() => {
self.emit(eventName);
self.repeat(intervalSeconds, eventName);
}, diff);
return self;
};
}
util.inherits(Timers, emitter);
module.exports = new Timers();

View File

@@ -1,60 +0,0 @@
// Converts seconds to human-readable time
String.prototype.toHHMMSS = function () {
let sec_num = parseInt(this, 10); // don't forget the second param
let hours = Math.floor(sec_num / 3600);
let minutes = Math.floor((sec_num - (hours * 3600)) / 60);
let seconds = sec_num - (hours * 3600) - (minutes * 60);
if (hours < 10) {hours = "0"+hours;}
if (minutes < 10) {minutes = "0"+minutes;}
if (seconds < 10) {seconds = "0"+seconds;}
return hours+':'+minutes+':'+seconds;
};
var exports = module.exports = {};
exports.asyncForEach = async function(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array)
}
};
exports.range = function(start,stop) {
var result=[];
for (var idx=start.charCodeAt(0),end=stop.charCodeAt(0); idx <=end; ++idx){
result.push(String.fromCharCode(idx));
}
return result;
};
exports.randElement = function(arr) {
return arr[Math.floor(Math.random() * arr.length)];
};
exports.sum = function(e) {
let sum = 0;
for (let i = 0; i < e.length; i++) {
sum += parseInt(e[i], 10);
}
return sum;
};
exports.average = function(e) {
let sum = exports.sum(e);
let avg = sum / e.length;
return avg;
};
exports.randSort = () => { return 0.5 - Math.random() };
exports.sleep = (milliseconds) => {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}

725
package-lock.json generated
View File

@@ -1,725 +0,0 @@
{
"name": "ghbot",
"version": "1.0.0",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
"ajv": {
"version": "5.5.2",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz",
"integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=",
"requires": {
"co": "4.6.0",
"fast-deep-equal": "1.1.0",
"fast-json-stable-stringify": "2.0.0",
"json-schema-traverse": "0.3.1"
}
},
"asn1": {
"version": "0.2.4",
"resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
"integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
"requires": {
"safer-buffer": "2.1.2"
}
},
"assert-plus": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"async": {
"version": "2.6.1",
"resolved": "https://registry.npmjs.org/async/-/async-2.6.1.tgz",
"integrity": "sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ==",
"requires": {
"lodash": "4.17.11"
}
},
"async-limiter": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz",
"integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
},
"aws-sign2": {
"version": "0.7.0",
"resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
"integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
},
"aws4": {
"version": "1.8.0",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
"integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
},
"babel-polyfill": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-polyfill/-/babel-polyfill-6.26.0.tgz",
"integrity": "sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=",
"requires": {
"babel-runtime": "6.26.0",
"core-js": "2.5.7",
"regenerator-runtime": "0.10.5"
}
},
"babel-runtime": {
"version": "6.26.0",
"resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz",
"integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=",
"requires": {
"core-js": "2.5.7",
"regenerator-runtime": "0.11.1"
},
"dependencies": {
"regenerator-runtime": {
"version": "0.11.1",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz",
"integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg=="
}
}
},
"bcrypt-pbkdf": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
"integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
"optional": true,
"requires": {
"tweetnacl": "0.14.5"
}
},
"bindings": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.2.1.tgz",
"integrity": "sha1-FK1hE4EtLTfXLme0ystLtyZQXxE="
},
"caseless": {
"version": "0.12.0",
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"charenc": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz",
"integrity": "sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc="
},
"clone": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
"integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18="
},
"co": {
"version": "4.6.0",
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"commander": {
"version": "2.11.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-2.11.0.tgz",
"integrity": "sha1-FXFS/R56bI2YpbcVzzdt+SgARWM="
},
"core-js": {
"version": "2.5.7",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.7.tgz",
"integrity": "sha512-RszJCAxg/PP6uzXVXL6BsxSXx/B05oJAQ2vkJRjyjrEcNVycaqOmNb5OTxZPE3xa5gwZduqza6L9JOCenh/Ecw=="
},
"core-util-is": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
"integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
},
"cron-parser": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.6.0.tgz",
"integrity": "sha512-KGfDDTjBIx85MnVYcdhLccoJH/7jcYW+5Z/t3Wsg2QlJhmmjf+97z+9sQftS71lopOYYapjEKEvmWaCsym5Z4g==",
"requires": {
"is-nan": "1.2.1",
"moment-timezone": "0.5.21"
}
},
"crypt": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz",
"integrity": "sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs="
},
"dashdash": {
"version": "1.14.1",
"resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
"integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
"requires": {
"assert-plus": "1.0.0"
}
},
"debug": {
"version": "2.6.9",
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
"requires": {
"ms": "2.0.0"
}
},
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"object-keys": "1.0.12"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
},
"discord.js": {
"version": "11.4.2",
"resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.4.2.tgz",
"integrity": "sha512-MDwpu0lMFTjqomijDl1Ed9miMQe6kB4ifKdP28QZllmLv/HVOJXhatRgjS8urp/wBlOfx+qAYSXcdI5cKGYsfg==",
"requires": {
"long": "4.0.0",
"prism-media": "0.0.3",
"snekfetch": "3.6.4",
"tweetnacl": "1.0.0",
"ws": "4.1.0"
},
"dependencies": {
"tweetnacl": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.0.tgz",
"integrity": "sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins="
}
}
},
"ecc-jsbn": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
"integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
"optional": true,
"requires": {
"jsbn": "0.1.1",
"safer-buffer": "2.1.2"
}
},
"extsprintf": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
"integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
},
"fast-deep-equal": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz",
"integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ="
},
"fast-json-stable-stringify": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
"integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
},
"ffmpeg-binaries": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/ffmpeg-binaries/-/ffmpeg-binaries-3.2.2.tgz",
"integrity": "sha1-Nw8wIO9rTbpipVGkJcY01sdaOiE="
},
"fluent-ffmpeg": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz",
"integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=",
"requires": {
"async": "2.6.1",
"which": "1.3.1"
}
},
"forever-agent": {
"version": "0.6.1",
"resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
"integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
},
"getpass": {
"version": "0.1.7",
"resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
"integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
"requires": {
"assert-plus": "1.0.0"
}
},
"har-schema": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
"integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
},
"har-validator": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.0.tgz",
"integrity": "sha512-+qnmNjI4OfH2ipQ9VQOw23bBd/ibtfbVdK2fYbY4acTDqKTW/YDp9McimZdDbG8iV9fZizUqQMD5xvriB146TA==",
"requires": {
"ajv": "5.5.2",
"har-schema": "2.0.0"
}
},
"http-signature": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
"integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
"requires": {
"assert-plus": "1.0.0",
"jsprim": "1.4.1",
"sshpk": "1.14.2"
}
},
"iconv": {
"version": "2.2.3",
"resolved": "https://registry.npmjs.org/iconv/-/iconv-2.2.3.tgz",
"integrity": "sha1-4ITWDut9c9p/CpwJbkyKvgkL+u0=",
"optional": true,
"requires": {
"nan": "2.10.0"
}
},
"inherits": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
"integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
},
"irc": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/irc/-/irc-0.5.2.tgz",
"integrity": "sha1-NxT0doNlqW0LL3dryRFmvrJGS7w=",
"requires": {
"iconv": "2.2.3",
"irc-colors": "1.4.3",
"node-icu-charset-detector": "0.2.0"
}
},
"irc-colors": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/irc-colors/-/irc-colors-1.4.3.tgz",
"integrity": "sha512-VeAnFC9fkb4nB/s6UtTNf3BH2wOk/sSBSzIzCpFwrgoFxVl6J5sov7FXXA0kmbk/mVAZQXfXAsQFjWnGPf4cRg=="
},
"is-buffer": {
"version": "1.1.6",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz",
"integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w=="
},
"is-nan": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz",
"integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=",
"requires": {
"define-properties": "1.1.3"
}
},
"is-typedarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
"integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
},
"isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
"integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
},
"jsbn": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
"integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=",
"optional": true
},
"json-schema": {
"version": "0.2.3",
"resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
"integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
},
"json-schema-traverse": {
"version": "0.3.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz",
"integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A="
},
"json-stringify-safe": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
"integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
},
"jsprim": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
"integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
"requires": {
"assert-plus": "1.0.0",
"extsprintf": "1.3.0",
"json-schema": "0.2.3",
"verror": "1.10.0"
}
},
"lodash": {
"version": "4.17.11",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
},
"long": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz",
"integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA=="
},
"long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ="
},
"md5": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/md5/-/md5-2.2.1.tgz",
"integrity": "sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=",
"requires": {
"charenc": "0.0.2",
"crypt": "0.0.2",
"is-buffer": "1.1.6"
}
},
"memcache": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/memcache/-/memcache-0.3.0.tgz",
"integrity": "sha1-vbuXjqS+4P3TFmmXsYg9KX4IWdw="
},
"moment": {
"version": "2.22.2",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.22.2.tgz",
"integrity": "sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y="
},
"moment-timezone": {
"version": "0.5.21",
"resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.21.tgz",
"integrity": "sha512-j96bAh4otsgj3lKydm3K7kdtA3iKf2m6MY2iSYCzCm5a1zmHo1g+aK3068dDEeocLZQIS9kU8bsdQHLqEvgW0A==",
"requires": {
"moment": "2.22.2"
}
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
},
"nan": {
"version": "2.10.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz",
"integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA=="
},
"node-cache": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/node-cache/-/node-cache-4.2.0.tgz",
"integrity": "sha512-obRu6/f7S024ysheAjoYFEEBqqDWv4LOMNJEuO8vMeEw2AT4z+NCzO4hlc2lhI4vATzbCQv6kke9FVdx0RbCOw==",
"requires": {
"clone": "2.1.2",
"lodash": "4.17.11"
}
},
"node-icu-charset-detector": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/node-icu-charset-detector/-/node-icu-charset-detector-0.2.0.tgz",
"integrity": "sha1-wjINo3Tdy2cfxUy0oOBB4Vb/1jk=",
"optional": true,
"requires": {
"nan": "2.10.0"
}
},
"node-opus": {
"version": "0.2.9",
"resolved": "https://registry.npmjs.org/node-opus/-/node-opus-0.2.9.tgz",
"integrity": "sha512-+IIOdav5D7vHsuLDNk55t17kK2s6c1w4DbfKw8UQxZ635n+AO/SqDE3RpuO3PZKqpWjPtL/chzYZNVxz8/4TUQ==",
"requires": {
"bindings": "1.2.1",
"commander": "2.11.0",
"nan": "2.10.0",
"ogg-packet": "1.0.0"
}
},
"node-schedule": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.0.tgz",
"integrity": "sha512-NNwO9SUPjBwFmPh3vXiPVEhJLn4uqYmZYvJV358SRGM06BR4UoIqxJpeJwDDXB6atULsgQA97MfD1zMd5xsu+A==",
"requires": {
"cron-parser": "2.6.0",
"long-timeout": "0.1.1",
"sorted-array-functions": "1.2.0"
}
},
"oauth-sign": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
"integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
},
"object-keys": {
"version": "1.0.12",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.12.tgz",
"integrity": "sha512-FTMyFUm2wBcGHnH2eXmz7tC6IwlqQZ6mVZ+6dm6vZ4IQIHjs6FdNsQBuKGPuUUUY6NfJw2PshC08Tn6LzLDOag=="
},
"obs-websocket-js": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/obs-websocket-js/-/obs-websocket-js-1.2.0.tgz",
"integrity": "sha1-aE/Br0r+JlV4wXd2dqW7LKKrwGw=",
"requires": {
"babel-polyfill": "6.26.0",
"debug": "3.1.0",
"sha.js": "2.4.11",
"ws": "5.2.2"
},
"dependencies": {
"debug": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
"integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"requires": {
"ms": "2.0.0"
}
},
"ws": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/ws/-/ws-5.2.2.tgz",
"integrity": "sha512-jaHFD6PFv6UgoIVda6qZllptQsMlDEJkTQcybzzXDYM1XO9Y8em691FGMPmM46WGyLU4z9KMgQN+qrux/nhlHA==",
"requires": {
"async-limiter": "1.0.0"
}
}
}
},
"ogg-packet": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/ogg-packet/-/ogg-packet-1.0.0.tgz",
"integrity": "sha1-RbiFchrI991c8iOR1CEGrlM6xng=",
"optional": true,
"requires": {
"ref-struct": "1.1.0"
}
},
"performance-now": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
"integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
},
"prism-media": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.3.tgz",
"integrity": "sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ=="
},
"psl": {
"version": "1.1.29",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.1.29.tgz",
"integrity": "sha512-AeUmQ0oLN02flVHXWh9sSJF7mcdFq0ppid/JkErufc3hGIV/AMa8Fo9VgDo/cT2jFdOWoFvHp90qqBH54W+gjQ=="
},
"punycode": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
"integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
},
"ref": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/ref/-/ref-1.3.5.tgz",
"integrity": "sha512-2cBCniTtxcGUjDpvFfVpw323a83/0RLSGJJY5l5lcomZWhYpU2cuLdsvYqMixvsdLJ9+sTdzEkju8J8ZHDM2nA==",
"optional": true,
"requires": {
"bindings": "1.2.1",
"debug": "2.6.9",
"nan": "2.10.0"
}
},
"ref-struct": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/ref-struct/-/ref-struct-1.1.0.tgz",
"integrity": "sha1-XV7mWtQc78Olxf60BYcmHkee3BM=",
"optional": true,
"requires": {
"debug": "2.6.9",
"ref": "1.3.5"
}
},
"regenerator-runtime": {
"version": "0.10.5",
"resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz",
"integrity": "sha1-M2w+/BIgrc7dosn6tntaeVWjNlg="
},
"request": {
"version": "2.88.0",
"resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
"integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
"requires": {
"aws-sign2": "0.7.0",
"aws4": "1.8.0",
"caseless": "0.12.0",
"combined-stream": "1.0.6",
"extend": "3.0.2",
"forever-agent": "0.6.1",
"form-data": "2.3.2",
"har-validator": "5.1.0",
"http-signature": "1.2.0",
"is-typedarray": "1.0.0",
"isstream": "0.1.2",
"json-stringify-safe": "5.0.1",
"mime-types": "2.1.19",
"oauth-sign": "0.9.0",
"performance-now": "2.1.0",
"qs": "6.5.2",
"safe-buffer": "5.1.2",
"tough-cookie": "2.4.3",
"tunnel-agent": "0.6.0",
"uuid": "3.3.2"
},
"dependencies": {
"combined-stream": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz",
"integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=",
"requires": {
"delayed-stream": "1.0.0"
}
},
"extend": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
"integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
},
"form-data": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz",
"integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=",
"requires": {
"asynckit": "0.4.0",
"combined-stream": "1.0.6",
"mime-types": "2.1.19"
}
},
"mime-db": {
"version": "1.35.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.35.0.tgz",
"integrity": "sha512-JWT/IcCTsB0Io3AhWUMjRqucrHSPsSf2xKLaRldJVULioggvkJvggZ3VXNNSRkCddE6D+BUI4HEIZIA2OjwIvg=="
},
"mime-types": {
"version": "2.1.19",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.19.tgz",
"integrity": "sha512-P1tKYHVSZ6uFo26mtnve4HQFE3koh1UWVkp8YUC+ESBHe945xWSoXuHHiGarDqcEZ+whpCDnlNw5LON0kLo+sw==",
"requires": {
"mime-db": "1.35.0"
}
},
"qs": {
"version": "6.5.2",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
"integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
}
}
},
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
},
"safer-buffer": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sha.js": {
"version": "2.4.11",
"resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
"integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
"requires": {
"inherits": "2.0.3",
"safe-buffer": "5.1.2"
}
},
"snekfetch": {
"version": "3.6.4",
"resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz",
"integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw=="
},
"sorted-array-functions": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz",
"integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg=="
},
"sshpk": {
"version": "1.14.2",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.2.tgz",
"integrity": "sha1-xvxhZIo9nE52T9P8306hBeSSupg=",
"requires": {
"asn1": "0.2.4",
"assert-plus": "1.0.0",
"bcrypt-pbkdf": "1.0.2",
"dashdash": "1.14.1",
"ecc-jsbn": "0.1.2",
"getpass": "0.1.7",
"jsbn": "0.1.1",
"safer-buffer": "2.1.2",
"tweetnacl": "0.14.5"
}
},
"tough-cookie": {
"version": "2.4.3",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
"integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
"requires": {
"psl": "1.1.29",
"punycode": "1.4.1"
}
},
"tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
"requires": {
"safe-buffer": "5.1.2"
}
},
"tweetnacl": {
"version": "0.14.5",
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=",
"optional": true
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"verror": {
"version": "1.10.0",
"resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
"integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
"requires": {
"assert-plus": "1.0.0",
"core-util-is": "1.0.2",
"extsprintf": "1.3.0"
}
},
"which": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
"integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
"requires": {
"isexe": "2.0.0"
}
},
"ws": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-4.1.0.tgz",
"integrity": "sha512-ZGh/8kF9rrRNffkLFV4AzhvooEclrOH0xaugmqGsIfFgOE/pIz4fMc4Ef+5HSQqTEug2S9JZIWDR47duDSLfaA==",
"requires": {
"async-limiter": "1.0.0",
"safe-buffer": "5.1.2"
},
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
}
}
}
}
}

50
package.json Executable file → Normal file
View File

@@ -1,27 +1,41 @@
{
"name": "ghbot",
"version": "1.0.0",
"version": "2.0.0",
"description": "",
"main": "main.js",
"main": "src/index.js",
"dependencies": {
"async": "^2.6.1",
"discord.js": "^11.4.2",
"ffmpeg-binaries": "^3.2.2",
"fluent-ffmpeg": "^2.1.2",
"irc": "^0.5.2",
"md5": "^2.2.1",
"memcache": "^0.3.0",
"moment": "^2.22.2",
"node-cache": "^4.2.0",
"node-opus": "^0.2.9",
"node-schedule": "^1.3.0",
"obs-websocket-js": "^1.2.0",
"request": "^2.88.0"
"@discordjs/opus": "^0.9.0",
"@discordjs/voice": "^0.18.0",
"axios": "^1.11.0",
"better-sqlite3": "^11.10.0",
"discord.js": "^14.21.0",
"ffmpeg-static": "^5.2.0",
"node-schedule": "^2.1.1",
"opusscript": "^0.1.1",
"sodium-native": "^4.3.3"
},
"devDependencies": {
"@types/node": "^22.17.2",
"nodemon": "^3.1.10"
},
"pnpm": {
"overrides": {
"@discordjs/opus": "npm:opusscript@^0.1.1"
}
},
"devDependencies": {},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
"dev": "nodemon src/index.js",
"start": "docker compose up -d",
"start:logs": "pnpm start && pnpm logs",
"stop": "docker compose down",
"build": "docker compose build",
"restart": "docker compose restart",
"logs": "docker compose logs -f",
"boom": "pnpm stop && pnpm build && pnpm start",
"reset-db": "pnpm stop && rm -f data/*.db* && echo 'Database reset complete. Run pnpm start to re-seed from seed.json'",
"image:build": "docker build -t ghbot:${VERSION:-latest} .",
"image:run": "docker run -d --name ghbot --restart always ghbot:${VERSION:-latest}"
},
"author": "",
"author": "https://github.com/greenham",
"license": "MIT"
}

1031
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,16 +0,0 @@
const rooms = require('./conf/rooms.json');
const util = require('./lib/util');
const fs = require('fs');
populateDatabase();
async function populateDatabase() {
let database = "'ID','Dungeon','Room'\r\n";
await util.asyncForEach(rooms, async (room, index) => {
let entry = `'${room.id}','${room.dungeonName||'?'}','${room.roomName||'?'}'\r\n`;
database += entry;
console.log('added entry', entry);
});
fs.writeFile('rooms-list.csv', database, 'utf8', () => {console.log('done')});
}

View File

@@ -1,70 +0,0 @@
const fs = require('fs');
const path = require('path');
const util = require('./lib/util');
const ffmpeg = require('fluent-ffmpeg');
let roomVidPath = `/var/hypnoadmin/media/videos/ALttP/my-vids/room-vids`;
const getAllFiles = dir =>
fs.readdirSync(dir).reduce((files, file) => {
const name = path.join(dir, file);
const isDirectory = fs.statSync(name).isDirectory();
return isDirectory ? [...files, ...getAllFiles(name)] : [...files, name];
}, []);
let roomVidFiles = getAllFiles(roomVidPath);
populateDatabase();
async function populateDatabase() {
let database = [];
await util.asyncForEach(roomVidFiles, async (file, index) => {
// ignore anything that's not an mp4
let shortPath = file.replace(roomVidPath, '');
if (!/\.mp4$/.test(shortPath)) {
return;
}
let entry = {
id: index+1,
shortPath: shortPath,
winPath: shortPath.replace(/\//g, '\\')
};
// chop up the short path and extract metadata
let matches = shortPath.match(/^\/([0-9]{2})-([a-z]+)\/([0-9]{2})-(.+)\.mp4/);
if (matches) {
entry.dungeonId = matches[1];
entry.dungeonName = matches[2];
entry.roomId = matches[3];
entry.roomName = matches[4];
}
// @TODO support some other paths / structures
entry.videoData = await getVideoMetadata(file);
database.push(entry);
console.log('added entry', entry);
});
fs.writeFile('conf/rooms.json', JSON.stringify(database), 'utf8', () => {console.log('done')});
}
function getVideoMetadata(videoPath) {
return new Promise((resolve, reject) => {
ffmpeg.ffprobe(videoPath, (err, metadata) => {
// find the video stream
let stream = metadata.streams.find(e => e.codec_type === "video");
if (!stream) {
resolve(false);
}
resolve({
width: stream.width,
height: stream.height,
fps: parseInt(stream.r_frame_rate.replace('/1', '')),
length: stream.duration
});
});
});
}

View File

@@ -1,818 +0,0 @@
"ID","Dungeon","Room"
"0","escape","links-house-2911"
"1","escape","outside-links-house-723"
"2","escape","outside-castle-1628"
"3","escape","outside-castle-1631"
"4","escape","uncle-1342"
"5","escape","uncle-1348"
"6","escape","passage-exit-442"
"7","escape","courtyard-923"
"8","escape","castle-lobby-631"
"9","escape","castle-lobby-pumpless-636"
"10","escape","sw-room-439"
"11","escape","sw-room-pumpless-448"
"12","escape","west-guard-hallway1-603"
"13","escape","statue-hallway1-415"
"14","escape","1st-keyguard-leftslash-801"
"15","escape","first-keyguard1-3slash-825"
"16","escape","first-keyguard1-upspin-811"
"17","escape","b1-pit-lower-404"
"18","escape","b1-pit2-lower-945"
"19","escape","stealth-room-927"
"20","escape","stealth-room-929"
"21","escape","green-guard-bestrng-432"
"22","escape","green-guard-bestrng-rta-438"
"23","escape","blue-boomguard-739"
"24","escape","blue-boomguard-skip-627"
"25","escape","green-guard2-426"
"26","escape","stairs-to-b2-1-359"
"27","escape","b2-1-533"
"28","escape","bnc-1pot4slash-3227"
"29","escape","bnc-8slash-3120"
"30","escape","bnc-8slash-3130"
"31","escape","bnc-joestrat-3238"
"32","escape","bnc-joestrat-3244"
"33","escape","b2-2-530"
"34","escape","stairs-to-b2-2-651"
"35","escape","stairs-to-b2-2-652"
"36","escape","green-guard3-426"
"37","escape","stealth-room2-920"
"38","escape","b1-pit2-upper-301"
"39","escape","b1-pit1-upper-627"
"40","escape","b1-pit1-upper-pumpless-628"
"41","escape","1st-keyguard2-314"
"42","escape","statue-hallway2-709"
"43","escape","west-guard-hallway2-612"
"44","escape","sw-room2-445"
"45","escape","castle-lobby2-721"
"46","escape","throne-room-1632"
"47","escape","throne-room-1634"
"48","escape","sewer-passage-857"
"49","escape","sewer-passage-pumpless-903"
"50","escape","snake-room-700"
"51","escape","snake-room-perfect-rng-655"
"52","escape","sewer-key-chest-risky-1212"
"53","escape","sewer-key-chest-rta-1215"
"54","escape","sewer1-706"
"55","escape","sewer1-707"
"56","escape","sewer2-608"
"57","escape","keyrat-840"
"58","escape","rat-hallway-827"
"59","escape","behind-sanc-705"
"60","escape","behind-sanc-rta-719"
"61","escape","behind-sanc2-720"
"62","?","?"
"63","eastern","sanc-2946"
"64","eastern","sanc-heart-3303"
"65","eastern","sanc-heart-chestturn-3305"
"66","eastern","outside-sanc-807"
"67","eastern","graveyard-646"
"68","eastern","east-path-403"
"69","eastern","wooden-bridge-906"
"70","eastern","octofield-724"
"71","eastern","guard-bridge-311"
"72","eastern","east-fairy-cave-right-exit-326"
"73","eastern","outside-eastern-2540"
"74","eastern","entrance-504"
"75","eastern","three-popos-434"
"76","eastern","cannonballs-859"
"77","eastern","bigchest-upper-leftpot-736"
"78","eastern","westwing-upper-455"
"79","eastern","stalfos-spawn-617"
"80","eastern","compass-room-615"
"81","eastern","westwing-lower1-335"
"82","eastern","bigchest-lower1-arrows-805"
"83","eastern","bigchest-lower1-noarrows-704"
"84","eastern","east-wing-645"
"85","eastern","dark-af-boom-623"
"86","eastern","dark-af-sword-630"
"87","eastern","dark-potkey-diagboom-656"
"88","eastern","dark-potkey-walk-658"
"89","eastern","dark-af2-607"
"90","eastern","cannonballs-upper-851"
"91","eastern","bigkey-1105"
"92","eastern","westwing-lower2-756"
"93","eastern","bigchest-bow-1452"
"94","eastern","gwg-noslash-menu-913"
"95","eastern","afpots-802"
"96","eastern","eyegore-floor-switch-519"
"97","eastern","eyegore-floor-switch-pumpless-522"
"98","eastern","cannonball-switch-503"
"99","eastern","cannonball-switch-pumpless-507"
"100","eastern","zgr-601"
"101","eastern","double-red-eyegores-617"
"102","eastern","armos-topright-1321"
"103","desert","outside-eastern-1322"
"104","desert","saha-walkout-1951-449-8"
"105","desert","outside-eastern2-1301-99-0"
"106","desert","east-fairy-cave-253"
"107","desert","guard-bridge-601-35-0"
"108","desert","links-yard-354-32-0"
"109","desert","annoying-bushes-403"
"110","desert","west-of-swamp1-356"
"111","desert","south-of-grove1-right-exit-452-32-0"
"112","desert","grove-westside1-nocharge-255"
"113","desert","grove-westside1-swordcharge-308"
"114","desert","outside-library1-dashes-708"
"115","desert","outside-library1-spindash-626"
"116","desert","library-1038-127-0"
"117","desert","outside-library2-noss-751-99-0"
"118","desert","outside-library2-spindash-707"
"119","desert","grove-westside2-leftexit-233-36"
"120","desert","grove-westside2-rta-243-36"
"121","desert","south-of-grove2-fromleft-belowbush-458-32"
"122","desert","south-of-grove2-fromleft-bushdash-455-32"
"123","desert","south-of-grove2-fromright-bushdash-447-32"
"124","desert","west-of-swamp2-frombelow-350"
"125","desert","west-of-swamp2-frombushes-346"
"126","desert","west-of-swamp2-fromlowbushes-346"
"127","desert","west-of-swamp2-fromlowbushes-farleftexit-346"
"128","desert","annoying-bushes2-352-33"
"129","desert","links-yard-339-32"
"130","desert","links-yard-earlydash-336-32"
"131","desert","waterdash-menu-629-35-1"
"132","desert","waterdash-nomenu-524-37-0"
"133","desert","east-of-watergate-216-35"
"134","desert","watergate-552-37"
"135","desert","middle-aged-man-643-31"
"136","desert","outside-desert-2250-175"
"137","desert","desert1-risky-726-97"
"138","desert","desert1-rta-730-98"
"139","desert","north-hall1-leftpoke-510-21"
"140","desert","north-hall1-rtapoke-512-21"
"141","desert","nw-room1-258-19"
"142","desert","torchkey-rta-614"
"143","desert","torchkey-underleft-614"
"144","desert","nw-room2-235"
"145","desert","north-hall2-412-21"
"146","desert","ne-room1-351"
"147","desert","east-wing1-416-21"
"148","desert","compass-room1-519"
"149","desert","bigkey-chestturn-758-49"
"150","desert","bigkey-rta-goodrng-803"
"151","desert","compass-room2-405"
"152","desert","compass-room2-pumpless-407"
"153","desert","east-wing2-318"
"154","desert","east-wing2-pumpless-319"
"155","desert","ne-room2-418-20"
"156","desert","north-hall3-412-21"
"157","desert","nw-room3-346-18"
"158","desert","cop1-528"
"159","desert","bigchest-606-29-1"
"160","desert","cop2-355"
"161","desert","nw-room4-dashes-348"
"162","desert","nw-room4-walk-rta-349"
"163","desert","west-wing-451-21"
"164","desert","d1-exit-324"
"165","desert","outside-desert-noss-709-102"
"166","desert","outside-desert-spindash-732-102-3"
"167","desert","d2-entrance-528-96"
"168","desert","d2-entrance-pumpless-532-97"
"169","desert","tile-room1-458"
"170","desert","bridge-539-58"
"171","desert","popos-546"
"172","desert","beamos-hall-706-1"
"173","desert","beamos-hall-rta-720"
"174","desert","tile-room2-517-21"
"175","desert","torches-nomenu-1549-0-0"
"176","desert","torches-rta-1656-0-4"
"177","desert","lanmos-2cyc-1953"
"178","desert","lanmos-2cyc-2059"
"179","hera","outside-desert-cactusdash-1044-77-0"
"180","hera","outside-desert-geldmandash-1053-76-0"
"181","hera","outside-desert-rta-highdash-1109-76-0"
"182","hera","outside-desert-rta-lowdash-1116-76-0"
"183","hera","middle-aged-man-634-30-0"
"184","hera","watergate-458-34-0"
"185","hera","beach-445-35-0"
"186","hera","beach-rta-530-35-0"
"187","hera","fakeflips-832-36-0"
"188","hera","fakeflips-rta-808-35-0"
"189","hera","whirlpool-enter-336-36-0"
"190","hera","whirlpool-exit-806-72-0"
"191","hera","rupee-tree-picklearrow-808-35-0"
"192","hera","rupee-tree-rta-821-35-0"
"193","hera","old-man-cave-1026-134-0"
"194","hera","old-man-tunnel-2139-295-8"
"195","hera","death-mountain-lower-3039-246-009"
"196","hera","bunny-link-739"
"197","hera","death-mountain-upper-ledgehop-736-76-0"
"198","hera","death-mountain-upper-rta-748-76-0"
"199","hera","lobby1-503-101-0"
"200","hera","lobby1-pumpless-507-101-0"
"201","hera","basement-key-629-71-0"
"202","hera","lobby2-boom-859-107-007"
"203","hera","lobby2-boomdash-851-107-010"
"204","hera","lobby2-boomless-824"
"205","hera","tileroom-boomdash-4340-66-0"
"206","hera","tileroom-boomless-4326"
"207","hera","3mold-boom-menu-429-0-2"
"208","hera","3mold-boomless-538"
"209","hera","torches-812-31-008"
"210","hera","lobby3-540-107-0"
"211","hera","beetles-936-63-0"
"212","hera","bkdoor-342-0-0"
"213","hera","bkdoor-pumpless-345-0-0"
"214","hera","safety-switch-634"
"215","hera","safety-switch-pumpless-dash-639"
"216","hera","safety-switch-pumpless-walk-639"
"217","hera","bigchest-ebj-2143-95-17"
"218","hera","bigchest-waffle-rta-2324-95-20"
"219","hera","bigchest-waffle-sparkmanip-2301-95-18"
"220","hera","bumper-skip-729-66-0"
"221","hera","moldorm-2752-116-0"
"222","hera","moldorm-2858-116-0"
"223","atower","death-mountain-upper-943"
"224","atower","kak-tunnel-01-806"
"225","atower","kak-tunnel-02-607"
"226","atower","kak-tunnel-02-arrows-835"
"227","atower","kak-tunnel-02-rta-710"
"228","atower","first-rupee-tree-928"
"229","atower","first-rupee-tree-birddash-1029"
"230","atower","first-rupee-tree-rta-x4FA-exit-921"
"231","atower","lumberjacks-652"
"232","atower","lumberjacks-picklearrow-y078-enter-455"
"233","atower","lumberjacks-rta-y078-enter-701"
"234","atower","lost-woods-01-1709"
"235","atower","lost-woods-01-y078-enter-1713"
"236","atower","master-sword-3909"
"237","atower","lost-woods-02-2252"
"238","atower","lost-woods-02-rta-2243"
"239","atower","fortune-teller-hut-rta-619"
"240","atower","whirlpool-pond-348"
"241","atower","wsb-543"
"242","atower","outside-hyrule-castle-1000"
"243","atower","castle-lobby-abovetorch-531"
"244","atower","castle-lobby-rta-540"
"245","atower","sw-room-455-bad-recording"
"246","atower","sw-room-456"
"247","atower","ramparts-617"
"248","atower","entrance-334"
"249","atower","goldknights-pui-734"
"250","atower","prizepack-guards-703"
"251","atower","first-dark-room-605"
"252","atower","first-dark-room-rta-619"
"253","atower","despair-1617-44"
"254","atower","last-3f-dark-room-436"
"255","atower","first-4f-dark-room-625"
"256","atower","first-4f-dark-room-pumpless-628"
"257","atower","melancholy-rta-715"
"258","atower","archerkey-beams-keydash-534"
"259","atower","archerkey-beams-rta-558"
"260","atower","redspearguards-809"
"261","atower","alert-guards-arrowkill-516"
"262","atower","alert-guards-beamkill-528"
"263","atower","cop-menu-638"
"264","atower","neopolitan-room-325"
"265","atower","statue-room-646"
"266","atower","catwalk-boom-624"
"267","atower","catwalk-boomless-621-bad-recording"
"268","atower","7f-hallway-521"
"269","atower","agah-cutscene-2451"
"270","atower","agah-0bb-4451"
"271","pod","pyramid-1043"
"272","pod","dark-octo-319"
"273","pod","hammer-bridge-444"
"274","pod","dark-east-fairy-cave-250"
"275","pod","outside-4157"
"276","pod","entrance1-dash-554"
"277","pod","entrance1-walk-556"
"278","pod","west-bomb-pickup-336"
"279","pod","shooter-key-847"
"280","pod","entrance2-333"
"281","pod","bomb-pickup1-428"
"282","pod","junction1-ledgehop-524"
"283","pod","junction1-slashjelly-556"
"284","pod","turtle-key-619"
"285","pod","entrance3-333"
"286","pod","bomb-pickup2-412"
"287","pod","junction2-907"
"288","pod","junction2-badrng-917"
"289","pod","upper-turtle-key-715"
"290","pod","bigkey-leftside-754"
"291","pod","entrance4-333"
"292","pod","bomb-pickup3-412"
"293","pod","junction3-651"
"294","pod","beetle-room1-954"
"295","pod","beetle-room1-dash2chest-1002"
"296","pod","beetle-room1-rta-1012"
"297","pod","hammeryump-1bombpickup-1324"
"298","pod","hammeryump-2bombpickup-1348"
"299","pod","darkmaze-738"
"300","pod","entrance5-333"
"301","pod","bomb-pickup4-412"
"302","pod","junction4-651"
"303","pod","beetle-room2-dash-and-hop-644"
"304","pod","beetle-room2-rta-walk-702"
"305","pod","beetle-room2-walk-and-hop-657"
"306","pod","sexy-statue-1333"
"307","pod","ugly-statue-1406"
"308","pod","mimics-454"
"309","pod","eye-statue-dashes-2454"
"310","pod","eye-statue-walk-rta-2456"
"311","pod","darkpegs-607"
"312","pod","lonely-turtle-457"
"313","pod","turtle-party-701-cropped"
"314","pod","turtle-party-701"
"315","pod","turtle-party-809"
"316","pod","warp-tile-512"
"317","pod","turtle-hallway-dash-and-keydash-709"
"318","pod","turtle-hallway-rta-walk-and-slash-745"
"319","pod","turtle-hallway-walk-and-keydash-720"
"320","pod","helma-2739"
"321","pod","helma-2813"
"322","pod","helma-2830"
"323","pod","helma-2843"
"324","pod","helma-2927"
"325","thieves","outside-pod-2627"
"326","thieves","dark-east-fairy-cave-259"
"327","thieves","pegbridge-earlydash-639"
"328","thieves","pegbridge-safedash-646"
"329","thieves","bombshop-359"
"330","thieves","annoying-bushes-402"
"331","thieves","annoying-bushes-rightcancel-402"
"332","thieves","southeast-of-fluteboy-dash-334"
"333","thieves","southeast-of-fluteboy-walk-rta-328"
"334","thieves","south-of-fluteboy-458"
"335","thieves","south-of-fluteboy-walk-rta-508"
"336","thieves","fluteboy-rta-1503"
"337","thieves","grove-rta-1840"
"338","thieves","south-of-grove-445"
"339","thieves","grove-westside-236"
"340","thieves","usain-bolt-720"
"341","thieves","kak-village-centerdash-3335"
"342","thieves","kak-village-leftdash-3335"
"343","thieves","three-musketeers-3xdash-557"
"344","thieves","lost-woods-830"
"345","thieves","warp-rock-705"
"346","thieves","warp-rock-no-hammerdash-724"
"347","thieves","north-of-outcast-748"
"348","thieves","voo-943"
"349","thieves","sw-quad-badrng-745"
"350","thieves","sw-quad-goodrng-736"
"351","thieves","nw-quad-333"
"352","thieves","ne-quad-badrng-350"
"353","thieves","ne-quad-goodrng-337"
"354","thieves","se-quad1-551"
"355","thieves","bigkey-610"
"356","thieves","se-quad2-628"
"357","thieves","ne-quad2-733"
"358","thieves","stalfos-hallway-hammer-755"
"359","thieves","stalfos-hallway-pickup-754"
"360","thieves","snek-and-zazaks-335"
"361","thieves","conveyor-gibos-north-352"
"362","thieves","conveyor-gibos-north-353"
"363","thieves","hellway-429"
"364","thieves","spikeroom1-hammer-515"
"365","thieves","spikeroom1-mikestrat-502"
"366","thieves","attic-pot-switch1-badrng-828"
"367","thieves","attic-pot-switch1-goodrng-pickup-750"
"368","thieves","grasshopper-hall1-1-252"
"369","thieves","grasshopper-hall2-1-308"
"370","thieves","attic-window-slashgrass-556"
"371","thieves","grasshopper-hall2-2-240"
"372","thieves","grasshopper-hall1-2-307"
"373","thieves","attic-pot-switch2-306"
"374","thieves","spikeroom2-606"
"375","thieves","hellway2-322"
"376","thieves","zazaks-and-gibos-543"
"377","thieves","zazaks-and-gibos-545"
"378","thieves","conveyor-toilet-bomb-404"
"379","thieves","conveyor-toilet-nobomb-321"
"380","thieves","bigblock-837"
"381","thieves","lonely-zazak-315"
"382","thieves","prison-dashes-1053"
"383","thieves","prison-walk-1106"
"384","thieves","prison-walk-oneslash-1124"
"385","thieves","lonely-zazak2-252"
"386","thieves","conveyor-jellies1-328"
"387","thieves","bigchest-606"
"388","thieves","conveyor-jellies2-509"
"389","thieves","bigblock2-arrows-412"
"390","thieves","bigblock2-noarrows-345"
"391","thieves","conveyor-toilet2-dash-hammer-736"
"392","thieves","conveyor-toilet2-hammerdash-726"
"393","thieves","conveyor-toilet2-rta-751"
"394","thieves","stalfos-hallway2-324"
"395","thieves","blind-beams-2701"
"396","thieves","blind-beams-walkin-2707"
"397","skull","voo1-902"
"398","skull","voo1-rta-914"
"399","skull","300-hut-721"
"400","skull","300-hut-rta-720"
"401","skull","voo2-dash-628"
"402","skull","voo2-walk-630"
"403","skull","cursed-dwarf-justpressa-1210"
"404","skull","cursed-dwarf-rta-1224"
"405","skull","voo3-339"
"406","skull","hammerpegs1-820"
"407","skull","outside-smiths1-350"
"408","skull","smiths1-708"
"409","skull","outside-smiths2-237"
"410","skull","smiths2-910"
"411","skull","outside-smiths3-239"
"412","skull","hammerpegs2-342"
"413","skull","outside-smiths4-338"
"414","skull","smiths3-746"
"415","skull","outside-smiths5-236"
"416","skull","hammerpegs3-849"
"417","skull","voo4-fencedash-441"
"418","skull","c-shaped-house-918"
"419","skull","voo5-709"
"420","skull","moblins-badrng-524"
"421","skull","moblins-goodrng-518"
"422","skull","skeleton-forest1-1216"
"423","skull","mummy-statue-1220"
"424","skull","bigkey-badrng-659"
"425","skull","bigkey-leftside-620"
"426","skull","bigkey-rightside-635"
"427","skull","mumm-statue-exit-232"
"428","skull","skeleton-forest2-949"
"429","skull","bigchest-earlybj-1259"
"430","skull","bigchest-rta-bonk-1407"
"431","skull","bigchest-rta-bonkless-1406"
"432","skull","skeleton-forest3-933"
"433","skull","mummy-statue-passthru-427"
"434","skull","bumper-hallway-305"
"435","skull","potkey-654"
"436","skull","skeleton-forest4-738"
"437","skull","skeleton-forest4-rta-740"
"438","skull","firesnake-keydash-522"
"439","skull","potpit-left-442"
"440","skull","potpit-right-438"
"441","skull","mummy-hellway-744"
"442","skull","mummy-hellway-joestrat-812"
"443","skull","vineroom-badrng-415"
"444","skull","vineroom-goodrng-359"
"445","skull","mummy-key-629"
"446","skull","mummy-key-rta-633"
"447","skull","mothhole-top-422"
"448","skull","mothula-1118"
"449","skull","mothula-1122"
"450","ice","outside-skull-menu-352"
"451","ice","lost-woods-dash-758"
"452","ice","lost-woods-walk-806"
"453","ice","links-house-632"
"454","ice","castle-gate-menu-rta-qw-637"
"455","ice","castle-gate-menu-walk-qw-639"
"456","ice","castle-gate-walk-qw-534"
"457","ice","pyramid-659"
"458","ice","dark-octofield-236"
"459","ice","broken-bridge-738"
"460","ice","lonely-ropa-243"
"461","ice","ropa-lottery-910"
"462","ice","dark-cuckoos-leftgrab-737"
"463","ice","dark-cuckoos-rta-746"
"464","ice","quake-1925"
"465","ice","quake-earlythrow-1858"
"466","ice","whirlpool1-dashcancel-605"
"467","ice","whirlpool1-nocancel-603"
"468","ice","zoras-domain-dashout-5934"
"469","ice","zoras-domain-walkout-5933"
"470","ice","tiny-warp-dik-605"
"471","ice","ice-warp-833"
"472","ice","ice-island-358"
"473","ice","ice-entrance-438"
"474","ice","ice2-508"
"475","ice","floor-switch-910"
"476","ice","block-intersection1-358"
"477","ice","compass-room-144"
"478","ice","block-intersection2-456"
"479","ice","penguins1-spin-535"
"480","ice","penguins1-tmstrat-523"
"481","ice","block-intersection3-512"
"482","ice","bombable-floor-dboost-916"
"483","ice","bombable-floor-nodboost-944"
"484","ice","bombable-floor-rta-good-rng-957"
"485","ice","big-stalfos-819"
"486","ice","conveyor-hellway-709"
"487","ice","ipbj-1209"
"488","ice","floor-zols-300"
"489","ice","penguins2-804"
"490","ice","big-spike-405"
"491","ice","lonely-firebar-419"
"492","ice","lonely-firebar-dash-451"
"493","ice","double-freezor-503"
"494","ice","big-chest-401"
"495","ice","big-pit-351"
"496","ice","block-push-hammerpot-badlag-542"
"497","ice","block-push-potpickup-badlag-539"
"498","ice","statue-room-hammer-both-pots-1323"
"499","ice","statue-room-hammer-one-pot-1306"
"500","ice","statue-room-pickup-both-pots-1318"
"501","ice","statue-room-pickup-one-pot-1304"
"502","ice","kholdstare-1poke-1621"
"503","ice","kholdstare-2poke-1559"
"504","ice","kholdstare-2poke-1602"
"505","ice","kholdstare-6slash-1655"
"506","swamp","ice-island-343"
"507","swamp","lake-hylia-birddash-803"
"508","swamp","lake-hylia-walk-812"
"509","swamp","links-house-521"
"510","swamp","grassy-area-menu-437"
"511","swamp","warp-nokill-422"
"512","swamp","warp-octokill-437"
"513","swamp","postwarp-hammerdash-950"
"514","swamp","postwarp-walk-959"
"515","swamp","outside-swamp1-noqw-629"
"516","swamp","outside-swamp1-qw-617"
"517","swamp","outside-watergate-noqw-427"
"518","swamp","outside-watergate-qw-407"
"519","swamp","watergate-entrance-mirrordash-455"
"520","swamp","watergate-entrance-walk-500"
"521","swamp","afskip-715"
"522","swamp","afskip-bonk-736"
"523","swamp","afskip-rta-732"
"524","swamp","watergate-entrance2-317"
"525","swamp","outside-watergate2-closewarp-236"
"526","swamp","outside-watergate2-qw-413"
"527","swamp","outside-swamp2-closewarp-424"
"528","swamp","outside-swamp2-qw-352"
"529","swamp","dungeon-entrance-1017"
"530","swamp","potkey1-menu-1050"
"531","swamp","potkey1-nomenu-923"
"532","swamp","pool1-empty-627"
"533","swamp","potkey2-dash-336"
"534","swamp","potkey2-walk-339"
"535","swamp","pool1-empty2-dash-515"
"536","swamp","pool1-empty2-walk-514"
"537","swamp","waterlever1-1232"
"538","swamp","pool1-full-542"
"539","swamp","bigchest1-839"
"540","swamp","pool2-empty-710"
"541","swamp","potkey3-dash-336"
"542","swamp","potkey3-walk-339"
"543","swamp","pool2-empty2-554"
"544","swamp","bigchest2-759"
"545","swamp","waterlever2-1355"
"546","swamp","bigchest3-844"
"547","swamp","pool2-full1-hammerdash-920"
"548","swamp","pool2-full1-rta-939"
"549","swamp","jelly-dash-1015"
"550","swamp","sociable-firebar-1030"
"551","swamp","jellydash-northside-433"
"552","swamp","bigkey-719"
"553","swamp","jelly-dash2-928"
"554","swamp","pool2-full2-dboost-907"
"555","swamp","pool2-full2-hammerdash-925"
"556","swamp","pool2-full2-rta-936"
"557","swamp","bigchest4-2026"
"558","swamp","bigchest4-rta-2112"
"559","swamp","statue-room-arrows-1205"
"560","swamp","statue-room-no-arrows-1127"
"561","swamp","red-jelly-2slash-351"
"562","swamp","red-jelly-beam-340"
"563","swamp","red-jelly-dash-326"
"564","swamp","water-lever3-1146"
"565","swamp","smallhall-313"
"566","swamp","waterfall-room-509"
"567","swamp","restock-all-448"
"568","swamp","restock-arrows-only-422"
"569","swamp","restock-bomb-only-420"
"570","swamp","restock-skip-dash-330"
"571","swamp","restock-skip-walk-334"
"572","swamp","c-room-602"
"573","swamp","phelps-way-943"
"574","swamp","t-room-234"
"575","swamp","arrghus-frqk-1737"
"576","swamp","arrghus-frqk-1745"
"577","swamp","arrghus-frqk-1748"
"578","swamp","arrghus-noqk-1902"
"579","swamp","arrghus-noqk-1930"
"580","mire","outside-swamp-357"
"581","mire","outside-swamp-from-fr-341"
"582","mire","outside-swamp-from-fr-with-qw-348"
"583","mire","outside-swamp-from-mirror-qw-236"
"584","mire","outside-watergate-852"
"585","mire","outside-watergate-from-qw-836"
"586","mire","dm-both-swordclimbs-2145"
"587","mire","dm-hookspeed-noswordclimbs-2202"
"588","mire","dm-hybrid-2205"
"589","mire","dm-spindash-2123"
"590","mire","dm-spindash-2134"
"591","mire","dm-spindash-dmgcancel-2111"
"592","mire","dark-dm-534"
"593","mire","ether-dashoff-2305"
"594","mire","ether-quickhop-2243"
"595","mire","octoballoon-610"
"596","mire","icerod-entrance-931"
"597","mire","icerod1-334"
"598","mire","icerodchest-nomenu-659"
"599","mire","icerodchest-rta-menu-815"
"600","mire","icerod2-menu-350"
"601","mire","icerod2-rta-nomenu-233"
"602","mire","icerod-leaving-547"
"603","mire","icerod-leaving-rta-556"
"604","mire","mirewarp-menu-701"
"605","mire","mire-entrance-nomenu-2501"
"606","mire","mire01-821"
"607","mire","mire02-1031"
"608","mire","mainhub1-toproute-947"
"609","mire","poporoom1-337"
"610","mire","spikekey1-351"
"611","mire","poporoom2-337"
"612","mire","beatthefireball-938"
"613","mire","jellykey-449"
"614","mire","tileroom-321"
"615","mire","bombslugs-508"
"616","mire","torches1-752"
"617","mire","torches2-rta-2127"
"618","mire","torches2-toplighting-2121"
"619","mire","bighole-322"
"620","mire","bigkey-621"
"621","mire","warptile-230"
"622","mire","wizzroom-412"
"623","mire","bigspike-318"
"624","mire","lonelystalfos-337"
"625","mire","sparkgamble-bestboost-1106"
"626","mire","sparkgamble-damageless-1144"
"627","mire","sparkgamble-rta-menu-1236"
"628","mire","sparkgamble-snekblock-1205"
"629","mire","maproom-south-327"
"630","mire","maproom-south-rta-327"
"631","mire","bigchest-menu-1012"
"632","mire","bigchest-nomenu-852"
"633","mire","maproom-north-328"
"634","mire","spikeykey2-rta-walk-702"
"635","mire","spikeykey2-rta-walk-magicgrab-753"
"636","mire","spikeykey2-rta-walk-magicgrab-with-hook-752"
"637","mire","jadinledge-hookdash-333"
"638","mire","jadinledge-quickhop-328"
"639","mire","jadinledge-rta-2dash-332"
"640","mire","wizzpot-rta-hook-610"
"641","mire","wizzpot-topdash-558"
"642","mire","bridge-504"
"643","mire","caneblockswitch-old-928"
"644","mire","caneblockswitch-spooky-934"
"645","mire","bigblock-429"
"646","mire","caneswitch-spooky-1132"
"647","mire","caneswitch-spooky-roddash-bonk-1108"
"648","mire","bombwall-spooky-dashout-513"
"649","mire","bombwall-spooky-dashout-risky-var-443"
"650","mire","bombwall-spooky-walk-534"
"651","mire","badfarmroom-spooky-dash-above-612"
"652","mire","badfarmroom-spooky-dash-below-608"
"653","mire","badfarmroom-spooky-riskier-walk-619"
"654","mire","badfarmroom-spooky-rta-walk-624"
"655","mire","firesnek-skip-menu-956"
"656","mire","firesnek-skip-rta-nomenu-909"
"657","mire","firesnek-skip-rta-nomenu-915"
"658","mire","vitty-nomenu-1732"
"659","mire","vitty-rta-menu-1846"
"660","?","?"
"661","trock","outside-mire-335"
"662","trock","outside-desert-818"
"663","trock","wafflehouse-drivethru-1241"
"664","trock","wafflehouse-drivethru-nostairclimb-1243"
"665","trock","broken-bridge-1034"
"666","trock","broken-bridge-fromfacingup-1037"
"667","trock","paradox-lower-454"
"668","trock","paradox-upper-740"
"669","trock","run-killing-deadrocks-525"
"670","trock","hammerpegs-1251"
"671","trock","outside-trock-2057"
"672","trock","outside-trock-rta-1957"
"673","trock","dungeon-entrance-menu-933"
"674","trock","dungeon-entrance-no-menu-818"
"675","trock","largepit1-928"
"676","trock","torches-1428"
"677","trock","torches-rta-1432"
"678","trock","rollerroom-1038"
"679","trock","rollerroom-topdmg-1032"
"680","trock","torches-backtrack-346"
"681","trock","largepit2-1025"
"682","trock","pokey0-526"
"683","trock","pokey0-rta-551"
"684","trock","chomps-2blocks-805"
"685","trock","chomps-bbb-758"
"686","trock","chomps-blocknbeam-748"
"687","trock","tunnels-arrows-2040"
"688","trock","tunnels-no-arrows-1948"
"689","trock","lava-room1-739"
"690","trock","pokey1-bl-keydash-849"
"691","trock","bigkey-fromdash-1245"
"692","trock","bigkey-fromwalk-1241"
"693","trock","pokey1-backtrack-beamless-644"
"694","trock","pokey1-backtrack-beams-632"
"695","trock","pokey1-backtrack-canedash-624"
"696","trock","lava-room3-1247"
"697","trock","double-pokeys-429"
"698","trock","miniroller-2dash-339"
"699","trock","lavaroom4-619"
"700","trock","walldash-414"
"701","trock","crystalroller-beams-812"
"702","trock","darkroom-2428"
"703","trock","helmadash1-623"
"704","trock","laserskip-block-921"
"705","trock","laserskip-dash-823"
"706","trock","helmadash2-414"
"707","trock","helmadash2-rta-417"
"708","trock","canedash-725[30lf]"
"709","trock","canedash-725[31lf]"
"710","trock","canedash-magicless-814"
"711","trock","canedash-rta-walk-823"
"712","trock","restock-graball-1238"
"713","trock","restock-magicgrab-1232"
"714","trock","restock-skipall-1145"
"715","trock","trinexx-rta-3759"
"716","gtower","outside-trock-454"
"717","gtower","lynel-bridge-909"
"718","gtower","entrance-2239"
"719","gtower","entrance-nostairclimb-2251"
"720","gtower","foyer-624"
"721","gtower","torchkey-bonkandwalk-759"
"722","gtower","torchkey-hook-758"
"723","gtower","torchkey-nokey-606"
"724","gtower","conveyor-potkey-905"
"725","gtower","spikeskip-919"
"726","gtower","spikeskip-noskip-937"
"727","gtower","double-crystal-switch-719"
"728","gtower","double-crystal-switch-rta-739"
"729","gtower","double-crystal-switch-rta-740"
"730","gtower","spike-and-pegs-356"
"731","gtower","firesnake-room-1029"
"732","gtower","firesnake-room-bonk-1117"
"733","gtower","firesnake-room-framerule-1011"
"734","gtower","warp-tile1-157"
"735","gtower","warp-tile2-344"
"736","gtower","warp-tile2-justholddown-350"
"737","gtower","warp-tile3-430"
"738","gtower","warp-tileskip-816"
"739","gtower","warp-tileskip-with-menu-931"
"740","gtower","false-floor-no-menu-1017"
"741","gtower","false-floor-with-menu-1122"
"742","gtower","bombable-floor-717"
"743","gtower","icearmos-1529"
"744","gtower","icearmos-321-1548"
"745","gtower","icearmos-321-dboost-1546"
"746","gtower","bk-arrows-738"
"747","gtower","bk-no-arrows-606"
"748","gtower","foyer2-745"
"749","gtower","mj-room-1045-arrow-dash"
"750","gtower","mimics1-joestrat-647"
"751","gtower","mimics1-myramics-653"
"752","gtower","mimics2-blunt-553"
"753","gtower","mimics2-opt-519"
"754","gtower","east-spike-room-302"
"755","gtower","spiketrap-1341"
"756","gtower","spiketrap-abovepot-1406"
"757","gtower","gbz-513"
"758","gtower","gbz-walk-637"
"759","gtower","g1-838"
"760","gtower","g2-441"
"761","gtower","g3-644"
"762","gtower","g4-446"
"763","gtower","g5-459"
"764","gtower","bunnybeamhall-305"
"765","gtower","lanmo2-1141"
"766","gtower","restock-248"
"767","gtower","wizz1-849"
"768","gtower","guardbridge-529"
"769","gtower","wizz2-429"
"770","gtower","foosdabridge-653"
"771","gtower","foosdabridge-rightdash-703"
"772","gtower","torches1-1251-magic-noroddash"
"773","gtower","eyelasers-529"
"774","gtower","torches2-637"
"775","gtower","torches2-safewalk-647"
"776","gtower","helmakey-500-dmg"
"777","gtower","helmakey-507-dmgless"
"778","gtower","bombwall-521"
"779","gtower","bombwall-damageless-530"
"780","gtower","bombwall-rta-543"
"781","gtower","pegschestkey-843"
"782","gtower","pegschestkey-magicskip-454"
"783","gtower","pegschestkey-safe-634"
"784","gtower","mold2-hover-1232"
"785","gtower","mold2-hover-1301"
"786","gtower","mold2-hover-1337"
"787","gtower","mold2-kill-hook-1949"
"788","gtower","mold2-kill-hook-1958"
"789","gtower","helma-hallway-607"
"790","gtower","helma-hallway-safe-620"
"791","gtower","torch-hallway-635"
"792","gtower","agah2-4927"
"793","?","?"
"794","?","?"
"795","?","?"
"796","?","?"
"797","?","?"
"798","ganon","ganon-139.55-nfc"
"799","ganon","ganon-139.55"
"800","?","?"
"801","?","?"
"802","?","?"
"803","?","?"
"804","?","?"
"805","?","?"
"806","?","?"
"807","?","?"
"808","?","?"
"809","?","?"
"810","?","?"
"811","?","?"
"812","?","?"
"813","?","?"
"814","?","?"
"815","?","?"
"816","?","?"
1 ID Dungeon Room
2 0 escape links-house-2911
3 1 escape outside-links-house-723
4 2 escape outside-castle-1628
5 3 escape outside-castle-1631
6 4 escape uncle-1342
7 5 escape uncle-1348
8 6 escape passage-exit-442
9 7 escape courtyard-923
10 8 escape castle-lobby-631
11 9 escape castle-lobby-pumpless-636
12 10 escape sw-room-439
13 11 escape sw-room-pumpless-448
14 12 escape west-guard-hallway1-603
15 13 escape statue-hallway1-415
16 14 escape 1st-keyguard-leftslash-801
17 15 escape first-keyguard1-3slash-825
18 16 escape first-keyguard1-upspin-811
19 17 escape b1-pit-lower-404
20 18 escape b1-pit2-lower-945
21 19 escape stealth-room-927
22 20 escape stealth-room-929
23 21 escape green-guard-bestrng-432
24 22 escape green-guard-bestrng-rta-438
25 23 escape blue-boomguard-739
26 24 escape blue-boomguard-skip-627
27 25 escape green-guard2-426
28 26 escape stairs-to-b2-1-359
29 27 escape b2-1-533
30 28 escape bnc-1pot4slash-3227
31 29 escape bnc-8slash-3120
32 30 escape bnc-8slash-3130
33 31 escape bnc-joestrat-3238
34 32 escape bnc-joestrat-3244
35 33 escape b2-2-530
36 34 escape stairs-to-b2-2-651
37 35 escape stairs-to-b2-2-652
38 36 escape green-guard3-426
39 37 escape stealth-room2-920
40 38 escape b1-pit2-upper-301
41 39 escape b1-pit1-upper-627
42 40 escape b1-pit1-upper-pumpless-628
43 41 escape 1st-keyguard2-314
44 42 escape statue-hallway2-709
45 43 escape west-guard-hallway2-612
46 44 escape sw-room2-445
47 45 escape castle-lobby2-721
48 46 escape throne-room-1632
49 47 escape throne-room-1634
50 48 escape sewer-passage-857
51 49 escape sewer-passage-pumpless-903
52 50 escape snake-room-700
53 51 escape snake-room-perfect-rng-655
54 52 escape sewer-key-chest-risky-1212
55 53 escape sewer-key-chest-rta-1215
56 54 escape sewer1-706
57 55 escape sewer1-707
58 56 escape sewer2-608
59 57 escape keyrat-840
60 58 escape rat-hallway-827
61 59 escape behind-sanc-705
62 60 escape behind-sanc-rta-719
63 61 escape behind-sanc2-720
64 62 ? ?
65 63 eastern sanc-2946
66 64 eastern sanc-heart-3303
67 65 eastern sanc-heart-chestturn-3305
68 66 eastern outside-sanc-807
69 67 eastern graveyard-646
70 68 eastern east-path-403
71 69 eastern wooden-bridge-906
72 70 eastern octofield-724
73 71 eastern guard-bridge-311
74 72 eastern east-fairy-cave-right-exit-326
75 73 eastern outside-eastern-2540
76 74 eastern entrance-504
77 75 eastern three-popos-434
78 76 eastern cannonballs-859
79 77 eastern bigchest-upper-leftpot-736
80 78 eastern westwing-upper-455
81 79 eastern stalfos-spawn-617
82 80 eastern compass-room-615
83 81 eastern westwing-lower1-335
84 82 eastern bigchest-lower1-arrows-805
85 83 eastern bigchest-lower1-noarrows-704
86 84 eastern east-wing-645
87 85 eastern dark-af-boom-623
88 86 eastern dark-af-sword-630
89 87 eastern dark-potkey-diagboom-656
90 88 eastern dark-potkey-walk-658
91 89 eastern dark-af2-607
92 90 eastern cannonballs-upper-851
93 91 eastern bigkey-1105
94 92 eastern westwing-lower2-756
95 93 eastern bigchest-bow-1452
96 94 eastern gwg-noslash-menu-913
97 95 eastern afpots-802
98 96 eastern eyegore-floor-switch-519
99 97 eastern eyegore-floor-switch-pumpless-522
100 98 eastern cannonball-switch-503
101 99 eastern cannonball-switch-pumpless-507
102 100 eastern zgr-601
103 101 eastern double-red-eyegores-617
104 102 eastern armos-topright-1321
105 103 desert outside-eastern-1322
106 104 desert saha-walkout-1951-449-8
107 105 desert outside-eastern2-1301-99-0
108 106 desert east-fairy-cave-253
109 107 desert guard-bridge-601-35-0
110 108 desert links-yard-354-32-0
111 109 desert annoying-bushes-403
112 110 desert west-of-swamp1-356
113 111 desert south-of-grove1-right-exit-452-32-0
114 112 desert grove-westside1-nocharge-255
115 113 desert grove-westside1-swordcharge-308
116 114 desert outside-library1-dashes-708
117 115 desert outside-library1-spindash-626
118 116 desert library-1038-127-0
119 117 desert outside-library2-noss-751-99-0
120 118 desert outside-library2-spindash-707
121 119 desert grove-westside2-leftexit-233-36
122 120 desert grove-westside2-rta-243-36
123 121 desert south-of-grove2-fromleft-belowbush-458-32
124 122 desert south-of-grove2-fromleft-bushdash-455-32
125 123 desert south-of-grove2-fromright-bushdash-447-32
126 124 desert west-of-swamp2-frombelow-350
127 125 desert west-of-swamp2-frombushes-346
128 126 desert west-of-swamp2-fromlowbushes-346
129 127 desert west-of-swamp2-fromlowbushes-farleftexit-346
130 128 desert annoying-bushes2-352-33
131 129 desert links-yard-339-32
132 130 desert links-yard-earlydash-336-32
133 131 desert waterdash-menu-629-35-1
134 132 desert waterdash-nomenu-524-37-0
135 133 desert east-of-watergate-216-35
136 134 desert watergate-552-37
137 135 desert middle-aged-man-643-31
138 136 desert outside-desert-2250-175
139 137 desert desert1-risky-726-97
140 138 desert desert1-rta-730-98
141 139 desert north-hall1-leftpoke-510-21
142 140 desert north-hall1-rtapoke-512-21
143 141 desert nw-room1-258-19
144 142 desert torchkey-rta-614
145 143 desert torchkey-underleft-614
146 144 desert nw-room2-235
147 145 desert north-hall2-412-21
148 146 desert ne-room1-351
149 147 desert east-wing1-416-21
150 148 desert compass-room1-519
151 149 desert bigkey-chestturn-758-49
152 150 desert bigkey-rta-goodrng-803
153 151 desert compass-room2-405
154 152 desert compass-room2-pumpless-407
155 153 desert east-wing2-318
156 154 desert east-wing2-pumpless-319
157 155 desert ne-room2-418-20
158 156 desert north-hall3-412-21
159 157 desert nw-room3-346-18
160 158 desert cop1-528
161 159 desert bigchest-606-29-1
162 160 desert cop2-355
163 161 desert nw-room4-dashes-348
164 162 desert nw-room4-walk-rta-349
165 163 desert west-wing-451-21
166 164 desert d1-exit-324
167 165 desert outside-desert-noss-709-102
168 166 desert outside-desert-spindash-732-102-3
169 167 desert d2-entrance-528-96
170 168 desert d2-entrance-pumpless-532-97
171 169 desert tile-room1-458
172 170 desert bridge-539-58
173 171 desert popos-546
174 172 desert beamos-hall-706-1
175 173 desert beamos-hall-rta-720
176 174 desert tile-room2-517-21
177 175 desert torches-nomenu-1549-0-0
178 176 desert torches-rta-1656-0-4
179 177 desert lanmos-2cyc-1953
180 178 desert lanmos-2cyc-2059
181 179 hera outside-desert-cactusdash-1044-77-0
182 180 hera outside-desert-geldmandash-1053-76-0
183 181 hera outside-desert-rta-highdash-1109-76-0
184 182 hera outside-desert-rta-lowdash-1116-76-0
185 183 hera middle-aged-man-634-30-0
186 184 hera watergate-458-34-0
187 185 hera beach-445-35-0
188 186 hera beach-rta-530-35-0
189 187 hera fakeflips-832-36-0
190 188 hera fakeflips-rta-808-35-0
191 189 hera whirlpool-enter-336-36-0
192 190 hera whirlpool-exit-806-72-0
193 191 hera rupee-tree-picklearrow-808-35-0
194 192 hera rupee-tree-rta-821-35-0
195 193 hera old-man-cave-1026-134-0
196 194 hera old-man-tunnel-2139-295-8
197 195 hera death-mountain-lower-3039-246-009
198 196 hera bunny-link-739
199 197 hera death-mountain-upper-ledgehop-736-76-0
200 198 hera death-mountain-upper-rta-748-76-0
201 199 hera lobby1-503-101-0
202 200 hera lobby1-pumpless-507-101-0
203 201 hera basement-key-629-71-0
204 202 hera lobby2-boom-859-107-007
205 203 hera lobby2-boomdash-851-107-010
206 204 hera lobby2-boomless-824
207 205 hera tileroom-boomdash-4340-66-0
208 206 hera tileroom-boomless-4326
209 207 hera 3mold-boom-menu-429-0-2
210 208 hera 3mold-boomless-538
211 209 hera torches-812-31-008
212 210 hera lobby3-540-107-0
213 211 hera beetles-936-63-0
214 212 hera bkdoor-342-0-0
215 213 hera bkdoor-pumpless-345-0-0
216 214 hera safety-switch-634
217 215 hera safety-switch-pumpless-dash-639
218 216 hera safety-switch-pumpless-walk-639
219 217 hera bigchest-ebj-2143-95-17
220 218 hera bigchest-waffle-rta-2324-95-20
221 219 hera bigchest-waffle-sparkmanip-2301-95-18
222 220 hera bumper-skip-729-66-0
223 221 hera moldorm-2752-116-0
224 222 hera moldorm-2858-116-0
225 223 atower death-mountain-upper-943
226 224 atower kak-tunnel-01-806
227 225 atower kak-tunnel-02-607
228 226 atower kak-tunnel-02-arrows-835
229 227 atower kak-tunnel-02-rta-710
230 228 atower first-rupee-tree-928
231 229 atower first-rupee-tree-birddash-1029
232 230 atower first-rupee-tree-rta-x4FA-exit-921
233 231 atower lumberjacks-652
234 232 atower lumberjacks-picklearrow-y078-enter-455
235 233 atower lumberjacks-rta-y078-enter-701
236 234 atower lost-woods-01-1709
237 235 atower lost-woods-01-y078-enter-1713
238 236 atower master-sword-3909
239 237 atower lost-woods-02-2252
240 238 atower lost-woods-02-rta-2243
241 239 atower fortune-teller-hut-rta-619
242 240 atower whirlpool-pond-348
243 241 atower wsb-543
244 242 atower outside-hyrule-castle-1000
245 243 atower castle-lobby-abovetorch-531
246 244 atower castle-lobby-rta-540
247 245 atower sw-room-455-bad-recording
248 246 atower sw-room-456
249 247 atower ramparts-617
250 248 atower entrance-334
251 249 atower goldknights-pui-734
252 250 atower prizepack-guards-703
253 251 atower first-dark-room-605
254 252 atower first-dark-room-rta-619
255 253 atower despair-1617-44
256 254 atower last-3f-dark-room-436
257 255 atower first-4f-dark-room-625
258 256 atower first-4f-dark-room-pumpless-628
259 257 atower melancholy-rta-715
260 258 atower archerkey-beams-keydash-534
261 259 atower archerkey-beams-rta-558
262 260 atower redspearguards-809
263 261 atower alert-guards-arrowkill-516
264 262 atower alert-guards-beamkill-528
265 263 atower cop-menu-638
266 264 atower neopolitan-room-325
267 265 atower statue-room-646
268 266 atower catwalk-boom-624
269 267 atower catwalk-boomless-621-bad-recording
270 268 atower 7f-hallway-521
271 269 atower agah-cutscene-2451
272 270 atower agah-0bb-4451
273 271 pod pyramid-1043
274 272 pod dark-octo-319
275 273 pod hammer-bridge-444
276 274 pod dark-east-fairy-cave-250
277 275 pod outside-4157
278 276 pod entrance1-dash-554
279 277 pod entrance1-walk-556
280 278 pod west-bomb-pickup-336
281 279 pod shooter-key-847
282 280 pod entrance2-333
283 281 pod bomb-pickup1-428
284 282 pod junction1-ledgehop-524
285 283 pod junction1-slashjelly-556
286 284 pod turtle-key-619
287 285 pod entrance3-333
288 286 pod bomb-pickup2-412
289 287 pod junction2-907
290 288 pod junction2-badrng-917
291 289 pod upper-turtle-key-715
292 290 pod bigkey-leftside-754
293 291 pod entrance4-333
294 292 pod bomb-pickup3-412
295 293 pod junction3-651
296 294 pod beetle-room1-954
297 295 pod beetle-room1-dash2chest-1002
298 296 pod beetle-room1-rta-1012
299 297 pod hammeryump-1bombpickup-1324
300 298 pod hammeryump-2bombpickup-1348
301 299 pod darkmaze-738
302 300 pod entrance5-333
303 301 pod bomb-pickup4-412
304 302 pod junction4-651
305 303 pod beetle-room2-dash-and-hop-644
306 304 pod beetle-room2-rta-walk-702
307 305 pod beetle-room2-walk-and-hop-657
308 306 pod sexy-statue-1333
309 307 pod ugly-statue-1406
310 308 pod mimics-454
311 309 pod eye-statue-dashes-2454
312 310 pod eye-statue-walk-rta-2456
313 311 pod darkpegs-607
314 312 pod lonely-turtle-457
315 313 pod turtle-party-701-cropped
316 314 pod turtle-party-701
317 315 pod turtle-party-809
318 316 pod warp-tile-512
319 317 pod turtle-hallway-dash-and-keydash-709
320 318 pod turtle-hallway-rta-walk-and-slash-745
321 319 pod turtle-hallway-walk-and-keydash-720
322 320 pod helma-2739
323 321 pod helma-2813
324 322 pod helma-2830
325 323 pod helma-2843
326 324 pod helma-2927
327 325 thieves outside-pod-2627
328 326 thieves dark-east-fairy-cave-259
329 327 thieves pegbridge-earlydash-639
330 328 thieves pegbridge-safedash-646
331 329 thieves bombshop-359
332 330 thieves annoying-bushes-402
333 331 thieves annoying-bushes-rightcancel-402
334 332 thieves southeast-of-fluteboy-dash-334
335 333 thieves southeast-of-fluteboy-walk-rta-328
336 334 thieves south-of-fluteboy-458
337 335 thieves south-of-fluteboy-walk-rta-508
338 336 thieves fluteboy-rta-1503
339 337 thieves grove-rta-1840
340 338 thieves south-of-grove-445
341 339 thieves grove-westside-236
342 340 thieves usain-bolt-720
343 341 thieves kak-village-centerdash-3335
344 342 thieves kak-village-leftdash-3335
345 343 thieves three-musketeers-3xdash-557
346 344 thieves lost-woods-830
347 345 thieves warp-rock-705
348 346 thieves warp-rock-no-hammerdash-724
349 347 thieves north-of-outcast-748
350 348 thieves voo-943
351 349 thieves sw-quad-badrng-745
352 350 thieves sw-quad-goodrng-736
353 351 thieves nw-quad-333
354 352 thieves ne-quad-badrng-350
355 353 thieves ne-quad-goodrng-337
356 354 thieves se-quad1-551
357 355 thieves bigkey-610
358 356 thieves se-quad2-628
359 357 thieves ne-quad2-733
360 358 thieves stalfos-hallway-hammer-755
361 359 thieves stalfos-hallway-pickup-754
362 360 thieves snek-and-zazaks-335
363 361 thieves conveyor-gibos-north-352
364 362 thieves conveyor-gibos-north-353
365 363 thieves hellway-429
366 364 thieves spikeroom1-hammer-515
367 365 thieves spikeroom1-mikestrat-502
368 366 thieves attic-pot-switch1-badrng-828
369 367 thieves attic-pot-switch1-goodrng-pickup-750
370 368 thieves grasshopper-hall1-1-252
371 369 thieves grasshopper-hall2-1-308
372 370 thieves attic-window-slashgrass-556
373 371 thieves grasshopper-hall2-2-240
374 372 thieves grasshopper-hall1-2-307
375 373 thieves attic-pot-switch2-306
376 374 thieves spikeroom2-606
377 375 thieves hellway2-322
378 376 thieves zazaks-and-gibos-543
379 377 thieves zazaks-and-gibos-545
380 378 thieves conveyor-toilet-bomb-404
381 379 thieves conveyor-toilet-nobomb-321
382 380 thieves bigblock-837
383 381 thieves lonely-zazak-315
384 382 thieves prison-dashes-1053
385 383 thieves prison-walk-1106
386 384 thieves prison-walk-oneslash-1124
387 385 thieves lonely-zazak2-252
388 386 thieves conveyor-jellies1-328
389 387 thieves bigchest-606
390 388 thieves conveyor-jellies2-509
391 389 thieves bigblock2-arrows-412
392 390 thieves bigblock2-noarrows-345
393 391 thieves conveyor-toilet2-dash-hammer-736
394 392 thieves conveyor-toilet2-hammerdash-726
395 393 thieves conveyor-toilet2-rta-751
396 394 thieves stalfos-hallway2-324
397 395 thieves blind-beams-2701
398 396 thieves blind-beams-walkin-2707
399 397 skull voo1-902
400 398 skull voo1-rta-914
401 399 skull 300-hut-721
402 400 skull 300-hut-rta-720
403 401 skull voo2-dash-628
404 402 skull voo2-walk-630
405 403 skull cursed-dwarf-justpressa-1210
406 404 skull cursed-dwarf-rta-1224
407 405 skull voo3-339
408 406 skull hammerpegs1-820
409 407 skull outside-smiths1-350
410 408 skull smiths1-708
411 409 skull outside-smiths2-237
412 410 skull smiths2-910
413 411 skull outside-smiths3-239
414 412 skull hammerpegs2-342
415 413 skull outside-smiths4-338
416 414 skull smiths3-746
417 415 skull outside-smiths5-236
418 416 skull hammerpegs3-849
419 417 skull voo4-fencedash-441
420 418 skull c-shaped-house-918
421 419 skull voo5-709
422 420 skull moblins-badrng-524
423 421 skull moblins-goodrng-518
424 422 skull skeleton-forest1-1216
425 423 skull mummy-statue-1220
426 424 skull bigkey-badrng-659
427 425 skull bigkey-leftside-620
428 426 skull bigkey-rightside-635
429 427 skull mumm-statue-exit-232
430 428 skull skeleton-forest2-949
431 429 skull bigchest-earlybj-1259
432 430 skull bigchest-rta-bonk-1407
433 431 skull bigchest-rta-bonkless-1406
434 432 skull skeleton-forest3-933
435 433 skull mummy-statue-passthru-427
436 434 skull bumper-hallway-305
437 435 skull potkey-654
438 436 skull skeleton-forest4-738
439 437 skull skeleton-forest4-rta-740
440 438 skull firesnake-keydash-522
441 439 skull potpit-left-442
442 440 skull potpit-right-438
443 441 skull mummy-hellway-744
444 442 skull mummy-hellway-joestrat-812
445 443 skull vineroom-badrng-415
446 444 skull vineroom-goodrng-359
447 445 skull mummy-key-629
448 446 skull mummy-key-rta-633
449 447 skull mothhole-top-422
450 448 skull mothula-1118
451 449 skull mothula-1122
452 450 ice outside-skull-menu-352
453 451 ice lost-woods-dash-758
454 452 ice lost-woods-walk-806
455 453 ice links-house-632
456 454 ice castle-gate-menu-rta-qw-637
457 455 ice castle-gate-menu-walk-qw-639
458 456 ice castle-gate-walk-qw-534
459 457 ice pyramid-659
460 458 ice dark-octofield-236
461 459 ice broken-bridge-738
462 460 ice lonely-ropa-243
463 461 ice ropa-lottery-910
464 462 ice dark-cuckoos-leftgrab-737
465 463 ice dark-cuckoos-rta-746
466 464 ice quake-1925
467 465 ice quake-earlythrow-1858
468 466 ice whirlpool1-dashcancel-605
469 467 ice whirlpool1-nocancel-603
470 468 ice zoras-domain-dashout-5934
471 469 ice zoras-domain-walkout-5933
472 470 ice tiny-warp-dik-605
473 471 ice ice-warp-833
474 472 ice ice-island-358
475 473 ice ice-entrance-438
476 474 ice ice2-508
477 475 ice floor-switch-910
478 476 ice block-intersection1-358
479 477 ice compass-room-144
480 478 ice block-intersection2-456
481 479 ice penguins1-spin-535
482 480 ice penguins1-tmstrat-523
483 481 ice block-intersection3-512
484 482 ice bombable-floor-dboost-916
485 483 ice bombable-floor-nodboost-944
486 484 ice bombable-floor-rta-good-rng-957
487 485 ice big-stalfos-819
488 486 ice conveyor-hellway-709
489 487 ice ipbj-1209
490 488 ice floor-zols-300
491 489 ice penguins2-804
492 490 ice big-spike-405
493 491 ice lonely-firebar-419
494 492 ice lonely-firebar-dash-451
495 493 ice double-freezor-503
496 494 ice big-chest-401
497 495 ice big-pit-351
498 496 ice block-push-hammerpot-badlag-542
499 497 ice block-push-potpickup-badlag-539
500 498 ice statue-room-hammer-both-pots-1323
501 499 ice statue-room-hammer-one-pot-1306
502 500 ice statue-room-pickup-both-pots-1318
503 501 ice statue-room-pickup-one-pot-1304
504 502 ice kholdstare-1poke-1621
505 503 ice kholdstare-2poke-1559
506 504 ice kholdstare-2poke-1602
507 505 ice kholdstare-6slash-1655
508 506 swamp ice-island-343
509 507 swamp lake-hylia-birddash-803
510 508 swamp lake-hylia-walk-812
511 509 swamp links-house-521
512 510 swamp grassy-area-menu-437
513 511 swamp warp-nokill-422
514 512 swamp warp-octokill-437
515 513 swamp postwarp-hammerdash-950
516 514 swamp postwarp-walk-959
517 515 swamp outside-swamp1-noqw-629
518 516 swamp outside-swamp1-qw-617
519 517 swamp outside-watergate-noqw-427
520 518 swamp outside-watergate-qw-407
521 519 swamp watergate-entrance-mirrordash-455
522 520 swamp watergate-entrance-walk-500
523 521 swamp afskip-715
524 522 swamp afskip-bonk-736
525 523 swamp afskip-rta-732
526 524 swamp watergate-entrance2-317
527 525 swamp outside-watergate2-closewarp-236
528 526 swamp outside-watergate2-qw-413
529 527 swamp outside-swamp2-closewarp-424
530 528 swamp outside-swamp2-qw-352
531 529 swamp dungeon-entrance-1017
532 530 swamp potkey1-menu-1050
533 531 swamp potkey1-nomenu-923
534 532 swamp pool1-empty-627
535 533 swamp potkey2-dash-336
536 534 swamp potkey2-walk-339
537 535 swamp pool1-empty2-dash-515
538 536 swamp pool1-empty2-walk-514
539 537 swamp waterlever1-1232
540 538 swamp pool1-full-542
541 539 swamp bigchest1-839
542 540 swamp pool2-empty-710
543 541 swamp potkey3-dash-336
544 542 swamp potkey3-walk-339
545 543 swamp pool2-empty2-554
546 544 swamp bigchest2-759
547 545 swamp waterlever2-1355
548 546 swamp bigchest3-844
549 547 swamp pool2-full1-hammerdash-920
550 548 swamp pool2-full1-rta-939
551 549 swamp jelly-dash-1015
552 550 swamp sociable-firebar-1030
553 551 swamp jellydash-northside-433
554 552 swamp bigkey-719
555 553 swamp jelly-dash2-928
556 554 swamp pool2-full2-dboost-907
557 555 swamp pool2-full2-hammerdash-925
558 556 swamp pool2-full2-rta-936
559 557 swamp bigchest4-2026
560 558 swamp bigchest4-rta-2112
561 559 swamp statue-room-arrows-1205
562 560 swamp statue-room-no-arrows-1127
563 561 swamp red-jelly-2slash-351
564 562 swamp red-jelly-beam-340
565 563 swamp red-jelly-dash-326
566 564 swamp water-lever3-1146
567 565 swamp smallhall-313
568 566 swamp waterfall-room-509
569 567 swamp restock-all-448
570 568 swamp restock-arrows-only-422
571 569 swamp restock-bomb-only-420
572 570 swamp restock-skip-dash-330
573 571 swamp restock-skip-walk-334
574 572 swamp c-room-602
575 573 swamp phelps-way-943
576 574 swamp t-room-234
577 575 swamp arrghus-frqk-1737
578 576 swamp arrghus-frqk-1745
579 577 swamp arrghus-frqk-1748
580 578 swamp arrghus-noqk-1902
581 579 swamp arrghus-noqk-1930
582 580 mire outside-swamp-357
583 581 mire outside-swamp-from-fr-341
584 582 mire outside-swamp-from-fr-with-qw-348
585 583 mire outside-swamp-from-mirror-qw-236
586 584 mire outside-watergate-852
587 585 mire outside-watergate-from-qw-836
588 586 mire dm-both-swordclimbs-2145
589 587 mire dm-hookspeed-noswordclimbs-2202
590 588 mire dm-hybrid-2205
591 589 mire dm-spindash-2123
592 590 mire dm-spindash-2134
593 591 mire dm-spindash-dmgcancel-2111
594 592 mire dark-dm-534
595 593 mire ether-dashoff-2305
596 594 mire ether-quickhop-2243
597 595 mire octoballoon-610
598 596 mire icerod-entrance-931
599 597 mire icerod1-334
600 598 mire icerodchest-nomenu-659
601 599 mire icerodchest-rta-menu-815
602 600 mire icerod2-menu-350
603 601 mire icerod2-rta-nomenu-233
604 602 mire icerod-leaving-547
605 603 mire icerod-leaving-rta-556
606 604 mire mirewarp-menu-701
607 605 mire mire-entrance-nomenu-2501
608 606 mire mire01-821
609 607 mire mire02-1031
610 608 mire mainhub1-toproute-947
611 609 mire poporoom1-337
612 610 mire spikekey1-351
613 611 mire poporoom2-337
614 612 mire beatthefireball-938
615 613 mire jellykey-449
616 614 mire tileroom-321
617 615 mire bombslugs-508
618 616 mire torches1-752
619 617 mire torches2-rta-2127
620 618 mire torches2-toplighting-2121
621 619 mire bighole-322
622 620 mire bigkey-621
623 621 mire warptile-230
624 622 mire wizzroom-412
625 623 mire bigspike-318
626 624 mire lonelystalfos-337
627 625 mire sparkgamble-bestboost-1106
628 626 mire sparkgamble-damageless-1144
629 627 mire sparkgamble-rta-menu-1236
630 628 mire sparkgamble-snekblock-1205
631 629 mire maproom-south-327
632 630 mire maproom-south-rta-327
633 631 mire bigchest-menu-1012
634 632 mire bigchest-nomenu-852
635 633 mire maproom-north-328
636 634 mire spikeykey2-rta-walk-702
637 635 mire spikeykey2-rta-walk-magicgrab-753
638 636 mire spikeykey2-rta-walk-magicgrab-with-hook-752
639 637 mire jadinledge-hookdash-333
640 638 mire jadinledge-quickhop-328
641 639 mire jadinledge-rta-2dash-332
642 640 mire wizzpot-rta-hook-610
643 641 mire wizzpot-topdash-558
644 642 mire bridge-504
645 643 mire caneblockswitch-old-928
646 644 mire caneblockswitch-spooky-934
647 645 mire bigblock-429
648 646 mire caneswitch-spooky-1132
649 647 mire caneswitch-spooky-roddash-bonk-1108
650 648 mire bombwall-spooky-dashout-513
651 649 mire bombwall-spooky-dashout-risky-var-443
652 650 mire bombwall-spooky-walk-534
653 651 mire badfarmroom-spooky-dash-above-612
654 652 mire badfarmroom-spooky-dash-below-608
655 653 mire badfarmroom-spooky-riskier-walk-619
656 654 mire badfarmroom-spooky-rta-walk-624
657 655 mire firesnek-skip-menu-956
658 656 mire firesnek-skip-rta-nomenu-909
659 657 mire firesnek-skip-rta-nomenu-915
660 658 mire vitty-nomenu-1732
661 659 mire vitty-rta-menu-1846
662 660 ? ?
663 661 trock outside-mire-335
664 662 trock outside-desert-818
665 663 trock wafflehouse-drivethru-1241
666 664 trock wafflehouse-drivethru-nostairclimb-1243
667 665 trock broken-bridge-1034
668 666 trock broken-bridge-fromfacingup-1037
669 667 trock paradox-lower-454
670 668 trock paradox-upper-740
671 669 trock run-killing-deadrocks-525
672 670 trock hammerpegs-1251
673 671 trock outside-trock-2057
674 672 trock outside-trock-rta-1957
675 673 trock dungeon-entrance-menu-933
676 674 trock dungeon-entrance-no-menu-818
677 675 trock largepit1-928
678 676 trock torches-1428
679 677 trock torches-rta-1432
680 678 trock rollerroom-1038
681 679 trock rollerroom-topdmg-1032
682 680 trock torches-backtrack-346
683 681 trock largepit2-1025
684 682 trock pokey0-526
685 683 trock pokey0-rta-551
686 684 trock chomps-2blocks-805
687 685 trock chomps-bbb-758
688 686 trock chomps-blocknbeam-748
689 687 trock tunnels-arrows-2040
690 688 trock tunnels-no-arrows-1948
691 689 trock lava-room1-739
692 690 trock pokey1-bl-keydash-849
693 691 trock bigkey-fromdash-1245
694 692 trock bigkey-fromwalk-1241
695 693 trock pokey1-backtrack-beamless-644
696 694 trock pokey1-backtrack-beams-632
697 695 trock pokey1-backtrack-canedash-624
698 696 trock lava-room3-1247
699 697 trock double-pokeys-429
700 698 trock miniroller-2dash-339
701 699 trock lavaroom4-619
702 700 trock walldash-414
703 701 trock crystalroller-beams-812
704 702 trock darkroom-2428
705 703 trock helmadash1-623
706 704 trock laserskip-block-921
707 705 trock laserskip-dash-823
708 706 trock helmadash2-414
709 707 trock helmadash2-rta-417
710 708 trock canedash-725[30lf]
711 709 trock canedash-725[31lf]
712 710 trock canedash-magicless-814
713 711 trock canedash-rta-walk-823
714 712 trock restock-graball-1238
715 713 trock restock-magicgrab-1232
716 714 trock restock-skipall-1145
717 715 trock trinexx-rta-3759
718 716 gtower outside-trock-454
719 717 gtower lynel-bridge-909
720 718 gtower entrance-2239
721 719 gtower entrance-nostairclimb-2251
722 720 gtower foyer-624
723 721 gtower torchkey-bonkandwalk-759
724 722 gtower torchkey-hook-758
725 723 gtower torchkey-nokey-606
726 724 gtower conveyor-potkey-905
727 725 gtower spikeskip-919
728 726 gtower spikeskip-noskip-937
729 727 gtower double-crystal-switch-719
730 728 gtower double-crystal-switch-rta-739
731 729 gtower double-crystal-switch-rta-740
732 730 gtower spike-and-pegs-356
733 731 gtower firesnake-room-1029
734 732 gtower firesnake-room-bonk-1117
735 733 gtower firesnake-room-framerule-1011
736 734 gtower warp-tile1-157
737 735 gtower warp-tile2-344
738 736 gtower warp-tile2-justholddown-350
739 737 gtower warp-tile3-430
740 738 gtower warp-tileskip-816
741 739 gtower warp-tileskip-with-menu-931
742 740 gtower false-floor-no-menu-1017
743 741 gtower false-floor-with-menu-1122
744 742 gtower bombable-floor-717
745 743 gtower icearmos-1529
746 744 gtower icearmos-321-1548
747 745 gtower icearmos-321-dboost-1546
748 746 gtower bk-arrows-738
749 747 gtower bk-no-arrows-606
750 748 gtower foyer2-745
751 749 gtower mj-room-1045-arrow-dash
752 750 gtower mimics1-joestrat-647
753 751 gtower mimics1-myramics-653
754 752 gtower mimics2-blunt-553
755 753 gtower mimics2-opt-519
756 754 gtower east-spike-room-302
757 755 gtower spiketrap-1341
758 756 gtower spiketrap-abovepot-1406
759 757 gtower gbz-513
760 758 gtower gbz-walk-637
761 759 gtower g1-838
762 760 gtower g2-441
763 761 gtower g3-644
764 762 gtower g4-446
765 763 gtower g5-459
766 764 gtower bunnybeamhall-305
767 765 gtower lanmo2-1141
768 766 gtower restock-248
769 767 gtower wizz1-849
770 768 gtower guardbridge-529
771 769 gtower wizz2-429
772 770 gtower foosdabridge-653
773 771 gtower foosdabridge-rightdash-703
774 772 gtower torches1-1251-magic-noroddash
775 773 gtower eyelasers-529
776 774 gtower torches2-637
777 775 gtower torches2-safewalk-647
778 776 gtower helmakey-500-dmg
779 777 gtower helmakey-507-dmgless
780 778 gtower bombwall-521
781 779 gtower bombwall-damageless-530
782 780 gtower bombwall-rta-543
783 781 gtower pegschestkey-843
784 782 gtower pegschestkey-magicskip-454
785 783 gtower pegschestkey-safe-634
786 784 gtower mold2-hover-1232
787 785 gtower mold2-hover-1301
788 786 gtower mold2-hover-1337
789 787 gtower mold2-kill-hook-1949
790 788 gtower mold2-kill-hook-1958
791 789 gtower helma-hallway-607
792 790 gtower helma-hallway-safe-620
793 791 gtower torch-hallway-635
794 792 gtower agah2-4927
795 793 ? ?
796 794 ? ?
797 795 ? ?
798 796 ? ?
799 797 ? ?
800 798 ganon ganon-139.55-nfc
801 799 ganon ganon-139.55
802 800 ? ?
803 801 ? ?
804 802 ? ?
805 803 ? ?
806 804 ? ?
807 805 ? ?
808 806 ? ?
809 807 ? ?
810 808 ? ?
811 809 ? ?
812 810 ? ?
813 811 ? ?
814 812 ? ?
815 813 ? ?
816 814 ? ?
817 815 ? ?
818 816 ? ?

View File

@@ -1,818 +0,0 @@
'ID','Dungeon','Room'
'0','?','links-house-2911'
'1','?','outside-links-house-723'
'2','?','outside-castle-1628'
'3','?','outside-castle-1631'
'4','?','uncle-1342'
'5','?','uncle-1348'
'6','?','passage-exit-442'
'7','?','courtyard-923'
'8','?','castle-lobby-631'
'9','?','castle-lobby-pumpless-636'
'10','?','sw-room-439'
'11','?','sw-room-pumpless-448'
'12','?','west-guard-hallway1-603'
'13','?','statue-hallway1-415'
'14','?','1st-keyguard-leftslash-801'
'15','?','first-keyguard1-3slash-825'
'16','?','first-keyguard1-upspin-811'
'17','?','b1-pit-lower-404'
'18','?','b1-pit2-lower-945'
'19','?','stealth-room-927'
'20','?','stealth-room-929'
'21','?','green-guard-bestrng-432'
'22','?','green-guard-bestrng-rta-438'
'23','?','blue-boomguard-739'
'24','?','blue-boomguard-skip-627'
'25','?','green-guard2-426'
'26','?','stairs-to-b2-1-359'
'27','?','b2-1-533'
'28','?','bnc-1pot4slash-3227'
'29','?','bnc-8slash-3120'
'30','?','bnc-8slash-3130'
'31','?','bnc-joestrat-3238'
'32','?','bnc-joestrat-3244'
'33','?','b2-2-530'
'34','?','stairs-to-b2-2-651'
'35','?','stairs-to-b2-2-652'
'36','?','green-guard3-426'
'37','?','stealth-room2-920'
'38','?','b1-pit2-upper-301'
'39','?','b1-pit1-upper-627'
'40','?','b1-pit1-upper-pumpless-628'
'41','?','1st-keyguard2-314'
'42','?','statue-hallway2-709'
'43','?','west-guard-hallway2-612'
'44','?','sw-room2-445'
'45','?','castle-lobby2-721'
'46','?','throne-room-1632'
'47','?','throne-room-1634'
'48','?','sewer-passage-857'
'49','?','sewer-passage-pumpless-903'
'50','?','snake-room-700'
'51','?','snake-room-perfect-rng-655'
'52','?','sewer-key-chest-risky-1212'
'53','?','sewer-key-chest-rta-1215'
'54','?','sewer1-706'
'55','?','sewer1-707'
'56','?','sewer2-608'
'57','?','keyrat-840'
'58','?','rat-hallway-827'
'59','?','behind-sanc-705'
'60','?','behind-sanc-rta-719'
'61','?','behind-sanc2-720'
'62','?','?'
'63','?','sanc-2946'
'64','?','sanc-heart-3303'
'65','?','sanc-heart-chestturn-3305'
'66','?','outside-sanc-807'
'67','?','graveyard-646'
'68','?','east-path-403'
'69','?','wooden-bridge-906'
'70','?','octofield-724'
'71','?','guard-bridge-311'
'72','?','east-fairy-cave-right-exit-326'
'73','?','outside-eastern-2540'
'74','?','entrance-504'
'75','?','three-popos-434'
'76','?','cannonballs-859'
'77','?','bigchest-upper-leftpot-736'
'78','?','westwing-upper-455'
'79','?','stalfos-spawn-617'
'80','?','compass-room-615'
'81','?','westwing-lower1-335'
'82','?','bigchest-lower1-arrows-805'
'83','?','bigchest-lower1-noarrows-704'
'84','?','east-wing-645'
'85','?','dark-af-boom-623'
'86','?','dark-af-sword-630'
'87','?','dark-potkey-diagboom-656'
'88','?','dark-potkey-walk-658'
'89','?','dark-af2-607'
'90','?','cannonballs-upper-851'
'91','?','bigkey-1105'
'92','?','westwing-lower2-756'
'93','?','bigchest-bow-1452'
'94','?','gwg-noslash-menu-913'
'95','?','afpots-802'
'96','?','eyegore-floor-switch-519'
'97','?','eyegore-floor-switch-pumpless-522'
'98','?','cannonball-switch-503'
'99','?','cannonball-switch-pumpless-507'
'100','?','zgr-601'
'101','?','double-red-eyegores-617'
'102','?','armos-topright-1321'
'103','?','outside-eastern-1322'
'104','?','saha-walkout-1951-449-8'
'105','?','outside-eastern2-1301-99-0'
'106','?','east-fairy-cave-253'
'107','?','guard-bridge-601-35-0'
'108','?','links-yard-354-32-0'
'109','?','annoying-bushes-403'
'110','?','west-of-swamp1-356'
'111','?','south-of-grove1-right-exit-452-32-0'
'112','?','grove-westside1-nocharge-255'
'113','?','grove-westside1-swordcharge-308'
'114','?','outside-library1-dashes-708'
'115','?','outside-library1-spindash-626'
'116','?','library-1038-127-0'
'117','?','outside-library2-noss-751-99-0'
'118','?','outside-library2-spindash-707'
'119','?','grove-westside2-leftexit-233-36'
'120','?','grove-westside2-rta-243-36'
'121','?','south-of-grove2-fromleft-belowbush-458-32'
'122','?','south-of-grove2-fromleft-bushdash-455-32'
'123','?','south-of-grove2-fromright-bushdash-447-32'
'124','?','west-of-swamp2-frombelow-350'
'125','?','west-of-swamp2-frombushes-346'
'126','?','west-of-swamp2-fromlowbushes-346'
'127','?','west-of-swamp2-fromlowbushes-farleftexit-346'
'128','?','annoying-bushes2-352-33'
'129','?','links-yard-339-32'
'130','?','links-yard-earlydash-336-32'
'131','?','waterdash-menu-629-35-1'
'132','?','waterdash-nomenu-524-37-0'
'133','?','east-of-watergate-216-35'
'134','?','watergate-552-37'
'135','?','middle-aged-man-643-31'
'136','?','outside-desert-2250-175'
'137','?','desert1-risky-726-97'
'138','?','desert1-rta-730-98'
'139','?','north-hall1-leftpoke-510-21'
'140','?','north-hall1-rtapoke-512-21'
'141','?','nw-room1-258-19'
'142','?','torchkey-rta-614'
'143','?','torchkey-underleft-614'
'144','?','nw-room2-235'
'145','?','north-hall2-412-21'
'146','?','ne-room1-351'
'147','?','east-wing1-416-21'
'148','?','compass-room1-519'
'149','?','bigkey-chestturn-758-49'
'150','?','bigkey-rta-goodrng-803'
'151','?','compass-room2-405'
'152','?','compass-room2-pumpless-407'
'153','?','east-wing2-318'
'154','?','east-wing2-pumpless-319'
'155','?','ne-room2-418-20'
'156','?','north-hall3-412-21'
'157','?','nw-room3-346-18'
'158','?','cop1-528'
'159','?','bigchest-606-29-1'
'160','?','cop2-355'
'161','?','nw-room4-dashes-348'
'162','?','nw-room4-walk-rta-349'
'163','?','west-wing-451-21'
'164','?','d1-exit-324'
'165','?','outside-desert-noss-709-102'
'166','?','outside-desert-spindash-732-102-3'
'167','?','d2-entrance-528-96'
'168','?','d2-entrance-pumpless-532-97'
'169','?','tile-room1-458'
'170','?','bridge-539-58'
'171','?','popos-546'
'172','?','beamos-hall-706-1'
'173','?','beamos-hall-rta-720'
'174','?','tile-room2-517-21'
'175','?','torches-nomenu-1549-0-0'
'176','?','torches-rta-1656-0-4'
'177','?','lanmos-2cyc-1953'
'178','?','lanmos-2cyc-2059'
'179','?','outside-desert-cactusdash-1044-77-0'
'180','?','outside-desert-geldmandash-1053-76-0'
'181','?','outside-desert-rta-highdash-1109-76-0'
'182','?','outside-desert-rta-lowdash-1116-76-0'
'183','?','middle-aged-man-634-30-0'
'184','?','watergate-458-34-0'
'185','?','beach-445-35-0'
'186','?','beach-rta-530-35-0'
'187','?','fakeflips-832-36-0'
'188','?','fakeflips-rta-808-35-0'
'189','?','whirlpool-enter-336-36-0'
'190','?','whirlpool-exit-806-72-0'
'191','?','rupee-tree-picklearrow-808-35-0'
'192','?','rupee-tree-rta-821-35-0'
'193','?','old-man-cave-1026-134-0'
'194','?','old-man-tunnel-2139-295-8'
'195','?','death-mountain-lower-3039-246-009'
'196','?','bunny-link-739'
'197','?','death-mountain-upper-ledgehop-736-76-0'
'198','?','death-mountain-upper-rta-748-76-0'
'199','?','lobby1-503-101-0'
'200','?','lobby1-pumpless-507-101-0'
'201','?','basement-key-629-71-0'
'202','?','lobby2-boom-859-107-007'
'203','?','lobby2-boomdash-851-107-010'
'204','?','lobby2-boomless-824'
'205','?','tileroom-boomdash-4340-66-0'
'206','?','tileroom-boomless-4326'
'207','?','3mold-boom-menu-429-0-2'
'208','?','3mold-boomless-538'
'209','?','torches-812-31-008'
'210','?','lobby3-540-107-0'
'211','?','beetles-936-63-0'
'212','?','bkdoor-342-0-0'
'213','?','bkdoor-pumpless-345-0-0'
'214','?','safety-switch-634'
'215','?','safety-switch-pumpless-dash-639'
'216','?','safety-switch-pumpless-walk-639'
'217','?','bigchest-ebj-2143-95-17'
'218','?','bigchest-waffle-rta-2324-95-20'
'219','?','bigchest-waffle-sparkmanip-2301-95-18'
'220','?','bumper-skip-729-66-0'
'221','?','moldorm-2752-116-0'
'222','?','moldorm-2858-116-0'
'223','?','death-mountain-upper-943'
'224','?','kak-tunnel-01-806'
'225','?','kak-tunnel-02-607'
'226','?','kak-tunnel-02-arrows-835'
'227','?','kak-tunnel-02-rta-710'
'228','?','first-rupee-tree-928'
'229','?','first-rupee-tree-birddash-1029'
'230','?','first-rupee-tree-rta-x4FA-exit-921'
'231','?','lumberjacks-652'
'232','?','lumberjacks-picklearrow-y078-enter-455'
'233','?','lumberjacks-rta-y078-enter-701'
'234','?','lost-woods-01-1709'
'235','?','lost-woods-01-y078-enter-1713'
'236','?','master-sword-3909'
'237','?','lost-woods-02-2252'
'238','?','lost-woods-02-rta-2243'
'239','?','fortune-teller-hut-rta-619'
'240','?','whirlpool-pond-348'
'241','?','wsb-543'
'242','?','outside-hyrule-castle-1000'
'243','?','castle-lobby-abovetorch-531'
'244','?','castle-lobby-rta-540'
'245','?','sw-room-455-bad-recording'
'246','?','sw-room-456'
'247','?','ramparts-617'
'248','?','entrance-334'
'249','?','goldknights-pui-734'
'250','?','prizepack-guards-703'
'251','?','first-dark-room-605'
'252','?','first-dark-room-rta-619'
'253','?','despair-1617-44'
'254','?','last-3f-dark-room-436'
'255','?','first-4f-dark-room-625'
'256','?','first-4f-dark-room-pumpless-628'
'257','?','melancholy-rta-715'
'258','?','archerkey-beams-keydash-534'
'259','?','archerkey-beams-rta-558'
'260','?','redspearguards-809'
'261','?','alert-guards-arrowkill-516'
'262','?','alert-guards-beamkill-528'
'263','?','cop-menu-638'
'264','?','neopolitan-room-325'
'265','?','statue-room-646'
'266','?','catwalk-boom-624'
'267','?','catwalk-boomless-621-bad-recording'
'268','?','7f-hallway-521'
'269','?','agah-cutscene-2451'
'270','?','agah-0bb-4451'
'271','?','pyramid-1043'
'272','?','dark-octo-319'
'273','?','hammer-bridge-444'
'274','?','dark-east-fairy-cave-250'
'275','?','outside-4157'
'276','?','entrance1-dash-554'
'277','?','entrance1-walk-556'
'278','?','west-bomb-pickup-336'
'279','?','shooter-key-847'
'280','?','entrance2-333'
'281','?','bomb-pickup1-428'
'282','?','junction1-ledgehop-524'
'283','?','junction1-slashjelly-556'
'284','?','turtle-key-619'
'285','?','entrance3-333'
'286','?','bomb-pickup2-412'
'287','?','junction2-907'
'288','?','junction2-badrng-917'
'289','?','upper-turtle-key-715'
'290','?','bigkey-leftside-754'
'291','?','entrance4-333'
'292','?','bomb-pickup3-412'
'293','?','junction3-651'
'294','?','beetle-room1-954'
'295','?','beetle-room1-dash2chest-1002'
'296','?','beetle-room1-rta-1012'
'297','?','hammeryump-1bombpickup-1324'
'298','?','hammeryump-2bombpickup-1348'
'299','?','darkmaze-738'
'300','?','entrance5-333'
'301','?','bomb-pickup4-412'
'302','?','junction4-651'
'303','?','beetle-room2-dash-and-hop-644'
'304','?','beetle-room2-rta-walk-702'
'305','?','beetle-room2-walk-and-hop-657'
'306','?','sexy-statue-1333'
'307','?','ugly-statue-1406'
'308','?','mimics-454'
'309','?','eye-statue-dashes-2454'
'310','?','eye-statue-walk-rta-2456'
'311','?','darkpegs-607'
'312','?','lonely-turtle-457'
'313','?','turtle-party-701-cropped'
'314','?','turtle-party-701'
'315','?','turtle-party-809'
'316','?','warp-tile-512'
'317','?','turtle-hallway-dash-and-keydash-709'
'318','?','turtle-hallway-rta-walk-and-slash-745'
'319','?','turtle-hallway-walk-and-keydash-720'
'320','?','helma-2739'
'321','?','helma-2813'
'322','?','helma-2830'
'323','?','helma-2843'
'324','?','helma-2927'
'325','?','outside-pod-2627'
'326','?','dark-east-fairy-cave-259'
'327','?','pegbridge-earlydash-639'
'328','?','pegbridge-safedash-646'
'329','?','bombshop-359'
'330','?','annoying-bushes-402'
'331','?','annoying-bushes-rightcancel-402'
'332','?','southeast-of-fluteboy-dash-334'
'333','?','southeast-of-fluteboy-walk-rta-328'
'334','?','south-of-fluteboy-458'
'335','?','south-of-fluteboy-walk-rta-508'
'336','?','fluteboy-rta-1503'
'337','?','grove-rta-1840'
'338','?','south-of-grove-445'
'339','?','grove-westside-236'
'340','?','usain-bolt-720'
'341','?','kak-village-centerdash-3335'
'342','?','kak-village-leftdash-3335'
'343','?','three-musketeers-3xdash-557'
'344','?','lost-woods-830'
'345','?','warp-rock-705'
'346','?','warp-rock-no-hammerdash-724'
'347','?','north-of-outcast-748'
'348','?','voo-943'
'349','?','sw-quad-badrng-745'
'350','?','sw-quad-goodrng-736'
'351','?','nw-quad-333'
'352','?','ne-quad-badrng-350'
'353','?','ne-quad-goodrng-337'
'354','?','se-quad1-551'
'355','?','bigkey-610'
'356','?','se-quad2-628'
'357','?','ne-quad2-733'
'358','?','stalfos-hallway-hammer-755'
'359','?','stalfos-hallway-pickup-754'
'360','?','snek-and-zazaks-335'
'361','?','conveyor-gibos-north-352'
'362','?','conveyor-gibos-north-353'
'363','?','hellway-429'
'364','?','spikeroom1-hammer-515'
'365','?','spikeroom1-mikestrat-502'
'366','?','attic-pot-switch1-badrng-828'
'367','?','attic-pot-switch1-goodrng-pickup-750'
'368','?','grasshopper-hall1-1-252'
'369','?','grasshopper-hall2-1-308'
'370','?','attic-window-slashgrass-556'
'371','?','grasshopper-hall2-2-240'
'372','?','grasshopper-hall1-2-307'
'373','?','attic-pot-switch2-306'
'374','?','spikeroom2-606'
'375','?','hellway2-322'
'376','?','zazaks-and-gibos-543'
'377','?','zazaks-and-gibos-545'
'378','?','conveyor-toilet-bomb-404'
'379','?','conveyor-toilet-nobomb-321'
'380','?','bigblock-837'
'381','?','lonely-zazak-315'
'382','?','prison-dashes-1053'
'383','?','prison-walk-1106'
'384','?','prison-walk-oneslash-1124'
'385','?','lonely-zazak2-252'
'386','?','conveyor-jellies1-328'
'387','?','bigchest-606'
'388','?','conveyor-jellies2-509'
'389','?','bigblock2-arrows-412'
'390','?','bigblock2-noarrows-345'
'391','?','conveyor-toilet2-dash-hammer-736'
'392','?','conveyor-toilet2-hammerdash-726'
'393','?','conveyor-toilet2-rta-751'
'394','?','stalfos-hallway2-324'
'395','?','blind-beams-2701'
'396','?','blind-beams-walkin-2707'
'397','?','voo1-902'
'398','?','voo1-rta-914'
'399','?','300-hut-721'
'400','?','300-hut-rta-720'
'401','?','voo2-dash-628'
'402','?','voo2-walk-630'
'403','?','cursed-dwarf-justpressa-1210'
'404','?','cursed-dwarf-rta-1224'
'405','?','voo3-339'
'406','?','hammerpegs1-820'
'407','?','outside-smiths1-350'
'408','?','smiths1-708'
'409','?','outside-smiths2-237'
'410','?','smiths2-910'
'411','?','outside-smiths3-239'
'412','?','hammerpegs2-342'
'413','?','outside-smiths4-338'
'414','?','smiths3-746'
'415','?','outside-smiths5-236'
'416','?','hammerpegs3-849'
'417','?','voo4-fencedash-441'
'418','?','c-shaped-house-918'
'419','?','voo5-709'
'420','?','moblins-badrng-524'
'421','?','moblins-goodrng-518'
'422','?','skeleton-forest1-1216'
'423','?','mummy-statue-1220'
'424','?','bigkey-badrng-659'
'425','?','bigkey-leftside-620'
'426','?','bigkey-rightside-635'
'427','?','mumm-statue-exit-232'
'428','?','skeleton-forest2-949'
'429','?','bigchest-earlybj-1259'
'430','?','bigchest-rta-bonk-1407'
'431','?','bigchest-rta-bonkless-1406'
'432','?','skeleton-forest3-933'
'433','?','mummy-statue-passthru-427'
'434','?','bumper-hallway-305'
'435','?','potkey-654'
'436','?','skeleton-forest4-738'
'437','?','skeleton-forest4-rta-740'
'438','?','firesnake-keydash-522'
'439','?','potpit-left-442'
'440','?','potpit-right-438'
'441','?','mummy-hellway-744'
'442','?','mummy-hellway-joestrat-812'
'443','?','vineroom-badrng-415'
'444','?','vineroom-goodrng-359'
'445','?','mummy-key-629'
'446','?','mummy-key-rta-633'
'447','?','mothhole-top-422'
'448','?','mothula-1118'
'449','?','mothula-1122'
'450','?','outside-skull-menu-352'
'451','?','lost-woods-dash-758'
'452','?','lost-woods-walk-806'
'453','?','links-house-632'
'454','?','castle-gate-menu-rta-qw-637'
'455','?','castle-gate-menu-walk-qw-639'
'456','?','castle-gate-walk-qw-534'
'457','?','pyramid-659'
'458','?','dark-octofield-236'
'459','?','broken-bridge-738'
'460','?','lonely-ropa-243'
'461','?','ropa-lottery-910'
'462','?','dark-cuckoos-leftgrab-737'
'463','?','dark-cuckoos-rta-746'
'464','?','quake-1925'
'465','?','quake-earlythrow-1858'
'466','?','whirlpool1-dashcancel-605'
'467','?','whirlpool1-nocancel-603'
'468','?','zoras-domain-dashout-5934'
'469','?','zoras-domain-walkout-5933'
'470','?','tiny-warp-dik-605'
'471','?','ice-warp-833'
'472','?','ice-island-358'
'473','?','ice-entrance-438'
'474','?','ice2-508'
'475','?','floor-switch-910'
'476','?','block-intersection1-358'
'477','?','compass-room-144'
'478','?','block-intersection2-456'
'479','?','penguins1-spin-535'
'480','?','penguins1-tmstrat-523'
'481','?','block-intersection3-512'
'482','?','bombable-floor-dboost-916'
'483','?','bombable-floor-nodboost-944'
'484','?','bombable-floor-rta-good-rng-957'
'485','?','big-stalfos-819'
'486','?','conveyor-hellway-709'
'487','?','ipbj-1209'
'488','?','floor-zols-300'
'489','?','penguins2-804'
'490','?','big-spike-405'
'491','?','lonely-firebar-419'
'492','?','lonely-firebar-dash-451'
'493','?','double-freezor-503'
'494','?','big-chest-401'
'495','?','big-pit-351'
'496','?','block-push-hammerpot-badlag-542'
'497','?','block-push-potpickup-badlag-539'
'498','?','statue-room-hammer-both-pots-1323'
'499','?','statue-room-hammer-one-pot-1306'
'500','?','statue-room-pickup-both-pots-1318'
'501','?','statue-room-pickup-one-pot-1304'
'502','?','kholdstare-1poke-1621'
'503','?','kholdstare-2poke-1559'
'504','?','kholdstare-2poke-1602'
'505','?','kholdstare-6slash-1655'
'506','?','ice-island-343'
'507','?','lake-hylia-birddash-803'
'508','?','lake-hylia-walk-812'
'509','?','links-house-521'
'510','?','grassy-area-menu-437'
'511','?','warp-nokill-422'
'512','?','warp-octokill-437'
'513','?','postwarp-hammerdash-950'
'514','?','postwarp-walk-959'
'515','?','outside-swamp1-noqw-629'
'516','?','outside-swamp1-qw-617'
'517','?','outside-watergate-noqw-427'
'518','?','outside-watergate-qw-407'
'519','?','watergate-entrance-mirrordash-455'
'520','?','watergate-entrance-walk-500'
'521','?','afskip-715'
'522','?','afskip-bonk-736'
'523','?','afskip-rta-732'
'524','?','watergate-entrance2-317'
'525','?','outside-watergate2-closewarp-236'
'526','?','outside-watergate2-qw-413'
'527','?','outside-swamp2-closewarp-424'
'528','?','outside-swamp2-qw-352'
'529','?','dungeon-entrance-1017'
'530','?','potkey1-menu-1050'
'531','?','potkey1-nomenu-923'
'532','?','pool1-empty-627'
'533','?','potkey2-dash-336'
'534','?','potkey2-walk-339'
'535','?','pool1-empty2-dash-515'
'536','?','pool1-empty2-walk-514'
'537','?','waterlever1-1232'
'538','?','pool1-full-542'
'539','?','bigchest1-839'
'540','?','pool2-empty-710'
'541','?','potkey3-dash-336'
'542','?','potkey3-walk-339'
'543','?','pool2-empty2-554'
'544','?','bigchest2-759'
'545','?','waterlever2-1355'
'546','?','bigchest3-844'
'547','?','pool2-full1-hammerdash-920'
'548','?','pool2-full1-rta-939'
'549','?','jelly-dash-1015'
'550','?','sociable-firebar-1030'
'551','?','jellydash-northside-433'
'552','?','bigkey-719'
'553','?','jelly-dash2-928'
'554','?','pool2-full2-dboost-907'
'555','?','pool2-full2-hammerdash-925'
'556','?','pool2-full2-rta-936'
'557','?','bigchest4-2026'
'558','?','bigchest4-rta-2112'
'559','?','statue-room-arrows-1205'
'560','?','statue-room-no-arrows-1127'
'561','?','red-jelly-2slash-351'
'562','?','red-jelly-beam-340'
'563','?','red-jelly-dash-326'
'564','?','water-lever3-1146'
'565','?','smallhall-313'
'566','?','waterfall-room-509'
'567','?','restock-all-448'
'568','?','restock-arrows-only-422'
'569','?','restock-bomb-only-420'
'570','?','restock-skip-dash-330'
'571','?','restock-skip-walk-334'
'572','?','c-room-602'
'573','?','phelps-way-943'
'574','?','t-room-234'
'575','?','arrghus-frqk-1737'
'576','?','arrghus-frqk-1745'
'577','?','arrghus-frqk-1748'
'578','?','arrghus-noqk-1902'
'579','?','arrghus-noqk-1930'
'580','?','outside-swamp-357'
'581','?','outside-swamp-from-fr-341'
'582','?','outside-swamp-from-fr-with-qw-348'
'583','?','outside-swamp-from-mirror-qw-236'
'584','?','outside-watergate-852'
'585','?','outside-watergate-from-qw-836'
'586','?','dm-both-swordclimbs-2145'
'587','?','dm-hookspeed-noswordclimbs-2202'
'588','?','dm-hybrid-2205'
'589','?','dm-spindash-2123'
'590','?','dm-spindash-2134'
'591','?','dm-spindash-dmgcancel-2111'
'592','?','dark-dm-534'
'593','?','ether-dashoff-2305'
'594','?','ether-quickhop-2243'
'595','?','octoballoon-610'
'596','?','icerod-entrance-931'
'597','?','icerod1-334'
'598','?','icerodchest-nomenu-659'
'599','?','icerodchest-rta-menu-815'
'600','?','icerod2-menu-350'
'601','?','icerod2-rta-nomenu-233'
'602','?','icerod-leaving-547'
'603','?','icerod-leaving-rta-556'
'604','?','mirewarp-menu-701'
'605','?','mire-entrance-nomenu-2501'
'606','?','mire01-821'
'607','?','mire02-1031'
'608','?','mainhub1-toproute-947'
'609','?','poporoom1-337'
'610','?','spikekey1-351'
'611','?','poporoom2-337'
'612','?','beatthefireball-938'
'613','?','jellykey-449'
'614','?','tileroom-321'
'615','?','bombslugs-508'
'616','?','torches1-752'
'617','?','torches2-rta-2127'
'618','?','torches2-toplighting-2121'
'619','?','bighole-322'
'620','?','bigkey-621'
'621','?','warptile-230'
'622','?','wizzroom-412'
'623','?','bigspike-318'
'624','?','lonelystalfos-337'
'625','?','sparkgamble-bestboost-1106'
'626','?','sparkgamble-damageless-1144'
'627','?','sparkgamble-rta-menu-1236'
'628','?','sparkgamble-snekblock-1205'
'629','?','maproom-south-327'
'630','?','maproom-south-rta-327'
'631','?','bigchest-menu-1012'
'632','?','bigchest-nomenu-852'
'633','?','maproom-north-328'
'634','?','spikeykey2-rta-walk-702'
'635','?','spikeykey2-rta-walk-magicgrab-753'
'636','?','spikeykey2-rta-walk-magicgrab-with-hook-752'
'637','?','jadinledge-hookdash-333'
'638','?','jadinledge-quickhop-328'
'639','?','jadinledge-rta-2dash-332'
'640','?','wizzpot-rta-hook-610'
'641','?','wizzpot-topdash-558'
'642','?','bridge-504'
'643','?','caneblockswitch-old-928'
'644','?','caneblockswitch-spooky-934'
'645','?','bigblock-429'
'646','?','caneswitch-spooky-1132'
'647','?','caneswitch-spooky-roddash-bonk-1108'
'648','?','bombwall-spooky-dashout-513'
'649','?','bombwall-spooky-dashout-risky-var-443'
'650','?','bombwall-spooky-walk-534'
'651','?','badfarmroom-spooky-dash-above-612'
'652','?','badfarmroom-spooky-dash-below-608'
'653','?','badfarmroom-spooky-riskier-walk-619'
'654','?','badfarmroom-spooky-rta-walk-624'
'655','?','firesnek-skip-menu-956'
'656','?','firesnek-skip-rta-nomenu-909'
'657','?','firesnek-skip-rta-nomenu-915'
'658','?','vitty-nomenu-1732'
'659','?','vitty-rta-menu-1846'
'660','?','?'
'661','?','outside-mire-335'
'662','?','outside-desert-818'
'663','?','wafflehouse-drivethru-1241'
'664','?','wafflehouse-drivethru-nostairclimb-1243'
'665','?','broken-bridge-1034'
'666','?','broken-bridge-fromfacingup-1037'
'667','?','paradox-lower-454'
'668','?','paradox-upper-740'
'669','?','run-killing-deadrocks-525'
'670','?','hammerpegs-1251'
'671','?','outside-trock-2057'
'672','?','outside-trock-rta-1957'
'673','?','dungeon-entrance-menu-933'
'674','?','dungeon-entrance-no-menu-818'
'675','?','largepit1-928'
'676','?','torches-1428'
'677','?','torches-rta-1432'
'678','?','rollerroom-1038'
'679','?','rollerroom-topdmg-1032'
'680','?','torches-backtrack-346'
'681','?','largepit2-1025'
'682','?','pokey0-526'
'683','?','pokey0-rta-551'
'684','?','chomps-2blocks-805'
'685','?','chomps-bbb-758'
'686','?','chomps-blocknbeam-748'
'687','?','tunnels-arrows-2040'
'688','?','tunnels-no-arrows-1948'
'689','?','lava-room1-739'
'690','?','pokey1-bl-keydash-849'
'691','?','bigkey-fromdash-1245'
'692','?','bigkey-fromwalk-1241'
'693','?','pokey1-backtrack-beamless-644'
'694','?','pokey1-backtrack-beams-632'
'695','?','pokey1-backtrack-canedash-624'
'696','?','lava-room3-1247'
'697','?','double-pokeys-429'
'698','?','miniroller-2dash-339'
'699','?','lavaroom4-619'
'700','?','walldash-414'
'701','?','crystalroller-beams-812'
'702','?','darkroom-2428'
'703','?','helmadash1-623'
'704','?','laserskip-block-921'
'705','?','laserskip-dash-823'
'706','?','helmadash2-414'
'707','?','helmadash2-rta-417'
'708','?','canedash-725[30lf]'
'709','?','canedash-725[31lf]'
'710','?','canedash-magicless-814'
'711','?','canedash-rta-walk-823'
'712','?','restock-graball-1238'
'713','?','restock-magicgrab-1232'
'714','?','restock-skipall-1145'
'715','?','trinexx-rta-3759'
'716','?','outside-trock-454'
'717','?','lynel-bridge-909'
'718','?','entrance-2239'
'719','?','entrance-nostairclimb-2251'
'720','?','foyer-624'
'721','?','torchkey-bonkandwalk-759'
'722','?','torchkey-hook-758'
'723','?','torchkey-nokey-606'
'724','?','conveyor-potkey-905'
'725','?','spikeskip-919'
'726','?','spikeskip-noskip-937'
'727','?','double-crystal-switch-719'
'728','?','double-crystal-switch-rta-739'
'729','?','double-crystal-switch-rta-740'
'730','?','spike-and-pegs-356'
'731','?','firesnake-room-1029'
'732','?','firesnake-room-bonk-1117'
'733','?','firesnake-room-framerule-1011'
'734','?','warp-tile1-157'
'735','?','warp-tile2-344'
'736','?','warp-tile2-justholddown-350'
'737','?','warp-tile3-430'
'738','?','warp-tileskip-816'
'739','?','warp-tileskip-with-menu-931'
'740','?','false-floor-no-menu-1017'
'741','?','false-floor-with-menu-1122'
'742','?','bombable-floor-717'
'743','?','icearmos-1529'
'744','?','icearmos-321-1548'
'745','?','icearmos-321-dboost-1546'
'746','?','bk-arrows-738'
'747','?','bk-no-arrows-606'
'748','?','foyer2-745'
'749','?','mj-room-1045-arrow-dash'
'750','?','mimics1-joestrat-647'
'751','?','mimics1-myramics-653'
'752','?','mimics2-blunt-553'
'753','?','mimics2-opt-519'
'754','?','east-spike-room-302'
'755','?','spiketrap-1341'
'756','?','spiketrap-abovepot-1406'
'757','?','gbz-513'
'758','?','gbz-walk-637'
'759','?','g1-838'
'760','?','g2-441'
'761','?','g3-644'
'762','?','g4-446'
'763','?','g5-459'
'764','?','bunnybeamhall-305'
'765','?','lanmo2-1141'
'766','?','restock-248'
'767','?','wizz1-849'
'768','?','guardbridge-529'
'769','?','wizz2-429'
'770','?','foosdabridge-653'
'771','?','foosdabridge-rightdash-703'
'772','?','torches1-1251-magic-noroddash'
'773','?','eyelasers-529'
'774','?','torches2-637'
'775','?','torches2-safewalk-647'
'776','?','helmakey-500-dmg'
'777','?','helmakey-507-dmgless'
'778','?','bombwall-521'
'779','?','bombwall-damageless-530'
'780','?','bombwall-rta-543'
'781','?','pegschestkey-843'
'782','?','pegschestkey-magicskip-454'
'783','?','pegschestkey-safe-634'
'784','?','mold2-hover-1232'
'785','?','mold2-hover-1301'
'786','?','mold2-hover-1337'
'787','?','mold2-kill-hook-1949'
'788','?','mold2-kill-hook-1958'
'789','?','helma-hallway-607'
'790','?','helma-hallway-safe-620'
'791','?','torch-hallway-635'
'792','?','agah2-4927'
'793','?','?'
'794','?','?'
'795','?','?'
'796','?','?'
'797','?','?'
'798','?','ganon-139.55-nfc'
'799','?','ganon-139.55'
'800','?','?'
'801','?','?'
'802','?','?'
'803','?','?'
'804','?','?'
'805','?','?'
'806','?','?'
'807','?','?'
'808','?','?'
'809','?','?'
'810','?','?'
'811','?','?'
'812','?','?'
'813','?','?'
'814','?','?'
'815','?','?'
'816','?','?'

47
seed.example.json Normal file
View File

@@ -0,0 +1,47 @@
{
"botName": "greenhambot",
"discord": {
"clientId": "YOUR DISCORD APP ID",
"token": "YOUR DISCORD APP TOKEN",
"adminUserId": "YOUR DISCORD USER ID",
"guilds": [
{
"internalName": "GUILD NAME",
"id": "GUILD ID",
"prefix": "!",
"enableSfx": true,
"sfxVolume": 0.5,
"passes": 2,
"allowedRolesForRequest": ["DISCORD ROLE ID"],
"enableFunFacts": true,
"enableHamFacts": true,
"scheduledEvents": [
{
"id": "rise-up-and-kick-a-little-ass",
"schedule": {
"hour": 7,
"minute": 30,
"tz": "America/Los_Angeles"
},
"channelId": "DISCORD CHANNEL ID",
"pingRoleId": "DISCORD ROLE ID",
"message": "I'm gonna rise up, I'm gonna kick a little ass, Gonna kick some ass in the USA, Gonna climb a mountain, Gonna sew a flag, Gonna fly on an Eagle, I'm gonna kick some butt, I'm gonna drive a big truck, I'm gonna rule this world, Gonna kick some ass, Gonna rise up, Kick a little ass"
}
]
},
{
"internalName": "SECOND GUILD NAME",
"id": "SECOND GUILD ID",
"prefix": "!",
"enableSfx": true,
"sfxVolume": 0.5,
"passes": 2,
"enableFunFacts": true,
"enableHamFacts": true
}
],
"activities": ["Chardee MacDennis", "The Nightman Cometh", "Charlie Work"],
"blacklistedUsers": ["IGNORE COMMANDS FROM THESE DISCORD USER IDS"]
},
"debug": false
}

Binary file not shown.

BIN
sfx/2+2.mp3 Normal file → Executable file

Binary file not shown.

BIN
sfx/20gp.mp3 Executable file

Binary file not shown.

17
sfx/README.md Normal file
View File

@@ -0,0 +1,17 @@
**🔉 GENERAL**
```
2+2, ahhh, alert, aspen, auw, aww, bonk, bustin, cd, chafe, chipotle, chomp, choochoo, cooler,correct, date, dong, duck, enjoy, ez, fakehands, fbrage, fine, flippers, funnyhow, gatekeepah, gcn, groovy, gyst, help, herewego, heyheyhey, heymf, highscore, hop, how, hype, idgaf, imawot, interesting, jacked, knob, lab, laugh, lisa, long, mad, massage, mayo, meme, mmmm, mouthfeel, mybody, neat, nevergiveup, obaeb, ohno, okusa, onejoint, onfire, ow, poopy, popup, porkchop, pour, ppump, qty, raffle, rawr, rentfree, respect, robotears, rpgfarm, sdgtw, sendit, slowmo-in, slowmo-out, sofast, sogood, stick, store, suh, swag, tasty, tea, thatthing, theline, tmm, tojesus, tootski, trash, triple, urf, wahwah, wanker, waow, wdied, wow, yahoo, yippee, yoshi, youguys
```
**🤓 NERDS**
```
1v2, airplane, anders, anteater, archery, blazeit, blblbl, booty, bossmusic, butt, bwaa, bye, byeb, craft, dbio, deebsfart, deebslaugh, diaper, disagree, dominos, doomtaonline, duckscream, eggs, emetarage, emetarage2, emetarage3, emmapos, english, f, fdup, fencedash, fk, fu, fuckduck, fuzzy, goofy, h2o, hamhelp, hamlaugh, hamlaugh2, hamscream, hellway, jebaited, joshbitch, joshgoat, joshlaugh, joshlaugh2, joshlaugh3, joshlaugh4, joshscream, joshwhinny, joshx, lanmo, lanx, lanx2, lol, mcgasm, milk, mothhole, muttfart, muttfart2, muttlaugh, muttscream, muttwalksin, myman, ncd, nfc, oh, pprage, ppscream, pyle, rando, rip, run, runsover, sgqf, shj, smd, spam, speednoises, speedrunner, split, stank, stfu, tbhamfact, teats, timmon, trinexx, vitty, wait, whathaveidone, why, wiki, wobbuffet, xelboss, xelkiss
```
**🎥 TV & MOVIES**
```
20gp, albert, algore, andhot, arise, banana, bananas, bigshit, bill, bjqueen, carlcandy, clever, coffee, crush, cumin, dd, dead, digup, dinodna, dodson, dontmatter, door, drinkfull, esp, fish, goldcaps, gordon, gordon2, gordon3, gordon4, gotalight, gross, gum, hahaa, hammertime, hatethatman, hightonight, hrwah, human, hunt, idc, kudos, lang, learnding, letsrock, mapleham, mash, mrjackpots, myarms, nerd, newshoes, oldman, party, please, present, puzzles, pushpush, rake, rockin, seaparks, seeitnow, shirtballs, stahp, threat, tonight, toofat, tour, towel, trex, unhelpful, veranda, vision, watchyourback
```

BIN
sfx/ahhh.mp3 Normal file → Executable file

Binary file not shown.

Binary file not shown.

BIN
sfx/albert.mp3 Executable file

Binary file not shown.

BIN
sfx/alert.mp3 Executable file

Binary file not shown.

BIN
sfx/algore.mp3 Normal file

Binary file not shown.

BIN
sfx/anders.mp3 Executable file

Binary file not shown.

BIN
sfx/andhot.mp3 Executable file

Binary file not shown.

Binary file not shown.

BIN
sfx/anteater.mp3 Normal file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/aspen.mp3 Normal file → Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/banana.mp3 Normal file

Binary file not shown.

Binary file not shown.

BIN
sfx/bigshit.mp3 Executable file

Binary file not shown.

BIN
sfx/bill.mp3 Normal file

Binary file not shown.

BIN
sfx/bjqueen.mp3 Executable file

Binary file not shown.

BIN
sfx/blazeit.mp3 Normal file → Executable file

Binary file not shown.

BIN
sfx/blblbl.mp3 Executable file

Binary file not shown.

BIN
sfx/bonk.mp3 Executable file

Binary file not shown.

BIN
sfx/booty.mp3 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/butt.mp3 Normal file → Executable file

Binary file not shown.

Binary file not shown.

BIN
sfx/bwaa.mp3 Normal file → Executable file

Binary file not shown.

Binary file not shown.

BIN
sfx/byeb.mp3 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/chafe.mp3 Executable file

Binary file not shown.

BIN
sfx/chipotle.mp3 Normal file → Executable file

Binary file not shown.

BIN
sfx/chomp.mp3 Normal file → Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/clever.mp3 Executable file

Binary file not shown.

BIN
sfx/coffee.mp3 Executable file

Binary file not shown.

Binary file not shown.

BIN
sfx/correct.mp3 Executable file

Binary file not shown.

BIN
sfx/craft.mp3 Normal file

Binary file not shown.

BIN
sfx/crush.mp3 Normal file

Binary file not shown.

BIN
sfx/cumin.mp3 Normal file

Binary file not shown.

BIN
sfx/date.mp3 Normal file

Binary file not shown.

Binary file not shown.

BIN
sfx/dd.mp3 Normal file

Binary file not shown.

BIN
sfx/dead.mp3 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/digup.mp3 Executable file

Binary file not shown.

BIN
sfx/dinodna.mp3 Executable file

Binary file not shown.

Binary file not shown.

Binary file not shown.

BIN
sfx/dodson.mp3 Executable file

Binary file not shown.

BIN
sfx/dominos.mp3 Normal file

Binary file not shown.

BIN
sfx/dong.mp3 Executable file

Binary file not shown.

Binary file not shown.

BIN
sfx/doomtaonline.mp3 Normal file

Binary file not shown.

BIN
sfx/door.mp3 Executable file

Binary file not shown.

Some files were not shown because too many files have changed in this diff Show More