Add 7 new pure-stdlib plugins: whois (raw TCP port 43), portcheck (async TCP connect scan with internal-net guard), httpcheck (HTTP status/redirects/timing), tlscheck (TLS version/cipher/cert inspect), blacklist (parallel DNSBL check against 10 RBLs), rand (password/hex/ uuid/bytes/int/coin/dice), and timer (async countdown notifications). Add --cprofile flag to CLI for profiling bot runtime. Update all docs. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
118 lines
3.9 KiB
Python
118 lines
3.9 KiB
Python
"""Plugin: cryptographic random generators (pure stdlib)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import secrets
|
|
import string
|
|
import uuid
|
|
|
|
from derp.plugin import command
|
|
|
|
_MAX_LENGTH = 128
|
|
_DEFAULT_LENGTH = 16
|
|
_DEFAULT_HEX_LENGTH = 32
|
|
|
|
_CHARSETS = {
|
|
"alnum": string.ascii_letters + string.digits,
|
|
"alpha": string.ascii_letters,
|
|
"digits": string.digits,
|
|
"hex": string.hexdigits[:16],
|
|
"upper": string.ascii_uppercase + string.digits,
|
|
"lower": string.ascii_lowercase + string.digits,
|
|
"safe": string.ascii_letters + string.digits + "!@#$%^&*-_=+",
|
|
"all": string.ascii_letters + string.digits + string.punctuation,
|
|
}
|
|
|
|
|
|
@command("rand", help="Random gen: !rand <password|hex|uuid|bytes|int> [len]")
|
|
async def cmd_rand(bot, message):
|
|
"""Generate cryptographically random values.
|
|
|
|
Usage:
|
|
!rand password [len] [charset] -- random password (default 16)
|
|
!rand hex [len] -- random hex string (default 32)
|
|
!rand uuid -- random UUID4
|
|
!rand bytes [len] -- random bytes as hex (default 16)
|
|
!rand int [max] -- random integer 0..max (default 1000000)
|
|
!rand coin -- coin flip
|
|
!rand dice [NdM] -- dice roll (default 1d6)
|
|
"""
|
|
parts = message.text.split(None, 4)
|
|
if len(parts) < 2:
|
|
await bot.reply(message, "Usage: !rand <password|hex|uuid|bytes|int|coin|dice> [args]")
|
|
return
|
|
|
|
mode = parts[1].lower()
|
|
|
|
if mode == "password":
|
|
length = _DEFAULT_LENGTH
|
|
charset_name = "safe"
|
|
if len(parts) > 2:
|
|
try:
|
|
length = int(parts[2])
|
|
except ValueError:
|
|
charset_name = parts[2].lower()
|
|
if len(parts) > 3:
|
|
charset_name = parts[3].lower()
|
|
length = max(1, min(length, _MAX_LENGTH))
|
|
charset = _CHARSETS.get(charset_name, _CHARSETS["safe"])
|
|
pw = "".join(secrets.choice(charset) for _ in range(length))
|
|
await bot.reply(message, pw)
|
|
|
|
elif mode == "hex":
|
|
length = _DEFAULT_HEX_LENGTH
|
|
if len(parts) > 2:
|
|
try:
|
|
length = int(parts[2])
|
|
except ValueError:
|
|
pass
|
|
length = max(1, min(length, _MAX_LENGTH))
|
|
await bot.reply(message, secrets.token_hex(length // 2 + length % 2)[:length])
|
|
|
|
elif mode == "uuid":
|
|
await bot.reply(message, str(uuid.uuid4()))
|
|
|
|
elif mode == "bytes":
|
|
length = 16
|
|
if len(parts) > 2:
|
|
try:
|
|
length = int(parts[2])
|
|
except ValueError:
|
|
pass
|
|
length = max(1, min(length, _MAX_LENGTH // 2))
|
|
await bot.reply(message, secrets.token_bytes(length).hex())
|
|
|
|
elif mode == "int":
|
|
upper = 1000000
|
|
if len(parts) > 2:
|
|
try:
|
|
upper = int(parts[2])
|
|
except ValueError:
|
|
pass
|
|
upper = max(1, min(upper, 2**32))
|
|
await bot.reply(message, str(secrets.randbelow(upper)))
|
|
|
|
elif mode == "coin":
|
|
await bot.reply(message, secrets.choice(["heads", "tails"]))
|
|
|
|
elif mode == "dice":
|
|
spec = parts[2] if len(parts) > 2 else "1d6"
|
|
try:
|
|
num, _, sides = spec.lower().partition("d")
|
|
num_dice = int(num) if num else 1
|
|
num_sides = int(sides) if sides else 6
|
|
if num_dice < 1 or num_dice > 20 or num_sides < 2 or num_sides > 100:
|
|
raise ValueError
|
|
except ValueError:
|
|
await bot.reply(message, "Usage: !rand dice [NdM] (1-20 dice, 2-100 sides)")
|
|
return
|
|
rolls = [secrets.randbelow(num_sides) + 1 for _ in range(num_dice)]
|
|
total = sum(rolls)
|
|
if num_dice == 1:
|
|
await bot.reply(message, str(total))
|
|
else:
|
|
await bot.reply(message, f"{' + '.join(map(str, rolls))} = {total}")
|
|
|
|
else:
|
|
await bot.reply(message, "Modes: password, hex, uuid, bytes, int, coin, dice")
|