NL ▾
https://api.unrestrictedaiapi.com/v1uncensored2026-10-06
API-sleutel aanvragen

Bouw een Discord- of Telegrambot op een onbeperkt AI-API

Een chatbot is de snelste manier om een ongecensureerd model voor mensen te plaatsen. Deze handleiding bouwt zowel een discord.py-bot als een python-telegram-bot op één gedeeld module die de API-aanroep afhandelt, zodat geschiedenis per kanaal, rate limiting, retries en een leeftijdsgrens één keer worden geschreven en hergebruikt.

Bijgewerkt

Belangrijkste punten

  1. Isoleer API-logica in één module en houd elke platformadapter klein.
  2. Houd een aparte begrensde geschiedenis per kanaal of chat zodat gesprekken nooit mengen.
  3. Throttle in drie lagen: per-gebruiker cooldown, een globale glijdende venster onder de 300 verzoeken per minuut, en een concurrency cap.
  4. Beperk volwassen gebruik: alleen NSFW-gemarkeerde Discord-kanaalen, en expliciete /adult-bevestiging op Telegram.

Wat we bouwen

Twee kleine bots, één gedeeld brein. Een bestand genaamd llm.py beheert alles dat de API raakt: de persona, de rate limiter, de retry-logica. Een Discord-bot en een Telegram-bot doen vervolgens niets anders dan platformevents vertalen naar een lijst van berichten en terug. Voeg later een derde platform toe en je schrijft alleen de adapter.

Installeer de afhankelijkheden met pip install httpx discord.py python-telegram-bot. Exporteer API_KEY vanaf je accountpagina, plus DISCORD_TOKEN en/of TELEGRAM_TOKEN vanuit de eigen bot-setup van elk platform. Beide bots hieronder vereisen Python 3.10 of nieuwer.

Het verzoekformaat is de eenvoudige OpenAI chat-completions-vorm, gedekt in de multi-taal quickstart. Niets hier vereist een SDK.

De gedeelde kern: persona, limiter en retries

Plaats het gevaarlijke deel op één plek. De onderstaande module houdt een glijdend venster van verzoek-tijdstippen bij en wacht wanneer het 240 aanroepen per minuut nadert, een bewuste marge onder de 300 per minuut toegestaan per sleutel. Een semaphore begrenst concurrency tot vier, zodat een druk kanaal niet twintig verzoeken tegelijk kan afvuren.

# llm.py - shared by both bots
import asyncio
import os
import time
from collections import deque

import httpx

URL = "https://api.unrestrictedaiapi.com/v1/chat/completions"
PERSONA = "You are Pip, a sardonic bar-room storyteller. Keep replies under 150 words."

_stamps = deque()            # timestamps of recent calls
_gate = asyncio.Semaphore(4) # at most 4 requests in flight
_client = httpx.AsyncClient(timeout=60.0)

async def _wait_for_slot(limit=240, window=60.0):
    # Stay below the 300 requests/minute per-key limit with headroom.
    while True:
        now = time.monotonic()
        while _stamps and now - _stamps[0] > window:
            _stamps.popleft()
        if len(_stamps) < limit:
            _stamps.append(now)
            return
        await asyncio.sleep(window - (now - _stamps[0]) + 0.05)

async def chat(history):
    """history: list of {"role","content"} dicts, oldest first."""
    messages = [{"role": "system", "content": PERSONA}] + list(history)
    async with _gate:
        for attempt in range(3):
            await _wait_for_slot()
            r = await _client.post(
                URL,
                headers={"Authorization": f"Bearer {os.environ['API_KEY']}"},
                json={"model": "uncensored", "messages": messages, "max_tokens": 400},
            )
            if r.status_code in (429, 503):
                await asyncio.sleep(2 ** attempt)
                continue
            if r.status_code == 403:
                return "I can't continue with that."
            r.raise_for_status()
            return r.json()["choices"][0]["message"]["content"]
    return "The service is busy. Try again in a moment."

Kijk naar de statusafhandeling. Een 429 of 503 slaapt voor 1, 2, vervolgens 4 seconden en probeert opnieuw. Een 403 betekent dat de contentfilter afging, dus de bot antwoordt met een neutrale regel in plaats van opnieuw te proberen. Elke andere fout wordt gegooid, wat je platformbibliotheek zal loggen. Als je een 402 ziet, is je saldo leeg of is de proefperiode verlopen; laad het prepaid tegoed op.

De Discord-adapter

Discord-bots vereisen dat het message-content-intent is ingeschakeld in het developer-portal én in de code, anders komt msg.content leeg aan. De bot reageert alleen wanneer deze wordt genoemd, waardoor hij niet elk bericht in een drukke server leest.

# discord_bot.py
import os
import time
from collections import defaultdict, deque

import discord

from llm import chat

intents = discord.Intents.default()
intents.message_content = True      # also enable it in the developer portal
bot = discord.Client(intents=intents)

HISTORY_TURNS = 10                   # user+assistant messages kept per channel
history = defaultdict(lambda: deque(maxlen=HISTORY_TURNS))
last_use = {}                        # user id -> last request time
COOLDOWN = 5.0                       # seconds between requests per user

def chunks(text, size=1900):
    return [text[i:i + size] for i in range(0, len(text), size)] or [""]

@bot.event
async def on_message(msg: discord.Message):
    if msg.author.bot or bot.user not in msg.mentions:
        return
    # Age gate: guild text channels must be flagged NSFW. No DMs.
    if not isinstance(msg.channel, discord.TextChannel) or not msg.channel.is_nsfw():
        await msg.reply("I only chat in channels marked age-restricted (NSFW).")
        return
    now = time.monotonic()
    if now - last_use.get(msg.author.id, 0) < COOLDOWN:
        await msg.add_reaction("\u23f3")
        return
    last_use[msg.author.id] = now

    text = msg.clean_content.replace(f"@{bot.user.display_name}", "").strip()
    if not text:
        return
    h = history[msg.channel.id]
    h.append({"role": "user", "content": f"{msg.author.display_name}: {text}"})
    async with msg.channel.typing():
        answer = await chat(h)
    h.append({"role": "assistant", "content": answer})
    for part in chunks(answer):
        await msg.channel.send(part)

bot.run(os.environ["DISCORD_TOKEN"])

Geschiedenis is een deque met maxlen=10, gekeyed per kanaal-id, zodat elk kanaal zijn eigen gesprek is en oude beurt automatisch vervalt. Het voorvoegsel van elke gebruikersregel met de weergavenaam van de spreker stelt het model in staat mensen te onderscheiden in een gedeelde ruimte. Tien berichten is een conservatieve standaard; het contextvenster is 100.000 tokens, dus je kunt het veel verhogen voordat het uitmaakt.

De Telegram-adapter

python-telegram-bot v20 en hoger is async, wat natuurlijk past bij de gedeelde kern. chat_data is een per-chat woordenboek dat de bibliotheek voor je bijhoudt, zodat geschiedenis en cooldown-status geen extra opslagcode nodig hebben.

# telegram_bot.py  (python-telegram-bot v20+)
import os
import time
from collections import deque

from telegram import Update
from telegram.ext import (ApplicationBuilder, CommandHandler, ContextTypes,
                          MessageHandler, filters)

from llm import chat

async def adult(update: Update, context: ContextTypes.DEFAULT_TYPE):
    context.chat_data["adult"] = True
    await update.message.reply_text("Noted. Say something.")

async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
    await update.message.reply_text(
        "This bot produces adult fiction. Send /adult only if you are 18 or older."
    )

async def talk(update: Update, context: ContextTypes.DEFAULT_TYPE):
    cd = context.chat_data
    if not cd.get("adult"):
        await update.message.reply_text("Send /adult to confirm you are 18+.")
        return
    if time.monotonic() - cd.get("last", 0) < 4:
        return                                   # per-chat cooldown
    cd["last"] = time.monotonic()

    h = cd.setdefault("history", deque(maxlen=10))
    h.append({"role": "user", "content": update.message.text})
    await context.bot.send_chat_action(update.effective_chat.id, "typing")
    answer = await chat(h)
    h.append({"role": "assistant", "content": answer})
    for i in range(0, len(answer), 4000):        # Telegram limit is 4096 chars
        await update.message.reply_text(answer[i:i + 4000])

app = ApplicationBuilder().token(os.environ["TELEGRAM_TOKEN"]).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("adult", adult))
app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, talk))
app.run_polling()

run_polling() is de eenvoudigste manier om te starten en vereist geen openbare URL. Houd er rekening mee dat chat_data in het geheugen leeft, dus een herstart wist het. Als je wilt dat de geschiedenis herstarts overleeft, sla de deque-inhoud dan zelf op in een bestand of database.

Leeftijdsgrenzen en de NSFW-kanaalregel

Deze API is alleen voor volwassenen, en alles wat je erop bouwt neemt die verantwoordelijkheid over. Behandel leeftijdsgrenzen als een functie, niet als een nasleep.

  • Discord: antwoord alleen in guild-kanaalen gemarkeerd als leeftijdsgelimiteerd. De controle channel.is_nsfw() kost niets en de bot weigert elders, inclusief directe berichten.
  • Telegram: vereis een expliciete /adult bevestiging per chat voordat er iets wordt gegenereerd, en vermeld de regel in de welkomsttekst.
  • Beide: bied de bot niet aan in ruimten gericht op jonge mensen, en verwijder toegang onmiddellijk als je te weten komt dat een gebruiker onder de 18 is.

Een bevestigingscommando is een poort, geen bewijs van leeftijd. Seksuele inhoud met minderjarigen wordt altijd geblokkeerd door de API met een 403, inclusief fictie en rollenspel, en je bot moet nooit proberen die respons te omzeilen.

Het draaien en testen zonder menigte

Start elke bot in zijn eigen terminal met python discord_bot.py of python telegram_bot.py. Maak voor de eerste run een privé-testserver of een chat met alleen jezelf. Loop een korte checklist af en je zult bijna elke beginnerfout opvangen.

  1. Vermeld de bot in een niet-NSFW kanaal en bevestig dat het weigert. Dit bewijst dat de poort werkt.
  2. Vermeld het in een gemarkeerd kanaal en stuur twee berichten snel achter elkaar. De tweede moet de zandloper-reactie verdienen, wat bewijst dat de cooldown werkt.
  3. Houd een gesprek van vier berichten vast, en verwijst dan terug naar het eerste bericht. Als de bot zich herinnert, is de geschiedenis correct aangesloten.
  4. Stel de API-sleutel tijdelijk in op een verkeerde waarde en bevestig dat de fout in je logs verschijnt in plaats van stil te verdwijnen.
  5. Plak een lange tekstblok en kijk hoe het antwoord opsplits in meerdere berichten onder de lengtelimiet van het platform.

Als antwoorden generiek aanvoelen, is de persona meestal het probleem in plaats van de code. Verander één zin van PERSONA, herstart en vergelijk. Omdat de persona in de gedeelde module leeft, verandert één bewerking beide bots tegelijk, wat precies de reden is waarom de structuur de extra file waard is.

Wanneer je klaar bent om te deployen, doet elke always-on process manager het. De bots houden een langlevende verbinding met hun platform en maken korte outbound HTTPS-aanroepen naar de API, dus ze hebben geen inbound poorten en bijna geen geheugen nodig. Voeg een restart-beleid toe zodat een crash je community niet met een stille bot achterlaat.

Rate limits, geschiedenisgrootte en kosten

Drie knoppen bepalen hoe de bot zich gedraagt onder belasting. De per-gebruiker cooldown voorkomt dat één persoon de sleutel monopoliseert. Het globale venster beschermt de plafond van 300 verzoeken per minuut. De semaphore gladde pieken. Als je meerdere bot-processen op één sleutel draait, delen ze dat plafond, dus verlaag de limit van elk proces dienovereenkomstig.

Schat de uitgave voordat je een menigte uitnodigt. Aannames: elke call stuurt ongeveer 1.200 input tokens (persona plus tien beurten) en levert 250 tokens op. Eén call kost 1.200 x $0,25 / 1.000.000 + 250 x $1,00 / 1.000.000 = $0,0003 + $0,00025 = $0,00055. Een server die 2.000 antwoorden per dag produceert, geeft onder die aannames ongeveer $1,10 per dag uit. Jouw cijfers zullen verschillen, dus log het usage-veld een week lang en bereken opnieuw.

Om de persona zelf af te stemmen, lees de prompting guide. Prijzen staan op de pricing page.

Verstevigen voordat je de bot deelt

Houd tokens buiten source control; lees ze uit de omgeving zoals getoond. Beperk de berichtlengte voordat je het naar de API stuurt, zodat één grote plak je saldo niet kan opbranden. Log fouten, maar niet berichttekst, omdat je gebruikers redelijkerwijs verwachten dat hun chats privé blijven. Voeg een /reset commando toe dat de deque van een kanaal wist, wat de snelste oplossing is als een gesprek de fout in gaat. Zet tot slot een zichtbare noot in het bot-profiel dat het volwassen fictie schrijft en dat zijn antwoorden gegenereerd zijn.

Een ander ontwerpkeuze verdient een zin: de bots wachten op het volledige antwoord in plaats van het te streamen. Een chatbericht token voor token bewerken botst direct tegen platform-edit-limieten, en voor antwoorden die rond de 150 woorden zijn begrensd, is de wachttijd kort. Als je later live typen wilt in je eigen webfront-end, schakel dan stream: true in daar, waar je het transport beheert. Tot dan is de typ-indicator die je al verstuurt de goedkoopste manier om te laten zien dat de bot werkt, en het houdt de code kort genoeg om in één keer te lezen en te auditen voordat je iemand uitnodigt.

Vragen en antwoorden

Kan één API-sleutel meerdere bots bedienen?

Ja, maar ze delen de limiet van 300 verzoeken per minuut voor die sleutel, dus verdeel je interne throttling over de processen.

Onthoudt de bot gesprekken na een herstart?

Niet in deze voorbeelden; de geschiedenis wordt in het geheugen bewaard. Bewaar de deque-inhoud in een bestand of database als je continuïteit nodig hebt.

Hoe houd ik minderjarigen buiten?

Beperk Discord-antwoorden aan leeftijdsgesloten kanalen, vereis een Telegram-bevestigingscommando en vermijd op jongeren gerichte ruimtes. Dit zijn poorten, geen bewijs, dus reageer snel op elke melding.

Wat gebeurt er als het saldo op is?

De API retourneert 402 met de code no_credit. Vang dit op in de core module en vertel je gebruikers dat de bot gepauzeerd is.

Je sleutel is nog maar één formulier verwijderd

Maak een account aan, kopieer de sleutel, pas de base URL aan. Dat is de hele setup.

API-sleutel aanvragen