Metadata-Version: 2.4
Name: kroxy
Version: 1.0.3
Summary: A powerful Python toolkit for building automation, integrations, and bots.
Author-email: kroxy <kroxy@example.com>
License-Expression: LicenseRef-Proprietary
Project-URL: Homepage, https://pypi.org/project/kroxy
Keywords: bot,automation,antinuke,giveaway,music,scraper,webhook,cache,moderation,toolkit
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Operating System :: OS Independent
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Topic :: Internet
Classifier: Development Status :: 5 - Production/Stable
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: aiohttp>=3.8.0
Provides-Extra: dev
Requires-Dist: build; extra == "dev"
Requires-Dist: twine; extra == "dev"
Dynamic: license-file

# kroxy

> A powerful Python toolkit for building automation, integrations, and bots.

[![PyPI version](https://img.shields.io/pypi/v/kroxy)](https://pypi.org/project/kroxy/)
[![Python](https://img.shields.io/pypi/pyversions/kroxy)](https://pypi.org/project/kroxy/)
[![License](https://img.shields.io/badge/license-Proprietary-red)](https://pypi.org/project/kroxy/)

---

## Installation

```bash
pip install kroxy
pip install --upgrade kroxy
```

---

## Import Style

Everything in `kroxy` is importable directly from the top level.

```python
# Everything from one place
import kroxy

# Discord
antinuke = kroxy.AntiNuke(whitelist=[OWNER_ID])
manager  = kroxy.GiveawayManager()
api      = kroxy.DiscordAPI(token="Bot TOKEN")
embed    = kroxy.Utils.build_embed(title="Hello", color=0x5865F2)

# Website
async with kroxy.Fetcher() as f:
    html = await f.get("https://example.com")

title = kroxy.Scraper.get_title(html)
slug  = kroxy.WebUtils.slugify("Hello World")
```

```python
# Or import only what you need
from kroxy import AntiNuke, Checkers, Utils
from kroxy import GiveawayManager, MusicPlayerManager
from kroxy import DiscordAPI, SlashCommand, PrefixCommand
from kroxy import Fetcher, Scraper, WebUtils
```

```python
# Submodule access also works
from kroxy import discord, website

discord.AntiNuke(...)
website.Fetcher()
```

---

## Package Structure

```
kroxy/
├── discord/
│   ├── api.py        ← Async Discord REST API client (v10)
│   ├── commands.py   ← Slash and prefix command builders + registry
│   ├── utils.py      ← Embeds, mentions, timestamps, permissions, text formatting
│   ├── antinuke.py   ← Real-time nuke detection with auto-punishment
│   ├── checkers.py   ← Permission, role, and hierarchy validation
│   ├── giveaway.py   ← Weighted giveaway engine with scheduling and reroll
│   └── music.py      ← Multi-guild music queue and player state manager
└── website/
    ├── fetch.py      ← Async HTTP GET/POST/download client
    ├── scraper.py    ← HTML parsing: links, images, meta, tables, text
    └── utils.py      ← URL validation, parsing, slugify, file type detection
```

---

## Module Reference

---

### `DiscordAPI` — Discord REST API Client

Async HTTP wrapper for the Discord REST API v10.

**Constructor:** `DiscordAPI(token: str, bot: bool = True)`

```python
from kroxy import DiscordAPI

api = DiscordAPI(token="Bot YOUR_TOKEN")

# Guild & Channel
guild   = await api.get_guild(GUILD_ID)
channel = await api.get_channel(CHANNEL_ID)
user    = await api.get_user(USER_ID)

# Messaging
await api.send_message(CHANNEL_ID, content="Hello!")
await api.send_message(CHANNEL_ID, embed=embed_dict)
await api.send_message(CHANNEL_ID, content="Hi", components=[...])
await api.delete_message(CHANNEL_ID, MESSAGE_ID)

# Moderation
await api.ban_member(GUILD_ID, USER_ID, reason="Spam", delete_message_days=1)
await api.unban_member(GUILD_ID, USER_ID)
await api.kick_member(GUILD_ID, USER_ID, reason="AFK")

# Roles
await api.add_role(GUILD_ID, USER_ID, ROLE_ID)
await api.remove_role(GUILD_ID, USER_ID, ROLE_ID)

# Channels
await api.create_channel(GUILD_ID, name="general", channel_type=0)
await api.delete_channel(CHANNEL_ID)

# Audit & Invites & Webhooks
await api.get_audit_logs(GUILD_ID, limit=50)
await api.get_invites(GUILD_ID)
await api.delete_invite("invite_code")
await api.get_webhooks(GUILD_ID)
await api.delete_webhook(WEBHOOK_ID)

await api.close()
```

---

### `AntiNuke` — Anti-Nuke System

Detects mass destructive actions per user and triggers automatic punishment.

**Constructor:** `AntiNuke(whitelist: list = [], limits: dict = {})`

**Default Limits:**

| Action | Threshold | Window |
|---|---|---|
| `ban` | 3 | 10s |
| `kick` | 3 | 10s |
| `channel_delete` | 3 | 10s |
| `channel_create` | 5 | 10s |
| `role_delete` | 3 | 10s |
| `role_create` | 5 | 10s |
| `webhook_create` | 3 | 10s |
| `webhook_delete` | 3 | 10s |
| `bot_add` | 2 | 30s |
| `mass_mention` | 5 | 5s |

```python
from kroxy import AntiNuke

antinuke = AntiNuke(
    whitelist=[OWNER_ID],
    limits={"ban": (2, 5)}     # override: 2 bans in 5s triggers
)

async def on_nuke(action, user_id, guild):
    print(f"[NUKE] {action} by {user_id}")

antinuke.on_trigger = on_nuke
antinuke.punishment = "ban"    # "ban" | "kick" | "strip_roles"

# Wire to your event system:
await antinuke.on_member_ban(user_id=uid, guild=guild)
await antinuke.on_member_kick(user_id=uid, guild=guild)
await antinuke.on_channel_delete(user_id=uid, guild=guild)
await antinuke.on_channel_create(user_id=uid, guild=guild)
await antinuke.on_role_delete(user_id=uid, guild=guild)
await antinuke.on_role_create(user_id=uid, guild=guild)
await antinuke.on_webhook_create(user_id=uid, guild=guild)
await antinuke.on_webhook_delete(user_id=uid, guild=guild)
await antinuke.on_bot_add(user_id=uid, guild=guild)
await antinuke.on_mass_mention(user_id=uid, guild=guild)

# Whitelist management
antinuke.add_whitelist(MOD_ID)
antinuke.remove_whitelist(MOD_ID)
antinuke.reset_user(USER_ID)
antinuke.get_stats()           # current thresholds dict
```

---

### `Checkers` — Permission & Role Validation

All methods raise `CheckFailed` on failure, return `True` on success.

```python
from kroxy import Checkers, CheckFailed

try:
    Checkers.require_admin(member.permissions)
    Checkers.require_manage_guild(member.permissions)
    Checkers.require_manage_roles(member.permissions)
    Checkers.require_ban_members(member.permissions)
    Checkers.require_kick_members(member.permissions)
    Checkers.require_manage_channels(member.permissions)
    Checkers.require_manage_messages(member.permissions)
    Checkers.require_manage_webhooks(member.permissions)

    Checkers.require_role(member.role_ids, MOD_ROLE_ID, "Moderator")
    Checkers.require_any_role(member.role_ids, [MOD_ROLE_ID, ADMIN_ROLE_ID])
    Checkers.has_all_roles(member.role_ids, [ROLE_A, ROLE_B])

    Checkers.check_hierarchy(
        executor_top_role_pos=member.top_role.position,
        target_top_role_pos=target.top_role.position,
        bot_top_role_pos=bot.top_role.position,
    )

    Checkers.require_guild_owner(user_id, guild.owner_id)
    Checkers.require_guild_only(ctx.guild_id)
    Checkers.require_dm_only(ctx.guild_id)
    Checkers.block_bots(ctx.author.bot)
    Checkers.require_nsfw(channel.nsfw)

except CheckFailed as e:
    await ctx.send(f"❌ {e.message}")
```

---

### `Utils` — Discord Utilities

Embed builder, mentions, timestamps, permissions, and text formatters.

```python
from kroxy import Utils

# Embed builder
embed = Utils.build_embed(
    title="Report",
    description="Weekly summary",
    color=0x5865F2,
    fields=[
        {"name": "Members", "value": "1,200", "inline": True},
        {"name": "Messages", "value": "45,000", "inline": True},
    ],
    footer="kroxy",
    thumbnail="https://example.com/icon.png",
    image="https://example.com/banner.png",
    author_name="Bot",
    author_icon="https://example.com/bot.png",
    timestamp=True,
)

# Mentions
Utils.mention_user(123456)           # <@123456>
Utils.mention_role(654321)           # <@&654321>
Utils.mention_channel(999999)        # <#999999>
Utils.parse_mention("<@123456>")     # 123456

# Timestamps
Utils.discord_timestamp(dt, style="R")   # <t:...:R> relative
Utils.discord_timestamp(dt, style="F")   # <t:...:F> full
Utils.time_until(3725)                   # "1 hour, 2 minutes, 5 seconds"
Utils.snowflake_to_timestamp(id)         # datetime object

# Permissions
Utils.has_permission(bitfield, "administrator")   # True/False
Utils.has_permission(bitfield, "ban_members")
Utils.permissions_list(bitfield)                  # list of names

# Text formatting
Utils.truncate(text, max_length=2048)
Utils.code_block("print('hi')", language="python")
Utils.inline_code("var")
Utils.bold("text")            # **text**
Utils.italic("text")          # *text*
Utils.underline("text")       # __text__
Utils.strikethrough("text")   # ~~text~~
Utils.spoiler("text")         # ||text||
```

---

### `GiveawayManager` — Giveaway Engine

Weighted giveaway system with bonus roles, auto-scheduling, and reroll.

```python
from kroxy import GiveawayManager

manager = GiveawayManager()

async def on_end(giveaway, winners):
    print(f"Winners of '{giveaway.prize}': {winners}")

manager.on_end = on_end

giveaway = await manager.create(
    prize="Discord Nitro",
    host_id=HOST_ID,
    channel_id=CHANNEL_ID,
    guild_id=GUILD_ID,
    duration=86400,           # seconds
    winner_count=3,
    required_role_id=MEMBER_ROLE_ID,
    bonus_roles={
        BOOSTER_ROLE_ID: 2,   # +2 extra entries
        VIP_ROLE_ID: 4,       # +4 extra entries
    },
)

giveaway.add_entry(user_id=555, role_ids=[BOOSTER_ROLE_ID])
giveaway.remove_entry(user_id=555)

giveaway.is_active          # True/False
giveaway.time_remaining     # seconds
giveaway.total_entries      # weighted count
giveaway.participant_count  # unique users
giveaway.giveaway_id        # "A1B2C3D4"
giveaway.winners            # list of user IDs after end

await manager.end_now(giveaway.giveaway_id)
await manager.cancel(giveaway.giveaway_id)
new_winners = giveaway.reroll()

manager.get(giveaway_id)
manager.get_by_message(message_id)
manager.all_active()
manager.all_ended()
```

---

### `MusicPlayerManager` — Music Player

Multi-guild music queue and player state manager.

```python
from kroxy import MusicPlayerManager, Track, LoopMode

manager = MusicPlayerManager()
player  = manager.get_or_create(
    guild_id=GUILD_ID,
    channel_id=VOICE_CHANNEL_ID,
    text_channel_id=TEXT_CHANNEL_ID,
)

track = Track(
    title="Song Name",
    url="https://youtube.com/watch?v=...",
    stream_url="https://audio.stream/file.mp3",
    duration=240,
    requester_id=USER_ID,
    thumbnail="https://img.youtube.com/vi/.../0.jpg",
    source="youtube",
)

# Queue
player.queue.add(track)
player.queue.add_next(track)
player.queue.shuffle()
player.queue.remove(index=0)
player.queue.move(from_index=2, to_index=0)
player.queue.clear()
len(player.queue)
player.queue.total_duration
player.queue.is_empty

# Playback
await player.play_next()
await player.skip()
player.toggle_pause()          # returns new paused state
player.set_volume(0.75)        # 0.0 – 2.0
player.set_loop("track")       # "none" | "track" | "queue"
player.stop()

# State
player.current                 # Track or None
player.paused                  # bool
player.volume                  # float
player.position                # seconds elapsed
player.loop_mode               # LoopMode enum
player.get_state()             # full state dict

# Events
player.on_track_start = async_fn   # called with (track, player)
player.on_track_end   = async_fn   # called with (track, player)
player.on_queue_empty = async_fn   # called with (guild_id)

manager.remove(guild_id=GUILD_ID)
```

---

### `SlashCommand` / `PrefixCommand` — Command Builders

```python
from kroxy import SlashCommand, PrefixCommand, Option, CommandRegistry

@SlashCommand.decorator(
    name="warn",
    description="Warn a member",
    options=[
        Option("user", "Target member", option_type="user", required=True),
        Option("reason", "Reason", required=False),
    ],
    permissions="8",
)
async def warn(interaction, user, reason="No reason"):
    ...

@PrefixCommand.decorator(
    name="ban",
    aliases=["b", "yeet"],
    cooldown=5,
    permissions=["ban_members"],
)
async def ban(ctx, member, *, reason="No reason"):
    ...

warn.to_dict()   # export for Discord API registration

registry = CommandRegistry()
registry.add_slash(warn)
registry.add_prefix(ban)
registry.get_slash("warn")
registry.get_prefix("b")     # alias lookup works
registry.all_slash()
registry.all_prefix()
```

---

### `Fetcher` — Async HTTP Client

```python
from kroxy import Fetcher

# As context manager (auto-closes session)
async with Fetcher() as f:
    html     = await f.get("https://example.com")
    data     = await f.get_json("https://api.example.com/data")
    response = await f.post("https://example.com/form", data={"key": "value"})
    result   = await f.post_json("https://api.example.com", json={"q": "hello"})
    headers  = await f.head("https://example.com")
    code     = await f.status("https://example.com")       # 200
    ok       = await f.is_reachable("https://example.com") # True/False
    path     = await f.download("https://example.com/file.zip", save_path="./file.zip")
    raw      = await f.get_bytes("https://example.com/image.png")
    final    = await f.resolve_redirect("https://bit.ly/abc")

# With custom headers and timeout
async with Fetcher(headers={"Authorization": "Bearer TOKEN"}, timeout=30) as f:
    data = await f.get_json("https://api.example.com/protected")
```

---

### `Scraper` — HTML Parser

All methods are static — pass raw HTML strings, get structured data back.

```python
from kroxy import Scraper, Fetcher

async with Fetcher() as f:
    html = await f.get("https://example.com")

Scraper.get_title(html)                      # "Example Domain"
Scraper.get_description(html)               # meta description
Scraper.get_meta(html)                       # {"og:title": ..., "description": ...}
Scraper.get_og(html)                         # {"og:title": ..., "og:image": ...}

Scraper.get_links(html, base_url="https://example.com")
# [{"href": "https://example.com/page", "text": "Click here"}, ...]

Scraper.get_links(html, base_url="https://example.com", internal_only=True)
Scraper.get_links(html, base_url="https://example.com", external_only=True)

Scraper.get_images(html, base_url="https://example.com")
# [{"src": "...", "alt": "...", "width": "...", "height": "..."}, ...]

Scraper.get_tables(html)
# [[[row1col1, row1col2], [row2col1, row2col2]], ...]

Scraper.get_text(html)                       # plain text, no tags
Scraper.find_by_tag(html, "h1")             # ["Heading 1", "Heading 2"]
Scraper.find_by_id(html, "main")            # inner HTML of id="main"
Scraper.find_by_class(html, "card")         # list of inner HTML strings
Scraper.extract_emails(html)                # ["hello@example.com", ...]
Scraper.extract_phone_numbers(html)         # ["+1 800 555 0000", ...]
Scraper.word_count(html)                    # 342
```

---

### `WebUtils` — URL Utilities

All methods are static.

```python
from kroxy import WebUtils

# Validation
WebUtils.is_valid_url("https://example.com")    # True
WebUtils.is_https("https://example.com")        # True
WebUtils.is_http("http://example.com")          # True

# Parsing
WebUtils.get_domain("https://www.example.com/path")      # "www.example.com"
WebUtils.get_base_domain("https://www.example.com/path") # "example.com"
WebUtils.get_scheme("https://example.com")               # "https"
WebUtils.get_path("https://example.com/foo/bar")         # "/foo/bar"
WebUtils.get_query_params("https://example.com?q=hi&page=2")
# {"q": ["hi"], "page": ["2"]}
WebUtils.get_tld("https://www.example.co.uk")            # "uk"

# Transformation
WebUtils.add_query_params("https://example.com", {"q": "hello", "page": "2"})
WebUtils.remove_query_params("https://example.com?q=hi")  # "https://example.com"
WebUtils.join("https://example.com/foo", "../bar")         # "https://example.com/bar"
WebUtils.to_https("http://example.com")                    # "https://example.com"
WebUtils.normalize("www.example.com/path/")                # "https://example.com/path"
WebUtils.encode("hello world!")                            # "hello%20world%21"

# Slug
WebUtils.slugify("Hello World! 123")   # "hello-world-123"

# File type detection
WebUtils.get_file_extension("https://example.com/image.png?v=1")  # "png"
WebUtils.is_image_url("https://example.com/photo.jpg")            # True
WebUtils.is_video_url("https://example.com/video.mp4")            # True
WebUtils.is_audio_url("https://example.com/track.mp3")            # True

# Misc
WebUtils.extract_urls("Visit https://example.com or https://test.com now")
# ["https://example.com", "https://test.com"]
WebUtils.is_same_domain("https://example.com/a", "https://example.com/b")  # True
```

---

## Combined Example

```python
import kroxy
import asyncio

async def main():
    # Fetch a page
    async with kroxy.Fetcher() as f:
        html = await f.get("https://example.com")
        api_data = await f.get_json("https://api.example.com/stats")

    # Scrape it
    title  = kroxy.Scraper.get_title(html)
    links  = kroxy.Scraper.get_links(html, base_url="https://example.com")
    emails = kroxy.Scraper.extract_emails(html)

    # Validate a URL
    if kroxy.WebUtils.is_valid_url("https://example.com"):
        slug = kroxy.WebUtils.slugify(title)

    # Send results to Discord
    api = kroxy.DiscordAPI(token="Bot TOKEN")
    embed = kroxy.Utils.build_embed(
        title=title,
        description=f"Found {len(links)} links, {len(emails)} emails",
        color=0x5865F2,
        timestamp=True,
    )
    await api.send_message(CHANNEL_ID, embed=embed)
    await api.close()

asyncio.run(main())
```

---

## Requirements

- Python **3.9+**
- `aiohttp >= 3.8.0`

---

## Versioning

See [VERSIONS.md](https://pypi.org/project/kroxy/) for the full changelog.

| Version | What changed |
|---|---|
| 1.0.2 | `kroxy.website` — Fetcher, Scraper, WebUtils |
| 1.0.1 | All classes importable from `kroxy` top level |
| 1.0.0 | Proprietary license, professional metadata |
| 0.1.0 | Initial release — discord module |

---

## License

**Proprietary** — Copyright © 2026 kroxy. All rights reserved.

Unauthorized copying, redistribution, or modification is strictly prohibited.
Contact **@kroxy** for permissions.
