un bot que analiza una automatización de Discord web que reconozca patrones


ColesKo

Miembro

📌 ¿Por qué usar autocompletar en un bot de Discord?​


El autocompletar hace que los comandos sean más fáciles y rápidos de usar, sobre todo en servidores con mucha información (etnias, inventario, almacén, etc.).


¿Para qué sirve?​


  • ✅ Ayuda al usuario: no tiene que recordar nombres exactos, solo empieza a escribir.
  • ✅ Evita errores: el bot recibe valores correctos y coherentes con la base de datos.
  • ✅ Más fluidez: menos mensajes mal escritos, menos confusión.



⚙️ ¿Cómo funciona (explicado fácil)?​


  • El autocompletar se activa en opciones del comando.
  • Funciona solo con:
    • STRING (texto)
    • INTEGER (números)
    • NUMBER (decimales)
  • Mientras el usuario escribe, Discord pregunta al bot:
    “¿Qué opciones le sugiero?”
  • El bot responde en menos de 3 segundos con hasta 25 sugerencias.
  • El usuario no está obligado a elegir una sugerencia (es ayuda, no imposición).



🧠 En resumen​


El autocompletar es una ayuda visual inteligente que mejora la experiencia del usuario y reduce errores, ideal para servidores con datos complejos o listados grandes.

#Dialogo interno {"message": "405: Method Not Allowed", "code": 0}
/comando buscar producto
producto → STRING con autocompletar

El bot sugiere productos del almacén mientras el usuario escribe

El usuario selecciona uno o escribe el suyo

Ejemplo de uso:

/buscar producto: ma…
Sugerencias:

madera

martillo

maquinaria
Revolución francesa

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
import requests, re, time
from bs4 import BeautifulSoup
from urllib.parse import urlparse

app = FastAPI()

# -----------------------------
# Config básica
# -----------------------------
OLLAMA_URL = "http://127.0.0.1:11434/api/chat"
MODEL = "llama3.2"

# memoria simple (por sesión única)
MEMORY = [] # lista de {"role": "...", "content": "..."}
MAX_TURNS = 18

# -----------------------------
# Helpers de seguridad
# -----------------------------
PRIVATE_NET_PATTERNS = [
r"^127\.", r"^10\.", r"^192\.168\.", r"^172\.(1[6-9]|2\d|3[0-1])\."
]

def is_private_host(host: str) -> bool:
if not host:
return True
# IP literal
if re.match(r"^\d{1,3}(\.\d{1,3}){3}$", host):
return any(re.match(p, host) for p in PRIVATE_NET_PATTERNS)
# hostnames locales
if host in ("localhost",):
return True
return False

def safe_http_url(url: str) -> bool:
try:
u = urlparse(url)
if u.scheme not in ("http", "https"):
return False
if is_private_host(u.hostname or ""):
return False
return True
except Exception:
return False

# -----------------------------
# Tools con internet
# -----------------------------
def ddg_search(query: str, limit: int = 5) -> str:
"""Búsqueda sencilla (DuckDuckGo HTML)."""
q = query.strip()
if not q:
return "Consulta vacía."
url = "https://duckduckgo.com/html/"
r = requests.post(url, data={"q": q}, timeout=12, headers={"User-Agent": "Mozilla/5.0"})
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
results = []
for a in soup.select("a.result__a")[:limit]:
title = a.get_text(" ", strip=True)
href = a.get("href", "")
results.append(f"- {title}\n {href}")
if not results:
return "No encontré resultados (o DDG cambió el HTML)."
return "Resultados:\n" + "\n".join(results)

def fetch_url_text(url: str, max_chars: int = 6000) -> str:
"""Descarga texto de una URL pública y extrae contenido."""
if not safe_http_url(url):
return "URL bloqueada por seguridad (solo http/https y nada local/interno)."
r = requests.get(url, timeout=12, headers={"User-Agent": "Mozilla/5.0"})
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
# quita scripts/styles
for tag in soup(["script", "style", "noscript"]):
tag.decompose()
text = soup.get_text("\n", strip=True)
text = re.sub(r"\n{3,}", "\n\n", text)
return text[:max_chars]

# -----------------------------
# LLM (Ollama)
# -----------------------------
SYSTEM = """Eres un agente local en localhost. Hablas en español, claro y humano.
Tienes herramientas:
- SEARCH(query): busca en internet (devuelve lista de resultados)
- OPEN(url): descarga texto de una URL pública (bloquea redes internas)
Reglas:
- Si necesitas datos actuales, usa SEARCH.
- Si el usuario quiere detalles de una página concreta, usa OPEN.
- No inventes fuentes: cuando uses herramientas, menciona que lo hiciste y resume.
- Mantén la conversación con memoria breve.
"""

def call_ollama(messages):
payload = {"model": MODEL, "messages": messages, "stream": False}
res = requests.post(OLLAMA_URL, json=payload, timeout=60)
res.raise_for_status()
return res.json()["message"]["content"]

# -----------------------------
# “Cerebro”: router de herramientas
# (simple y robusto: comandos explícitos)
# -----------------------------
def agent_respond(user_text: str) -> str:
global MEMORY

text = user_text.strip()

# comandos:
# /search tu consulta
# /open https://...
if text.lower().startswith("/search "):
q = text[8:].strip()
try:
out = ddg_search(q)
except Exception as e:
out = f"Error buscando: {e}"
MEMORY.append({"role": "user", "content": text})
MEMORY.append({"role": "assistant", "content": out})
MEMORY = MEMORY[-MAX_TURNS*2:]
return out

if text.lower().startswith("/open "):
url = text[6:].strip()
try:
page = fetch_url_text(url)
# resumimos con LLM
msgs = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Resume en 8-12 líneas el siguiente texto y extrae 5 puntos clave:\n\n{page}"}
]
summary = call_ollama(msgs)
out = f"Leí la página y la resumí:\n\n{summary}"
except Exception as e:
out = f"Error abriendo URL: {e}"
MEMORY.append({"role": "user", "content": text})
MEMORY.append({"role": "assistant", "content": out})
MEMORY = MEMORY[-MAX_TURNS*2:]
return out

# respuesta normal con memoria
MEMORY.append({"role": "user", "content": text})
MEMORY = MEMORY[-MAX_TURNS*2:]

msgs = [{"role": "system", "content": SYSTEM}] + MEMORY
out = call_ollama(msgs)

MEMORY.append({"role": "assistant", "content": out})
MEMORY = MEMORY[-MAX_TURNS*2:]
return out

# -----------------------------
# API
# -----------------------------
class ChatIn(BaseModel):
text: str

@app.post("/api/chat")
def chat(inp: ChatIn):
reply = agent_respond(inp.text)
return {"reply": reply}

@app.get("/", response_class=HTMLResponse)
def home():
return """
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Agente Local</title>
<style>
body{font-family:system-ui,Segoe UI,Roboto,Arial;margin:0;background:#0b0f14;color:#e9f0f7}
.wrap{max-width:900px;margin:0 auto;padding:18px}
.card{background:#121a22;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:14px;box-shadow:0 12px 40px rgba(0,0,0,.35)}
#log{height:60vh;overflow:auto;padding:12px;border-radius:12px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08)}
.msg{margin:10px 0;line-height:1.35}
.me{color:#7db3ff}
.ai{color:#b8ffcf}
.row{display:flex;gap:10px;margin-top:12px}
input{flex:1;padding:12px;border-radius:12px;border:1px solid rgba(255,255,255,.12);background:#0b0f14;color:#e9f0f7}
button{padding:12px 14px;border-radius:12px;border:0;background:#2a7cff;color:#fff;font-weight:700;cursor:pointer}
small{color:rgba(233,240,247,.65)}
code{background:rgba(0,0,0,.25);padding:2px 6px;border-radius:8px}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h2 style="margin:0 0 8px">Agente Local (localhost)</h2>
<small>Comandos: <code>/search algo</code> · <code>/open https://...</code></small>
<div id="log" style="margin-top:12px"></div>
<div class="row">
<input id="inp" placeholder="Escribe… (o /search, /open)" autocomplete="off"/>
<button id="send">Enviar</button>
</div>
</div>
</div>

<script>
const log = document.getElementById('log');
const inp = document.getElementById('inp');
const send = document.getElementById('send');

function add(role, text){
const div = document.createElement('div');
div.className = 'msg ' + (role === 'me' ? 'me' : 'ai');
div.textContent = (role === 'me' ? 'Tú: ' : 'Agente: ') + text;
log.appendChild(div);
log.scrollTop = log.scrollHeight;
}

async function go(){
const text = inp.value.trim();
if(!text) return;
inp.value = '';
add('me', text);
send.disabled = true;
try{
const r = await fetch('/api/chat', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({text})
});
const j = await r.json();
add('ai', j.reply);
}catch(e){
add('ai', 'Error: ' + e);
}finally{
send.disabled = false;
inp.focus();
}
}

send.onclick = go;
inp.addEventListener('keydown', (e)=>{ if(e.key==='Enter') go(); });
add('ai', 'Estoy listo. Prueba: /search noticias IA o pregúntame algo.');
</script>
</body>
</html>
"""
 

Dark

🔥root313🔥
Staff
Moderador
Paladín de Nodo
Jinete de Nodo
Burgués de Nodo
Noderador
Nodero
Noder Pro
Noder

📌 ¿Por qué usar autocompletar en un bot de Discord?​


El autocompletar hace que los comandos sean más fáciles y rápidos de usar, sobre todo en servidores con mucha información (etnias, inventario, almacén, etc.).


¿Para qué sirve?​


  • ✅ Ayuda al usuario: no tiene que recordar nombres exactos, solo empieza a escribir.
  • ✅ Evita errores: el bot recibe valores correctos y coherentes con la base de datos.
  • ✅ Más fluidez: menos mensajes mal escritos, menos confusión.



⚙️ ¿Cómo funciona (explicado fácil)?​


  • El autocompletar se activa en opciones del comando.
  • Funciona solo con:
    • STRING (texto)
    • INTEGER (números)
    • NUMBER (decimales)
  • Mientras el usuario escribe, Discord pregunta al bot:
  • El bot responde en menos de 3 segundos con hasta 25 sugerencias.
  • El usuario no está obligado a elegir una sugerencia (es ayuda, no imposición).



🧠 En resumen​


El autocompletar es una ayuda visual inteligente que mejora la experiencia del usuario y reduce errores, ideal para servidores con datos complejos o listados grandes.

#Dialogo interno {"message": "405: Method Not Allowed", "code": 0}
/comando buscar producto
producto → STRING con autocompletar

El bot sugiere productos del almacén mientras el usuario escribe

El usuario selecciona uno o escribe el suyo

Ejemplo de uso:

/buscar producto: ma…
Sugerencias:

madera

martillo

maquinaria
Revolución francesa

from fastapi import FastAPI
from fastapi.responses import HTMLResponse
from pydantic import BaseModel
import requests, re, time
from bs4 import BeautifulSoup
from urllib.parse import urlparse

app = FastAPI()

# -----------------------------
# Config básica
# -----------------------------
OLLAMA_URL = "http://127.0.0.1:11434/api/chat"
MODEL = "llama3.2"

# memoria simple (por sesión única)
MEMORY = [] # lista de {"role": "...", "content": "..."}
MAX_TURNS = 18

# -----------------------------
# Helpers de seguridad
# -----------------------------
PRIVATE_NET_PATTERNS = [
r"^127\.", r"^10\.", r"^192\.168\.", r"^172\.(1[6-9]|2\d|3[0-1])\."
]

def is_private_host(host: str) -> bool:
if not host:
return True
# IP literal
if re.match(r"^\d{1,3}(\.\d{1,3}){3}$", host):
return any(re.match(p, host) for p in PRIVATE_NET_PATTERNS)
# hostnames locales
if host in ("localhost",):
return True
return False

def safe_http_url(url: str) -> bool:
try:
u = urlparse(url)
if u.scheme not in ("http", "https"):
return False
if is_private_host(u.hostname or ""):
return False
return True
except Exception:
return False

# -----------------------------
# Tools con internet
# -----------------------------
def ddg_search(query: str, limit: int = 5) -> str:
"""Búsqueda sencilla (DuckDuckGo HTML)."""
q = query.strip()
if not q:
return "Consulta vacía."
url = "https://duckduckgo.com/html/"
r = requests.post(url, data={"q": q}, timeout=12, headers={"User-Agent": "Mozilla/5.0"})
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
results = []
for a in soup.select("a.result__a")[:limit]:
title = a.get_text(" ", strip=True)
href = a.get("href", "")
results.append(f"- {title}\n {href}")
if not results:
return "No encontré resultados (o DDG cambió el HTML)."
return "Resultados:\n" + "\n".join(results)

def fetch_url_text(url: str, max_chars: int = 6000) -> str:
"""Descarga texto de una URL pública y extrae contenido."""
if not safe_http_url(url):
return "URL bloqueada por seguridad (solo http/https y nada local/interno)."
r = requests.get(url, timeout=12, headers={"User-Agent": "Mozilla/5.0"})
r.raise_for_status()
soup = BeautifulSoup(r.text, "html.parser")
# quita scripts/styles
for tag in soup(["script", "style", "noscript"]):
tag.decompose()
text = soup.get_text("\n", strip=True)
text = re.sub(r"\n{3,}", "\n\n", text)
return text[:max_chars]

# -----------------------------
# LLM (Ollama)
# -----------------------------
SYSTEM = """Eres un agente local en localhost. Hablas en español, claro y humano.
Tienes herramientas:
- SEARCH(query): busca en internet (devuelve lista de resultados)
- OPEN(url): descarga texto de una URL pública (bloquea redes internas)
Reglas:
- Si necesitas datos actuales, usa SEARCH.
- Si el usuario quiere detalles de una página concreta, usa OPEN.
- No inventes fuentes: cuando uses herramientas, menciona que lo hiciste y resume.
- Mantén la conversación con memoria breve.
"""

def call_ollama(messages):
payload = {"model": MODEL, "messages": messages, "stream": False}
res = requests.post(OLLAMA_URL, json=payload, timeout=60)
res.raise_for_status()
return res.json()["message"]["content"]

# -----------------------------
# “Cerebro”: router de herramientas
# (simple y robusto: comandos explícitos)
# -----------------------------
def agent_respond(user_text: str) -> str:
global MEMORY

text = user_text.strip()

# comandos:
# /search tu consulta
# /open https://...
if text.lower().startswith("/search "):
q = text[8:].strip()
try:
out = ddg_search(q)
except Exception as e:
out = f"Error buscando: {e}"
MEMORY.append({"role": "user", "content": text})
MEMORY.append({"role": "assistant", "content": out})
MEMORY = MEMORY[-MAX_TURNS*2:]
return out

if text.lower().startswith("/open "):
url = text[6:].strip()
try:
page = fetch_url_text(url)
# resumimos con LLM
msgs = [
{"role": "system", "content": SYSTEM},
{"role": "user", "content": f"Resume en 8-12 líneas el siguiente texto y extrae 5 puntos clave:\n\n{page}"}
]
summary = call_ollama(msgs)
out = f"Leí la página y la resumí:\n\n{summary}"
except Exception as e:
out = f"Error abriendo URL: {e}"
MEMORY.append({"role": "user", "content": text})
MEMORY.append({"role": "assistant", "content": out})
MEMORY = MEMORY[-MAX_TURNS*2:]
return out

# respuesta normal con memoria
MEMORY.append({"role": "user", "content": text})
MEMORY = MEMORY[-MAX_TURNS*2:]

msgs = [{"role": "system", "content": SYSTEM}] + MEMORY
out = call_ollama(msgs)

MEMORY.append({"role": "assistant", "content": out})
MEMORY = MEMORY[-MAX_TURNS*2:]
return out

# -----------------------------
# API
# -----------------------------
class ChatIn(BaseModel):
text: str

@app.post("/api/chat")
def chat(inp: ChatIn):
reply = agent_respond(inp.text)
return {"reply": reply}

@app.get("/", response_class=HTMLResponse)
def home():
return """
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width,initial-scale=1"/>
<title>Agente Local</title>
<style>
body{font-family:system-ui,Segoe UI,Roboto,Arial;margin:0;background:#0b0f14;color:#e9f0f7}
.wrap{max-width:900px;margin:0 auto;padding:18px}
.card{background:#121a22;border:1px solid rgba(255,255,255,.08);border-radius:16px;padding:14px;box-shadow:0 12px 40px rgba(0,0,0,.35)}
#log{height:60vh;overflow:auto;padding:12px;border-radius:12px;background:rgba(255,255,255,.03);border:1px solid rgba(255,255,255,.08)}
.msg{margin:10px 0;line-height:1.35}
.me{color:#7db3ff}
.ai{color:#b8ffcf}
.row{display:flex;gap:10px;margin-top:12px}
input{flex:1;padding:12px;border-radius:12px;border:1px solid rgba(255,255,255,.12);background:#0b0f14;color:#e9f0f7}
button{padding:12px 14px;border-radius:12px;border:0;background:#2a7cff;color:#fff;font-weight:700;cursor:pointer}
small{color:rgba(233,240,247,.65)}
code{background:rgba(0,0,0,.25);padding:2px 6px;border-radius:8px}
</style>
</head>
<body>
<div class="wrap">
<div class="card">
<h2 style="margin:0 0 8px">Agente Local (localhost)</h2>
<small>Comandos: <code>/search algo</code> · <code>/open https://...</code></small>
<div id="log" style="margin-top:12px"></div>
<div class="row">
<input id="inp" placeholder="Escribe… (o /search, /open)" autocomplete="off"/>
<button id="send">Enviar</button>
</div>
</div>
</div>

<script>
const log = document.getElementById('log');
const inp = document.getElementById('inp');
const send = document.getElementById('send');

function add(role, text){
const div = document.createElement('div');
div.className = 'msg ' + (role === 'me' ? 'me' : 'ai');
div.textContent = (role === 'me' ? 'Tú: ' : 'Agente: ') + text;
log.appendChild(div);
log.scrollTop = log.scrollHeight;
}

async function go(){
const text = inp.value.trim();
if(!text) return;
inp.value = '';
add('me', text);
send.disabled = true;
try{
const r = await fetch('/api/chat', {
method:'POST',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({text})
});
const j = await r.json();
add('ai', j.reply);
}catch(e){
add('ai', 'Error: ' + e);
}finally{
send.disabled = false;
inp.focus();
}
}

send.onclick = go;
inp.addEventListener('keydown', (e)=>{ if(e.key==='Enter') go(); });
add('ai', 'Estoy listo. Prueba: /search noticias IA o pregúntame algo.');
</script>
</body>
</html>
"""
Buen post supongo pero esto es como explicarte de que sirve una palanca de un tractor, la peña no sabe de que le hablas xd.

Y bueno, es todo copypaste, no?