| Resumen | Habilitar votos automáticos en temas cuando un usuario crea un tema | |
| Repositorio | GitHub - dereklputnam/discourse-topic-voting-auto-self-vote | |
| Guía de instalación | Cómo instalar un tema o componente de tema | |
| ¿Nuevo en los temas de Discourse? | Guía para principiantes sobre el uso de temas de Discourse |
Instalar este componente de tema
Si su comunidad tiene votos limitados para emitir, como aquí en Meta, tiene sentido que el voto del OP no se emita automáticamente (consulte la discusión aquí y aquí). Pero para las comunidades con votación ilimitada, es un punto de fricción, ¡y es posible que los usuarios ni siquiera voten en su propio tema! De ahí la necesidad de este componente.
Configuración
- Categorías de voto automático: si no desea que esto esté habilitado en todo el sitio, especifique a qué categorías debe aplicarse.
- Grupos excluidos: si no desea que esto se aplique a grupos específicos (quizás equipos internos), agréguelos aquí.
- Voto automático al visitar: una forma suave de limpiar cualquier tema existente donde el OP no votó.
Relleno de temas
Identificar los temas
Utilice esta consulta del explorador de datos para identificar temas donde el OP no votó:
-- [params]
-- null category_id :category_id
-- null string :category_slug
-- boolean :exclude_about = true
SELECT
t.id AS topic_id,
u.username AS "username",
t.title AS "Topic Title",
t.user_id AS "Author",
t.created_at AS "Created At",
c.name AS "Category Name",
c.slug AS "Category Slug"
FROM topics t
JOIN users u ON u.id = t.user_id
JOIN categories c ON c.id = t.category_id
LEFT JOIN topic_voting_topic_vote_count tvvc ON tvvc.topic_id = t.id
LEFT JOIN topic_voting_votes tvv ON tvv.topic_id = t.id AND tvv.user_id = t.user_id
WHERE t.deleted_at IS NULL
AND (
tvvc.votes_count IS NULL OR tvv.id IS NULL
)
AND (
:exclude_about = false
OR t.title ILIKE 'about the % category' = false
)
AND (
:category_id IS NULL OR c.id = :category_id
)
AND (
:category_slug IS NULL OR c.slug = :category_slug
)
Consejo: Vuelva a ejecutar esta consulta después de actualizar los temas para verificar que se emitieron los votos
Relleno a través de la API
¡Luego, use esa lista y ejecute un script de API para rellenar los votos! Para usar mi script a continuación, deberá recortar las columnas más allá de las dos primeras. Las dejé en la consulta del explorador de datos para que sea más fácil identificar los temas en toda su comunidad.
Nota: su clave de API debe tener el alcance de todos los usuarios, ya que está suplantando a cada uno de ellos.
No tengo permiso para adjuntar el script que usé, pero aquí está en texto:
Script de relleno
#!/usr/bin/env python3
"""
Relleno de votos propios a través de la API de Discourse
Este script emite votos para los autores de temas que no han votado en sus propios temas.
Utiliza la API de Discourse para suplantar a los usuarios y emitir votos en su nombre.
Requisitos:
- Python 3.7+
- biblioteca requests (pip install requests)
- Una clave de API de administrador con permisos de suplantación
Uso:
1. Actualice la sección de configuración a continuación
2. Prepare un archivo CSV con las columnas topic_id y username
3. Ejecute: python backfill_votes_api.py
Formato CSV:
topic_id,username
12345,john_doe
12346,jane_smith
"""
import csv
import time
import requests
from datetime import datetime
#==============================================================================
# CONFIGURACIÓN
#==============================================================================
# URL de la instancia de Discourse (sin barra final)
DISCOURSE_URL = 'https://yourcommunity.com'
# Clave de API de administrador (debe tener permisos de suplantación)
API_KEY = 'YOUR_API_KEY_HERE'
# Nombre de usuario administrador que posee la clave de API
API_USERNAME = 'system'
# Ruta al archivo CSV con las columnas topic_id y username
CSV_FILE = 'topics_to_vote.csv'
# Modo de ejecución simulada - establecido en False para emitir votos reales
DRY_RUN = True
# Retardo entre llamadas a la API (segundos) para evitar la limitación de velocidad
DELAY_BETWEEN_REQUESTS = 0.5
#==============================================================================
# SCRIPT
#==============================================================================
def cast_vote(topic_id: int, username: str) -> dict:
"""
Emitir un voto en un tema como un usuario específico.
Args:
topic_id: El ID del tema para votar
username: El nombre de usuario a suplantar al votar
Returns:
Diccionario con booleano 'success' y cadena 'message'
"""
url = f"{DISCOURSE_URL}/voting/vote"
headers = {
'Api-Key': API_KEY,
'Api-Username': username, # Suplantar al usuario
'Content-Type': 'application/json'
}
data = {
'topic_id': topic_id
}
try:
response = requests.post(url, headers=headers, json=data)
if response.status_code == 200:
return {'success': True, 'message': 'Vote cast successfully'}
elif response.status_code == 422:
# Generalmente significa que ya votó o la votación no está habilitada
return {'success': False, 'message': 'Already voted or voting not enabled'}
elif response.status_code == 403:
return {'success': False, 'message': 'Permission denied - check API key permissions'}
elif response.status_code == 404:
return {'success': False, 'message': 'Topic not found or voting not enabled on category'}
else:
return {'success': False, 'message': f'HTTP {response.status_code}: {response.text[:200]}'}
except requests.RequestException as e:
return {'success': False, 'message': f'Request error: {str(e)}'}
def main():
print("\n" + "=" * 60)
print("Relleno de votos propios a través de la API")
print("=" * 60)
print(f"Modo: {'EJECUCIÓN SIMULADA (no se realizarán cambios)' if DRY_RUN else 'EN VIVO (se emitirán votos)'}")
print(f"Objetivo: {DISCOURSE_URL}")
print(f"Archivo CSV: {CSV_FILE}")
print("=" * 60 + "\n")
# Leer archivo CSV
try:
with open(CSV_FILE, 'r', newline='', encoding='utf-8') as f:
reader = csv.DictReader(f)
rows = list(reader)
except FileNotFoundError:
print(f"ERROR: Archivo CSV no encontrado: {CSV_FILE}")
print("\nCree un archivo CSV con el siguiente formato:")
print("topic_id,username")
print("12345,john_doe")
print("12346,jane_smith")
return
except Exception as e:
print(f"ERROR: Falló la lectura del archivo CSV: {e}")
return
if not rows:
print("ERROR: El archivo CSV está vacío")
return
# Validar columnas del CSV
columns = set(rows[0].keys())
if 'topic_id' not in columns or 'username' not in columns:
print(f"ERROR: El CSV debe tener las columnas: topic_id y username")
print(f"Columnas encontradas: {columns}")
return
print(f"Encontrados {len(rows)} temas para procesar\n")
# Procesar cada fila
success_count = 0
skip_count = 0
error_count = 0
for i, row in enumerate(rows, 1):
topic_id = row['topic_id'].strip()
username = row['username'].strip()
print(f"[{i}/{len(rows)}] Tema #{topic_id} por @{username}", end=" ")
if DRY_RUN:
print("-> votaría")
success_count += 1
else:
result = cast_vote(int(topic_id), username)
if result['success']:
print("-> ¡votado!")
success_count += 1
elif 'Already voted' in result['message']:
print("-> ya votado (omitido)")
skip_count += 1
else:
print(f"-> ERROR: {result['message']}")
error_count += 1
# Limitación de velocidad
if i < len(rows):
time.sleep(DELAY_BETWEEN_REQUESTS)
# Resumen
print("\n" + "=" * 60)
print("RESUMEN")
print("=" * 60)
print(f"Temas totales: {len(rows)}")
print(f"Votos {'a emitir' if DRY_RUN else 'emitidos'}: {success_count}")
print(f"Ya votados (omitidos): {skip_count}")
print(f"Errores: {error_count}")
if DRY_RUN:
print("\n** EJECUCIÓN SIMULADA COMPLETADA **")
print("Para emitir votos, establezca DRY_RUN = False y vuelva a ejecutar.")
print("")
if __name__ == '__main__':
main()
