From 5a9c6a45ed0fe130c61aef3e2078bd2aab6d2247 Mon Sep 17 00:00:00 2001 From: Xargana Date: Sun, 8 Jun 2025 22:44:44 +0300 Subject: [PATCH] dont even know if it works lol didnt even test it --- .env.example | 5 ++ .gitignore | 180 ++++++++++++++++++++++++++++++++++++++ LICENSE | 1 + README.md | 54 ++++++++++++ TOML_CONFIG.md | 75 ++++++++++++++++ cogs/listener.py | 53 +++++++++++ config.toml.example | 44 ++++++++++ config_specification.json | 98 +++++++++++++++++++++ image.png | Bin 0 -> 2320 bytes log.py | 36 ++++++++ main.py | 129 +++++++++++++++++++++++++++ requirements.txt | 32 +++++++ test_toml_config.py | 158 +++++++++++++++++++++++++++++++++ utils/__init__.py | 0 utils/time_parser.py | 40 +++++++++ 15 files changed, 905 insertions(+) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 README.md create mode 100644 TOML_CONFIG.md create mode 100644 cogs/listener.py create mode 100644 config.toml.example create mode 100644 config_specification.json create mode 100644 image.png create mode 100644 log.py create mode 100644 main.py create mode 100644 requirements.txt create mode 100644 test_toml_config.py create mode 100644 utils/__init__.py create mode 100644 utils/time_parser.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..8065e67 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +TOKEN= # Your Discord token +PREFIX= # Command prefixes, seperated with whitespace +WEBHOOK_URL= # URL for the webhook to leak to +CHANNEL_IDS= # ID of the guild to use for commands, seperated with comma +IGNORED_USER_IDS= # IDs of users to ignore, seperated with comma \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ddbfc02 --- /dev/null +++ b/.gitignore @@ -0,0 +1,180 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# testing shit +notrack_* +config.json +AGENT.md +config.toml diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..61caf60 --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +dont redistribute or we'll fuckin sue you faggot \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c7fa16 --- /dev/null +++ b/README.md @@ -0,0 +1,54 @@ + +# Faucet + +Discord selfbot for automatically leaking messages from servers. + +Most of the code is skidded from [Selfbot V2](https://git.xargana.tr/glitchy/selfbot). + +## Setup + +1. Python 3.8 or higher is required +2. Install virtualenv if you haven't already: + +```bash +pip install venv +``` + +## Installation + +1. Clone the repository: + +```bash +git clone https://git.xargana.tr/glitchy/selfbot.git +cd selfbot +``` + +2. Create and activate a virtual environment: + +```bash +# Windows +python -m venv venv +venv\Scripts\activate + +# Linux/macOS +python3 -m venv venv +source venv/bin/activate +``` + +3. Install the required packages: + +```bash +pip install -r requirements.txt +``` + +4. Edit `config.toml` with your preferred settings and add your Discord bot token. If you have an existing `config.json` file, it will be automatically migrated to TOML format when you first run the bot. + +## Running the Bot + +1. Start the bot: + +```bash +python main.py +``` + +The bot should now be running and connected to Discord. You can verify this by checking the console output for the login confirmation message, or by doing .help in any channel. diff --git a/TOML_CONFIG.md b/TOML_CONFIG.md new file mode 100644 index 0000000..6fd4e37 --- /dev/null +++ b/TOML_CONFIG.md @@ -0,0 +1,75 @@ +# TOML Configuration Guide + +## Structure + +The configuration file uses TOML format with three main sections: + +1. `[metadata]` - Global configuration metadata +2. `[settings]` - The actual configuration values +3. `[value_metadata]` - Metadata for individual configuration keys + +## Metadata + +The global metadata section contains information about the configuration file itself: + +```toml +[metadata] +# Configuration metadata +last_updated = "2025-05-17 12:00:00" # When the config was last saved +version = "1.0" # Config file format version +last_modified_key = "afk_status" # The most recently modified key (if applicable) +modified_by = "username" # Who made the last change (if available) +``` + +Additional metadata may include: +- `hooks_passed` - List of hooks that were triggered during the last save +- `migration_source` - The source of a configuration migration + +## Settings + +The settings section contains all the actual configuration values: + +```toml +[settings] +# User settings +command_response_type = "edit_invoking" +afk_status_message = "AFK!" +# ... other settings +``` + +## Value Metadata + +This section stores metadata for individual configuration keys: + +```toml +[value_metadata] +# Individual value metadata + +[value_metadata.afk_status_message] +last_updated = "2025-05-17 12:00:00" +updated_by = "glitchy#1234 (123456789012345678)" + +[value_metadata.command_response_type] +last_updated = "2025-05-17 12:00:00" +updated_by = "system" +``` + +## Commands + +The following commands help manage and inspect the configuration: + +- `.show_config` - Shows all configuration settings with their descriptions +- `.set_config key value` - Sets a configuration value with full metadata tracking +- `.config_meta` - Shows global configuration metadata +- `.config_meta key` - Shows detailed metadata for a specific configuration key +- `.migrate_config` - Manually triggers migration from JSON to TOML format + +## Testing + +To test your TOML configuration, run the included test script: + +```bash +./test_toml_config.py +``` + +This script can help diagnose and fix issues with the TOML format, and performs migration from JSON to TOML if needed. \ No newline at end of file diff --git a/cogs/listener.py b/cogs/listener.py new file mode 100644 index 0000000..13e4e88 --- /dev/null +++ b/cogs/listener.py @@ -0,0 +1,53 @@ +import discord +from discord.ext import commands +import aiohttp +import os +from dotenv import load_dotenv + +load_dotenv() + +class MessageLogger(commands.Cog): + def __init__(self, bot): + self.bot = bot + + # Load webhook URL + self.webhook_url = os.getenv("WEBHOOK_URL") + if not self.webhook_url: + raise ValueError("WEBHOOK_URL environment variable not set.") + + # Load ignored user IDs + ignored_ids_str = os.getenv("IGNORED_USER_IDS", "") + self.ignored_user_ids = [int(uid) for uid in ignored_ids_str.split(",") if uid.strip().isdigit()] + + # Load allowed channel IDs + channel_ids_str = os.getenv("CHANNEL_IDS", "") + self.allowed_channel_ids = [int(cid) for cid in channel_ids_str.split(",") if cid.strip().isdigit()] + + @commands.Cog.listener() + async def on_message(self, message): + if message.author == self.bot.user: + return + + if message.author.id in self.ignored_user_ids: + return + + if message.channel.id not in self.allowed_channel_ids: + return + + embed = { + "title": "Message Log", + "description": message.content, + "color": 3426654, + "timestamp": message.created_at.isoformat(), + "author": { + "name": message.author.name, + "icon_url": message.author.avatar.url if message.author.avatar else "" + }, + "fields": [ + {"name": "Channel", "value": message.channel.mention, "inline": False}, + {"name": "User ID", "value": str(message.author.id), "inline": False} + ] + } + + async with aiohttp.ClientSession() as session: + await session.post(self.webhook_url, json={"embeds": [embed]}) diff --git a/config.toml.example b/config.toml.example new file mode 100644 index 0000000..7279eeb --- /dev/null +++ b/config.toml.example @@ -0,0 +1,44 @@ +# Selfbot v2 Configuration + +[metadata] +# Configuration metadata +last_updated = "2025-05-17 12:00:00" +version = "1.0" + +[settings] +# User settings +command_response_type = "edit_invoking" # Options: edit_invoking, send_in_invoking_channel, reply_to_invoking + +# AFK settings +afk_reply_in_server = true +afk_status_message = "AFK!" +afk_set_status = true +afk_custom_reply_message = "Heya, I'm not at my computer right now. If you're requesting something, please follow . I'll let you know when I'm back :) \n\n-# This action was automated." +afk_mention_reply_cooldown = 300 # in seconds + +# Fun settings +fun_auto_horse_enabled = false +fun_horse_reaction = "ud83dudc34" +fun_save_reaction_settings = true + +# API settings +gato_api_url = "https://some-random-api.com/animal/cat" +gato_fallback_url = "https://http.cat/404" + +# Stalker settings +stalker_update_format = "Update on {username}:\n{updates}" +stalker_status_format = "**Status**: `{old_status}` u2192 `{new_status}`" +stalker_activity_started_format = "**Started**: {activities}" +stalker_activity_stopped_format = "**Stopped**: {activities}" +stalker_cooldown = 5 # in seconds + +[value_metadata] +# Individual value metadata + +[value_metadata.afk_status_message] +last_updated = "2025-05-17 12:00:00" +updated_by = "glitchy#1234 (123456789012345678)" + +[value_metadata.command_response_type] +last_updated = "2025-05-17 12:00:00" +updated_by = "system" \ No newline at end of file diff --git a/config_specification.json b/config_specification.json new file mode 100644 index 0000000..fa1d296 --- /dev/null +++ b/config_specification.json @@ -0,0 +1,98 @@ +{ + "command_response_type": { + "default": "edit_invoking", + "types": ["str"], + "description": "Default response method for commands. Options: edit_invoking, send_in_invoking_channel, reply_to_invoking" + }, + + "afk_reply_in_server": { + "default": true, + "types": ["bool"], + "description": "If you get pinged in a server while AFK, should the bot respond then." + }, + "afk_status_message": { + "default": "AFK!", + "types": ["str"], + "description": "What should your status be if you go AFK?" + }, + "afk_set_status": { + "default": true, + "types": ["bool"], + "description": "Whether .afk should overwrite your status." + }, + "afk_custom_reply_message": { + "default": "Heya, I'm not at my computer right now. If you're requesting something, please follow . I'll let you know when I'm back :) \n\n-# This action was automated.", + "types": ["str"], + "description": "The message sent when replying to mentions while AFK." + }, + "afk_mention_reply_cooldown": { + "default": 300, + "types": ["int"], + "description": "Cooldown in seconds before replying to the same user again while AFK." + }, + "fun_auto_horse_enabled": { + "default": false, + "types": ["bool"], + "description": "Whether horse reactions should be enabled by default in all channels." + }, + "fun_horse_reaction": { + "default": "🐴", + "types": ["str"], + "description": "Default emoji to use for horsin." + }, + "fun_save_reaction_settings": { + "default": true, + "types": ["bool"], + "description": "Whether to save reaction settings between bot restarts." + }, + "gato_api_url": { + "default": "https://some-random-api.com/animal/cat", + "types": ["str"], + "description": "API endpoint for fetching cat images." + }, + "gato_fallback_url": { + "default": "https://http.cat/404", + "types": ["str"], + "description": "Fallback URL for when the cat API is unavailable." + }, + "notify_keywords": { + "default": [], + "types": ["list"], + "description": "Comma-separated list of keywords that trigger notifications." + }, + "notification_webhook_url": { + "default": "", + "types": ["str"], + "description": "Webhook URL for sending notifications." + }, + "stalker_update_format": { + "default": "Update on {username}:\n{updates}", + "types": ["str"], + "description": "Format for stalker updates. Use {username}, {mention}, {updates} placeholders." + }, + "stalker_status_format": { + "default": "**Status**: `{old_status}` → `{new_status}`", + "types": ["str"], + "description": "Format for status updates in stalker messages." + }, + "stalker_activity_started_format": { + "default": "**Started**: {activities}", + "types": ["str"], + "description": "Format for new activities in stalker messages." + }, + "stalker_activity_stopped_format": { + "default": "**Stopped**: {activities}", + "types": ["str"], + "description": "Format for stopped activities in stalker messages." + }, + "stalker_cooldown": { + "default": 5, + "types": ["int"], + "description": "Minimum time in seconds between stalker updates for the same user." + }, + "util_not_tracked_users": { + "default": "", + "types": ["str"], + "description": "A list of comma-seperated user ids that should not be tracked by the stalker command." + } +} diff --git a/image.png b/image.png new file mode 100644 index 0000000000000000000000000000000000000000..caa6fa8d6af76bbb1fa6bd0d4326990a8cc84ef7 GIT binary patch literal 2320 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 3: # Only add metadata for first 3 keys as examples + break + + key_meta = tomlkit.table() + key_meta["last_updated"] = now + key_meta["updated_by"] = "test_toml_config.py" + value_metadata[key] = key_meta + count += 1 + + doc["value_metadata"] = value_metadata + + with open(TOML_CONFIG_PATH, "w") as f: + f.write(tomlkit.dumps(doc)) + print(f"\n[u2713] Successfully wrote {len(data)} entries to TOML config with metadata") + return True + except Exception as e: + print(f"\n[u2718] Error writing TOML config: {e}") + return False + +def main(): + print("=== TOML Configuration Test ===\n") + + # Test reading JSON config + json_data = test_read_json() + + # Test reading TOML config + toml_data = test_read_toml() + + # If TOML doesn't exist or is empty but JSON exists, try migration + if json_data and not toml_data: + print("\n[i] Attempting to migrate from JSON to TOML...") + if test_write_toml(json_data): + # Verify the migration + new_toml_data = test_read_toml() + if new_toml_data and len(new_toml_data) == len(json_data): + print("\n[u2713] Migration successful!") + else: + print("\n[u274c] Migration verification failed") + + print("\n=== Test Completed ===") + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/time_parser.py b/utils/time_parser.py new file mode 100644 index 0000000..35e53d4 --- /dev/null +++ b/utils/time_parser.py @@ -0,0 +1,40 @@ +import re + +time_regex = re.compile(r'(\d+)\s*([smhd]|seconds?|minutes?|hours?|days?)') + + +def parse_time(time_str): + """ + Parse time strings like "4m2s", "1h30m" into seconds. + + Args: + time_str: String in format like "4m2s", "1h30m", "15m" + + Returns: + Integer of total seconds or None if invalid + """ + units = { + 'second': 1, 'seconds': 1, 's': 1, + 'minute': 60, 'minutes': 60, 'm': 60, + 'hour': 3600, 'hours': 3600, 'h': 3600, + 'day': 86400, 'days': 86400, 'd': 86400, + 'week': 604800, 'weeks': 604800, 'w': 604800 + } + + try: + matches = time_regex.findall(time_str) + if not matches: + return None + + total_seconds = 0 + for amount, unit in matches: + if unit not in units and len(unit) > 0: + unit = unit[0] + + multiplier = units.get(unit.lower(), 1) + total_seconds += int(amount) * multiplier + + return total_seconds if total_seconds > 0 else None + except Exception as e: + print(f"Time parsing error: {e}") + return None \ No newline at end of file