Commit 815db623 authored by Mindiell's avatar Mindiell

Added tweeting, parsing rss feed

parent fb7e703d
......@@ -3,6 +3,7 @@
Configuration for IRC bot Wantzel.
"""
# IRC access
server = 'irc.freenode.net'
port = 6667
nickname = 'testBot'
......@@ -10,7 +11,22 @@ password = '',
channels = [
'#testchannel'
]
# External drupal db, MySQL
dbuser = "root"
dbpassword = ""
dbserver = "localhost"
dbname = "db"
# Internal db, sqlite is sufficient
sqlite_db = "db.sqlite3"
# Timer for chrono-methods, in seconds (float)
timer = 180
# Twitter account
TOKEN = ""
TOKENSEC = ""
CONSKEY = ""
CONSSEC = ""
......@@ -9,7 +9,7 @@ messages = {
"""Bonjour, je suis le bot de la Quadrature du Net, vous pouvez me demander de l'aide si besoin. (wantzel help)""",
"help":
"""Mes commandes sont : !help !rp(cpa) !kill !stats.
"""Mes commandes sont : !help !rp(cpa) !kill !stats et !admin.
Pour plus d'informations, voir ici: https://wiki.laquadrature.net/Wantzel
Pour obtenir de l'aide sur une commande en particulier, il suffit de taper !help <commande>""",
......@@ -28,10 +28,18 @@ L'utilisation se fait sous la forme: !rp <url de l'article à ajouter>""",
Les statistiques sont calculées sur des notes supérieurs ou égales à 0, 3, et 4. Et sur les 1, 3, 7, et 15 derniers jours.""",
"help_kill":
"""Fixe la note de l'article donné en paramètre à -100.
"""*Attention* seuls les vrais rp-jedis ont accès à cette commande <3
Fixe la note de l'article donné en paramètre à -100.
Utile en cas d'erreur ou pour s'assurer que l'article ne sera pas publié dans la RP
Utilisation: !kill <url de l'article>
*Attention* seuls les vrais rp-jedis ont accès à cette commande <3""",
Utilisation: !kill <url de l'article>""",
"help_admin":
"""*Attention* seuls les vrais rp-jedis ont accès à cette commande <3
Permet de gérer la liste des utilisateurs ayant un accès privilégié. Il n'y a qu'un seul niveau de privilège.
Utilisations:
!admin list => Fournit la liste des utilisateurs privilégiés
!admin add user[, user]> => Ajoute un ou plusieurs utilisateurs à la liste
!admin del user[, user] => Supprime un ou plusieurs utilisateurs de la liste""",
"rp_http":
"""Merci %s, mais je prends en compte uniquement les adresses internet qui commencent par http ou https""",
......@@ -57,4 +65,27 @@ Utilisation: !kill <url de l'article>
"title":
"""Titre: %s (à %s)""",
"admin_list":
"""Liste des modérateurs actuels: %s""",
"admin_add":
"""%s a(ont) été ajouté(s) à la liste des modérateurs""",
"admin_add_empty":
"""Ces utilisateurs sont déjà modérateurs""",
"admin_del":
"""%s a(ont) été retiré(s) de la liste des modérateurs.""",
"not_moderator":
"""Désolé, il ne semble pas que vous ayez les droits pour cette commande.""",
"topic":
"""Canal de la revue de presse de La Quadrature du Net ~ %s articles en attente ~ Mode d'emploi https://wiki.laquadrature.net/Revue_de_presse ~ Une arme, le savoir est. Le diffuser, notre devoir c'est.""",
"tweet_rp":
"""[La Quadrature du Net - Revue de presse] %s — %s""",
"reload":
"""Configuration à jour""", #La configuration a été mise à jour, merci <3""",
}
......@@ -6,15 +6,22 @@ License : AGPLv3
Doc : https://wiki.laquadrature.net/Wantzel
TODO:
- Ajouter la gestion des droits pour certaines commandes
- Mettre une valeur par défaut pour les champs concernés
- Afficher les titres des urls fournies sur le canal
- Ajouter des commandes permettant de gérer le mediakit (moderators only)
- Parser les urls et voir ce qu'on peut faire:
- upload de vidéo
- tag de vidéo
- obtenir un lien vers une vidéo
"""
import feedparser
import importlib
from irc import IrcClientFactory
import MySQLdb
import re
import sqlite3
import time
from twisted.internet import reactor
from twitter import Twitter, OAuth
import urllib
import config
......@@ -22,16 +29,16 @@ from messages import messages
def get_cursor():
"""
This function connects to a database and returns a usable cursor.
This function connects to a MySQL database and returns a usable cursor.
"""
db = MySQLdb.connect(
connection = MySQLdb.connect(
host=config.dbserver,
user=config.dbuser,
passwd=config.dbpassword,
db=config.dbname
)
if db:
return db.cursor()
if connection:
return connection.cursor()
return None
def get_url(message):
......@@ -70,6 +77,18 @@ def get_title(message):
title = re.sub("&amp;", "&", title)
return (title, website)
def is_moderator(name):
"""
This function verify if a user is a moderator.
"""
connection = sqlite3.connect(config.sqlite_db)
cursor = connection.cursor()
cursor.execute("SELECT count(*) FROM moderator WHERE name=?", (name, ))
if int(cursor.fetchone()[0])==1:
return True
return False
class Wantzel(object):
"""
Wantzel bot.
......@@ -78,9 +97,32 @@ class Wantzel(object):
"""
Initialization of bot over IRC.
"""
self.number = 0
# default last_entry_published
self.last_entry_published = time.strptime("2000-01-01", "%Y-%m-%d")
# See if there is something in the db
connection = sqlite3.connect(config.sqlite_db)
for row in connection.execute("SELECT last_entry_published FROM tweets"):
self.last_entry_published = time.strptime(
row[0].encode("utf-8"),
"%Y-%m-%d %H:%M:%S %Z"
)
self.irc = IrcClientFactory(config)
self.irc.set_privmsg = self.set_privmsg
reactor.connectTCP(config.server, config.port, self.irc)
# Prepare timer
reactor.callLater(config.timer, self.timer)
def timer(self):
"""
This method launches function regularly (see config.timer).
"""
print("Timer called")
self.rp_to_twitter("http://www.laquadrature.net/fr/revue-de-presse/feed")
self.rp_to_twitter("http://www.laquadrature.net/en/press-review/feed")
self.count_articles()
# Recalling the timer
reactor.callLater(config.timer, self.timer)
def set_privmsg(self):
"""
......@@ -98,7 +140,8 @@ class Wantzel(object):
def on_privmsg(self, user, channel, msg):
"""
Wantzel can understand some commands :
Wantzel can understand a lot of commands. Commands followed by a (*)
are accessible only to moderators:
- help
Returns a message about how to use the bot.
If a command is passed after help, the message explains how to use
......@@ -107,8 +150,14 @@ class Wantzel(object):
Add an article in the database
- stats
Show some statistics about the RP
- kill
- kill (*)
Kill an article by giving it a score of -100
- moderate list (*)
List rights in private
- moderate add (*)
Add a new moderator to list
- moderate remove (*)
Remove a moderator from list
"""
# Cleaning user name
user = re.search("([^!]*)!", user).group(1)
......@@ -126,7 +175,7 @@ class Wantzel(object):
if "wantzel" in msg and ("help" in msg or "aide" in msg):
self.help(user, channel, msg)
# Find known command
command = re.search("!(rp[acp]*|kill|help|stats)", msg)
command = re.search("!(rp[acp]*|kill|help|stats|admin)", msg)
if command:
command = command.group(1)
print("Command: %s" % command)
......@@ -138,6 +187,8 @@ class Wantzel(object):
self.kill(user, channel, msg)
elif command=="stats":
self.stats(user, channel, msg)
elif command=="admin":
self.admin(user, channel, msg)
if title and website:
self.send_message(channel, messages["title"] % (title, website))
......@@ -160,7 +211,7 @@ class Wantzel(object):
"""
print("help command")
# Searching for a command after help keyword
command = re.search("!help (stats|rp|help|kill)", msg)
command = re.search("!help (stats|rp|help|kill|admin)", msg)
if command:
command = command.group(1)
self.send_message(user, messages["help_"+command])
......@@ -196,8 +247,6 @@ class Wantzel(object):
# Archive this article
if "a" in command:
note -= 2
#TODO: Gérer les autres champs qui n'ont pas de valeur par défaut
# lang, published, nid, screenshot, title, fetched, seemscite
print("Adding an article by %s: %s" % (user, url))
result = cursor.execute(
"""INSERT INTO presse SET
......@@ -218,13 +267,15 @@ class Wantzel(object):
self.send_message(channel, messages["rp_known_article"] % user)
else:
self.send_message(channel, messages["rp_taken_article"] % user)
# Update number of articles to do
self.count_articles()
def kill(self, user, channel, msg):
"""
Kill an article by setting its score to -100.
"""
#TODO: Gérer les droits de cette commande
print("kill command")
if is_moderator(user):
url = get_url(msg)
print("url: %s" % url)
if url=="":
......@@ -241,14 +292,15 @@ class Wantzel(object):
else:
cursor.execute("UPDATE presse SET note=-100 WHERE id=%s", (rows[0][0], ))
self.send_message(channel, messages["kill_done"] % url)
else:
self.send_message(channel, messages["not_moderator"])
def stats(self, user, channel, msg):
"""
Returns stats on articles in press review.
"""
print("stats command")
db = MySQLdb.connect(host="localhost", user="root", passwd="root", db="site")
cursor = db.cursor()
cursor = get_cursor()
periods = [1, 3, 7, 15]
notes = [0, 3 ,4]
notnull = 0
......@@ -276,6 +328,169 @@ class Wantzel(object):
result = messages["stats_bravo"] % periods[-1]
self.send_message(channel, result)
def admin(self, user, channel, msg):
"""
Manage moderation.
A sub-command should be behind the !admin command.
"""
print("admin command")
# Searching for a command after admin keyword
command = re.search("!admin (list|add|del)", msg)
if command:
command = command.group(1)
if command=="list":
self.admin_list(user, channel, msg)
elif command=="add":
self.admin_add(user, channel, msg)
elif command=="del":
self.admin_del(user, channel, msg)
def admin_list(self, user, channel, msg):
"""
List actual moderators.
"""
print("admin_list command")
if is_moderator(user):
connection = sqlite3.connect(config.sqlite_db)
names = []
for row in connection.execute("SELECT name FROM moderator"):
names.append(row[0].encode("utf-8"))
self.send_message(channel, messages["admin_list"] % ", ".join(names))
else:
self.send_message(channel, messages["not_moderator"])
def admin_add(self, user, channel, msg):
"""
Add some new moderators if not existing yet.
"""
print("admin_add command")
if is_moderator(user):
try:
names = []
connection = sqlite3.connect(config.sqlite_db)
result = re.search("!admin add (([^,]+, ?)+)?(.*)", msg)
if result.group(1):
names = [name for name in result.group(1).split(", ") if name!=""]
names.append(result.group(3))
# Do not add actual moderators
moderators = []
for row in connection.execute("SELECT name FROM moderator"):
moderators.append(row[0].encode("utf-8"))
names = set([name for name in names if name not in moderators])
if names:
# Converting set in list of tuples
values = [(name,) for name in names]
connection.executemany("INSERT INTO moderator (name) VALUES (?)", values)
connection.commit()
self.send_message(channel, messages["admin_add"] % ", ".join(names))
else:
self.send_message(channel, messages["admin_add_empty"])
except:
pass
else:
self.send_message(channel, messages["not_moderator"])
def admin_del(self, user, channel, msg):
"""
Delete a moderator from list.
"""
print("admin_del command")
if is_moderator(user):
try:
names = []
result = re.search("!admin del (([^,]+, ?)+)?(.*)", msg)
if result.group(1):
names = [name for name in result.group(1).split(", ") if name!=""]
names.append(result.group(3))
names = set(names)
print(names)
connection = sqlite3.connect(config.sqlite_db)
for name in names:
connection.execute("DELETE FROM moderator WHERE name=?", (name, ))
connection.commit()
self.send_message(channel, messages["admin_del"] % ", ".join(names))
except:
pass
else:
self.send_message(channel, messages["not_moderator"])
def count_articles(self):
"""
Count number of articles not done in RP and updates the topic of the
press review channel if needed.
"""
cursor = get_cursor()
cursor.execute("""SELECT COUNT(*) FROM presse
WHERE DATE_SUB(NOW(), INTERVAL 2 MONTH)<datec
AND note > 2
AND nid = 0""")
rows = cursor.fetchall()
number = int(rows[0][0])
if self.number!=number:
self.irc.client.topic("#mytipy", messages["topic"] % number)
pass
self.number = number
def rp_to_twitter(self, rss):
"""
By parsing the RSS feed of the press-review, we know what to tweet.
"""
print("rp_to_twitter method")
now = time.localtime()
today = time.strptime("%s-%s-%s %s" % (
now.tm_year,
now.tm_mon,
now.tm_mday,
tm_isdst
),
"%Y-%m-%d %Z"
)
entries = feedparser.parse(rss)['entries']
entries.reverse()
for entry in entries:
# if date of publication is greater than today, midnight, and
# lesser than future
if today < entry.published_parsed < now:
if self.last_entry_published < entry.published_parsed:
self.tweet(messages["tweet_rp"] % (
entry.title.encode("utf-8"),
entry.link.encode("utf-8")
))
print(entry.published_parsed)
print(entry.title)
# Save last_entry_published
self.last_entry_published = entry.published_parsed
last_entry_published = time.strftime(
"%Y-%m-%d %H:%M:%S %Z",
self.last_entry_published
)
connection = sqlite3.connect(config.sqlite_db)
connection.execute(
"UPDATE tweets SET last_entry_published=?",
(last_entry_published,)
)
connection.commit()
# Tweet only one message in order not to spam
return
def tweet(self, message):
"""
Tweet message on specified account
"""
print("tweet method")
auth = OAuth(
config.TOKEN,
config.TOKENSEC,
config.CONSKEY,
config.CONSSEC
)
twitter = Twitter(auth=auth)
try:
print("Tweeting: %s", message)
#twitter.statuses.update(status=message)
except:
pass
if __name__ == '__main__':
wantzel = Wantzel()
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment