Compare commits
1 commit
master
...
WIP/module
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f2ae59b9f |
194 changed files with 7534 additions and 13174 deletions
26
.drone.yml
26
.drone.yml
|
|
@ -1,26 +0,0 @@
|
|||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default-arm64
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: arm64
|
||||
|
||||
steps:
|
||||
- name: build
|
||||
image: python:3.11-alpine
|
||||
commands:
|
||||
- pip install --no-cache-dir -r requirements.txt
|
||||
- pip install .
|
||||
|
||||
- name: docker
|
||||
image: plugins/docker
|
||||
settings:
|
||||
repo: nemunaire/nemubot
|
||||
auto_tag: true
|
||||
auto_tag_suffix: ${DRONE_STAGE_OS}-${DRONE_STAGE_ARCH}
|
||||
username:
|
||||
from_secret: docker_username
|
||||
password:
|
||||
from_secret: docker_password
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -1,6 +1,5 @@
|
|||
*#
|
||||
*~
|
||||
*.log
|
||||
TAGS
|
||||
*.py[cod]
|
||||
__pycache__
|
||||
|
|
|
|||
3
.gitmodules
vendored
Normal file
3
.gitmodules
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
[submodule "modules/nextstop/external"]
|
||||
path = modules/nextstop/external
|
||||
url = git://github.com/nbr23/NextStop.git
|
||||
12
.travis.yml
12
.travis.yml
|
|
@ -1,12 +0,0 @@
|
|||
language: python
|
||||
python:
|
||||
- 3.4
|
||||
- 3.5
|
||||
- 3.6
|
||||
- 3.7
|
||||
- nightly
|
||||
install:
|
||||
- pip install -r requirements.txt
|
||||
- pip install .
|
||||
script: nosetests -w nemubot
|
||||
sudo: false
|
||||
241
DCC.py
Normal file
241
DCC.py
Normal file
|
|
@ -0,0 +1,241 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import imp
|
||||
import os
|
||||
import re
|
||||
import socket
|
||||
import sys
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
import message
|
||||
import server
|
||||
|
||||
#Store all used ports
|
||||
PORTS = list()
|
||||
|
||||
class DCC(server.Server):
|
||||
def __init__(self, srv, dest, socket=None):
|
||||
server.Server.__init__(self)
|
||||
|
||||
self.error = False # An error has occur, closing the connection?
|
||||
self.messages = list() # Message queued before connexion
|
||||
|
||||
# Informations about the sender
|
||||
self.sender = dest
|
||||
if self.sender is not None:
|
||||
self.nick = (self.sender.split('!'))[0]
|
||||
if self.nick != self.sender:
|
||||
self.realname = (self.sender.split('!'))[1]
|
||||
else:
|
||||
self.realname = self.nick
|
||||
|
||||
# Keep the server
|
||||
self.srv = srv
|
||||
self.treatement = self.treat_msg
|
||||
|
||||
# Found a port for the connection
|
||||
self.port = self.foundPort()
|
||||
|
||||
if self.port is None:
|
||||
print ("No more available slot for DCC connection")
|
||||
self.setError("Il n'y a plus de place disponible sur le serveur"
|
||||
" pour initialiser une session DCC.")
|
||||
|
||||
def foundPort(self):
|
||||
"""Found a free port for the connection"""
|
||||
for p in range(65432, 65535):
|
||||
if p not in PORTS:
|
||||
PORTS.append(p)
|
||||
return p
|
||||
return None
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Gives the server identifiant"""
|
||||
return self.srv.id + "/" + self.sender
|
||||
|
||||
def setError(self, msg):
|
||||
self.error = True
|
||||
self.srv.send_msg_usr(self.sender, msg)
|
||||
|
||||
def accept_user(self, host, port):
|
||||
"""Accept a DCC connection"""
|
||||
self.s = socket.socket()
|
||||
try:
|
||||
self.s.connect((host, port))
|
||||
print ('Accepted user from', host, port, "for", self.sender)
|
||||
self.connected = True
|
||||
self.stop = False
|
||||
except:
|
||||
self.connected = False
|
||||
self.error = True
|
||||
return False
|
||||
self.start()
|
||||
return True
|
||||
|
||||
|
||||
def request_user(self, type="CHAT", filename="CHAT", size=""):
|
||||
"""Create a DCC connection"""
|
||||
#Open the port
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
try:
|
||||
s.bind(('', self.port))
|
||||
except:
|
||||
try:
|
||||
self.port = self.foundPort()
|
||||
s.bind(('', self.port))
|
||||
except:
|
||||
self.setError("Une erreur s'est produite durant la tentative"
|
||||
" d'ouverture d'une session DCC.")
|
||||
return False
|
||||
print ('Listen on', self.port, "for", self.sender)
|
||||
|
||||
#Send CTCP request for DCC
|
||||
self.srv.send_ctcp(self.sender,
|
||||
"DCC %s %s %d %d %s" % (type, filename, self.srv.ip,
|
||||
self.port, size),
|
||||
"PRIVMSG")
|
||||
|
||||
s.listen(1)
|
||||
#Waiting for the client
|
||||
(self.s, addr) = s.accept()
|
||||
print ('Connected by', addr)
|
||||
self.connected = True
|
||||
return True
|
||||
|
||||
def send_dcc_raw(self, line):
|
||||
self.s.sendall(line + b'\n')
|
||||
|
||||
def send_dcc(self, msg, to = None):
|
||||
"""If we talk to this user, send a message through this connection
|
||||
else, send the message to the server class"""
|
||||
if to is None or to == self.sender or to == self.nick:
|
||||
if self.error:
|
||||
self.srv.send_msg_final(self.nick, msg)
|
||||
elif not self.connected or self.s is None:
|
||||
try:
|
||||
self.start()
|
||||
except RuntimeError:
|
||||
pass
|
||||
self.messages.append(msg)
|
||||
else:
|
||||
for line in msg.split("\n"):
|
||||
self.send_dcc_raw(line.encode())
|
||||
else:
|
||||
self.srv.send_dcc(msg, to)
|
||||
|
||||
def send_file(self, filename):
|
||||
"""Send a file over DCC"""
|
||||
if os.path.isfile(filename):
|
||||
self.messages = filename
|
||||
try:
|
||||
self.start()
|
||||
except RuntimeError:
|
||||
pass
|
||||
else:
|
||||
print("File not found `%s'" % filename)
|
||||
|
||||
def run(self):
|
||||
self.stopping.clear()
|
||||
|
||||
# Send file connection
|
||||
if not isinstance(self.messages, list):
|
||||
self.request_user("SEND",
|
||||
os.path.basename(self.messages),
|
||||
os.path.getsize(self.messages))
|
||||
if self.connected:
|
||||
with open(self.messages, 'rb') as f:
|
||||
d = f.read(268435456) #Packets size: 256Mo
|
||||
while d:
|
||||
self.s.sendall(d)
|
||||
self.s.recv(4) #The client send a confirmation after each packet
|
||||
d = f.read(268435456) #Packets size: 256Mo
|
||||
|
||||
# Messages connection
|
||||
else:
|
||||
if not self.connected:
|
||||
if not self.request_user():
|
||||
#TODO: do something here
|
||||
return False
|
||||
|
||||
#Start by sending all queued messages
|
||||
for mess in self.messages:
|
||||
self.send_dcc(mess)
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
readbuffer = b''
|
||||
self.nicksize = len(self.srv.nick)
|
||||
self.Bnick = self.srv.nick.encode()
|
||||
while not self.stop:
|
||||
raw = self.s.recv(1024) #recieve server messages
|
||||
if not raw:
|
||||
break
|
||||
readbuffer = readbuffer + raw
|
||||
temp = readbuffer.split(b'\n')
|
||||
readbuffer = temp.pop()
|
||||
|
||||
for line in temp:
|
||||
self.treatement(line)
|
||||
|
||||
if self.connected:
|
||||
self.s.close()
|
||||
self.connected = False
|
||||
|
||||
#Remove from DCC connections server list
|
||||
if self.realname in self.srv.dcc_clients:
|
||||
del self.srv.dcc_clients[self.realname]
|
||||
|
||||
print ("Closing connection with", self.nick)
|
||||
self.stopping.set()
|
||||
if self.closing_event is not None:
|
||||
self.closing_event()
|
||||
#Rearm Thread
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def treat_msg(self, line):
|
||||
"""Treat a receive message, *can be overwritten*"""
|
||||
if line == b'NEMUBOT###':
|
||||
bot = self.srv.add_networkbot(self.srv, self.sender, self)
|
||||
self.treatement = bot.treat_msg
|
||||
self.send_dcc("NEMUBOT###")
|
||||
elif (line[:self.nicksize] == self.Bnick and
|
||||
line[self.nicksize+1:].strip()[:10] == b'my name is'):
|
||||
name = line[self.nicksize+1:].strip()[11:].decode('utf-8',
|
||||
'replace')
|
||||
if re.match("^[a-zA-Z0-9_-]+$", name):
|
||||
if name not in self.srv.dcc_clients:
|
||||
del self.srv.dcc_clients[self.sender]
|
||||
self.nick = name
|
||||
self.sender = self.nick + "!" + self.realname
|
||||
self.srv.dcc_clients[self.realname] = self
|
||||
self.send_dcc("Hi " + self.nick)
|
||||
else:
|
||||
self.send_dcc("This nickname is already in use"
|
||||
", please choose another one.")
|
||||
else:
|
||||
self.send_dcc("The name you entered contain"
|
||||
" invalid char.")
|
||||
else:
|
||||
self.srv.treat_msg(
|
||||
(":%s PRIVMSG %s :" % (
|
||||
self.sender,self.srv.nick)).encode() + line,
|
||||
True)
|
||||
21
Dockerfile
21
Dockerfile
|
|
@ -1,21 +0,0 @@
|
|||
FROM python:3.11-alpine
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
COPY requirements.txt /usr/src/app/
|
||||
RUN apk add --no-cache bash build-base capstone-dev mandoc-doc man-db w3m youtube-dl aspell aspell-fr py3-matrix-nio && \
|
||||
pip install --no-cache-dir --ignore-installed -r requirements.txt && \
|
||||
pip install bs4 capstone dnspython openai && \
|
||||
apk del build-base capstone-dev && \
|
||||
ln -s /var/lib/nemubot/home /home/nemubot
|
||||
|
||||
VOLUME /var/lib/nemubot
|
||||
|
||||
COPY . /usr/src/app/
|
||||
|
||||
RUN ./setup.py install
|
||||
|
||||
WORKDIR /var/lib/nemubot
|
||||
USER guest
|
||||
ENTRYPOINT [ "python", "-m", "nemubot", "-d", "-P", "", "-M", "/usr/src/app/modules" ]
|
||||
CMD [ "-D", "/var/lib/nemubot" ]
|
||||
290
IRCServer.py
Normal file
290
IRCServer.py
Normal file
|
|
@ -0,0 +1,290 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import errno
|
||||
import os
|
||||
import socket
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
from channel import Channel
|
||||
from DCC import DCC
|
||||
from hooks import Hook
|
||||
import message
|
||||
import server
|
||||
import xmlparser
|
||||
|
||||
class IRCServer(server.Server):
|
||||
"""Class to interact with an IRC server"""
|
||||
|
||||
def __init__(self, node, nick, owner, realname):
|
||||
"""Initialize an IRC server
|
||||
|
||||
Arguments:
|
||||
node -- server node from XML configuration
|
||||
nick -- nick used by the bot on this server
|
||||
owner -- nick used by the bot owner on this server
|
||||
realname -- string used as realname on this server
|
||||
|
||||
"""
|
||||
server.Server.__init__(self)
|
||||
|
||||
self.node = node
|
||||
|
||||
self.nick = nick
|
||||
self.owner = owner
|
||||
self.realname = realname
|
||||
|
||||
# Listen private messages?
|
||||
self.listen_nick = True
|
||||
|
||||
self.dcc_clients = dict()
|
||||
|
||||
self.channels = dict()
|
||||
for chn in self.node.getNodes("channel"):
|
||||
chan = Channel(chn["name"], chn["password"])
|
||||
self.channels[chan.name] = chan
|
||||
|
||||
|
||||
@property
|
||||
def host(self):
|
||||
"""Return the server hostname"""
|
||||
if self.node is not None and self.node.hasAttribute("server"):
|
||||
return self.node["server"]
|
||||
else:
|
||||
return "localhost"
|
||||
|
||||
@property
|
||||
def port(self):
|
||||
"""Return the connection port used on this server"""
|
||||
if self.node is not None and self.node.hasAttribute("port"):
|
||||
return self.node.getInt("port")
|
||||
else:
|
||||
return "6667"
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
"""Return the password used to connect to this server"""
|
||||
if self.node is not None and self.node.hasAttribute("password"):
|
||||
return self.node["password"]
|
||||
else:
|
||||
return None
|
||||
|
||||
@property
|
||||
def allow_all(self):
|
||||
"""If True, treat message from all channels, not only listed one"""
|
||||
return (self.node is not None and self.node.hasAttribute("allowall")
|
||||
and self.node["allowall"] == "true")
|
||||
|
||||
@property
|
||||
def autoconnect(self):
|
||||
"""Autoconnect the server when added"""
|
||||
if self.node is not None and self.node.hasAttribute("autoconnect"):
|
||||
value = self.node["autoconnect"].lower()
|
||||
return value != "no" and value != "off" and value != "false"
|
||||
else:
|
||||
return False
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
"""Gives the server identifiant"""
|
||||
return self.host + ":" + str(self.port)
|
||||
|
||||
def register_hooks(self):
|
||||
self.add_hook(Hook(self.evt_channel, "JOIN"))
|
||||
self.add_hook(Hook(self.evt_channel, "PART"))
|
||||
self.add_hook(Hook(self.evt_server, "NICK"))
|
||||
self.add_hook(Hook(self.evt_server, "QUIT"))
|
||||
self.add_hook(Hook(self.evt_channel, "332"))
|
||||
self.add_hook(Hook(self.evt_channel, "353"))
|
||||
|
||||
def evt_server(self, msg, srv):
|
||||
for chan in self.channels:
|
||||
self.channels[chan].treat(msg.cmd, msg)
|
||||
|
||||
def evt_channel(self, msg, srv):
|
||||
if msg.channel is not None:
|
||||
if msg.channel in self.channels:
|
||||
self.channels[msg.channel].treat(msg.cmd, msg)
|
||||
|
||||
def accepted_channel(self, chan, sender=None):
|
||||
"""Return True if the channel (or the user) is authorized"""
|
||||
if self.allow_all:
|
||||
return True
|
||||
elif self.listen_nick:
|
||||
return (chan in self.channels and (sender is None or sender in
|
||||
self.channels[chan].people)
|
||||
) or chan == self.nick
|
||||
else:
|
||||
return chan in self.channels and (sender is None or sender
|
||||
in self.channels[chan].people)
|
||||
|
||||
def join(self, chan, password=None, force=False):
|
||||
"""Join a channel"""
|
||||
if force or (chan is not None and
|
||||
self.connected and chan not in self.channels):
|
||||
self.channels[chan] = Channel(chan, password)
|
||||
if password is not None:
|
||||
self.s.send(("JOIN %s %s\r\n" % (chan, password)).encode())
|
||||
else:
|
||||
self.s.send(("JOIN %s\r\n" % chan).encode())
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def leave(self, chan):
|
||||
"""Leave a channel"""
|
||||
if chan is not None and self.connected and chan in self.channels:
|
||||
if isinstance(chan, list):
|
||||
for c in chan:
|
||||
self.leave(c)
|
||||
else:
|
||||
self.s.send(("PART %s\r\n" % self.channels[chan].name).encode())
|
||||
del self.channels[chan]
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
# Main loop
|
||||
def run(self):
|
||||
if not self.connected:
|
||||
self.s = socket.socket() #Create the socket
|
||||
try:
|
||||
self.s.connect((self.host, self.port)) #Connect to server
|
||||
except socket.error as e:
|
||||
self.s = None
|
||||
print ("\033[1;31mError:\033[0m Unable to connect to %s:%d: %s"
|
||||
% (self.host, self.port, os.strerror(e.errno)))
|
||||
return
|
||||
self.stopping.clear()
|
||||
|
||||
if self.password != None:
|
||||
self.s.send(b"PASS " + self.password.encode () + b"\r\n")
|
||||
self.s.send(("NICK %s\r\n" % self.nick).encode ())
|
||||
self.s.send(("USER %s %s bla :%s\r\n" % (self.nick, self.host,
|
||||
self.realname)).encode())
|
||||
raw = self.s.recv(1024)
|
||||
if not raw:
|
||||
print ("Unable to connect to %s:%d" % (self.host, self.port))
|
||||
return
|
||||
self.connected = True
|
||||
print ("Connection to %s:%d completed" % (self.host, self.port))
|
||||
|
||||
if len(self.channels) > 0:
|
||||
for chn in self.channels.keys():
|
||||
self.join(self.channels[chn].name,
|
||||
self.channels[chn].password, force=True)
|
||||
|
||||
|
||||
readbuffer = b'' #Here we store all the messages from server
|
||||
try:
|
||||
while not self.stop:
|
||||
readbuffer = readbuffer + raw
|
||||
temp = readbuffer.split(b'\n')
|
||||
readbuffer = temp.pop()
|
||||
|
||||
for line in temp:
|
||||
self.treat_msg(line)
|
||||
raw = self.s.recv(1024) #recieve server messages
|
||||
except socket.error:
|
||||
pass
|
||||
|
||||
if self.connected:
|
||||
self.s.close()
|
||||
self.connected = False
|
||||
if self.closing_event is not None:
|
||||
self.closing_event()
|
||||
print ("Server `%s' successfully stopped." % self.id)
|
||||
self.stopping.set()
|
||||
# Rearm Thread
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
|
||||
# Overwritted methods
|
||||
|
||||
def disconnect(self):
|
||||
"""Close the socket with the server and all DCC client connections"""
|
||||
#Close all DCC connection
|
||||
clts = [c for c in self.dcc_clients]
|
||||
for clt in clts:
|
||||
self.dcc_clients[clt].disconnect()
|
||||
return server.Server.disconnect(self)
|
||||
|
||||
|
||||
|
||||
# Abstract methods
|
||||
|
||||
def send_pong(self, cnt):
|
||||
"""Send a PONG command to the server with argument cnt"""
|
||||
self.s.send(("PONG %s\r\n" % cnt).encode())
|
||||
|
||||
def msg_treated(self, origin):
|
||||
"""Do nothing; here for implement abstract class"""
|
||||
pass
|
||||
|
||||
def send_dcc(self, msg, to):
|
||||
"""Send a message through DCC connection"""
|
||||
if msg is not None and to is not None:
|
||||
realname = to.split("!")[1]
|
||||
if realname not in self.dcc_clients.keys():
|
||||
d = DCC(self, to)
|
||||
self.dcc_clients[realname] = d
|
||||
self.dcc_clients[realname].send_dcc(msg)
|
||||
|
||||
def send_msg_final(self, channel, line, cmd="PRIVMSG", endl="\r\n"):
|
||||
"""Send a message without checks or format"""
|
||||
#TODO: add something for post message treatment here
|
||||
if channel == self.nick:
|
||||
print ("\033[1;35mWarning:\033[0m Nemubot talks to himself: %s" % msg)
|
||||
traceback.print_stack()
|
||||
if line is not None and channel is not None:
|
||||
if self.s is None:
|
||||
print ("\033[1;35mWarning:\033[0m Attempt to send message on a non connected server: %s: %s" % (self.id, line))
|
||||
traceback.print_stack()
|
||||
elif len(line) < 442:
|
||||
self.s.send (("%s %s :%s%s" % (cmd, channel, line, endl)).encode ())
|
||||
else:
|
||||
print ("\033[1;35mWarning:\033[0m Message truncated due to size (%d ; max : 442) : %s" % (len(line), line))
|
||||
traceback.print_stack()
|
||||
self.s.send (("%s %s :%s%s" % (cmd, channel, line[0:442]+"...", endl)).encode ())
|
||||
|
||||
def send_msg_usr(self, user, msg):
|
||||
"""Send a message to a user instead of a channel"""
|
||||
if user is not None and user[0] != "#":
|
||||
realname = user.split("!")[1]
|
||||
if realname in self.dcc_clients or user in self.dcc_clients:
|
||||
self.send_dcc(msg, user)
|
||||
else:
|
||||
for line in msg.split("\n"):
|
||||
if line != "":
|
||||
self.send_msg_final(user.split('!')[0], msg)
|
||||
|
||||
def send_msg(self, channel, msg, cmd="PRIVMSG", endl="\r\n"):
|
||||
"""Send a message to a channel"""
|
||||
if self.accepted_channel(channel):
|
||||
server.Server.send_msg(self, channel, msg, cmd, endl)
|
||||
|
||||
def send_msg_verified(self, sender, channel, msg, cmd = "PRIVMSG", endl = "\r\n"):
|
||||
"""Send a message to a channel, only if the source user is on this channel too"""
|
||||
if self.accepted_channel(channel, sender):
|
||||
self.send_msg_final(channel, msg, cmd, endl)
|
||||
|
||||
def send_global(self, msg, cmd="PRIVMSG", endl="\r\n"):
|
||||
"""Send a message to all channels on this server"""
|
||||
for channel in self.channels.keys():
|
||||
self.send_msg(channel, msg, cmd, endl)
|
||||
49
README.md
49
README.md
|
|
@ -1,50 +1,7 @@
|
|||
nemubot
|
||||
=======
|
||||
# *nemubot*
|
||||
|
||||
An extremely modulable IRC bot, built around XML configuration files!
|
||||
|
||||
## Documentation
|
||||
|
||||
Requirements
|
||||
------------
|
||||
|
||||
*nemubot* requires at least Python 3.3 to work.
|
||||
|
||||
Some modules (like `cve`, `nextstop` or `laposte`) require the
|
||||
[BeautifulSoup module](https://www.crummy.com/software/BeautifulSoup/),
|
||||
but the core and framework has no dependency.
|
||||
|
||||
|
||||
Installation
|
||||
------------
|
||||
|
||||
Use the `setup.py` file: `python setup.py install`.
|
||||
|
||||
### VirtualEnv setup
|
||||
|
||||
The easiest way to do this is through a virtualenv:
|
||||
|
||||
```sh
|
||||
virtualenv venv
|
||||
. venv/bin/activate
|
||||
python setup.py install
|
||||
```
|
||||
|
||||
### Create a new configuration file
|
||||
|
||||
There is a sample configuration file, called `bot_sample.xml`. You can
|
||||
create your own configuration file from it.
|
||||
|
||||
|
||||
Usage
|
||||
-----
|
||||
|
||||
Don't forget to activate your virtualenv in further terminals, if you
|
||||
use it.
|
||||
|
||||
To launch the bot, run:
|
||||
|
||||
```sh
|
||||
nemubot bot.xml
|
||||
```
|
||||
|
||||
Where `bot.xml` is your configuration file.
|
||||
Have a look to the wiki at https://github.com/nemunaire/nemubot/wiki
|
||||
|
|
|
|||
24
bin/nemubot
24
bin/nemubot
|
|
@ -1,24 +0,0 @@
|
|||
#!/usr/bin/env python3
|
||||
|
||||
# Nemubot is a smart and modulable IM bot.
|
||||
# Copyright (C) 2012-2016 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import sys
|
||||
|
||||
from nemubot.__main__ import main
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
641
bot.py
Normal file
641
bot.py
Normal file
|
|
@ -0,0 +1,641 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from queue import Queue
|
||||
import threading
|
||||
import time
|
||||
import re
|
||||
|
||||
import consumer
|
||||
import event
|
||||
import hooks
|
||||
from networkbot import NetworkBot
|
||||
from IRCServer import IRCServer
|
||||
from DCC import DCC
|
||||
import response
|
||||
|
||||
ID_letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
|
||||
class Bot:
|
||||
def __init__(self, ip, realname, mp=list()):
|
||||
# Bot general informations
|
||||
self.version = 3.3
|
||||
self.version_txt = "3.3-dev"
|
||||
|
||||
# Save various informations
|
||||
self.ip = ip
|
||||
self.realname = realname
|
||||
self.ctcp_capabilities = dict()
|
||||
self.init_ctcp_capabilities()
|
||||
|
||||
# Keep global context: servers and modules
|
||||
self.servers = dict()
|
||||
self.modules = dict()
|
||||
|
||||
# Context paths
|
||||
self.modules_path = mp
|
||||
self.datas_path = './datas/'
|
||||
|
||||
# Events
|
||||
self.events = list()
|
||||
self.event_timer = None
|
||||
|
||||
# Own hooks
|
||||
self.hooks = hooks.MessagesHook(self, self)
|
||||
|
||||
# Other known bots, making a bots network
|
||||
self.network = dict()
|
||||
self.hooks_cache = dict()
|
||||
|
||||
# Messages to be treated
|
||||
self.cnsr_queue = Queue()
|
||||
self.cnsr_thrd = list()
|
||||
self.cnsr_thrd_size = -1
|
||||
|
||||
self.hooks.add_hook("irc_hook",
|
||||
hooks.Hook(self.treat_prvmsg, "PRIVMSG"),
|
||||
self)
|
||||
|
||||
|
||||
def init_ctcp_capabilities(self):
|
||||
"""Reset existing CTCP capabilities to default one"""
|
||||
self.ctcp_capabilities["ACTION"] = lambda msg: print ("ACTION receive")
|
||||
self.ctcp_capabilities["CLIENTINFO"] = self._ctcp_clientinfo
|
||||
self.ctcp_capabilities["DCC"] = self._ctcp_dcc
|
||||
self.ctcp_capabilities["NEMUBOT"] = lambda srv, msg: _ctcp_response(
|
||||
msg.sender, "NEMUBOT %f" % self.version)
|
||||
self.ctcp_capabilities["TIME"] = lambda srv, msg: _ctcp_response(
|
||||
msg.sender, "TIME %s" % (datetime.now()))
|
||||
self.ctcp_capabilities["USERINFO"] = lambda srv, msg: _ctcp_response(
|
||||
msg.sender, "USERINFO %s" % self.realname)
|
||||
self.ctcp_capabilities["VERSION"] = lambda srv, msg: _ctcp_response(
|
||||
msg.sender, "VERSION nemubot v%s" % self.version_txt)
|
||||
|
||||
def _ctcp_clientinfo(self, srv, msg):
|
||||
"""Response to CLIENTINFO CTCP message"""
|
||||
return _ctcp_response(msg.sndr,
|
||||
" ".join(self.ctcp_capabilities.keys()))
|
||||
|
||||
def _ctcp_dcc(self, srv, msg):
|
||||
"""Response to DCC CTCP message"""
|
||||
ip = srv.toIP(int(msg.cmds[3]))
|
||||
conn = DCC(srv, msg.sender)
|
||||
if conn.accept_user(ip, int(msg.cmds[4])):
|
||||
srv.dcc_clients[conn.sender] = conn
|
||||
conn.send_dcc("Hello %s!" % conn.nick)
|
||||
else:
|
||||
print ("DCC: unable to connect to %s:%s" % (ip, msg.cmds[4]))
|
||||
|
||||
|
||||
def add_event(self, evt, eid=None, module_src=None):
|
||||
"""Register an event and return its identifiant for futur update"""
|
||||
if eid is None:
|
||||
# Find an ID
|
||||
now = datetime.now()
|
||||
evt.id = "%d%c%d%d%c%d%d%c%d" % (now.year, ID_letters[now.microsecond % 52],
|
||||
now.month, now.day, ID_letters[now.microsecond % 42],
|
||||
now.hour, now.minute, ID_letters[now.microsecond % 32],
|
||||
now.second)
|
||||
else:
|
||||
evt.id = eid
|
||||
|
||||
# Add the event in place
|
||||
t = evt.current
|
||||
i = -1
|
||||
for i in range(0, len(self.events)):
|
||||
if self.events[i].current > t:
|
||||
i -= 1
|
||||
break
|
||||
self.events.insert(i + 1, evt)
|
||||
if i == -1:
|
||||
self.update_timer()
|
||||
if len(self.events) <= 0 or self.events[i+1] != evt:
|
||||
return None
|
||||
|
||||
if module_src is not None:
|
||||
module_src.REGISTERED_EVENTS.append(evt.id)
|
||||
|
||||
return evt.id
|
||||
|
||||
def del_event(self, id, module_src=None):
|
||||
"""Find and remove an event from list"""
|
||||
if len(self.events) > 0 and id == self.events[0].id:
|
||||
self.events.remove(self.events[0])
|
||||
self.update_timer()
|
||||
if module_src is not None:
|
||||
module_src.REGISTERED_EVENTS.remove(evt.id)
|
||||
return True
|
||||
|
||||
for evt in self.events:
|
||||
if evt.id == id:
|
||||
self.events.remove(evt)
|
||||
|
||||
if module_src is not None:
|
||||
module_src.REGISTERED_EVENTS.remove(evt.id)
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_timer(self):
|
||||
"""Relaunch the timer to end with the closest event"""
|
||||
# Reset the timer if this is the first item
|
||||
if self.event_timer is not None:
|
||||
self.event_timer.cancel()
|
||||
if len(self.events) > 0:
|
||||
#print ("Update timer, next in", self.events[0].time_left.seconds,
|
||||
# "seconds")
|
||||
if datetime.now() + timedelta(seconds=5) >= self.events[0].current:
|
||||
while datetime.now() < self.events[0].current:
|
||||
time.sleep(0.6)
|
||||
self.end_timer()
|
||||
else:
|
||||
self.event_timer = threading.Timer(
|
||||
self.events[0].time_left.seconds + 1, self.end_timer)
|
||||
self.event_timer.start()
|
||||
#else:
|
||||
# print ("Update timer: no timer left")
|
||||
|
||||
def end_timer(self):
|
||||
"""Function called at the end of the timer"""
|
||||
#print ("end timer")
|
||||
while len(self.events)>0 and datetime.now() >= self.events[0].current:
|
||||
#print ("end timer: while")
|
||||
evt = self.events.pop(0)
|
||||
self.cnsr_queue.put_nowait(consumer.EventConsumer(evt))
|
||||
self.update_consumers()
|
||||
|
||||
self.update_timer()
|
||||
|
||||
|
||||
def addServer(self, node, nick, owner, realname):
|
||||
"""Add a new server to the context"""
|
||||
srv = IRCServer(node, nick, owner, realname)
|
||||
srv.add_hook = lambda h: self.hooks.add_hook("irc_hook", h, self)
|
||||
srv.add_networkbot = self.add_networkbot
|
||||
srv.send_bot = lambda d: self.send_networkbot(srv, d)
|
||||
srv.register_hooks()
|
||||
if srv.id not in self.servers:
|
||||
self.servers[srv.id] = srv
|
||||
if srv.autoconnect:
|
||||
srv.launch(self.receive_message)
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def add_module(self, module):
|
||||
"""Add a module to the context, if already exists, unload the
|
||||
old one before"""
|
||||
# Check if the module already exists
|
||||
for mod in self.modules.keys():
|
||||
if self.modules[mod].name == module.name:
|
||||
self.unload_module(self.modules[mod].name)
|
||||
break
|
||||
|
||||
self.modules[module.name] = module
|
||||
return True
|
||||
|
||||
|
||||
def add_modules_path(self, path):
|
||||
"""Add a path to the modules_path array, used by module loader"""
|
||||
# The path must end by / char
|
||||
if path[len(path)-1] != "/":
|
||||
path = path + "/"
|
||||
|
||||
if path not in self.modules_path:
|
||||
self.modules_path.append(path)
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def unload_module(self, name, verb=False):
|
||||
"""Unload a module"""
|
||||
if name in self.modules:
|
||||
print (name)
|
||||
self.modules[name].save()
|
||||
if hasattr(self.modules[name], "unload"):
|
||||
self.modules[name].unload(self)
|
||||
# Remove registered hooks
|
||||
for (s, h) in self.modules[name].REGISTERED_HOOKS:
|
||||
self.hooks.del_hook(s, h)
|
||||
# Remove registered events
|
||||
for e in self.modules[name].REGISTERED_EVENTS:
|
||||
self.del_event(e)
|
||||
# Remove from the dict
|
||||
del self.modules[name]
|
||||
return True
|
||||
return False
|
||||
|
||||
def update_consumers(self):
|
||||
"""Launch new consumer thread if necessary"""
|
||||
if self.cnsr_queue.qsize() > self.cnsr_thrd_size:
|
||||
c = consumer.Consumer(self)
|
||||
self.cnsr_thrd.append(c)
|
||||
c.start()
|
||||
self.cnsr_thrd_size += 2
|
||||
|
||||
|
||||
def receive_message(self, srv, raw_msg, private=False, data=None):
|
||||
"""Queued the message for treatment"""
|
||||
#print (raw_msg)
|
||||
self.cnsr_queue.put_nowait(consumer.MessageConsumer(srv, raw_msg, datetime.now(), private, data))
|
||||
|
||||
# Launch a new thread if necessary
|
||||
self.update_consumers()
|
||||
|
||||
|
||||
def add_networkbot(self, srv, dest, dcc=None):
|
||||
"""Append a new bot into the network"""
|
||||
id = srv.id + "/" + dest
|
||||
if id not in self.network:
|
||||
self.network[id] = NetworkBot(self, srv, dest, dcc)
|
||||
return self.network[id]
|
||||
|
||||
def send_networkbot(self, srv, cmd, data=None):
|
||||
for bot in self.network:
|
||||
if self.network[bot].srv == srv:
|
||||
self.network[bot].send_cmd(cmd, data)
|
||||
|
||||
def quit(self, verb=False):
|
||||
"""Save and unload modules and disconnect servers"""
|
||||
if self.event_timer is not None:
|
||||
if verb: print ("Stop the event timer...")
|
||||
self.event_timer.cancel()
|
||||
|
||||
if verb: print ("Save and unload all modules...")
|
||||
k = list(self.modules.keys())
|
||||
for mod in k:
|
||||
self.unload_module(mod, verb)
|
||||
|
||||
if verb: print ("Close all servers connection...")
|
||||
k = list(self.servers.keys())
|
||||
for srv in k:
|
||||
self.servers[srv].disconnect()
|
||||
|
||||
# Hooks cache
|
||||
|
||||
def create_cache(self, name):
|
||||
if name not in self.hooks_cache:
|
||||
if isinstance(self.hooks.__dict__[name], list):
|
||||
self.hooks_cache[name] = list()
|
||||
|
||||
# Start by adding locals hooks
|
||||
for h in self.hooks.__dict__[name]:
|
||||
tpl = (h, 0, self.hooks.__dict__[name], self.hooks.bot)
|
||||
self.hooks_cache[name].append(tpl)
|
||||
|
||||
# Now, add extermal hooks
|
||||
level = 0
|
||||
while level == 0 or lvl_exist:
|
||||
lvl_exist = False
|
||||
for ext in self.network:
|
||||
if len(self.network[ext].hooks) > level:
|
||||
lvl_exist = True
|
||||
for h in self.network[ext].hooks[level].__dict__[name]:
|
||||
if h not in self.hooks_cache[name]:
|
||||
self.hooks_cache[name].append((h, level + 1,
|
||||
self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot))
|
||||
level += 1
|
||||
|
||||
elif isinstance(self.hooks.__dict__[name], dict):
|
||||
self.hooks_cache[name] = dict()
|
||||
|
||||
# Start by adding locals hooks
|
||||
for h in self.hooks.__dict__[name]:
|
||||
self.hooks_cache[name][h] = (self.hooks.__dict__[name][h], 0,
|
||||
self.hooks.__dict__[name],
|
||||
self.hooks.bot)
|
||||
|
||||
# Now, add extermal hooks
|
||||
level = 0
|
||||
while level == 0 or lvl_exist:
|
||||
lvl_exist = False
|
||||
for ext in self.network:
|
||||
if len(self.network[ext].hooks) > level:
|
||||
lvl_exist = True
|
||||
for h in self.network[ext].hooks[level].__dict__[name]:
|
||||
if h not in self.hooks_cache[name]:
|
||||
self.hooks_cache[name][h] = (self.network[ext].hooks[level].__dict__[name][h], level + 1, self.network[ext].hooks[level].__dict__[name], self.network[ext].hooks[level].bot)
|
||||
level += 1
|
||||
|
||||
else:
|
||||
raise Exception(name + " hook type unrecognized")
|
||||
|
||||
return self.hooks_cache[name]
|
||||
|
||||
# Treatment
|
||||
|
||||
def check_rest_times(self, store, hook):
|
||||
"""Remove from store the hook if it has been executed given time"""
|
||||
if hook.times == 0:
|
||||
if isinstance(store, dict):
|
||||
store[hook.name].remove(hook)
|
||||
if len(store) == 0:
|
||||
del store[hook.name]
|
||||
elif isinstance(store, list):
|
||||
store.remove(hook)
|
||||
|
||||
def treat_pre(self, msg, srv):
|
||||
"""Treat a message before all other treatment"""
|
||||
for h, lvl, store, bot in self.create_cache("all_pre"):
|
||||
if h.is_matching(None, server=srv):
|
||||
h.run(msg, self.create_cache)
|
||||
self.check_rest_times(store, h)
|
||||
|
||||
|
||||
def treat_post(self, res):
|
||||
"""Treat a message before send"""
|
||||
for h, lvl, store, bot in self.create_cache("all_post"):
|
||||
if h.is_matching(None, channel=res.channel, server=res.server):
|
||||
c = h.run(res)
|
||||
self.check_rest_times(store, h)
|
||||
if not c:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def treat_irc(self, msg, srv):
|
||||
"""Treat all incoming IRC commands"""
|
||||
treated = list()
|
||||
|
||||
irc_hooks = self.create_cache("irc_hook")
|
||||
if msg.cmd in irc_hooks:
|
||||
(hks, lvl, store, bot) = irc_hooks[msg.cmd]
|
||||
for h in hks:
|
||||
if h.is_matching(msg.cmd, server=srv):
|
||||
res = h.run(msg, srv, msg.cmd)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, h)
|
||||
|
||||
return treated
|
||||
|
||||
|
||||
def treat_prvmsg_ask(self, msg, srv):
|
||||
# Treat ping
|
||||
if re.match("^ *(m[' ]?entends?[ -]+tu|h?ear me|do you copy|ping)",
|
||||
msg.content, re.I) is not None:
|
||||
return response.Response(msg.sender, message="pong",
|
||||
channel=msg.channel, nick=msg.nick)
|
||||
|
||||
# Ask hooks
|
||||
else:
|
||||
return self.treat_ask(msg, srv)
|
||||
|
||||
def treat_prvmsg(self, msg, srv):
|
||||
# First, treat CTCP
|
||||
if msg.ctcp:
|
||||
if msg.cmds[0] in self.ctcp_capabilities:
|
||||
return self.ctcp_capabilities[msg.cmds[0]](srv, msg)
|
||||
else:
|
||||
return _ctcp_response(msg.sender, "ERRMSG Unknown or unimplemented CTCP request")
|
||||
|
||||
# Treat all messages starting with 'nemubot:' as distinct commands
|
||||
elif msg.content.find("%s:"%srv.nick) == 0:
|
||||
# Remove the bot name
|
||||
msg.content = msg.content[len(srv.nick)+1:].strip()
|
||||
|
||||
return self.treat_prvmsg_ask(msg, srv)
|
||||
|
||||
# Owner commands
|
||||
elif msg.content[0] == '`' and msg.nick == srv.owner:
|
||||
#TODO: owner commands
|
||||
pass
|
||||
|
||||
elif msg.content[0] == '!' and len(msg.content) > 1:
|
||||
# Remove the !
|
||||
msg.cmds[0] = msg.cmds[0][1:]
|
||||
|
||||
if msg.cmds[0] == "help":
|
||||
return _help_msg(msg.sender, self.modules, msg.cmds)
|
||||
|
||||
elif msg.cmds[0] == "more":
|
||||
if msg.channel == srv.nick:
|
||||
if msg.sender in srv.moremessages:
|
||||
return srv.moremessages[msg.sender]
|
||||
else:
|
||||
if msg.channel in srv.moremessages:
|
||||
return srv.moremessages[msg.channel]
|
||||
|
||||
elif msg.cmds[0] == "dcc":
|
||||
print("dcctest for", msg.sender)
|
||||
srv.send_dcc("Hello %s!" % msg.nick, msg.sender)
|
||||
elif msg.cmds[0] == "pvdcctest":
|
||||
print("dcctest")
|
||||
return Response(msg.sender, message="Test DCC")
|
||||
elif msg.cmds[0] == "dccsendtest":
|
||||
print("dccsendtest")
|
||||
conn = DCC(srv, msg.sender)
|
||||
conn.send_file("bot_sample.xml")
|
||||
|
||||
else:
|
||||
return self.treat_cmd(msg, srv)
|
||||
|
||||
else:
|
||||
res = self.treat_answer(msg, srv)
|
||||
# Assume the message starts with nemubot:
|
||||
if (res is None or len(res) <= 0) and msg.private:
|
||||
return self.treat_prvmsg_ask(msg, srv)
|
||||
return res
|
||||
|
||||
|
||||
def treat_cmd(self, msg, srv):
|
||||
"""Treat a command message"""
|
||||
treated = list()
|
||||
|
||||
# First, treat simple hook
|
||||
cmd_hook = self.create_cache("cmd_hook")
|
||||
if msg.cmds[0] in cmd_hook:
|
||||
(hks, lvl, store, bot) = cmd_hook[msg.cmds[0]]
|
||||
for h in hks:
|
||||
if h.is_matching(msg.cmds[0], channel=msg.channel, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.channel].people):
|
||||
res = h.run(msg, strcmp=msg.cmds[0])
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, h)
|
||||
|
||||
# Then, treat regexp based hook
|
||||
cmd_rgxp = self.create_cache("cmd_rgxp")
|
||||
for hook, lvl, store, bot in cmd_rgxp:
|
||||
if hook.is_matching(msg.cmds[0], msg.channel, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.channel].people):
|
||||
res = hook.run(msg)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, hook)
|
||||
|
||||
# Finally, treat default hooks if not catched before
|
||||
cmd_default = self.create_cache("cmd_default")
|
||||
for hook, lvl, store, bot in cmd_default:
|
||||
if treated:
|
||||
break
|
||||
res = hook.run(msg)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, hook)
|
||||
|
||||
return treated
|
||||
|
||||
def treat_ask(self, msg, srv):
|
||||
"""Treat an ask message"""
|
||||
treated = list()
|
||||
|
||||
# First, treat simple hook
|
||||
ask_hook = self.create_cache("ask_hook")
|
||||
if msg.content in ask_hook:
|
||||
hks, lvl, store, bot = ask_hook[msg.content]
|
||||
for h in hks:
|
||||
if h.is_matching(msg.content, channel=msg.channel, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.channel].people):
|
||||
res = h.run(msg, strcmp=msg.content)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, h)
|
||||
|
||||
# Then, treat regexp based hook
|
||||
ask_rgxp = self.create_cache("ask_rgxp")
|
||||
for hook, lvl, store, bot in ask_rgxp:
|
||||
if hook.is_matching(msg.content, channel=msg.channel, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.channel].people):
|
||||
res = hook.run(msg, strcmp=msg.content)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, hook)
|
||||
|
||||
# Finally, treat default hooks if not catched before
|
||||
ask_default = self.create_cache("ask_default")
|
||||
for hook, lvl, store, bot in ask_default:
|
||||
if treated:
|
||||
break
|
||||
res = hook.run(msg)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, hook)
|
||||
|
||||
return treated
|
||||
|
||||
def treat_answer(self, msg, srv):
|
||||
"""Treat a normal message"""
|
||||
treated = list()
|
||||
|
||||
# First, treat simple hook
|
||||
msg_hook = self.create_cache("msg_hook")
|
||||
if msg.content in msg_hook:
|
||||
hks, lvl, store, bot = msg_hook[msg.content]
|
||||
for h in hks:
|
||||
if h.is_matching(msg.content, channel=msg.channel, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.channel].people):
|
||||
res = h.run(msg, strcmp=msg.content)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, h)
|
||||
|
||||
# Then, treat regexp based hook
|
||||
msg_rgxp = self.create_cache("msg_rgxp")
|
||||
for hook, lvl, store, bot in msg_rgxp:
|
||||
if hook.is_matching(msg.content, channel=msg.channel, server=srv) and (msg.private or lvl == 0 or bot.nick not in srv.channels[msg.channel].people):
|
||||
res = hook.run(msg, strcmp=msg.content)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, hook)
|
||||
|
||||
# Finally, treat default hooks if not catched before
|
||||
msg_default = self.create_cache("msg_default")
|
||||
for hook, lvl, store, bot in msg_default:
|
||||
if len(treated) > 0:
|
||||
break
|
||||
res = hook.run(msg)
|
||||
if res is not None and res != False:
|
||||
treated.append(res)
|
||||
self.check_rest_times(store, hook)
|
||||
|
||||
return treated
|
||||
|
||||
def _ctcp_response(sndr, msg):
|
||||
return response.Response(sndr, msg, ctcp=True)
|
||||
|
||||
|
||||
def _help_msg(sndr, modules, cmd):
|
||||
"""Parse and response to help messages"""
|
||||
res = response.Response(sndr)
|
||||
if len(cmd) > 1:
|
||||
if cmd[1] in modules:
|
||||
if len(cmd) > 2:
|
||||
if hasattr(modules[cmd[1]], "HELP_cmd"):
|
||||
res.append_message(modules[cmd[1]].HELP_cmd(cmd[2]))
|
||||
else:
|
||||
res.append_message("No help for command %s in module %s" % (cmd[2], cmd[1]))
|
||||
elif hasattr(modules[cmd[1]], "help_full"):
|
||||
res.append_message(modules[cmd[1]].help_full())
|
||||
else:
|
||||
res.append_message("No help for module %s" % cmd[1])
|
||||
else:
|
||||
res.append_message("No module named %s" % cmd[1])
|
||||
else:
|
||||
res.append_message("Pour me demander quelque chose, commencez "
|
||||
"votre message par mon nom ; je réagis "
|
||||
"également à certaine commandes commençant par"
|
||||
" !. Pour plus d'informations, envoyez le "
|
||||
"message \"!more\".")
|
||||
res.append_message("Mon code source est libre, publié sous "
|
||||
"licence AGPL (http://www.gnu.org/licenses/). "
|
||||
"Vous pouvez le consulter, le dupliquer, "
|
||||
"envoyer des rapports de bogues ou bien "
|
||||
"contribuer au projet sur GitHub : "
|
||||
"http://github.com/nemunaire/nemubot/")
|
||||
res.append_message(title="Pour plus de détails sur un module, "
|
||||
"envoyez \"!help nomdumodule\". Voici la liste"
|
||||
" de tous les modules disponibles localement",
|
||||
message=["\x03\x02%s\x03\x02 (%s)" % (im, modules[im].help_tiny ()) for im in modules if hasattr(modules[im], "help_tiny")])
|
||||
return res
|
||||
|
||||
def hotswap(bak):
|
||||
return Bot(bak.servers, bak.modules, bak.modules_path)
|
||||
|
||||
def reload():
|
||||
import imp
|
||||
|
||||
import channel
|
||||
imp.reload(channel)
|
||||
|
||||
import consumer
|
||||
imp.reload(consumer)
|
||||
|
||||
import DCC
|
||||
imp.reload(DCC)
|
||||
|
||||
import event
|
||||
imp.reload(event)
|
||||
|
||||
import hooks
|
||||
imp.reload(hooks)
|
||||
|
||||
import importer
|
||||
imp.reload(importer)
|
||||
|
||||
import message
|
||||
imp.reload(message)
|
||||
|
||||
import prompt.builtins
|
||||
imp.reload(prompt.builtins)
|
||||
|
||||
import server
|
||||
imp.reload(server)
|
||||
|
||||
import xmlparser
|
||||
imp.reload(xmlparser)
|
||||
import xmlparser.node
|
||||
imp.reload(xmlparser.node)
|
||||
|
|
@ -1,23 +1,13 @@
|
|||
<nemubotconfig nick="nemubot" realname="nemubot clone" owner="someone">
|
||||
|
||||
<server uri="irc://irc.rezosup.org:6667" autoconnect="true" caps="znc.in/server-time-iso">
|
||||
<nemubotconfig nick="nemubot" realname="nemubot speaker" owner="someone">
|
||||
<server server="irc.freenode.org" port="6667" password="secret" autoconnect="true">
|
||||
<channel name="#nemutest" />
|
||||
</server>
|
||||
|
||||
<!--
|
||||
<server host="ircs://my_host.local:6667" password="secret" autoconnect="true">
|
||||
<channel name="#nemutest" />
|
||||
</server>
|
||||
-->
|
||||
|
||||
<!--
|
||||
<module name="wolframalpha" apikey="YOUR-APIKEY" />
|
||||
-->
|
||||
|
||||
<module name="cmd_server" />
|
||||
|
||||
<module name="alias" />
|
||||
<module name="ycc" />
|
||||
<module name="events" />
|
||||
|
||||
<load path="modules/birthday.xml" />
|
||||
<load path="modules/ycc.xml" />
|
||||
<load path="modules/qcm.xml" />
|
||||
<load path="modules/soutenance.xml" />
|
||||
<load path="modules/velib.xml" />
|
||||
<load path="modules/whereis.xml" />
|
||||
<load path="modules/watchWebsite.xml" />
|
||||
<load path="modules/events.xml" />
|
||||
</nemubotconfig>
|
||||
|
|
|
|||
102
channel.py
Normal file
102
channel.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# coding=utf-8
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
class Channel:
|
||||
def __init__(self, name, password=None):
|
||||
self.name = name
|
||||
self.password = password
|
||||
self.people = dict()
|
||||
self.topic = ""
|
||||
|
||||
def treat(self, cmd, msg):
|
||||
if cmd == "353":
|
||||
self.parse353(msg)
|
||||
elif cmd == "332":
|
||||
self.parse332(msg)
|
||||
elif cmd == "MODE":
|
||||
self.mode(msg)
|
||||
elif cmd == "JOIN":
|
||||
self.join(msg.nick)
|
||||
elif cmd == "NICK":
|
||||
self.nick(msg.nick, msg.content)
|
||||
elif cmd == "PART" or cmd == "QUIT":
|
||||
self.part(msg.nick)
|
||||
elif cmd == "TOPIC":
|
||||
self.topic = self.content
|
||||
|
||||
def join(self, nick, level = 0):
|
||||
"""Someone join the channel"""
|
||||
#print ("%s arrive sur %s" % (nick, self.name))
|
||||
self.people[nick] = level
|
||||
|
||||
def chtopic(self, newtopic):
|
||||
"""Send command to change the topic"""
|
||||
self.srv.send_msg(self.name, newtopic, "TOPIC")
|
||||
self.topic = newtopic
|
||||
|
||||
def nick(self, oldnick, newnick):
|
||||
"""Someone change his nick"""
|
||||
if oldnick in self.people:
|
||||
#print ("%s change de nom pour %s sur %s" % (oldnick, newnick, self.name))
|
||||
lvl = self.people[oldnick]
|
||||
del self.people[oldnick]
|
||||
self.people[newnick] = lvl
|
||||
|
||||
def part(self, nick):
|
||||
"""Someone leave the channel"""
|
||||
if nick in self.people:
|
||||
#print ("%s vient de quitter %s" % (nick, self.name))
|
||||
del self.people[nick]
|
||||
|
||||
def mode(self, msg):
|
||||
if msg.content[0] == "-k":
|
||||
self.password = ""
|
||||
elif msg.content[0] == "+k":
|
||||
if len(msg.content) > 1:
|
||||
self.password = ' '.join(msg.content[1:])[1:]
|
||||
else:
|
||||
self.password = msg.content[1]
|
||||
elif msg.content[0] == "+o":
|
||||
self.people[msg.nick] |= 4
|
||||
elif msg.content[0] == "-o":
|
||||
self.people[msg.nick] &= ~4
|
||||
elif msg.content[0] == "+h":
|
||||
self.people[msg.nick] |= 2
|
||||
elif msg.content[0] == "-h":
|
||||
self.people[msg.nick] &= ~2
|
||||
elif msg.content[0] == "+v":
|
||||
self.people[msg.nick] |= 1
|
||||
elif msg.content[0] == "-v":
|
||||
self.people[msg.nick] &= ~1
|
||||
|
||||
def parse332(self, msg):
|
||||
self.topic = msg.content
|
||||
|
||||
def parse353(self, msg):
|
||||
for p in msg.content:
|
||||
p = p.decode()
|
||||
if p[0] == "@":
|
||||
level = 4
|
||||
elif p[0] == "%":
|
||||
level = 2
|
||||
elif p[0] == "+":
|
||||
level = 1
|
||||
else:
|
||||
self.join(p, 0)
|
||||
continue
|
||||
self.join(p[1:], level)
|
||||
143
consumer.py
Normal file
143
consumer.py
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import queue
|
||||
import re
|
||||
import threading
|
||||
import traceback
|
||||
import sys
|
||||
|
||||
import bot
|
||||
from DCC import DCC
|
||||
from message import Message
|
||||
import response
|
||||
import server
|
||||
|
||||
class MessageConsumer:
|
||||
"""Store a message before treating"""
|
||||
def __init__(self, srv, raw, time, prvt, data):
|
||||
self.srv = srv
|
||||
self.raw = raw
|
||||
self.time = time
|
||||
self.prvt = prvt
|
||||
self.data = data
|
||||
|
||||
|
||||
def treat_in(self, context, msg):
|
||||
"""Treat the input message"""
|
||||
if msg.cmd == "PING":
|
||||
self.srv.send_pong(msg.content)
|
||||
else:
|
||||
# TODO: Manage credits
|
||||
if msg.channel is None or self.srv.accepted_channel(msg.channel):
|
||||
# All messages
|
||||
context.treat_pre(msg, self.srv)
|
||||
|
||||
return context.treat_irc(msg, self.srv)
|
||||
|
||||
def treat_out(self, context, res):
|
||||
"""Treat the output message"""
|
||||
if isinstance(res, list):
|
||||
for r in res:
|
||||
if r is not None: self.treat_out(context, r)
|
||||
|
||||
elif isinstance(res, response.Response):
|
||||
# Define the destination server
|
||||
if (res.server is not None and
|
||||
isinstance(res.server, str) and res.server in context.servers):
|
||||
res.server = context.servers[res.server]
|
||||
if (res.server is not None and
|
||||
not isinstance(res.server, server.Server)):
|
||||
print ("\033[1;35mWarning:\033[0m the server defined in this "
|
||||
"response doesn't exist: %s" % (res.server))
|
||||
res.server = None
|
||||
if res.server is None:
|
||||
res.server = self.srv
|
||||
|
||||
# Sent the message only if treat_post authorize it
|
||||
if context.treat_post(res):
|
||||
res.server.send_response(res, self.data)
|
||||
|
||||
elif isinstance(res, response.Hook):
|
||||
context.hooks.add_hook(res.type, res.hook, res.src)
|
||||
|
||||
elif res is not None:
|
||||
print ("\033[1;35mWarning:\033[0m unrecognized response type "
|
||||
": %s" % res)
|
||||
|
||||
def run(self, context):
|
||||
"""Create, parse and treat the message"""
|
||||
try:
|
||||
msg = Message(self.raw, self.time, self.prvt)
|
||||
msg.server = self.srv.id
|
||||
if msg.cmd == "PRIVMSG":
|
||||
msg.is_owner = (msg.nick == self.srv.owner)
|
||||
res = self.treat_in(context, msg)
|
||||
except:
|
||||
print ("\033[1;31mERROR:\033[0m occurred during the "
|
||||
"processing of the message: %s" % self.raw)
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
traceback.print_exception(exc_type, exc_value,
|
||||
exc_traceback)
|
||||
return
|
||||
|
||||
# Send message
|
||||
self.treat_out(context, res)
|
||||
|
||||
# Inform that the message has been treated
|
||||
self.srv.msg_treated(self.data)
|
||||
|
||||
|
||||
|
||||
class EventConsumer:
|
||||
"""Store a event before treating"""
|
||||
def __init__(self, evt, timeout=20):
|
||||
self.evt = evt
|
||||
self.timeout = timeout
|
||||
|
||||
|
||||
def run(self, context):
|
||||
try:
|
||||
self.evt.launch_check()
|
||||
except:
|
||||
print ("\033[1;31mError:\033[0m during event end")
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
traceback.print_exception(exc_type, exc_value,
|
||||
exc_traceback)
|
||||
if self.evt.next is not None:
|
||||
context.add_event(self.evt, self.evt.id)
|
||||
|
||||
|
||||
|
||||
class Consumer(threading.Thread):
|
||||
"""Dequeue and exec requested action"""
|
||||
def __init__(self, context):
|
||||
self.context = context
|
||||
self.stop = False
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
while not self.stop:
|
||||
stm = self.context.cnsr_queue.get(True, 20)
|
||||
stm.run(self.context)
|
||||
|
||||
except queue.Empty:
|
||||
pass
|
||||
finally:
|
||||
self.context.cnsr_thrd_size -= 2
|
||||
43
credits.py
Normal file
43
credits.py
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import random
|
||||
|
||||
BANLIST = []
|
||||
|
||||
class Credits:
|
||||
def __init__ (self, name):
|
||||
self.name = name
|
||||
self.credits = 5
|
||||
self.randsec = timedelta(seconds=random.randint(0, 55))
|
||||
self.lastmessage = datetime.now() + self.randsec
|
||||
self.iask = True
|
||||
|
||||
def ask(self):
|
||||
if self.name in BANLIST:
|
||||
return False
|
||||
|
||||
now = datetime.now() + self.randsec
|
||||
if self.lastmessage.minute == now.minute and (self.lastmessage.second == now.second or self.lastmessage.second == now.second - 1):
|
||||
print("\033[1;36mAUTOBAN\033[0m %s: too low time between messages" % self.name)
|
||||
#BANLIST.append(self.name)
|
||||
self.credits -= self.credits / 2 #Une alternative
|
||||
return False
|
||||
|
||||
self.iask = True
|
||||
return self.credits > 0 or self.lastmessage.minute != now.minute
|
||||
|
||||
def speak(self):
|
||||
if self.iask:
|
||||
self.iask = False
|
||||
now = datetime.now() + self.randsec
|
||||
if self.lastmessage.minute != now.minute:
|
||||
self.credits = min (15, self.credits + 5)
|
||||
self.lastmessage = now
|
||||
|
||||
self.credits -= 1
|
||||
return self.credits > -3
|
||||
|
||||
def to_string(self):
|
||||
print ("%s: %d ; reset: %d" % (self.name, self.credits, self.randsec.seconds))
|
||||
0
datas/datas
Normal file
0
datas/datas
Normal file
118
event.py
Normal file
118
event.py
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
class ModuleEvent:
|
||||
def __init__(self, func=None, func_data=None, check=None, cmp_data=None,
|
||||
intervalle=60, offset=0, call=None, call_data=None, times=1):
|
||||
# What have we to check?
|
||||
self.func = func
|
||||
self.func_data = func_data
|
||||
|
||||
# How detect a change?
|
||||
self.check = check
|
||||
if cmp_data is not None:
|
||||
self.cmp_data = cmp_data
|
||||
elif self.func is not None:
|
||||
if self.func_data is None:
|
||||
self.cmp_data = self.func()
|
||||
elif isinstance(self.func_data, dict):
|
||||
self.cmp_data = self.func(**self.func_data)
|
||||
else:
|
||||
self.cmp_data = self.func(self.func_data)
|
||||
else:
|
||||
self.cmp_data = None
|
||||
|
||||
self.offset = timedelta(seconds=offset) # Time to wait before the first check
|
||||
self.intervalle = timedelta(seconds=intervalle)
|
||||
self.end = None
|
||||
|
||||
# What should we call when
|
||||
self.call = call
|
||||
if call_data is not None:
|
||||
self.call_data = call_data
|
||||
else:
|
||||
self.call_data = func_data
|
||||
|
||||
# How many times do this event?
|
||||
self.times = times
|
||||
|
||||
|
||||
@property
|
||||
def current(self):
|
||||
"""Return the date of the near check"""
|
||||
if self.times != 0:
|
||||
if self.end is None:
|
||||
self.end = datetime.now() + self.offset + self.intervalle
|
||||
return self.end
|
||||
return None
|
||||
|
||||
@property
|
||||
def next(self):
|
||||
"""Return the date of the next check"""
|
||||
if self.times != 0:
|
||||
if self.end is None:
|
||||
return self.current
|
||||
elif self.end < datetime.now():
|
||||
self.end += self.intervalle
|
||||
return self.end
|
||||
return None
|
||||
|
||||
@property
|
||||
def time_left(self):
|
||||
"""Return the time left before/after the near check"""
|
||||
if self.current is not None:
|
||||
return self.current - datetime.now()
|
||||
return 99999
|
||||
|
||||
def launch_check(self):
|
||||
if self.func is None:
|
||||
d = self.func_data
|
||||
elif self.func_data is None:
|
||||
d = self.func()
|
||||
elif isinstance(self.func_data, dict):
|
||||
d = self.func(**self.func_data)
|
||||
else:
|
||||
d = self.func(self.func_data)
|
||||
#print ("do test with", d, self.cmp_data)
|
||||
|
||||
if self.check is None:
|
||||
if self.cmp_data is None:
|
||||
r = True
|
||||
else:
|
||||
r = d != self.cmp_data
|
||||
elif self.cmp_data is None:
|
||||
r = self.check(d)
|
||||
elif isinstance(self.cmp_data, dict):
|
||||
r = self.check(d, **self.cmp_data)
|
||||
else:
|
||||
r = self.check(d, self.cmp_data)
|
||||
|
||||
if r:
|
||||
self.times -= 1
|
||||
if self.call_data is None:
|
||||
if d is None:
|
||||
self.call()
|
||||
else:
|
||||
self.call(d)
|
||||
elif isinstance(self.call_data, dict):
|
||||
self.call(d, **self.call_data)
|
||||
else:
|
||||
self.call(d, self.call_data)
|
||||
220
hooks.py
Normal file
220
hooks.py
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
|
||||
from response import Response
|
||||
|
||||
class MessagesHook:
|
||||
def __init__(self, context, bot):
|
||||
self.context = context
|
||||
self.bot = bot
|
||||
|
||||
# Store specials hooks
|
||||
self.all_pre = list() # Treated before any parse
|
||||
self.all_post = list() # Treated before send message to user
|
||||
|
||||
# Store IRC commands hooks
|
||||
self.irc_hook = dict()
|
||||
|
||||
# Store direct hooks
|
||||
self.cmd_hook = dict()
|
||||
self.ask_hook = dict()
|
||||
self.msg_hook = dict()
|
||||
|
||||
# Store regexp hooks
|
||||
self.cmd_rgxp = list()
|
||||
self.ask_rgxp = list()
|
||||
self.msg_rgxp = list()
|
||||
|
||||
# Store default hooks (after other hooks if no match)
|
||||
self.cmd_default = list()
|
||||
self.ask_default = list()
|
||||
self.msg_default = list()
|
||||
|
||||
|
||||
def add_hook(self, store, hook, module_src=None):
|
||||
"""Insert in the right place a hook into the given store"""
|
||||
if module_src is None:
|
||||
print ("\033[1;35mWarning:\033[0m No source module was passed to "
|
||||
"add_hook function, please fix it in order to be "
|
||||
"compatible with unload feature")
|
||||
|
||||
if store in self.context.hooks_cache:
|
||||
del self.context.hooks_cache[store]
|
||||
|
||||
if not hasattr(self, store):
|
||||
print ("\033[1;35mWarning:\033[0m unrecognized hook store")
|
||||
return
|
||||
attr = getattr(self, store)
|
||||
|
||||
if isinstance(attr, dict) and hook.name is not None:
|
||||
if hook.name not in attr:
|
||||
attr[hook.name] = list()
|
||||
attr[hook.name].append(hook)
|
||||
if hook.end is not None:
|
||||
if hook.end not in attr:
|
||||
attr[hook.end] = list()
|
||||
attr[hook.end].append(hook)
|
||||
elif isinstance(attr, list):
|
||||
attr.append(hook)
|
||||
else:
|
||||
print ("\033[1;32mWarning:\033[0m unrecognized hook store type")
|
||||
return
|
||||
if module_src is not None and hasattr(module_src, "REGISTERED_HOOKS"):
|
||||
module_src.REGISTERED_HOOKS.append((store, hook))
|
||||
|
||||
def register_hook_attributes(self, store, module, node):
|
||||
if node.hasAttribute("data"):
|
||||
data = node["data"]
|
||||
else:
|
||||
data = None
|
||||
if node.hasAttribute("name"):
|
||||
self.add_hook(store + "_hook", Hook(getattr(module, node["call"]),
|
||||
node["name"], data=data),
|
||||
module)
|
||||
elif node.hasAttribute("regexp"):
|
||||
self.add_hook(store + "_rgxp", Hook(getattr(module, node["call"]),
|
||||
regexp=node["regexp"], data=data),
|
||||
module)
|
||||
|
||||
def register_hook(self, module, node):
|
||||
"""Create a hook from configuration node"""
|
||||
if node.name == "message" and node.hasAttribute("type"):
|
||||
if node["type"] == "cmd" or node["type"] == "all":
|
||||
self.register_hook_attributes("cmd", module, node)
|
||||
|
||||
if node["type"] == "ask" or node["type"] == "all":
|
||||
self.register_hook_attributes("ask", module, node)
|
||||
|
||||
if (node["type"] == "msg" or node["type"] == "answer" or
|
||||
node["type"] == "all"):
|
||||
self.register_hook_attributes("answer", module, node)
|
||||
|
||||
def clear(self):
|
||||
for h in self.all_pre:
|
||||
self.del_hook("all_pre", h)
|
||||
for h in self.all_post:
|
||||
self.del_hook("all_post", h)
|
||||
|
||||
for l in self.irc_hook:
|
||||
for h in self.irc_hook[l]:
|
||||
self.del_hook("irc_hook", h)
|
||||
|
||||
for l in self.cmd_hook:
|
||||
for h in self.cmd_hook[l]:
|
||||
self.del_hook("cmd_hook", h)
|
||||
for l in self.ask_hook:
|
||||
for h in self.ask_hook[l]:
|
||||
self.del_hook("ask_hook", h)
|
||||
for l in self.msg_hook:
|
||||
for h in self.msg_hook[l]:
|
||||
self.del_hook("msg_hook", h)
|
||||
|
||||
for h in self.cmd_rgxp:
|
||||
self.del_hook("cmd_rgxp", h)
|
||||
for h in self.ask_rgxp:
|
||||
self.del_hook("ask_rgxp", h)
|
||||
for h in self.msg_rgxp:
|
||||
self.del_hook("msg_rgxp", h)
|
||||
|
||||
for h in self.cmd_default:
|
||||
self.del_hook("cmd_default", h)
|
||||
for h in self.ask_default:
|
||||
self.del_hook("ask_default", h)
|
||||
for h in self.msg_default:
|
||||
self.del_hook("msg_default", h)
|
||||
|
||||
def del_hook(self, store, hook, module_src=None):
|
||||
"""Remove a registered hook from a given store"""
|
||||
if store in self.context.hooks_cache:
|
||||
del self.context.hooks_cache[store]
|
||||
|
||||
if not hasattr(self, store):
|
||||
print ("Warning: unrecognized hook store type")
|
||||
return
|
||||
attr = getattr(self, store)
|
||||
|
||||
if isinstance(attr, dict) and hook.name is not None:
|
||||
if hook.name in attr:
|
||||
attr[hook.name].remove(hook)
|
||||
if hook.end is not None and hook.end in attr:
|
||||
attr[hook.end].remove(hook)
|
||||
else:
|
||||
attr.remove(hook)
|
||||
|
||||
if module_src is not None:
|
||||
module_src.REGISTERED_HOOKS.remove((store, hook))
|
||||
|
||||
|
||||
class Hook:
|
||||
"""Class storing hook informations"""
|
||||
def __init__(self, call, name=None, data=None, regexp=None, channels=list(), server=None, end=None, call_end=None):
|
||||
self.name = name
|
||||
self.end = end
|
||||
self.call = call
|
||||
if call_end is None:
|
||||
self.call_end = self.call
|
||||
else:
|
||||
self.call_end = call_end
|
||||
self.regexp = regexp
|
||||
self.data = data
|
||||
self.times = -1
|
||||
self.server = server
|
||||
self.channels = channels
|
||||
|
||||
def is_matching(self, strcmp, channel=None, server=None):
|
||||
"""Test if the current hook correspond to the message"""
|
||||
return (channel is None or len(self.channels) <= 0 or
|
||||
channel in self.channels) and (server is None or
|
||||
self.server is None or self.server == server) and (
|
||||
(self.name is None or strcmp == self.name) and (
|
||||
self.end is None or strcmp == self.end) and (
|
||||
self.regexp is None or re.match(self.regexp, strcmp)))
|
||||
|
||||
def run(self, msg, data2=None, strcmp=None):
|
||||
"""Run the hook"""
|
||||
if self.times != 0:
|
||||
self.times -= 1
|
||||
|
||||
if (self.end is not None and strcmp is not None and
|
||||
self.call_end is not None and strcmp == self.end):
|
||||
call = self.call_end
|
||||
self.times = 0
|
||||
else:
|
||||
call = self.call
|
||||
|
||||
if self.data is None:
|
||||
if data2 is None:
|
||||
return call(msg)
|
||||
elif isinstance(data2, dict):
|
||||
return call(msg, **data2)
|
||||
else:
|
||||
return call(msg, data2)
|
||||
elif isinstance(self.data, dict):
|
||||
if data2 is None:
|
||||
return call(msg, **self.data)
|
||||
else:
|
||||
return call(msg, data2, **self.data)
|
||||
else:
|
||||
if data2 is None:
|
||||
return call(msg, self.data)
|
||||
elif isinstance(data2, dict):
|
||||
return call(msg, self.data, **data2)
|
||||
else:
|
||||
return call(msg, self.data, data2)
|
||||
264
importer.py
Normal file
264
importer.py
Normal file
|
|
@ -0,0 +1,264 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from importlib.abc import Finder
|
||||
from importlib.abc import SourceLoader
|
||||
import imp
|
||||
import os
|
||||
import sys
|
||||
|
||||
import event
|
||||
from hooks import Hook
|
||||
import response
|
||||
import xmlparser
|
||||
|
||||
class ModuleFinder(Finder):
|
||||
def __init__(self, context, prompt):
|
||||
self.context = context
|
||||
self.prompt = prompt
|
||||
|
||||
def find_module(self, fullname, path=None):
|
||||
#print ("looking for", fullname, "in", path)
|
||||
# Search only for new nemubot modules (packages init)
|
||||
if path is None:
|
||||
for mpath in self.context.modules_path:
|
||||
#print ("looking for", fullname, "in", mpath)
|
||||
if os.path.isfile(mpath + fullname + ".xml"):
|
||||
return ModuleLoader(self.context, self.prompt, fullname,
|
||||
mpath, mpath + fullname + ".xml")
|
||||
elif (os.path.isfile(mpath + fullname + ".py") or
|
||||
os.path.isfile(mpath + fullname + "/__init__.py")):
|
||||
return ModuleLoader(self.context, self.prompt,
|
||||
fullname, mpath, None)
|
||||
#print ("not found")
|
||||
return None
|
||||
|
||||
|
||||
class ModuleLoader(SourceLoader):
|
||||
def __init__(self, context, prompt, fullname, path, config_path):
|
||||
self.context = context
|
||||
self.prompt = prompt
|
||||
self.name = fullname
|
||||
self.config_path = config_path
|
||||
|
||||
if config_path is not None:
|
||||
self.config = xmlparser.parse_file(config_path)
|
||||
if self.config.hasAttribute("name"):
|
||||
self.name = self.config["name"]
|
||||
else:
|
||||
self.config = None
|
||||
|
||||
if os.path.isfile(path + fullname + ".py"):
|
||||
self.source_path = path + self.name + ".py"
|
||||
self.package = False
|
||||
self.mpath = path
|
||||
elif os.path.isfile(path + fullname + "/__init__.py"):
|
||||
self.source_path = path + self.name + "/__init__.py"
|
||||
self.package = True
|
||||
self.mpath = path + self.name + "/"
|
||||
else:
|
||||
raise ImportError
|
||||
|
||||
def get_filename(self, fullname):
|
||||
"""Return the path to the source file as found by the finder."""
|
||||
return self.source_path
|
||||
|
||||
def get_data(self, path):
|
||||
"""Return the data from path as raw bytes."""
|
||||
with open(path, 'rb') as file:
|
||||
return file.read()
|
||||
|
||||
def path_mtime(self, path):
|
||||
st = os.stat(path)
|
||||
return int(st.st_mtime)
|
||||
|
||||
def set_data(self, path, data):
|
||||
"""Write bytes data to a file."""
|
||||
parent, filename = os.path.split(path)
|
||||
path_parts = []
|
||||
# Figure out what directories are missing.
|
||||
while parent and not os.path.isdir(parent):
|
||||
parent, part = os.path.split(parent)
|
||||
path_parts.append(part)
|
||||
# Create needed directories.
|
||||
for part in reversed(path_parts):
|
||||
parent = os.path.join(parent, part)
|
||||
try:
|
||||
os.mkdir(parent)
|
||||
except FileExistsError:
|
||||
# Probably another Python process already created the dir.
|
||||
continue
|
||||
except PermissionError:
|
||||
# If can't get proper access, then just forget about writing
|
||||
# the data.
|
||||
return
|
||||
try:
|
||||
with open(path, 'wb') as file:
|
||||
file.write(data)
|
||||
except (PermissionError, FileExistsError):
|
||||
pass
|
||||
|
||||
def get_code(self, fullname):
|
||||
return SourceLoader.get_code(self, fullname)
|
||||
|
||||
def get_source(self, fullname):
|
||||
return SourceLoader.get_source(self, fullname)
|
||||
|
||||
def is_package(self, fullname):
|
||||
return self.package
|
||||
|
||||
def load_module(self, fullname):
|
||||
module = self._load_module(fullname, sourceless=True)
|
||||
|
||||
# Remove the module from sys list
|
||||
del sys.modules[fullname]
|
||||
|
||||
# If the module was already loaded, then reload it
|
||||
if hasattr(module, '__LOADED__'):
|
||||
reload(module)
|
||||
|
||||
# Check that is a valid nemubot module
|
||||
if not hasattr(module, "nemubotversion"):
|
||||
raise ImportError("Module `%s' is not a nemubot module."%self.name)
|
||||
# Check module version
|
||||
if module.nemubotversion != self.context.version:
|
||||
raise ImportError("Module `%s' is not compatible with this "
|
||||
"version." % self.name)
|
||||
|
||||
# Set module common functions and datas
|
||||
module.__LOADED__ = True
|
||||
|
||||
# Set module common functions and datas
|
||||
module.REGISTERED_HOOKS = list()
|
||||
module.REGISTERED_EVENTS = list()
|
||||
module.DEBUG = False
|
||||
module.DIR = self.mpath
|
||||
module.name = fullname
|
||||
module.print = lambda msg: print("[%s] %s"%(module.name, msg))
|
||||
module.print_debug = lambda msg: mod_print_dbg(module, msg)
|
||||
module.send_response = lambda srv, res: mod_send_response(self.context, srv, res)
|
||||
module.add_hook = lambda store, hook: self.context.hooks.add_hook(store, hook, module)
|
||||
module.del_hook = lambda store, hook: self.context.hooks.del_hook(store, hook)
|
||||
module.add_event = lambda evt: self.context.add_event(evt, module_src=module)
|
||||
module.add_event_eid = lambda evt, eid: self.context.add_event(evt, eid, module_src=module)
|
||||
module.del_event = lambda evt: self.context.del_event(evt, module_src=module)
|
||||
|
||||
if not hasattr(module, "NODATA"):
|
||||
module.DATAS = xmlparser.parse_file(self.context.datas_path
|
||||
+ module.name + ".xml")
|
||||
module.save = lambda: mod_save(module, self.context.datas_path)
|
||||
else:
|
||||
module.DATAS = None
|
||||
module.save = lambda: False
|
||||
module.CONF = self.config
|
||||
module.has_access = lambda msg: mod_has_access(module,
|
||||
module.CONF, msg)
|
||||
|
||||
module.ModuleEvent = event.ModuleEvent
|
||||
module.ModuleState = xmlparser.module_state.ModuleState
|
||||
module.Response = response.Response
|
||||
|
||||
# Load dependancies
|
||||
if module.CONF is not None and module.CONF.hasNode("dependson"):
|
||||
module.MODS = dict()
|
||||
for depend in module.CONF.getNodes("dependson"):
|
||||
for md in MODS:
|
||||
if md.name == depend["name"]:
|
||||
mod.MODS[md.name] = md
|
||||
break
|
||||
if depend["name"] not in module.MODS:
|
||||
print ("\033[1;31mERROR:\033[0m in module `%s', module "
|
||||
"`%s' require by this module but is not loaded."
|
||||
% (module.name, depend["name"]))
|
||||
return
|
||||
|
||||
# Add the module to the global modules list
|
||||
if self.context.add_module(module):
|
||||
|
||||
# Launch the module
|
||||
if hasattr(module, "load"):
|
||||
module.load(self.context)
|
||||
|
||||
# Register hooks
|
||||
register_hooks(module, self.context, self.prompt)
|
||||
|
||||
print (" Module `%s' successfully loaded." % module.name)
|
||||
else:
|
||||
raise ImportError("An error occurs while importing `%s'."
|
||||
% module.name)
|
||||
return module
|
||||
|
||||
|
||||
def add_cap_hook(prompt, module, cmd):
|
||||
if hasattr(module, cmd["call"]):
|
||||
prompt.add_cap_hook(cmd["name"], getattr(module, cmd["call"]))
|
||||
else:
|
||||
print ("Warning: In module `%s', no function `%s' defined for `%s' "
|
||||
"command hook." % (module.name, cmd["call"], cmd["name"]))
|
||||
|
||||
def register_hooks(module, context, prompt):
|
||||
"""Register all available hooks"""
|
||||
if module.CONF is not None:
|
||||
# Register command hooks
|
||||
if module.CONF.hasNode("command"):
|
||||
for cmd in module.CONF.getNodes("command"):
|
||||
if cmd.hasAttribute("name") and cmd.hasAttribute("call"):
|
||||
add_cap_hook(prompt, module, cmd)
|
||||
|
||||
# Register message hooks
|
||||
if module.CONF.hasNode("message"):
|
||||
for msg in module.CONF.getNodes("message"):
|
||||
context.hooks.register_hook(module, msg)
|
||||
|
||||
# Register legacy hooks
|
||||
if hasattr(module, "parseanswer"):
|
||||
context.hooks.add_hook("cmd_default", Hook(module.parseanswer), module)
|
||||
if hasattr(module, "parseask"):
|
||||
context.hooks.add_hook("ask_default", Hook(module.parseask), module)
|
||||
if hasattr(module, "parselisten"):
|
||||
context.hooks.add_hook("msg_default", Hook(module.parselisten), module)
|
||||
|
||||
##########################
|
||||
# #
|
||||
# Module functions #
|
||||
# #
|
||||
##########################
|
||||
|
||||
def mod_print_dbg(mod, msg):
|
||||
if mod.DEBUG:
|
||||
print("{%s} %s"%(mod.name, msg))
|
||||
|
||||
def mod_save(mod, datas_path):
|
||||
mod.DATAS.save(datas_path + "/" + mod.name + ".xml")
|
||||
mod.print_debug("Saving!")
|
||||
|
||||
def mod_has_access(mod, config, msg):
|
||||
if config is not None and config.hasNode("channel"):
|
||||
for chan in config.getNodes("channel"):
|
||||
if (chan["server"] is None or chan["server"] == msg.srv.id) and (
|
||||
chan["channel"] is None or chan["channel"] == msg.channel):
|
||||
return True
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def mod_send_response(context, server, res):
|
||||
if server in context.servers:
|
||||
context.servers[server].send_response(res, None)
|
||||
else:
|
||||
print("\033[1;35mWarning:\033[0m Try to send a message to the unknown server: %s" % server)
|
||||
294
message.py
Normal file
294
message.py
Normal file
|
|
@ -0,0 +1,294 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
import re
|
||||
import shlex
|
||||
import time
|
||||
|
||||
import credits
|
||||
from credits import Credits
|
||||
from response import Response
|
||||
import xmlparser
|
||||
|
||||
CREDITS = {}
|
||||
filename = ""
|
||||
|
||||
def load(config_file):
|
||||
global CREDITS, filename
|
||||
CREDITS = dict ()
|
||||
filename = config_file
|
||||
credits.BANLIST = xmlparser.parse_file(filename)
|
||||
|
||||
def save():
|
||||
global filename
|
||||
credits.BANLIST.save(filename)
|
||||
|
||||
|
||||
class Message:
|
||||
def __init__ (self, line, timestamp, private = False):
|
||||
self.raw = line
|
||||
self.time = timestamp
|
||||
self.channel = None
|
||||
self.content = b''
|
||||
self.ctcp = False
|
||||
line = line.rstrip() #remove trailing 'rn'
|
||||
|
||||
words = line.split(b' ')
|
||||
if words[0][0] == 58: #58 is : in ASCII table
|
||||
self.sender = words[0][1:].decode()
|
||||
self.cmd = words[1].decode()
|
||||
else:
|
||||
self.cmd = words[0].decode()
|
||||
self.sender = None
|
||||
|
||||
if self.cmd == 'PING':
|
||||
self.content = words[1]
|
||||
elif self.sender is not None:
|
||||
self.nick = (self.sender.split('!'))[0]
|
||||
if self.nick != self.sender:
|
||||
self.realname = (self.sender.split('!'))[1]
|
||||
else:
|
||||
self.realname = self.nick
|
||||
self.sender = self.nick + "!" + self.realname
|
||||
|
||||
if len(words) > 2:
|
||||
self.channel = self.pickWords(words[2:]).decode()
|
||||
|
||||
if self.cmd == 'PRIVMSG':
|
||||
# Check for CTCP request
|
||||
self.ctcp = len(words[3]) > 1 and (words[3][0] == 0x01 or words[3][1] == 0x01)
|
||||
self.content = self.pickWords(words[3:])
|
||||
elif self.cmd == '353' and len(words) > 3:
|
||||
for i in range(2, len(words)):
|
||||
if words[i][0] == 58:
|
||||
self.content = words[i:]
|
||||
#Remove the first :
|
||||
self.content[0] = self.content[0][1:]
|
||||
self.channel = words[i-1].decode()
|
||||
break
|
||||
elif self.cmd == 'NICK':
|
||||
self.content = self.pickWords(words[2:])
|
||||
elif self.cmd == 'MODE':
|
||||
self.content = words[3:]
|
||||
elif self.cmd == '332':
|
||||
self.channel = words[3]
|
||||
self.content = self.pickWords(words[4:])
|
||||
else:
|
||||
#print (line)
|
||||
self.content = self.pickWords(words[3:])
|
||||
else:
|
||||
print (line)
|
||||
if self.cmd == 'PRIVMSG':
|
||||
self.channel = words[2].decode()
|
||||
self.content = b' '.join(words[3:])
|
||||
self.decode()
|
||||
if self.cmd == 'PRIVMSG':
|
||||
self.parse_content()
|
||||
self.private = private
|
||||
|
||||
def parse_content(self):
|
||||
"""Parse or reparse the message content"""
|
||||
# If CTCP, remove 0x01
|
||||
if self.ctcp:
|
||||
self.content = self.content[1:len(self.content)-1]
|
||||
|
||||
# Split content by words
|
||||
try:
|
||||
self.cmds = shlex.split(self.content)
|
||||
except ValueError:
|
||||
self.cmds = self.content.split(' ')
|
||||
|
||||
def pickWords(self, words):
|
||||
"""Parse last argument of a line: can be a single word or a sentence starting with :"""
|
||||
if len(words) > 0 and len(words[0]) > 0:
|
||||
if words[0][0] == 58:
|
||||
return b' '.join(words[0:])[1:]
|
||||
else:
|
||||
return words[0]
|
||||
else:
|
||||
return b''
|
||||
|
||||
def decode(self):
|
||||
"""Decode the content string usign a specific encoding"""
|
||||
if isinstance(self.content, bytes):
|
||||
try:
|
||||
self.content = self.content.decode()
|
||||
except UnicodeDecodeError:
|
||||
#TODO: use encoding from config file
|
||||
self.content = self.content.decode('utf-8', 'replace')
|
||||
|
||||
def authorize_DEPRECATED(self):
|
||||
"""Is nemubot listening for the sender on this channel?"""
|
||||
# TODO: deprecated
|
||||
if self.srv.isDCC(self.sender):
|
||||
return True
|
||||
elif self.realname not in CREDITS:
|
||||
CREDITS[self.realname] = Credits(self.realname)
|
||||
elif self.content[0] == '`':
|
||||
return True
|
||||
elif not CREDITS[self.realname].ask():
|
||||
return False
|
||||
return self.srv.accepted_channel(self.channel)
|
||||
|
||||
##############################
|
||||
# #
|
||||
# Extraction/Format text #
|
||||
# #
|
||||
##############################
|
||||
|
||||
def just_countdown (self, delta, resolution = 5):
|
||||
sec = delta.seconds
|
||||
hours, remainder = divmod(sec, 3600)
|
||||
minutes, seconds = divmod(remainder, 60)
|
||||
an = int(delta.days / 365.25)
|
||||
days = delta.days % 365.25
|
||||
|
||||
sentence = ""
|
||||
force = False
|
||||
|
||||
if resolution > 0 and (force or an > 0):
|
||||
force = True
|
||||
sentence += " %i an"%(an)
|
||||
|
||||
if an > 1:
|
||||
sentence += "s"
|
||||
if resolution > 2:
|
||||
sentence += ","
|
||||
elif resolution > 1:
|
||||
sentence += " et"
|
||||
|
||||
if resolution > 1 and (force or days > 0):
|
||||
force = True
|
||||
sentence += " %i jour"%(days)
|
||||
|
||||
if days > 1:
|
||||
sentence += "s"
|
||||
if resolution > 3:
|
||||
sentence += ","
|
||||
elif resolution > 2:
|
||||
sentence += " et"
|
||||
|
||||
if resolution > 2 and (force or hours > 0):
|
||||
force = True
|
||||
sentence += " %i heure"%(hours)
|
||||
if hours > 1:
|
||||
sentence += "s"
|
||||
if resolution > 4:
|
||||
sentence += ","
|
||||
elif resolution > 3:
|
||||
sentence += " et"
|
||||
|
||||
if resolution > 3 and (force or minutes > 0):
|
||||
force = True
|
||||
sentence += " %i minute"%(minutes)
|
||||
if minutes > 1:
|
||||
sentence += "s"
|
||||
if resolution > 4:
|
||||
sentence += " et"
|
||||
|
||||
if resolution > 4 and (force or seconds > 0):
|
||||
force = True
|
||||
sentence += " %i seconde"%(seconds)
|
||||
if seconds > 1:
|
||||
sentence += "s"
|
||||
return sentence[1:]
|
||||
|
||||
|
||||
def countdown_format (self, date, msg_before, msg_after, timezone = None):
|
||||
"""Replace in a text %s by a sentence incidated the remaining time before/after an event"""
|
||||
if timezone != None:
|
||||
os.environ['TZ'] = timezone
|
||||
time.tzset()
|
||||
|
||||
#Calculate time before the date
|
||||
if datetime.now() > date:
|
||||
sentence_c = msg_after
|
||||
delta = datetime.now() - date
|
||||
else:
|
||||
sentence_c = msg_before
|
||||
delta = date - datetime.now()
|
||||
|
||||
if timezone != None:
|
||||
os.environ['TZ'] = "Europe/Paris"
|
||||
|
||||
return sentence_c % self.just_countdown(delta)
|
||||
|
||||
|
||||
def extractDate (self):
|
||||
"""Parse a message to extract a time and date"""
|
||||
msgl = self.content.lower ()
|
||||
result = re.match("^[^0-9]+(([0-9]{1,4})[^0-9]+([0-9]{1,2}|janvier|january|fevrier|février|february|mars|march|avril|april|mai|maï|may|juin|juni|juillet|july|jully|august|aout|août|septembre|september|october|octobre|oktober|novembre|november|decembre|décembre|december)([^0-9]+([0-9]{1,4}))?)[^0-9]+(([0-9]{1,2})[^0-9]*[h':]([^0-9]*([0-9]{1,2})([^0-9]*[m\":][^0-9]*([0-9]{1,2}))?)?)?.*$", msgl + " TXT")
|
||||
if result is not None:
|
||||
day = result.group(2)
|
||||
if len(day) == 4:
|
||||
year = day
|
||||
day = 0
|
||||
month = result.group(3)
|
||||
if month == "janvier" or month == "january" or month == "januar":
|
||||
month = 1
|
||||
elif month == "fevrier" or month == "février" or month == "february":
|
||||
month = 2
|
||||
elif month == "mars" or month == "march":
|
||||
month = 3
|
||||
elif month == "avril" or month == "april":
|
||||
month = 4
|
||||
elif month == "mai" or month == "may" or month == "maï":
|
||||
month = 5
|
||||
elif month == "juin" or month == "juni" or month == "junni":
|
||||
month = 6
|
||||
elif month == "juillet" or month == "jully" or month == "july":
|
||||
month = 7
|
||||
elif month == "aout" or month == "août" or month == "august":
|
||||
month = 8
|
||||
elif month == "september" or month == "septembre":
|
||||
month = 9
|
||||
elif month == "october" or month == "october" or month == "oktober":
|
||||
month = 10
|
||||
elif month == "november" or month == "novembre":
|
||||
month = 11
|
||||
elif month == "december" or month == "decembre" or month == "décembre":
|
||||
month = 12
|
||||
|
||||
if day == 0:
|
||||
day = result.group(5)
|
||||
else:
|
||||
year = result.group(5)
|
||||
|
||||
hour = result.group(7)
|
||||
minute = result.group(9)
|
||||
second = result.group(11)
|
||||
|
||||
print ("Chaîne reconnue : %s/%s/%s %s:%s:%s"%(day, month, year, hour, minute, second))
|
||||
if year == None:
|
||||
year = date.today().year
|
||||
if hour == None:
|
||||
hour = 0
|
||||
if minute == None:
|
||||
minute = 0
|
||||
if second == None:
|
||||
second = 1
|
||||
else:
|
||||
second = int (second) + 1
|
||||
if second > 59:
|
||||
minute = int (minute) + 1
|
||||
second = 0
|
||||
|
||||
return datetime(int(year), int(month), int(day), int(hour), int(minute), int(second))
|
||||
else:
|
||||
return None
|
||||
277
modules/alias.py
277
modules/alias.py
|
|
@ -1,277 +0,0 @@
|
|||
"""Create alias of commands"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import re
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.message import Command
|
||||
from nemubot.tools.human import guess
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
"""Load this module"""
|
||||
if not context.data.hasNode("aliases"):
|
||||
context.data.addChild(ModuleState("aliases"))
|
||||
context.data.getNode("aliases").setIndex("alias")
|
||||
if not context.data.hasNode("variables"):
|
||||
context.data.addChild(ModuleState("variables"))
|
||||
context.data.getNode("variables").setIndex("name")
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
## Alias management
|
||||
|
||||
def list_alias(channel=None):
|
||||
"""List known aliases.
|
||||
|
||||
Argument:
|
||||
channel -- optional, if defined, return a list of aliases only defined on this channel, else alias widly defined
|
||||
"""
|
||||
|
||||
for alias in context.data.getNode("aliases").index.values():
|
||||
if (channel is None and "channel" not in alias) or (channel is not None and "channel" in alias and alias["channel"] == channel):
|
||||
yield alias
|
||||
|
||||
def create_alias(alias, origin, channel=None, creator=None):
|
||||
"""Create or erase an existing alias
|
||||
"""
|
||||
|
||||
anode = ModuleState("alias")
|
||||
anode["alias"] = alias
|
||||
anode["origin"] = origin
|
||||
if channel is not None:
|
||||
anode["creator"] = channel
|
||||
if creator is not None:
|
||||
anode["creator"] = creator
|
||||
context.data.getNode("aliases").addChild(anode)
|
||||
context.save()
|
||||
|
||||
|
||||
## Variables management
|
||||
|
||||
def get_variable(name, msg=None):
|
||||
"""Get the value for the given variable
|
||||
|
||||
Arguments:
|
||||
name -- The variable identifier
|
||||
msg -- optional, original message where some variable can be picked
|
||||
"""
|
||||
|
||||
if msg is not None and (name == "sender" or name == "from" or name == "nick"):
|
||||
return msg.frm
|
||||
elif msg is not None and (name == "chan" or name == "channel"):
|
||||
return msg.channel
|
||||
elif name == "date":
|
||||
return datetime.now(timezone.utc).strftime("%c")
|
||||
elif name in context.data.getNode("variables").index:
|
||||
return context.data.getNode("variables").index[name]["value"]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def list_variables(user=None):
|
||||
"""List known variables.
|
||||
|
||||
Argument:
|
||||
user -- optional, if defined, display only variable created by the given user
|
||||
"""
|
||||
if user is not None:
|
||||
return [x for x in context.data.getNode("variables").index.values() if x["creator"] == user]
|
||||
else:
|
||||
return context.data.getNode("variables").index.values()
|
||||
|
||||
|
||||
def set_variable(name, value, creator):
|
||||
"""Define or erase a variable.
|
||||
|
||||
Arguments:
|
||||
name -- The variable identifier
|
||||
value -- Variable value
|
||||
creator -- User who has created this variable
|
||||
"""
|
||||
|
||||
var = ModuleState("variable")
|
||||
var["name"] = name
|
||||
var["value"] = value
|
||||
var["creator"] = creator
|
||||
context.data.getNode("variables").addChild(var)
|
||||
context.save()
|
||||
|
||||
|
||||
def replace_variables(cnts, msg):
|
||||
"""Replace variables contained in the content
|
||||
|
||||
Arguments:
|
||||
cnt -- content where search variables
|
||||
msg -- Message where pick some variables
|
||||
"""
|
||||
|
||||
unsetCnt = list()
|
||||
if not isinstance(cnts, list):
|
||||
cnts = list(cnts)
|
||||
resultCnt = list()
|
||||
|
||||
for cnt in cnts:
|
||||
for res, name, default in re.findall("\\$\{(([a-zA-Z0-9:]+)(?:-([^}]+))?)\}", cnt):
|
||||
rv = re.match("([0-9]+)(:([0-9]*))?", name)
|
||||
if rv is not None:
|
||||
varI = int(rv.group(1)) - 1
|
||||
if varI >= len(msg.args):
|
||||
cnt = cnt.replace("${%s}" % res, default, 1)
|
||||
elif rv.group(2) is not None:
|
||||
if rv.group(3) is not None and len(rv.group(3)):
|
||||
varJ = int(rv.group(3)) - 1
|
||||
cnt = cnt.replace("${%s}" % res, " ".join(msg.args[varI:varJ]), 1)
|
||||
for v in range(varI, varJ):
|
||||
unsetCnt.append(v)
|
||||
else:
|
||||
cnt = cnt.replace("${%s}" % res, " ".join(msg.args[varI:]), 1)
|
||||
for v in range(varI, len(msg.args)):
|
||||
unsetCnt.append(v)
|
||||
else:
|
||||
cnt = cnt.replace("${%s}" % res, msg.args[varI], 1)
|
||||
unsetCnt.append(varI)
|
||||
else:
|
||||
cnt = cnt.replace("${%s}" % res, get_variable(name) or default, 1)
|
||||
resultCnt.append(cnt)
|
||||
|
||||
# Remove used content
|
||||
for u in sorted(set(unsetCnt), reverse=True):
|
||||
msg.args.pop(u)
|
||||
|
||||
return resultCnt
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
## Variables management
|
||||
|
||||
@hook.command("listvars",
|
||||
help="list defined variables for substitution in input commands",
|
||||
help_usage={
|
||||
None: "List all known variables",
|
||||
"USER": "List variables created by USER"})
|
||||
def cmd_listvars(msg):
|
||||
if len(msg.args):
|
||||
res = list()
|
||||
for user in msg.args:
|
||||
als = [v["name"] for v in list_variables(user)]
|
||||
if len(als) > 0:
|
||||
res.append("%s's variables: %s" % (user, ", ".join(als)))
|
||||
else:
|
||||
res.append("%s didn't create variable yet." % user)
|
||||
return Response(" ; ".join(res), channel=msg.channel)
|
||||
elif len(context.data.getNode("variables").index):
|
||||
return Response(list_variables(),
|
||||
channel=msg.channel,
|
||||
title="Known variables")
|
||||
else:
|
||||
return Response("There is currently no variable stored.", channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("set",
|
||||
help="Create or set variables for substitution in input commands",
|
||||
help_usage={"KEY VALUE": "Define the variable named KEY and fill it with VALUE as content"})
|
||||
def cmd_set(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("!set take two args: the key and the value.")
|
||||
set_variable(msg.args[0], " ".join(msg.args[1:]), msg.frm)
|
||||
return Response("Variable $%s successfully defined." % msg.args[0],
|
||||
channel=msg.channel)
|
||||
|
||||
|
||||
## Alias management
|
||||
|
||||
@hook.command("listalias",
|
||||
help="List registered aliases",
|
||||
help_usage={
|
||||
None: "List all registered aliases",
|
||||
"USER": "List all aliases created by USER"})
|
||||
def cmd_listalias(msg):
|
||||
aliases = [a for a in list_alias(None)] + [a for a in list_alias(msg.channel)]
|
||||
if len(aliases):
|
||||
return Response([a["alias"] for a in aliases],
|
||||
channel=msg.channel,
|
||||
title="Known aliases")
|
||||
return Response("There is no alias currently.", channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("alias",
|
||||
help="Display or define the replacement command for a given alias",
|
||||
help_usage={
|
||||
"ALIAS": "Extends the given alias",
|
||||
"ALIAS COMMAND [ARGS ...]": "Create a new alias named ALIAS as replacement to the given COMMAND and ARGS",
|
||||
})
|
||||
def cmd_alias(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("!alias takes as argument an alias to extend.")
|
||||
|
||||
alias = context.subparse(msg, msg.args[0])
|
||||
if alias is None or not isinstance(alias, Command):
|
||||
raise IMException("%s is not a valid alias" % msg.args[0])
|
||||
|
||||
if alias.cmd in context.data.getNode("aliases").index:
|
||||
return Response("%s corresponds to %s" % (alias.cmd, context.data.getNode("aliases").index[alias.cmd]["origin"]),
|
||||
channel=msg.channel, nick=msg.frm)
|
||||
|
||||
elif len(msg.args) > 1:
|
||||
create_alias(alias.cmd,
|
||||
" ".join(msg.args[1:]),
|
||||
channel=msg.channel,
|
||||
creator=msg.frm)
|
||||
return Response("New alias %s successfully registered." % alias.cmd,
|
||||
channel=msg.channel)
|
||||
|
||||
else:
|
||||
wym = [m for m in guess(alias.cmd, context.data.getNode("aliases").index)]
|
||||
raise IMException(msg.args[0] + " is not an alias." + (" Would you mean: %s?" % ", ".join(wym) if len(wym) else ""))
|
||||
|
||||
|
||||
@hook.command("unalias",
|
||||
help="Remove a previously created alias")
|
||||
def cmd_unalias(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Which alias would you want to remove?")
|
||||
res = list()
|
||||
for alias in msg.args:
|
||||
if alias[0] == "!" and len(alias) > 1:
|
||||
alias = alias[1:]
|
||||
if alias in context.data.getNode("aliases").index:
|
||||
context.data.getNode("aliases").delChild(context.data.getNode("aliases").index[alias])
|
||||
res.append(Response("%s doesn't exist anymore." % alias,
|
||||
channel=msg.channel))
|
||||
else:
|
||||
res.append(Response("%s is not an alias" % alias,
|
||||
channel=msg.channel))
|
||||
return res
|
||||
|
||||
|
||||
## Alias replacement
|
||||
|
||||
@hook.add(["pre","Command"])
|
||||
def treat_alias(msg):
|
||||
if context.data.getNode("aliases") is not None and msg.cmd in context.data.getNode("aliases").index:
|
||||
origin = context.data.getNode("aliases").index[msg.cmd]["origin"]
|
||||
rpl_msg = context.subparse(msg, origin)
|
||||
if isinstance(rpl_msg, Command):
|
||||
rpl_msg.args = replace_variables(rpl_msg.args, msg)
|
||||
rpl_msg.args += msg.args
|
||||
rpl_msg.kwargs.update(msg.kwargs)
|
||||
elif len(msg.args) or len(msg.kwargs):
|
||||
raise IMException("This kind of alias doesn't take any argument (haven't you forgotten the '!'?).")
|
||||
|
||||
# Avoid infinite recursion
|
||||
if not isinstance(rpl_msg, Command) or msg.cmd != rpl_msg.cmd:
|
||||
return rpl_msg
|
||||
|
||||
return msg
|
||||
156
modules/alias/__init__.py
Normal file
156
modules/alias/__init__.py
Normal file
|
|
@ -0,0 +1,156 @@
|
|||
# coding=utf-8
|
||||
|
||||
import re
|
||||
import sys
|
||||
from datetime import datetime
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
def load(context):
|
||||
"""Load this module"""
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_unalias, "unalias"))
|
||||
add_hook("cmd_hook", Hook(cmd_alias, "alias"))
|
||||
add_hook("cmd_hook", Hook(cmd_set, "set"))
|
||||
add_hook("all_pre", Hook(treat_alias))
|
||||
add_hook("all_post", Hook(treat_variables))
|
||||
|
||||
global DATAS
|
||||
if not DATAS.hasNode("aliases"):
|
||||
DATAS.addChild(ModuleState("aliases"))
|
||||
DATAS.getNode("aliases").setIndex("alias")
|
||||
if not DATAS.hasNode("variables"):
|
||||
DATAS.addChild(ModuleState("variables"))
|
||||
DATAS.getNode("variables").setIndex("name")
|
||||
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "alias module"
|
||||
|
||||
def help_full ():
|
||||
return "TODO"
|
||||
|
||||
def set_variable(name, value):
|
||||
var = ModuleState("variable")
|
||||
var["name"] = name
|
||||
var["value"] = value
|
||||
DATAS.getNode("variables").addChild(var)
|
||||
|
||||
def get_variable(name, msg=None):
|
||||
if name == "sender":
|
||||
return msg.sender
|
||||
elif name == "nick":
|
||||
return msg.nick
|
||||
elif name == "chan" or name == "channel":
|
||||
return msg.channel
|
||||
elif name == "date":
|
||||
now = datetime.now()
|
||||
return ("%d/%d/%d %d:%d:%d"%(now.day, now.month, now.year, now.hour,
|
||||
now.minute, now.second))
|
||||
elif name in DATAS.getNode("variables").index:
|
||||
return DATAS.getNode("variables").index[name]["value"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def cmd_set(msg):
|
||||
if len (msg.cmds) > 2:
|
||||
set_variable(msg.cmds[1], " ".join(msg.cmds[2:]))
|
||||
res = Response(msg.sender, "Variable \$%s définie." % msg.cmds[1])
|
||||
save()
|
||||
return res
|
||||
return Response(msg.sender, "!set prend au minimum deux arguments : le nom de la variable et sa valeur.")
|
||||
|
||||
def cmd_alias(msg):
|
||||
if len (msg.cmds) > 1:
|
||||
res = list()
|
||||
for alias in msg.cmds[1:]:
|
||||
if alias[0] == "!":
|
||||
alias = alias[1:]
|
||||
if alias in DATAS.getNode("aliases").index:
|
||||
res.append(Response(msg.sender, "!%s correspond à %s" % (alias,
|
||||
DATAS.getNode("aliases").index[alias]["origin"]),
|
||||
channel=msg.channel))
|
||||
else:
|
||||
res.append(Response(msg.sender, "!%s n'est pas un alias" % alias,
|
||||
channel=msg.channel))
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, "!alias prend en argument l'alias à étendre.",
|
||||
channel=msg.channel)
|
||||
|
||||
def cmd_unalias(msg):
|
||||
if len (msg.cmds) > 1:
|
||||
res = list()
|
||||
for alias in msg.cmds[1:]:
|
||||
if alias[0] == "!" and len(alias) > 1:
|
||||
alias = alias[1:]
|
||||
if alias in DATAS.getNode("aliases").index:
|
||||
if DATAS.getNode("aliases").index[alias]["creator"] == msg.nick or msg.is_owner:
|
||||
DATAS.getNode("aliases").delChild(DATAS.getNode("aliases").index[alias])
|
||||
res.append(Response(msg.sender, "%s a bien été supprimé" % alias, channel=msg.channel))
|
||||
else:
|
||||
res.append(Response(msg.sender, "Vous n'êtes pas le createur de l'alias %s." % alias, channel=msg.channel))
|
||||
else:
|
||||
res.append(Response(msg.sender, "%s n'est pas un alias" % alias, channel=msg.channel))
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, "!unalias prend en argument l'alias à supprimer.", channel=msg.channel)
|
||||
|
||||
def replace_variables(cnt, msg=None):
|
||||
cnt = cnt.split(' ')
|
||||
unsetCnt = list()
|
||||
for i in range(0, len(cnt)):
|
||||
if i not in unsetCnt:
|
||||
res = re.match("^([^$]*)(\\\\)?\\$([a-zA-Z0-9]+)(.*)$", cnt[i])
|
||||
if res is not None:
|
||||
try:
|
||||
varI = int(res.group(3))
|
||||
unsetCnt.append(varI)
|
||||
cnt[i] = res.group(1) + msg.cmds[varI] + res.group(4)
|
||||
except:
|
||||
if res.group(2) != "":
|
||||
cnt[i] = res.group(1) + "$" + res.group(3) + res.group(4)
|
||||
else:
|
||||
cnt[i] = res.group(1) + get_variable(res.group(3), msg) + res.group(4)
|
||||
return " ".join(cnt)
|
||||
|
||||
|
||||
def treat_variables(res):
|
||||
for i in range(0, len(res.messages)):
|
||||
if isinstance(res.messages[i], list):
|
||||
res.messages[i] = replace_variables(", ".join(res.messages[i]), res)
|
||||
else:
|
||||
res.messages[i] = replace_variables(res.messages[i], res)
|
||||
return True
|
||||
|
||||
def treat_alias(msg, hooks_cache):
|
||||
if msg.cmd == "PRIVMSG" and (len(msg.cmds[0]) > 0 and msg.cmds[0][0] == "!"
|
||||
and msg.cmds[0][1:] in DATAS.getNode("aliases").index
|
||||
and msg.cmds[0][1:] not in hooks_cache("cmd_hook")):
|
||||
msg.content = msg.content.replace(msg.cmds[0],
|
||||
DATAS.getNode("aliases").index[msg.cmds[0][1:]]["origin"], 1)
|
||||
|
||||
msg.content = replace_variables(msg.content, msg)
|
||||
|
||||
msg.parse_content()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def parseask(msg):
|
||||
global ALIAS
|
||||
if re.match(".*(set|cr[ée]{2}|nouvel(le)?) alias.*", msg.content) is not None:
|
||||
result = re.match(".*alias !?([^ ]+) (pour|=|:) (.+)$", msg.content)
|
||||
if result.group(1) in DATAS.getNode("aliases").index or result.group(3).find("alias") >= 0:
|
||||
return Response(msg.sender, "Cet alias est déjà défini.")
|
||||
else:
|
||||
alias = ModuleState("alias")
|
||||
alias["alias"] = result.group(1)
|
||||
alias["origin"] = result.group(3)
|
||||
alias["creator"] = msg.nick
|
||||
DATAS.getNode("aliases").addChild(alias)
|
||||
res = Response(msg.sender, "Nouvel alias %s défini avec succès." % result.group(1))
|
||||
save()
|
||||
return res
|
||||
return False
|
||||
|
|
@ -1,68 +1,56 @@
|
|||
"""People birthdays and ages"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
# coding=utf-8
|
||||
|
||||
import re
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from datetime import datetime
|
||||
from datetime import date
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.countdown import countdown_format
|
||||
from nemubot.tools.date import extractDate
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
from xmlparser.node import ModuleState
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
nemubotversion = 3.3
|
||||
|
||||
def load(context):
|
||||
context.data.setIndex("name", "birthday")
|
||||
global DATAS
|
||||
DATAS.setIndex("name", "birthday")
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "People birthdays and ages"
|
||||
|
||||
|
||||
def help_full ():
|
||||
return "!anniv /who/: gives the remaining time before the anniversary of /who/\n!age /who/: gives the age of /who/\nIf /who/ is not given, gives the remaining time before your anniversary.\n\n To set yout birthday, say it to nemubot :)"
|
||||
|
||||
|
||||
def findName(msg):
|
||||
if (not len(msg.args) or msg.args[0].lower() == "moi" or
|
||||
msg.args[0].lower() == "me"):
|
||||
name = msg.frm.lower()
|
||||
if len(msg.cmds) < 2 or msg.cmds[1].lower() == "moi" or msg.cmds[1].lower() == "me":
|
||||
name = msg.nick.lower()
|
||||
else:
|
||||
name = msg.args[0].lower()
|
||||
name = msg.cmds[1].lower()
|
||||
|
||||
matches = []
|
||||
|
||||
if name in context.data.index:
|
||||
if name in DATAS.index:
|
||||
matches.append(name)
|
||||
else:
|
||||
for k in context.data.index.keys():
|
||||
for k in DATAS.index.keys ():
|
||||
if k.find (name) == 0:
|
||||
matches.append (k)
|
||||
return (matches, name)
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
## Commands
|
||||
|
||||
@hook.command("anniv",
|
||||
help="gives the remaining time before the anniversary of known people",
|
||||
help_usage={
|
||||
None: "Calculate the time remaining before your birthday",
|
||||
"WHO": "Calculate the time remaining before WHO's birthday",
|
||||
})
|
||||
def cmd_anniv(msg):
|
||||
(matches, name) = findName(msg)
|
||||
if len(matches) == 1:
|
||||
name = matches[0]
|
||||
tyd = context.data.index[name].getDate("born")
|
||||
tyd = DATAS.index[name].getDate("born")
|
||||
tyd = datetime(date.today().year, tyd.month, tyd.day)
|
||||
|
||||
if (tyd.day == datetime.today().day and
|
||||
tyd.month == datetime.today().month):
|
||||
return Response(countdown_format(
|
||||
context.data.index[name].getDate("born"), "",
|
||||
return Response(msg.sender, msg.countdown_format(
|
||||
DATAS.index[name].getDate("born"), "",
|
||||
"C'est aujourd'hui l'anniversaire de %s !"
|
||||
" Il a %s. Joyeux anniversaire :)" % (name, "%s")),
|
||||
msg.channel)
|
||||
|
|
@ -70,65 +58,54 @@ def cmd_anniv(msg):
|
|||
if tyd < datetime.today():
|
||||
tyd = datetime(date.today().year + 1, tyd.month, tyd.day)
|
||||
|
||||
return Response(countdown_format(tyd,
|
||||
return Response(msg.sender, msg.countdown_format(tyd,
|
||||
"Il reste %s avant l'anniversaire de %s !" % ("%s",
|
||||
name), ""),
|
||||
msg.channel)
|
||||
else:
|
||||
return Response("désolé, je ne connais pas la date d'anniversaire"
|
||||
return Response(msg.sender, "désolé, je ne connais pas la date d'anniversaire"
|
||||
" de %s. Quand est-il né ?" % name,
|
||||
msg.channel, msg.frm)
|
||||
msg.channel, msg.nick)
|
||||
|
||||
|
||||
@hook.command("age",
|
||||
help="Calculate age of known people",
|
||||
help_usage={
|
||||
None: "Calculate your age",
|
||||
"WHO": "Calculate the age of WHO"
|
||||
})
|
||||
def cmd_age(msg):
|
||||
(matches, name) = findName(msg)
|
||||
if len(matches) == 1:
|
||||
name = matches[0]
|
||||
d = context.data.index[name].getDate("born")
|
||||
d = DATAS.index[name].getDate("born")
|
||||
|
||||
return Response(countdown_format(d,
|
||||
return Response(msg.sender, msg.countdown_format(d,
|
||||
"%s va naître dans %s." % (name, "%s"),
|
||||
"%s a %s." % (name, "%s")),
|
||||
msg.channel)
|
||||
else:
|
||||
return Response("désolé, je ne connais pas l'âge de %s."
|
||||
" Quand est-il né ?" % name, msg.channel, msg.frm)
|
||||
return Response(msg.sender, "désolé, je ne connais pas l'âge de %s."
|
||||
" Quand est-il né ?" % name, msg.channel, msg.nick)
|
||||
return True
|
||||
|
||||
|
||||
## Input parsing
|
||||
|
||||
@hook.ask()
|
||||
def parseask(msg):
|
||||
res = re.match(r"^(\S+)\s*('s|suis|est|is|was|were)?\s+(birthday|geburtstag|née? |nee? le|born on).*$", msg.message, re.I)
|
||||
if res is not None:
|
||||
msgl = msg.content.lower ()
|
||||
if re.match("^.*(date de naissance|birthday|geburtstag|née? |nee? le|born on).*$", msgl) is not None:
|
||||
try:
|
||||
extDate = extractDate(msg.message)
|
||||
extDate = msg.extractDate()
|
||||
if extDate is None or extDate.year > datetime.now().year:
|
||||
return Response("la date de naissance ne paraît pas valide...",
|
||||
return Response(msg.sender,
|
||||
"ta date de naissance ne paraît pas valide...",
|
||||
msg.channel,
|
||||
msg.frm)
|
||||
msg.nick)
|
||||
else:
|
||||
nick = res.group(1)
|
||||
if nick == "my" or nick == "I" or nick == "i" or nick == "je" or nick == "mon" or nick == "ma":
|
||||
nick = msg.frm
|
||||
if nick.lower() in context.data.index:
|
||||
context.data.index[nick.lower()]["born"] = extDate
|
||||
if msg.nick.lower() in DATAS.index:
|
||||
DATAS.index[msg.nick.lower()]["born"] = extDate
|
||||
else:
|
||||
ms = ModuleState("birthday")
|
||||
ms.setAttribute("name", nick.lower())
|
||||
ms.setAttribute("name", msg.nick.lower())
|
||||
ms.setAttribute("born", extDate)
|
||||
context.data.addChild(ms)
|
||||
context.save()
|
||||
return Response("ok, c'est noté, %s est né le %s"
|
||||
% (nick, extDate.strftime("%A %d %B %Y à %H:%M")),
|
||||
DATAS.addChild(ms)
|
||||
save()
|
||||
return Response(msg.sender,
|
||||
"ok, c'est noté, ta date de naissance est le %s"
|
||||
% extDate.strftime("%A %d %B %Y à %H:%M"),
|
||||
msg.channel,
|
||||
msg.frm)
|
||||
msg.nick)
|
||||
except:
|
||||
raise IMException("la date de naissance ne paraît pas valide.")
|
||||
return Response(msg.sender, "ta date de naissance ne paraît pas valide...",
|
||||
msg.channel, msg.nick)
|
||||
|
|
|
|||
5
modules/birthday.xml
Normal file
5
modules/birthday.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="birthday">
|
||||
<message type="cmd" name="anniv" call="cmd_anniv" />
|
||||
<message type="cmd" name="age" call="cmd_age" />
|
||||
</nemubotmodule>
|
||||
|
|
@ -1,74 +1,51 @@
|
|||
"""Wishes Happy New Year when the time comes"""
|
||||
# coding=utf-8
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
from datetime import datetime
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from nemubot.event import ModuleEvent
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.countdown import countdown_format
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
yr = datetime.now(timezone.utc).year
|
||||
yrn = datetime.now(timezone.utc).year + 1
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
nemubotversion = 3.3
|
||||
|
||||
def load(context):
|
||||
if not context.config or not context.config.hasNode("sayon"):
|
||||
print("You can append in your configuration some balise to "
|
||||
"automaticaly wish an happy new year on some channels like:\n"
|
||||
"<sayon hostid=\"nemubot@irc.freenode.net:6667\" "
|
||||
"channel=\"#nemutest\" />")
|
||||
yr = datetime.today().year
|
||||
yrn = datetime.today().year + 1
|
||||
|
||||
d = datetime(yrn, 1, 1, 0, 0, 0) - datetime.now()
|
||||
# d = datetime(yr, 12, 31, 19, 34, 0) - datetime.now()
|
||||
add_event(ModuleEvent(intervalle=0, offset=d.total_seconds(), call=bonneannee))
|
||||
|
||||
from hooks import Hook
|
||||
add_hook("cmd_rgxp", Hook(cmd_timetoyear, data=yrn, regexp="^[0-9]{4}$"))
|
||||
add_hook("cmd_hook", Hook(cmd_newyear, str(yrn), yrn))
|
||||
add_hook("cmd_hook", Hook(cmd_newyear, "ny", yrn))
|
||||
add_hook("cmd_hook", Hook(cmd_newyear, "newyear", yrn))
|
||||
add_hook("cmd_hook", Hook(cmd_newyear, "new-year", yrn))
|
||||
add_hook("cmd_hook", Hook(cmd_newyear, "new year", yrn))
|
||||
|
||||
def bonneannee():
|
||||
txt = "Bonne année %d !" % yrn
|
||||
txt = "Bonne année %d !" % datetime.today().year
|
||||
print (txt)
|
||||
if context.config and context.config.hasNode("sayon"):
|
||||
for sayon in context.config.getNodes("sayon"):
|
||||
if "hostid" not in sayon or "channel" not in sayon:
|
||||
print("Error: missing hostif or channel")
|
||||
continue
|
||||
srv = sayon["hostid"]
|
||||
chan = sayon["channel"]
|
||||
context.send_response(srv, Response(txt, chan))
|
||||
send_response("localhost:2771", Response(None, txt, "#epitagueule"))
|
||||
send_response("localhost:2771", Response(None, txt, "#yaka"))
|
||||
send_response("localhost:2771", Response(None, txt, "#epita2014"))
|
||||
send_response("localhost:2771", Response(None, txt, "#ykar"))
|
||||
send_response("localhost:2771", Response(None, txt, "#ordissimo"))
|
||||
send_response("localhost:2771", Response(None, txt, "#42sh"))
|
||||
send_response("localhost:2771", Response(None, txt, "#nemubot"))
|
||||
|
||||
d = datetime(yrn, 1, 1, 0, 0, 0, 0,
|
||||
timezone.utc) - datetime.now(timezone.utc)
|
||||
context.add_event(ModuleEvent(interval=0, offset=d.total_seconds(),
|
||||
call=bonneannee))
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("newyear",
|
||||
help="Display the remaining time before the next new year")
|
||||
@hook.command(str(yrn),
|
||||
help="Display the remaining time before %d" % yrn)
|
||||
def cmd_newyear(msg):
|
||||
return Response(countdown_format(datetime(yrn, 1, 1, 0, 0, 1, 0,
|
||||
timezone.utc),
|
||||
def cmd_newyear(msg, yr):
|
||||
return Response(msg.sender,
|
||||
msg.countdown_format(datetime(yr, 1, 1, 0, 0, 1),
|
||||
"Il reste %s avant la nouvelle année.",
|
||||
"Nous faisons déjà la fête depuis %s !"),
|
||||
channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command(data=yrn, regexp="^[0-9]{4}$",
|
||||
help="Calculate time remaining/passed before/since the requested year")
|
||||
def cmd_timetoyear(msg, cur):
|
||||
yr = int(msg.cmd)
|
||||
yr = int(msg.cmds[0])
|
||||
|
||||
if yr == cur:
|
||||
return None
|
||||
|
||||
return Response(countdown_format(datetime(yr, 1, 1, 0, 0, 1, 0,
|
||||
timezone.utc),
|
||||
return Response(msg.sender,
|
||||
msg.countdown_format(datetime(yr, 1, 1, 0, 0, 1),
|
||||
"Il reste %s avant %d." % ("%s", yr),
|
||||
"Le premier janvier %d est passé "
|
||||
"depuis %s !" % (yr, "%s")),
|
||||
"Le premier janvier %d est passé depuis %s !" % (yr, "%s")),
|
||||
channel=msg.channel)
|
||||
|
|
|
|||
115
modules/books.py
115
modules/books.py
|
|
@ -1,115 +0,0 @@
|
|||
"""Looking for books"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import urllib
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
if not context.config or "goodreadskey" not in context.config:
|
||||
raise ImportError("You need a Goodreads API key in order to use this "
|
||||
"module. Add it to the module configuration file:\n"
|
||||
"<module name=\"books\" goodreadskey=\"XXXXXX\" />\n"
|
||||
"Get one at https://www.goodreads.com/api/keys")
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def get_book(title):
|
||||
"""Retrieve a book from its title"""
|
||||
response = web.getXML("https://www.goodreads.com/book/title.xml?key=%s&title=%s" %
|
||||
(context.config["goodreadskey"], urllib.parse.quote(title)))
|
||||
if response is not None and len(response.getElementsByTagName("book")):
|
||||
return response.getElementsByTagName("book")[0]
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
def search_books(title):
|
||||
"""Get a list of book matching given title"""
|
||||
response = web.getXML("https://www.goodreads.com/search.xml?key=%s&q=%s" %
|
||||
(context.config["goodreadskey"], urllib.parse.quote(title)))
|
||||
if response is not None and len(response.getElementsByTagName("search")):
|
||||
return response.getElementsByTagName("search")[0].getElementsByTagName("results")[0].getElementsByTagName("work")
|
||||
else:
|
||||
return []
|
||||
|
||||
|
||||
def search_author(name):
|
||||
"""Looking for an author"""
|
||||
response = web.getXML("https://www.goodreads.com/api/author_url/%s?key=%s" %
|
||||
(urllib.parse.quote(name), context.config["goodreadskey"]))
|
||||
if response is not None and len(response.getElementsByTagName("author")) and response.getElementsByTagName("author")[0].hasAttribute("id"):
|
||||
response = web.getXML("https://www.goodreads.com/author/show/%s.xml?key=%s" %
|
||||
(urllib.parse.quote(response.getElementsByTagName("author")[0].getAttribute("id")), context.config["goodreadskey"]))
|
||||
if response is not None and len(response.getElementsByTagName("author")):
|
||||
return response.getElementsByTagName("author")[0]
|
||||
return None
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("book",
|
||||
help="Get information about a book from its title",
|
||||
help_usage={
|
||||
"TITLE": "Get information about a book titled TITLE"
|
||||
})
|
||||
def cmd_book(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("please give me a title to search")
|
||||
|
||||
book = get_book(" ".join(msg.args))
|
||||
if book is None:
|
||||
raise IMException("unable to find book named like this")
|
||||
res = Response(channel=msg.channel)
|
||||
res.append_message("%s, written by %s: %s" % (book.getElementsByTagName("title")[0].firstChild.nodeValue,
|
||||
book.getElementsByTagName("author")[0].getElementsByTagName("name")[0].firstChild.nodeValue,
|
||||
web.striphtml(book.getElementsByTagName("description")[0].firstChild.nodeValue if book.getElementsByTagName("description")[0].firstChild else "")))
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("search_books",
|
||||
help="Search book's title",
|
||||
help_usage={
|
||||
"APPROX_TITLE": "Search for a book approximately titled APPROX_TITLE"
|
||||
})
|
||||
def cmd_books(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("please give me a title to search")
|
||||
|
||||
title = " ".join(msg.args)
|
||||
res = Response(channel=msg.channel,
|
||||
title="%s" % (title),
|
||||
count=" (%d more books)")
|
||||
|
||||
for book in search_books(title):
|
||||
res.append_message("%s, writed by %s" % (book.getElementsByTagName("best_book")[0].getElementsByTagName("title")[0].firstChild.nodeValue,
|
||||
book.getElementsByTagName("best_book")[0].getElementsByTagName("author")[0].getElementsByTagName("name")[0].firstChild.nodeValue))
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("author_books",
|
||||
help="Looking for books writen by a given author",
|
||||
help_usage={
|
||||
"AUTHOR": "Looking for books writen by AUTHOR"
|
||||
})
|
||||
def cmd_author(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("please give me an author to search")
|
||||
|
||||
name = " ".join(msg.args)
|
||||
ath = search_author(name)
|
||||
if ath is None:
|
||||
raise IMException("%s does not appear to be a published author." % name)
|
||||
return Response([b.getElementsByTagName("title")[0].firstChild.nodeValue for b in ath.getElementsByTagName("book")],
|
||||
channel=msg.channel,
|
||||
title=ath.getElementsByTagName("name")[0].firstChild.nodeValue)
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
"""Concatenate commands"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.message import Command, DirectAsk, Text
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def cat(msg, *terms):
|
||||
res = Response(channel=msg.to_response, server=msg.server)
|
||||
for term in terms:
|
||||
m = context.subparse(msg, term)
|
||||
if isinstance(m, Command) or isinstance(m, DirectAsk):
|
||||
for r in context.subtreat(m):
|
||||
if isinstance(r, Response):
|
||||
for t in range(len(r.messages)):
|
||||
res.append_message(r.messages[t],
|
||||
title=r.rawtitle if not isinstance(r.rawtitle, list) else r.rawtitle[t])
|
||||
|
||||
elif isinstance(r, Text):
|
||||
res.append_message(r.message)
|
||||
|
||||
elif isinstance(r, str):
|
||||
res.append_message(r)
|
||||
|
||||
else:
|
||||
res.append_message(term)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("cat",
|
||||
help="Concatenate responses of commands given as argument",
|
||||
help_usage={"!SUBCMD [!SUBCMD [...]]": "Concatenate response of subcommands"},
|
||||
keywords={
|
||||
"merge": "Merge messages into the same",
|
||||
})
|
||||
def cmd_cat(msg):
|
||||
if len(msg.args) < 1:
|
||||
raise IMException("No subcommand to concatenate")
|
||||
|
||||
r = cat(msg, *msg.args)
|
||||
|
||||
if "merge" in msg.kwargs and len(r.messages) > 1:
|
||||
r.messages = [ r.messages ]
|
||||
|
||||
return r
|
||||
96
modules/chronos.py
Normal file
96
modules/chronos.py
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
from urllib.parse import quote
|
||||
|
||||
from tools import web
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "Gets informations about current and next Épita courses"
|
||||
|
||||
def help_full ():
|
||||
return "!chronos [spé] : gives current and next courses."
|
||||
|
||||
|
||||
def get_courses(classe=None, room=None, teacher=None, date=None):
|
||||
url = CONF.getNode("server")["url"]
|
||||
if classe is not None:
|
||||
url += "&class=" + quote(classe)
|
||||
if room is not None:
|
||||
url += "&room=" + quote(room)
|
||||
if teacher is not None:
|
||||
url += "&teacher=" + quote(teacher)
|
||||
#TODO: date, not implemented at 23.tf
|
||||
|
||||
print_debug(url)
|
||||
response = web.getXML(url)
|
||||
if response is not None:
|
||||
print_debug(response)
|
||||
return response.getNodes("course")
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_next_courses(classe=None, room=None, teacher=None, date=None):
|
||||
courses = get_courses(classe, room, teacher, date)
|
||||
now = datetime.now()
|
||||
for c in courses:
|
||||
start = c.getFirstNode("start").getDate()
|
||||
|
||||
if now > start:
|
||||
return c
|
||||
return None
|
||||
|
||||
def get_near_courses(classe=None, room=None, teacher=None, date=None):
|
||||
courses = get_courses(classe, room, teacher, date)
|
||||
return courses[0]
|
||||
|
||||
def cmd_chronos(msg):
|
||||
if len(msg.cmds) > 1:
|
||||
classe = msg.cmds[1]
|
||||
else:
|
||||
classe = ""
|
||||
|
||||
res = Response(msg.sender, channel=msg.channel, nomore="Je n'ai pas d'autre cours à afficher")
|
||||
|
||||
courses = get_courses(classe)
|
||||
print_debug(courses)
|
||||
if courses is not None:
|
||||
now = datetime.now()
|
||||
tomorrow = now + timedelta(days=1)
|
||||
for c in courses:
|
||||
idc = c.getFirstNode("id").getContent()
|
||||
crs = c.getFirstNode("title").getContent()
|
||||
start = c.getFirstNode("start").getDate()
|
||||
end = c.getFirstNode("end").getDate()
|
||||
where = c.getFirstNode("where").getContent()
|
||||
teacher = c.getFirstNode("teacher").getContent()
|
||||
students = c.getFirstNode("students").getContent()
|
||||
|
||||
if now > start:
|
||||
title = "Actuellement "
|
||||
msg = "\x03\x02" + crs + "\x03\x02 jusqu'"
|
||||
if end < tomorrow:
|
||||
msg += "à \x03\x02" + end.strftime("%H:%M")
|
||||
else:
|
||||
msg += "au \x03\x02" + end.strftime("%a %d à %H:%M")
|
||||
msg += "\x03\x02 en \x03\x02" + where + "\x03\x02"
|
||||
else:
|
||||
title = "Prochainement "
|
||||
duration = (end - start).total_seconds() / 60
|
||||
|
||||
msg = "\x03\x02" + crs + "\x03\x02 le \x03\x02" + start.strftime("%a %d à %H:%M") + "\x03\x02 pour " + "%dh%02d" % (int(duration / 60), duration % 60) + " en \x03\x02" + where + "\x03\x02"
|
||||
|
||||
if teacher != "":
|
||||
msg += " avec " + teacher
|
||||
if students != "":
|
||||
msg += " pour les " + students
|
||||
|
||||
res.append_message(msg, title)
|
||||
else:
|
||||
res.append_message("Aucun cours n'a été trouvé")
|
||||
|
||||
return res
|
||||
6
modules/chronos.xml
Normal file
6
modules/chronos.xml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="chronos">
|
||||
<server url="http://chronos.23.tf/index.php?xml" />
|
||||
<message type="cmd" name="chronos" call="cmd_chronos" />
|
||||
<message type="cmd" name="Χρονος" call="cmd_chronos" />
|
||||
</nemubotmodule>
|
||||
201
modules/cmd_server.py
Normal file
201
modules/cmd_server.py
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
|
||||
# Nemubot is a modulable IRC bot, built around XML configuration files.
|
||||
# Copyright (C) 2012 Mercier Pierre-Olivier
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Affero General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Affero General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from networkbot import NetworkBot
|
||||
|
||||
nemubotversion = 3.3
|
||||
NODATA = True
|
||||
|
||||
def getserver(toks, context, prompt):
|
||||
"""Choose the server in toks or prompt"""
|
||||
if len(toks) > 1 and toks[0] in context.servers:
|
||||
return (context.servers[toks[0]], toks[1:])
|
||||
elif prompt.selectedServer is not None:
|
||||
return (prompt.selectedServer, toks)
|
||||
else:
|
||||
return (None, toks)
|
||||
|
||||
def close(data, toks, context, prompt):
|
||||
"""Disconnect and forget (remove from the servers list) the server"""
|
||||
if len(toks) > 1:
|
||||
for s in toks[1:]:
|
||||
if s in servers:
|
||||
context.servers[s].disconnect()
|
||||
del context.servers[s]
|
||||
else:
|
||||
print ("close: server `%s' not found." % s)
|
||||
elif prompt.selectedServer is not None:
|
||||
prompt.selectedServer.disconnect()
|
||||
del prompt.servers[selectedServer.id]
|
||||
prompt.selectedServer = None
|
||||
return
|
||||
|
||||
def connect(data, toks, context, prompt):
|
||||
"""Make the connexion to a server"""
|
||||
if len(toks) > 1:
|
||||
for s in toks[1:]:
|
||||
if s in context.servers:
|
||||
context.servers[s].launch(context.receive_message)
|
||||
else:
|
||||
print ("connect: server `%s' not found." % s)
|
||||
|
||||
elif prompt.selectedServer is not None:
|
||||
prompt.selectedServer.launch(context.receive_message)
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in argument.")
|
||||
|
||||
def disconnect(data, toks, context, prompt):
|
||||
"""Close the connection to a server"""
|
||||
if len(toks) > 1:
|
||||
for s in toks[1:]:
|
||||
if s in context.servers:
|
||||
if not context.servers[s].disconnect():
|
||||
print ("disconnect: server `%s' already disconnected." % s)
|
||||
else:
|
||||
print ("disconnect: server `%s' not found." % s)
|
||||
elif prompt.selectedServer is not None:
|
||||
if not prompt.selectedServer.disconnect():
|
||||
print ("disconnect: server `%s' already disconnected."
|
||||
% prompt.selectedServer.id)
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in argument.")
|
||||
|
||||
def discover(data, toks, context, prompt):
|
||||
"""Discover a new bot on a server"""
|
||||
(srv, toks) = getserver(toks, context, prompt)
|
||||
if srv is not None:
|
||||
for name in toks[1:]:
|
||||
if "!" in name:
|
||||
bot = context.add_networkbot(srv, name)
|
||||
bot.connect()
|
||||
else:
|
||||
print (" %s is not a valid fullname, for example: nemubot!nemubotV3@bot.nemunai.re")
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in first argument.")
|
||||
|
||||
def hotswap(data, toks, context, prompt):
|
||||
"""Reload a server class"""
|
||||
if len(toks) > 1:
|
||||
print ("hotswap: apply only on selected server")
|
||||
elif prompt.selectedServer is not None:
|
||||
del context.servers[prompt.selectedServer.id]
|
||||
srv = server.Server(selectedServer.node, selectedServer.nick,
|
||||
selectedServer.owner, selectedServer.realname,
|
||||
selectedServer.s)
|
||||
context.servers[srv.id] = srv
|
||||
prompt.selectedServer.kill()
|
||||
prompt.selectedServer = srv
|
||||
prompt.selectedServer.start()
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in argument.")
|
||||
|
||||
def join(data, toks, context, prompt):
|
||||
"""Join or leave a channel"""
|
||||
rd = 1
|
||||
if len(toks) <= rd:
|
||||
print ("%s: not enough arguments." % toks[0])
|
||||
return
|
||||
|
||||
if toks[rd] in context.servers:
|
||||
srv = context.servers[toks[rd]]
|
||||
rd += 1
|
||||
elif prompt.selectedServer is not None:
|
||||
srv = prompt.selectedServer
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in argument.")
|
||||
return
|
||||
|
||||
if len(toks) <= rd:
|
||||
print ("%s: not enough arguments." % toks[0])
|
||||
return
|
||||
|
||||
if toks[0] == "join":
|
||||
if len(toks) > rd + 1:
|
||||
srv.join(toks[rd], toks[rd + 1])
|
||||
else:
|
||||
srv.join(toks[rd])
|
||||
elif toks[0] == "leave" or toks[0] == "part":
|
||||
srv.leave(toks[rd])
|
||||
return
|
||||
|
||||
def save_mod(data, toks, context, prompt):
|
||||
"""Force save module data"""
|
||||
if len(toks) < 2:
|
||||
print ("save: not enough arguments.")
|
||||
return
|
||||
|
||||
for mod in toks[1:]:
|
||||
if mod in context.modules:
|
||||
context.modules[mod].save()
|
||||
print ("save: module `%s´ saved successfully" % mod)
|
||||
else:
|
||||
print ("save: no module named `%s´" % mod)
|
||||
return
|
||||
|
||||
def send(data, toks, context, prompt):
|
||||
"""Send a message on a channel"""
|
||||
rd = 1
|
||||
if len(toks) <= rd:
|
||||
print ("send: not enough arguments.")
|
||||
return
|
||||
|
||||
if toks[rd] in context.servers:
|
||||
srv = context.servers[toks[rd]]
|
||||
rd += 1
|
||||
elif prompt.selectedServer is not None:
|
||||
srv = prompt.selectedServer
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in argument.")
|
||||
return
|
||||
|
||||
if len(toks) <= rd:
|
||||
print ("send: not enough arguments.")
|
||||
return
|
||||
|
||||
#Check the server is connected
|
||||
if not srv.connected:
|
||||
print ("send: server `%s' not connected." % srv.id)
|
||||
return
|
||||
|
||||
if toks[rd] in srv.channels:
|
||||
chan = toks[rd]
|
||||
rd += 1
|
||||
else:
|
||||
print ("send: channel `%s' not authorized in server `%s'."
|
||||
% (toks[rd], srv.id))
|
||||
return
|
||||
|
||||
if len(toks) <= rd:
|
||||
print ("send: not enough arguments.")
|
||||
return
|
||||
|
||||
srv.send_msg_final(chan, toks[rd])
|
||||
return "done"
|
||||
|
||||
def zap(data, toks, context, prompt):
|
||||
"""Hard change connexion state"""
|
||||
if len(toks) > 1:
|
||||
for s in toks[1:]:
|
||||
if s in context.servers:
|
||||
context.servers[s].connected = not context.servers[s].connected
|
||||
else:
|
||||
print ("zap: server `%s' not found." % s)
|
||||
elif prompt.selectedServer is not None:
|
||||
prompt.selectedServer.connected = not prompt.selectedServer.connected
|
||||
else:
|
||||
print (" Please SELECT a server or give its name in argument.")
|
||||
14
modules/cmd_server.xml
Normal file
14
modules/cmd_server.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="cmd_server">
|
||||
<command name="close" call="close" />
|
||||
<command name="connect" call="connect" />
|
||||
<command name="discover" call="discover" />
|
||||
<command name="disconnect" call="disconnect" />
|
||||
<command name="hotswap" call="hotswap" />
|
||||
<command name="join" call="join" />
|
||||
<command name="leave" call="join" />
|
||||
<command name="part" call="join" />
|
||||
<command name="save" call="save_mod" />
|
||||
<command name="send" call="send" />
|
||||
<command name="zap" call="zap" />
|
||||
</nemubotmodule>
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
"""Find french conjugaison"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from collections import defaultdict
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
from nemubot.tools.web import striphtml
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
s = [('present', '0'), ('présent', '0'), ('pr', '0'),
|
||||
('passé simple', '12'), ('passe simple', '12'), ('ps', '12'),
|
||||
('passé antérieur', '112'), ('passe anterieur', '112'), ('pa', '112'),
|
||||
('passé composé', '100'), ('passe compose', '100'), ('pc', '100'),
|
||||
('futur', '18'), ('f', '18'),
|
||||
('futur antérieur', '118'), ('futur anterieur', '118'), ('fa', '118'),
|
||||
('subjonctif présent', '24'), ('subjonctif present', '24'), ('spr', '24'),
|
||||
('subjonctif passé', '124'), ('subjonctif passe', '124'), ('spa', '124'),
|
||||
('plus que parfait', '106'), ('pqp', '106'),
|
||||
('imparfait', '6'), ('ii', '6')]
|
||||
|
||||
d = defaultdict(list)
|
||||
|
||||
for k, v in s:
|
||||
d[k].append(v)
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def get_conjug(verb, stringTens):
|
||||
url = ("https://leconjugueur.lefigaro.fr/conjugaison/verbe/%s.html" %
|
||||
quote(verb.encode("ISO-8859-1")))
|
||||
page = web.getURLContent(url)
|
||||
|
||||
if page is not None:
|
||||
for line in page.split("\n"):
|
||||
if re.search('<div class="modeBloc">', line) is not None:
|
||||
return compute_line(line, stringTens)
|
||||
return list()
|
||||
|
||||
|
||||
def compute_line(line, stringTens):
|
||||
try:
|
||||
idTemps = d[stringTens]
|
||||
except:
|
||||
raise IMException("le temps demandé n'existe pas")
|
||||
|
||||
if len(idTemps) == 0:
|
||||
raise IMException("le temps demandé n'existe pas")
|
||||
|
||||
index = line.index('<div id="temps' + idTemps[0] + '\"')
|
||||
endIndex = line[index:].index('<div class=\"conjugBloc\"')
|
||||
|
||||
endIndex += index
|
||||
newLine = line[index:endIndex]
|
||||
|
||||
res = list()
|
||||
for elt in re.finditer("[p|/]>([^/]*/b>)", newLine):
|
||||
res.append(striphtml(elt.group(1)
|
||||
.replace("<b>", "\x02")
|
||||
.replace("</b>", "\x0F")))
|
||||
return res
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("conjugaison",
|
||||
help_usage={
|
||||
"TENS VERB": "give the conjugaison for VERB in TENS."
|
||||
})
|
||||
def cmd_conjug(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("donne moi un temps et un verbe, et je te donnerai "
|
||||
"sa conjugaison!")
|
||||
|
||||
tens = ' '.join(msg.args[:-1])
|
||||
|
||||
verb = msg.args[-1]
|
||||
|
||||
conjug = get_conjug(verb, tens)
|
||||
|
||||
if len(conjug) > 0:
|
||||
return Response(conjug, channel=msg.channel,
|
||||
title="Conjugaison de %s" % verb)
|
||||
else:
|
||||
raise IMException("aucune conjugaison de '%s' n'a été trouvé" % verb)
|
||||
64
modules/cristal.py
Normal file
64
modules/cristal.py
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
# coding=utf-8
|
||||
|
||||
from tools import web
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "Gets information about Cristal missions"
|
||||
|
||||
def help_full ():
|
||||
return "!cristal [id|name] : gives information about id Cristal mission."
|
||||
|
||||
|
||||
def get_all_missions():
|
||||
print (web.getContent(CONF.getNode("server")["url"]))
|
||||
response = web.getXML(CONF.getNode("server")["url"])
|
||||
print (CONF.getNode("server")["url"])
|
||||
if response is not None:
|
||||
return response.getNodes("mission")
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_mission(id=None, name=None, people=None):
|
||||
missions = get_all_missions()
|
||||
if missions is not None:
|
||||
for m in missions.childs:
|
||||
if id is not None and m.getFirstNode("id").getContent() == id:
|
||||
return m
|
||||
elif (name is not None or name in m.getFirstNode("title").getContent()) and (people is not None or people in m.getFirstNode("contact").getContent()):
|
||||
return m
|
||||
return None
|
||||
|
||||
def cmd_cristal(msg):
|
||||
if len(msg.cmds) > 1:
|
||||
srch = msg.cmds[1]
|
||||
else:
|
||||
srch = ""
|
||||
|
||||
res = Response(msg.sender, channel=msg.channel, nomore="Je n'ai pas d'autre mission à afficher")
|
||||
|
||||
try:
|
||||
id=int(srch)
|
||||
name=""
|
||||
except:
|
||||
id=None
|
||||
name=srch
|
||||
|
||||
missions = get_all_missions()
|
||||
if missions is not None:
|
||||
print (missions)
|
||||
for m in missions:
|
||||
print (m)
|
||||
idm = m.getFirstNode("id").getContent()
|
||||
crs = m.getFirstNode("title").getContent()
|
||||
contact = m.getFirstNode("contact").getDate()
|
||||
updated = m.getFirstNode("updated").getDate()
|
||||
content = m.getFirstNode("content").getContent()
|
||||
|
||||
res.append_message(msg, crs + " ; contacter : " + contact + " : " + content)
|
||||
else:
|
||||
res.append_message("Aucune mission n'a été trouvée")
|
||||
|
||||
return res
|
||||
5
modules/cristal.xml
Normal file
5
modules/cristal.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="cristal">
|
||||
<server url="http://p0m.fr/cristal.php?f=xml" />
|
||||
<message type="cmd" name="cristal" call="cmd_cristal" />
|
||||
</nemubotmodule>
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
"""List upcoming CTFs"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.web import getURLContent, striphtml
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
URL = 'https://ctftime.org/event/list/upcoming'
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("ctfs",
|
||||
help="Display the upcoming CTFs")
|
||||
def get_info_yt(msg):
|
||||
soup = BeautifulSoup(getURLContent(URL))
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more upcoming CTF")
|
||||
|
||||
for line in soup.body.find_all('tr'):
|
||||
n = line.find_all('td')
|
||||
if len(n) == 7:
|
||||
res.append_message("\x02%s:\x0F from %s type %s at %s. Weight: %s. %s%s" %
|
||||
tuple([striphtml(x.text).strip() for x in n]))
|
||||
|
||||
return res
|
||||
|
|
@ -1,99 +0,0 @@
|
|||
"""Read CVE in your IM client"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.web import getURLContent, striphtml
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
BASEURL_NIST = 'https://nvd.nist.gov/vuln/detail/'
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
VULN_DATAS = {
|
||||
"alert-title": "vuln-warning-status-name",
|
||||
"alert-content": "vuln-warning-banner-content",
|
||||
|
||||
"description": "vuln-description",
|
||||
"published": "vuln-published-on",
|
||||
"last_modified": "vuln-last-modified-on",
|
||||
|
||||
"base_score": "vuln-cvssv3-base-score-link",
|
||||
"severity": "vuln-cvssv3-base-score-severity",
|
||||
"impact_score": "vuln-cvssv3-impact-score",
|
||||
"exploitability_score": "vuln-cvssv3-exploitability-score",
|
||||
|
||||
"av": "vuln-cvssv3-av",
|
||||
"ac": "vuln-cvssv3-ac",
|
||||
"pr": "vuln-cvssv3-pr",
|
||||
"ui": "vuln-cvssv3-ui",
|
||||
"s": "vuln-cvssv3-s",
|
||||
"c": "vuln-cvssv3-c",
|
||||
"i": "vuln-cvssv3-i",
|
||||
"a": "vuln-cvssv3-a",
|
||||
}
|
||||
|
||||
|
||||
def get_cve(cve_id):
|
||||
search_url = BASEURL_NIST + quote(cve_id.upper())
|
||||
|
||||
soup = BeautifulSoup(getURLContent(search_url))
|
||||
|
||||
vuln = {}
|
||||
|
||||
for vd in VULN_DATAS:
|
||||
r = soup.body.find(attrs={"data-testid": VULN_DATAS[vd]})
|
||||
if r:
|
||||
vuln[vd] = r.text.strip()
|
||||
|
||||
return vuln
|
||||
|
||||
|
||||
def display_metrics(av, ac, pr, ui, s, c, i, a, **kwargs):
|
||||
ret = []
|
||||
if av != "None": ret.append("Attack Vector: \x02%s\x0F" % av)
|
||||
if ac != "None": ret.append("Attack Complexity: \x02%s\x0F" % ac)
|
||||
if pr != "None": ret.append("Privileges Required: \x02%s\x0F" % pr)
|
||||
if ui != "None": ret.append("User Interaction: \x02%s\x0F" % ui)
|
||||
if s != "Unchanged": ret.append("Scope: \x02%s\x0F" % s)
|
||||
if c != "None": ret.append("Confidentiality: \x02%s\x0F" % c)
|
||||
if i != "None": ret.append("Integrity: \x02%s\x0F" % i)
|
||||
if a != "None": ret.append("Availability: \x02%s\x0F" % a)
|
||||
return ', '.join(ret)
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("cve",
|
||||
help="Display given CVE",
|
||||
help_usage={"CVE_ID": "Display the description of the given CVE"})
|
||||
def get_cve_desc(msg):
|
||||
res = Response(channel=msg.channel)
|
||||
|
||||
for cve_id in msg.args:
|
||||
if cve_id[:3].lower() != 'cve':
|
||||
cve_id = 'cve-' + cve_id
|
||||
|
||||
cve = get_cve(cve_id)
|
||||
if not cve:
|
||||
raise IMException("CVE %s doesn't exists." % cve_id)
|
||||
|
||||
if "alert-title" in cve or "alert-content" in cve:
|
||||
alert = "\x02%s:\x0F %s " % (cve["alert-title"] if "alert-title" in cve else "",
|
||||
cve["alert-content"] if "alert-content" in cve else "")
|
||||
else:
|
||||
alert = ""
|
||||
|
||||
if "base_score" not in cve and "description" in cve:
|
||||
res.append_message("{alert}Last modified on \x02{last_modified}\x0F. {description}".format(alert=alert, **cve), title=cve_id)
|
||||
else:
|
||||
metrics = display_metrics(**cve)
|
||||
res.append_message("{alert}Base score: \x02{base_score} {severity}\x0F (impact: \x02{impact_score}\x0F, exploitability: \x02{exploitability_score}\x0F; {metrics}), last modified on \x02{last_modified}\x0F. {description}".format(alert=alert, metrics=metrics, **cve), title=cve_id)
|
||||
|
||||
return res
|
||||
138
modules/ddg.py
138
modules/ddg.py
|
|
@ -1,138 +0,0 @@
|
|||
"""Search around DuckDuckGo search engine"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def do_search(terms):
|
||||
if "!safeoff" in terms:
|
||||
terms.remove("!safeoff")
|
||||
safeoff = True
|
||||
else:
|
||||
safeoff = False
|
||||
|
||||
sterm = " ".join(terms)
|
||||
return DDGResult(sterm, web.getJSON(
|
||||
"https://api.duckduckgo.com/?q=%s&format=json&no_redirect=1%s" %
|
||||
(quote(sterm), "&kp=-1" if safeoff else "")))
|
||||
|
||||
|
||||
class DDGResult:
|
||||
|
||||
def __init__(self, terms, res):
|
||||
if res is None:
|
||||
raise IMException("An error occurs during search")
|
||||
|
||||
self.terms = terms
|
||||
self.ddgres = res
|
||||
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
if not self.ddgres or "Type" not in self.ddgres:
|
||||
return ""
|
||||
return self.ddgres["Type"]
|
||||
|
||||
|
||||
@property
|
||||
def definition(self):
|
||||
if "Definition" not in self.ddgres or not self.ddgres["Definition"]:
|
||||
return None
|
||||
return self.ddgres["Definition"] + " <" + self.ddgres["DefinitionURL"] + "> from " + self.ddgres["DefinitionSource"]
|
||||
|
||||
|
||||
@property
|
||||
def relatedTopics(self):
|
||||
if "RelatedTopics" in self.ddgres:
|
||||
for rt in self.ddgres["RelatedTopics"]:
|
||||
if "Text" in rt:
|
||||
yield rt["Text"] + " <" + rt["FirstURL"] + ">"
|
||||
elif "Topics" in rt:
|
||||
yield rt["Name"] + ": " + "; ".join([srt["Text"] + " <" + srt["FirstURL"] + ">" for srt in rt["Topics"]])
|
||||
|
||||
|
||||
@property
|
||||
def redirect(self):
|
||||
if "Redirect" not in self.ddgres or not self.ddgres["Redirect"]:
|
||||
return None
|
||||
return self.ddgres["Redirect"]
|
||||
|
||||
|
||||
@property
|
||||
def entity(self):
|
||||
if "Entity" not in self.ddgres or not self.ddgres["Entity"]:
|
||||
return None
|
||||
return self.ddgres["Entity"]
|
||||
|
||||
|
||||
@property
|
||||
def heading(self):
|
||||
if "Heading" not in self.ddgres or not self.ddgres["Heading"]:
|
||||
return " ".join(self.terms)
|
||||
return self.ddgres["Heading"]
|
||||
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
if "Results" in self.ddgres:
|
||||
for res in self.ddgres["Results"]:
|
||||
yield res["Text"] + " <" + res["FirstURL"] + ">"
|
||||
|
||||
|
||||
@property
|
||||
def answer(self):
|
||||
if "Answer" not in self.ddgres or not self.ddgres["Answer"]:
|
||||
return None
|
||||
return web.striphtml(self.ddgres["Answer"])
|
||||
|
||||
|
||||
@property
|
||||
def abstract(self):
|
||||
if "Abstract" not in self.ddgres or not self.ddgres["Abstract"]:
|
||||
return None
|
||||
return self.ddgres["AbstractText"] + " <" + self.ddgres["AbstractURL"] + "> from " + self.ddgres["AbstractSource"]
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("define")
|
||||
def define(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate a term to define")
|
||||
|
||||
s = do_search(msg.args)
|
||||
|
||||
if not s.definition:
|
||||
raise IMException("no definition found for '%s'." % " ".join(msg.args))
|
||||
|
||||
return Response(s.definition, channel=msg.channel)
|
||||
|
||||
@hook.command("search")
|
||||
def search(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate a term to search")
|
||||
|
||||
s = do_search(msg.args)
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more results",
|
||||
count=" (%d more results)")
|
||||
|
||||
res.append_message(s.redirect)
|
||||
res.append_message(s.answer)
|
||||
res.append_message(s.abstract)
|
||||
res.append_message([r for r in s.result])
|
||||
|
||||
for rt in s.relatedTopics:
|
||||
res.append_message(rt)
|
||||
|
||||
res.append_message(s.definition)
|
||||
|
||||
return res
|
||||
68
modules/ddg/DDGSearch.py
Normal file
68
modules/ddg/DDGSearch.py
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# coding=utf-8
|
||||
|
||||
from urllib.parse import quote
|
||||
from urllib.request import urlopen
|
||||
|
||||
import xmlparser
|
||||
from tools import web
|
||||
|
||||
class DDGSearch:
|
||||
def __init__(self, terms):
|
||||
self.terms = terms
|
||||
|
||||
raw = urlopen("https://api.duckduckgo.com/?q=%s&format=xml" % quote(terms), timeout=10)
|
||||
self.ddgres = xmlparser.parse_string(raw.read())
|
||||
|
||||
@property
|
||||
def type(self):
|
||||
if self.ddgres and self.ddgres.hasNode("Type"):
|
||||
return self.ddgres.getFirstNode("Type").getContent()
|
||||
else:
|
||||
return ""
|
||||
|
||||
@property
|
||||
def definition(self):
|
||||
if self.ddgres.hasNode("Definition"):
|
||||
return self.ddgres.getFirstNode("Definition").getContent()
|
||||
else:
|
||||
return "Sorry, no definition found for %s" % self.terms
|
||||
|
||||
@property
|
||||
def relatedTopics(self):
|
||||
try:
|
||||
for rt in self.ddgres.getFirstNode("RelatedTopics").getNodes("RelatedTopic"):
|
||||
yield rt.getFirstNode("Text").getContent()
|
||||
except:
|
||||
pass
|
||||
|
||||
@property
|
||||
def redirect(self):
|
||||
try:
|
||||
return self.ddgres.getFirstNode("Redirect").getContent()
|
||||
except:
|
||||
return None
|
||||
|
||||
@property
|
||||
def result(self):
|
||||
try:
|
||||
node = self.ddgres.getFirstNode("Results").getFirstNode("Result")
|
||||
return node.getFirstNode("Text").getContent() + ": " + node.getFirstNode("FirstURL").getContent()
|
||||
except:
|
||||
return None
|
||||
|
||||
@property
|
||||
def answer(self):
|
||||
try:
|
||||
return web.striphtml(self.ddgres.getFirstNode("Answer").getContent())
|
||||
except:
|
||||
return None
|
||||
|
||||
@property
|
||||
def abstract(self):
|
||||
try:
|
||||
if self.ddgres.getNode("Abstract").getContent() != "":
|
||||
return self.ddgres.getNode("Abstract").getContent() + " <" + self.ddgres.getNode("AbstractURL").getContent() + ">"
|
||||
else:
|
||||
return None
|
||||
except:
|
||||
return None
|
||||
71
modules/ddg/WFASearch.py
Normal file
71
modules/ddg/WFASearch.py
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
# coding=utf-8
|
||||
|
||||
from urllib.parse import quote
|
||||
from urllib.request import urlopen
|
||||
|
||||
import xmlparser
|
||||
|
||||
class WFASearch:
|
||||
def __init__(self, terms):
|
||||
self.terms = terms
|
||||
try:
|
||||
raw = urlopen("http://api.wolframalpha.com/v2/query?"
|
||||
"input=%s&appid=%s"
|
||||
% (quote(terms),
|
||||
CONF.getNode("wfaapi")["key"]), timeout=15)
|
||||
self.wfares = xmlparser.parse_string(raw.read())
|
||||
except (TypeError, KeyError):
|
||||
print ("You need a Wolfram|Alpha API key in order to use this "
|
||||
"module. Add it to the module configuration file:\n<wfaapi"
|
||||
" key=\"XXXXXX-XXXXXXXXXX\" />\nRegister at "
|
||||
"http://products.wolframalpha.com/api/")
|
||||
self.wfares = None
|
||||
|
||||
@property
|
||||
def success(self):
|
||||
try:
|
||||
return self.wfares["success"] == "true"
|
||||
except:
|
||||
return False
|
||||
|
||||
@property
|
||||
def error(self):
|
||||
if self.wfares is None:
|
||||
return "An error occurs during computation."
|
||||
elif self.wfares["error"] == "true":
|
||||
return "An error occurs during computation: " + self.wfares.getNode("error").getNode("msg").getContent()
|
||||
elif self.wfares.hasNode("didyoumeans"):
|
||||
start = "Did you mean: "
|
||||
tag = "didyoumean"
|
||||
end = "?"
|
||||
elif self.wfares.hasNode("tips"):
|
||||
start = "Tips: "
|
||||
tag = "tip"
|
||||
end = ""
|
||||
elif self.wfares.hasNode("relatedexamples"):
|
||||
start = "Related examples: "
|
||||
tag = "relatedexample"
|
||||
end = ""
|
||||
elif self.wfares.hasNode("futuretopic"):
|
||||
return self.wfares.getNode("futuretopic")["msg"]
|
||||
else:
|
||||
return "An error occurs during computation"
|
||||
proposal = list()
|
||||
for dym in self.wfares.getNode(tag + "s").getNodes(tag):
|
||||
if tag == "tip":
|
||||
proposal.append(dym["text"])
|
||||
elif tag == "relatedexample":
|
||||
proposal.append(dym["desc"])
|
||||
else:
|
||||
proposal.append(dym.getContent())
|
||||
return start + ', '.join(proposal) + end
|
||||
|
||||
@property
|
||||
def nextRes(self):
|
||||
try:
|
||||
for node in self.wfares.getNodes("pod"):
|
||||
for subnode in node.getNodes("subpod"):
|
||||
if subnode.getFirstNode("plaintext").getContent() != "":
|
||||
yield node["title"] + " " + subnode["title"] + ": " + subnode.getFirstNode("plaintext").getContent()
|
||||
except IndexError:
|
||||
pass
|
||||
56
modules/ddg/Wikipedia.py
Normal file
56
modules/ddg/Wikipedia.py
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
# coding=utf-8
|
||||
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
import urllib.request
|
||||
|
||||
import xmlparser
|
||||
|
||||
class Wikipedia:
|
||||
def __init__(self, terms, lang="fr", site="wikipedia.org", section=0):
|
||||
self.terms = terms
|
||||
self.lang = lang
|
||||
self.curRT = 0
|
||||
|
||||
raw = urllib.request.urlopen(urllib.request.Request("http://" + self.lang + "." + site + "/w/api.php?format=xml&redirects&action=query&prop=revisions&rvprop=content&titles=%s" % (quote(terms)), headers={"User-agent": "Nemubot v3"}))
|
||||
self.wres = xmlparser.parse_string(raw.read())
|
||||
if self.wres is None or not (self.wres.hasNode("query") and self.wres.getFirstNode("query").hasNode("pages") and self.wres.getFirstNode("query").getFirstNode("pages").hasNode("page") and self.wres.getFirstNode("query").getFirstNode("pages").getFirstNode("page").hasNode("revisions")):
|
||||
self.wres = None
|
||||
else:
|
||||
self.wres = self.wres.getFirstNode("query").getFirstNode("pages").getFirstNode("page").getFirstNode("revisions").getFirstNode("rev").getContent()
|
||||
self.wres = striplink(self.wres)
|
||||
|
||||
@property
|
||||
def nextRes(self):
|
||||
if self.wres is not None:
|
||||
for cnt in self.wres.split("\n"):
|
||||
if self.curRT > 0:
|
||||
self.curRT -= 1
|
||||
continue
|
||||
|
||||
(c, u) = RGXP_s.subn(' ', cnt)
|
||||
c = c.strip()
|
||||
if c != "":
|
||||
yield c
|
||||
|
||||
RGXP_p = re.compile(r"(<!--.*-->|<ref[^>]*/>|<ref[^>]*>[^>]*</ref>|<dfn[^>]*>[^>]*</dfn>|\{\{[^{}]*\}\}|\[\[([^\[\]]*\[\[[^\]\[]*\]\])+[^\[\]]*\]\]|\{\{([^{}]*\{\{[^{}]*\}\}[^{}]*)+\}\}|\{\{([^{}]*\{\{([^{}]*\{\{[^{}]*\}\}[^{}]*)+\}\}[^{}]*)+\}\}|\[\[[^\]|]+(\|[^\]\|]+)*\]\])|#\* ''" + "\n", re.I)
|
||||
RGXP_l = re.compile(r'\{\{(nobr|lang\|[^|}]+)\|([^}]+)\}\}', re.I)
|
||||
RGXP_m = re.compile(r'\{\{pron\|([^|}]+)\|[^}]+\}\}', re.I)
|
||||
RGXP_t = re.compile("==+ *([^=]+) *=+=\n+([^\n])", re.I)
|
||||
RGXP_q = re.compile(r'\[\[([^\[\]|]+)\|([^\]|]+)]]', re.I)
|
||||
RGXP_r = re.compile(r'\[\[([^\[\]|]+)\]\]', re.I)
|
||||
RGXP_s = re.compile(r'\s+')
|
||||
|
||||
def striplink(s):
|
||||
s.replace("{{m}}", "masculin").replace("{{f}}", "feminin").replace("{{n}}", "neutre")
|
||||
(s, n) = RGXP_m.subn(r"[\1]", s)
|
||||
(s, n) = RGXP_l.subn(r"\2", s)
|
||||
|
||||
(s, n) = RGXP_q.subn(r"\1", s)
|
||||
(s, n) = RGXP_r.subn(r"\1", s)
|
||||
|
||||
(s, n) = RGXP_p.subn('', s)
|
||||
if s == "": return s
|
||||
|
||||
(s, n) = RGXP_t.subn("\x03\x16" + r"\1" + " :\x03\x16 " + r"\2", s)
|
||||
return s.replace("'''", "\x03\x02").replace("''", "\x03\x1f")
|
||||
129
modules/ddg/__init__.py
Normal file
129
modules/ddg/__init__.py
Normal file
|
|
@ -0,0 +1,129 @@
|
|||
# coding=utf-8
|
||||
|
||||
import imp
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
from . import DDGSearch
|
||||
from . import WFASearch
|
||||
from . import Wikipedia
|
||||
|
||||
def load(context):
|
||||
global CONF
|
||||
WFASearch.CONF = CONF
|
||||
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(define, "define"))
|
||||
add_hook("cmd_hook", Hook(search, "search"))
|
||||
add_hook("cmd_hook", Hook(search, "ddg"))
|
||||
add_hook("cmd_hook", Hook(search, "g"))
|
||||
add_hook("cmd_hook", Hook(calculate, "wa"))
|
||||
add_hook("cmd_hook", Hook(calculate, "calc"))
|
||||
add_hook("cmd_hook", Hook(wiki, "dico"))
|
||||
add_hook("cmd_hook", Hook(wiki, "wiki"))
|
||||
|
||||
def reload():
|
||||
imp.reload(DDGSearch)
|
||||
imp.reload(WFASearch)
|
||||
imp.reload(Wikipedia)
|
||||
|
||||
|
||||
def define(msg):
|
||||
if len(msg.cmds) <= 1:
|
||||
return Response(msg.sender,
|
||||
"Indicate a term to define",
|
||||
msg.channel, nick=msg.nick)
|
||||
|
||||
s = DDGSearch.DDGSearch(' '.join(msg.cmds[1:]))
|
||||
|
||||
res = Response(msg.sender, channel=msg.channel)
|
||||
|
||||
res.append_message(s.definition)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def search(msg):
|
||||
if len(msg.cmds) <= 1:
|
||||
return Response(msg.sender,
|
||||
"Indicate a term to search",
|
||||
msg.channel, nick=msg.nick)
|
||||
|
||||
s = DDGSearch.DDGSearch(' '.join(msg.cmds[1:]))
|
||||
|
||||
res = Response(msg.sender, channel=msg.channel, nomore="No more results",
|
||||
count=" (%d more results)")
|
||||
|
||||
res.append_message(s.redirect)
|
||||
res.append_message(s.abstract)
|
||||
res.append_message(s.result)
|
||||
res.append_message(s.answer)
|
||||
|
||||
for rt in s.relatedTopics:
|
||||
res.append_message(rt)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
def calculate(msg):
|
||||
if len(msg.cmds) <= 1:
|
||||
return Response(msg.sender,
|
||||
"Indicate a calcul to compute",
|
||||
msg.channel, nick=msg.nick)
|
||||
|
||||
s = WFASearch.WFASearch(' '.join(msg.cmds[1:]))
|
||||
|
||||
if s.success:
|
||||
res = Response(msg.sender, channel=msg.channel, nomore="No more results")
|
||||
for result in s.nextRes:
|
||||
res.append_message(result)
|
||||
if (len(res.messages) > 0):
|
||||
res.messages.pop(0)
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, s.error, msg.channel)
|
||||
|
||||
|
||||
def wiki(msg):
|
||||
if len(msg.cmds) <= 1:
|
||||
return Response(msg.sender,
|
||||
"Indicate a term to search",
|
||||
msg.channel, nick=msg.nick)
|
||||
if len(msg.cmds) > 2 and len(msg.cmds[1]) < 4:
|
||||
lang = msg.cmds[1]
|
||||
extract = 2
|
||||
else:
|
||||
lang = "fr"
|
||||
extract = 1
|
||||
if msg.cmds[0] == "dico":
|
||||
site = "wiktionary.org"
|
||||
section = 1
|
||||
else:
|
||||
site = "wikipedia.org"
|
||||
section = 0
|
||||
|
||||
s = Wikipedia.Wikipedia(' '.join(msg.cmds[extract:]), lang, site, section)
|
||||
|
||||
res = Response(msg.sender, channel=msg.channel, nomore="No more results")
|
||||
if site == "wiktionary.org":
|
||||
tout = [result for result in s.nextRes if result.find("\x03\x16 :\x03\x16 ") != 0]
|
||||
if len(tout) > 0:
|
||||
tout.remove(tout[0])
|
||||
defI=1
|
||||
for t in tout:
|
||||
if t.find("# ") == 0:
|
||||
t = t.replace("# ", "%d. " % defI)
|
||||
defI += 1
|
||||
elif t.find("#* ") == 0:
|
||||
t = t.replace("#* ", " * ")
|
||||
res.append_message(t)
|
||||
else:
|
||||
for result in s.nextRes:
|
||||
res.append_message(result)
|
||||
|
||||
if len(res.messages) > 0:
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender,
|
||||
"No information about " + " ".join(msg.cmds[extract:]),
|
||||
msg.channel)
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
"""DNS resolver"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import ipaddress
|
||||
import socket
|
||||
|
||||
import dns.exception
|
||||
import dns.name
|
||||
import dns.rdataclass
|
||||
import dns.rdatatype
|
||||
import dns.resolver
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("dig",
|
||||
help="Resolve domain name with a basic syntax similar to dig(1)")
|
||||
def dig(msg):
|
||||
lclass = "IN"
|
||||
ltype = "A"
|
||||
ledns = None
|
||||
ltimeout = 6.0
|
||||
ldomain = None
|
||||
lnameservers = []
|
||||
lsearchlist = []
|
||||
loptions = []
|
||||
for a in msg.args:
|
||||
if a in dns.rdatatype._by_text:
|
||||
ltype = a
|
||||
elif a in dns.rdataclass._by_text:
|
||||
lclass = a
|
||||
elif a[0] == "@":
|
||||
try:
|
||||
lnameservers.append(str(ipaddress.ip_address(a[1:])))
|
||||
except ValueError:
|
||||
for r in socket.getaddrinfo(a[1:], 53, proto=socket.IPPROTO_UDP):
|
||||
lnameservers.append(r[4][0])
|
||||
|
||||
elif a[0:8] == "+domain=":
|
||||
lsearchlist.append(dns.name.from_unicode(a[8:]))
|
||||
elif a[0:6] == "+edns=":
|
||||
ledns = int(a[6:])
|
||||
elif a[0:6] == "+time=":
|
||||
ltimeout = float(a[6:])
|
||||
elif a[0] == "+":
|
||||
loptions.append(a[1:])
|
||||
else:
|
||||
ldomain = a
|
||||
|
||||
if not ldomain:
|
||||
raise IMException("indicate a domain to resolve")
|
||||
|
||||
resolv = dns.resolver.Resolver()
|
||||
if ledns:
|
||||
resolv.edns = ledns
|
||||
resolv.lifetime = ltimeout
|
||||
resolv.timeout = ltimeout
|
||||
resolv.flags = (
|
||||
dns.flags.QR | dns.flags.RA |
|
||||
dns.flags.AA if "aaonly" in loptions or "aaflag" in loptions else 0 |
|
||||
dns.flags.AD if "adflag" in loptions else 0 |
|
||||
dns.flags.CD if "cdflag" in loptions else 0 |
|
||||
dns.flags.RD if "norecurse" not in loptions else 0
|
||||
)
|
||||
if lsearchlist:
|
||||
resolv.search = lsearchlist
|
||||
else:
|
||||
resolv.search = [dns.name.from_text(".")]
|
||||
|
||||
if lnameservers:
|
||||
resolv.nameservers = lnameservers
|
||||
|
||||
try:
|
||||
answers = resolv.query(ldomain, ltype, lclass, tcp="tcp" in loptions)
|
||||
except dns.exception.DNSException as e:
|
||||
raise IMException(str(e))
|
||||
|
||||
res = Response(channel=msg.channel, count=" (%s others entries)")
|
||||
for rdata in answers:
|
||||
res.append_message("%s %s %s %s %s" % (
|
||||
answers.qname.to_text(),
|
||||
answers.ttl if not "nottlid" in loptions else "",
|
||||
dns.rdataclass.to_text(answers.rdclass) if not "nocl" in loptions else "",
|
||||
dns.rdatatype.to_text(answers.rdtype),
|
||||
rdata.to_text())
|
||||
)
|
||||
|
||||
return res
|
||||
|
|
@ -1,89 +0,0 @@
|
|||
"""The Ultimate Disassembler Module"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import capstone
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
ARCHITECTURES = {
|
||||
"arm": capstone.CS_ARCH_ARM,
|
||||
"arm64": capstone.CS_ARCH_ARM64,
|
||||
"mips": capstone.CS_ARCH_MIPS,
|
||||
"ppc": capstone.CS_ARCH_PPC,
|
||||
"sparc": capstone.CS_ARCH_SPARC,
|
||||
"sysz": capstone.CS_ARCH_SYSZ,
|
||||
"x86": capstone.CS_ARCH_X86,
|
||||
"xcore": capstone.CS_ARCH_XCORE,
|
||||
}
|
||||
|
||||
MODES = {
|
||||
"arm": capstone.CS_MODE_ARM,
|
||||
"thumb": capstone.CS_MODE_THUMB,
|
||||
"mips32": capstone.CS_MODE_MIPS32,
|
||||
"mips64": capstone.CS_MODE_MIPS64,
|
||||
"mips32r6": capstone.CS_MODE_MIPS32R6,
|
||||
"16": capstone.CS_MODE_16,
|
||||
"32": capstone.CS_MODE_32,
|
||||
"64": capstone.CS_MODE_64,
|
||||
"le": capstone.CS_MODE_LITTLE_ENDIAN,
|
||||
"be": capstone.CS_MODE_BIG_ENDIAN,
|
||||
"micro": capstone.CS_MODE_MICRO,
|
||||
"mclass": capstone.CS_MODE_MCLASS,
|
||||
"v8": capstone.CS_MODE_V8,
|
||||
"v9": capstone.CS_MODE_V9,
|
||||
}
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("disas",
|
||||
help="Display assembly code",
|
||||
help_usage={"CODE": "Display assembly code corresponding to the given CODE"},
|
||||
keywords={
|
||||
"arch=ARCH": "Specify the architecture of the code to disassemble (default: x86, choose between: %s)" % ', '.join(ARCHITECTURES.keys()),
|
||||
"modes=MODE[,MODE]": "Specify hardware mode of the code to disassemble (default: 32, between: %s)" % ', '.join(MODES.keys()),
|
||||
})
|
||||
def cmd_disas(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("please give me some code")
|
||||
|
||||
# Determine the architecture
|
||||
if "arch" in msg.kwargs:
|
||||
if msg.kwargs["arch"] not in ARCHITECTURES:
|
||||
raise IMException("unknown architectures '%s'" % msg.kwargs["arch"])
|
||||
architecture = ARCHITECTURES[msg.kwargs["arch"]]
|
||||
else:
|
||||
architecture = capstone.CS_ARCH_X86
|
||||
|
||||
# Determine hardware modes
|
||||
modes = 0
|
||||
if "modes" in msg.kwargs:
|
||||
for mode in msg.kwargs["modes"].split(','):
|
||||
if mode not in MODES:
|
||||
raise IMException("unknown mode '%s'" % mode)
|
||||
modes += MODES[mode]
|
||||
elif architecture == capstone.CS_ARCH_X86 or architecture == capstone.CS_ARCH_PPC:
|
||||
modes = capstone.CS_MODE_32
|
||||
elif architecture == capstone.CS_ARCH_ARM or architecture == capstone.CS_ARCH_ARM64:
|
||||
modes = capstone.CS_MODE_ARM
|
||||
elif architecture == capstone.CS_ARCH_MIPS:
|
||||
modes = capstone.CS_MODE_MIPS32
|
||||
|
||||
# Get the code
|
||||
code = bytearray.fromhex(''.join([a.replace("0x", "") for a in msg.args]))
|
||||
|
||||
# Setup capstone
|
||||
md = capstone.Cs(architecture, modes)
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more instruction")
|
||||
|
||||
for isn in md.disasm(code, 0x1000):
|
||||
res.append_message("%s %s" %(isn.mnemonic, isn.op_str), title="0x%x" % isn.address)
|
||||
|
||||
return res
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
"""Create countdowns and reminders"""
|
||||
|
||||
import calendar
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from functools import partial
|
||||
import re
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.event import ModuleEvent
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.message import Command
|
||||
from nemubot.tools.countdown import countdown_format, countdown
|
||||
from nemubot.tools.date import extractDate
|
||||
from nemubot.tools.xmlparser.basic import DictNode
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
class Event:
|
||||
|
||||
def __init__(self, server, channel, creator, start_time, end_time=None):
|
||||
self._server = server
|
||||
self._channel = channel
|
||||
self._creator = creator
|
||||
self._start = datetime.utcfromtimestamp(float(start_time)).replace(tzinfo=timezone.utc) if not isinstance(start_time, datetime) else start_time
|
||||
self._end = datetime.utcfromtimestamp(float(end_time)).replace(tzinfo=timezone.utc) if end_time else None
|
||||
self._evt = None
|
||||
|
||||
|
||||
def __del__(self):
|
||||
if self._evt is not None:
|
||||
context.del_event(self._evt)
|
||||
self._evt = None
|
||||
|
||||
|
||||
def saveElement(self, store, tag="event"):
|
||||
attrs = {
|
||||
"server": str(self._server),
|
||||
"channel": str(self._channel),
|
||||
"creator": str(self._creator),
|
||||
"start_time": str(calendar.timegm(self._start.timetuple())),
|
||||
}
|
||||
if self._end:
|
||||
attrs["end_time"] = str(calendar.timegm(self._end.timetuple()))
|
||||
store.startElement(tag, attrs)
|
||||
store.endElement(tag)
|
||||
|
||||
@property
|
||||
def creator(self):
|
||||
return self._creator
|
||||
|
||||
@property
|
||||
def start(self):
|
||||
return self._start
|
||||
|
||||
@property
|
||||
def end(self):
|
||||
return self._end
|
||||
|
||||
@end.setter
|
||||
def end(self, c):
|
||||
self._end = c
|
||||
|
||||
@end.deleter
|
||||
def end(self):
|
||||
self._end = None
|
||||
|
||||
|
||||
def help_full ():
|
||||
return "This module store a lot of events: ny, we, " + (", ".join(context.datas.keys()) if hasattr(context, "datas") else "") + "\n!eventslist: gets list of timer\n!start /something/: launch a timer"
|
||||
|
||||
|
||||
def load(context):
|
||||
context.set_knodes({
|
||||
"dict": DictNode,
|
||||
"event": Event,
|
||||
})
|
||||
|
||||
if context.data is None:
|
||||
context.set_default(DictNode())
|
||||
|
||||
# Relaunch all timers
|
||||
for kevt in context.data:
|
||||
if context.data[kevt].end:
|
||||
context.data[kevt]._evt = context.add_event(ModuleEvent(partial(fini, kevt, context.data[kevt]), offset=context.data[kevt].end - datetime.now(timezone.utc), interval=0))
|
||||
|
||||
|
||||
def fini(name, evt):
|
||||
context.send_response(evt._server, Response("%s arrivé à échéance." % name, channel=evt._channel, nick=evt.creator))
|
||||
evt._evt = None
|
||||
del context.data[name]
|
||||
context.save()
|
||||
|
||||
|
||||
@hook.command("goûter")
|
||||
def cmd_gouter(msg):
|
||||
ndate = datetime.now(timezone.utc)
|
||||
ndate = datetime(ndate.year, ndate.month, ndate.day, 16, 42, 0, 0, timezone.utc)
|
||||
return Response(countdown_format(ndate,
|
||||
"Le goûter aura lieu dans %s, préparez vos biscuits !",
|
||||
"Nous avons %s de retard pour le goûter :("),
|
||||
channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("week-end")
|
||||
def cmd_we(msg):
|
||||
ndate = datetime.now(timezone.utc) + timedelta(5 - datetime.today().weekday())
|
||||
ndate = datetime(ndate.year, ndate.month, ndate.day, 0, 0, 1, 0, timezone.utc)
|
||||
return Response(countdown_format(ndate,
|
||||
"Il reste %s avant le week-end, courage ;)",
|
||||
"Youhou, on est en week-end depuis %s."),
|
||||
channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("start")
|
||||
def start_countdown(msg):
|
||||
"""!start /something/: launch a timer"""
|
||||
if len(msg.args) < 1:
|
||||
raise IMException("indique le nom d'un événement à chronométrer")
|
||||
if msg.args[0] in context.data:
|
||||
raise IMException("%s existe déjà." % msg.args[0])
|
||||
|
||||
evt = Event(server=msg.server, channel=msg.channel, creator=msg.frm, start_time=msg.date)
|
||||
|
||||
if len(msg.args) > 1:
|
||||
result1 = re.findall("([0-9]+)([smhdjwyaSMHDJWYA])?", msg.args[1])
|
||||
result2 = re.match("(.*[^0-9])?([0-3]?[0-9])/([0-1]?[0-9])/((19|20)?[01239][0-9])", msg.args[1])
|
||||
result3 = re.match("(.*[^0-9])?([0-2]?[0-9]):([0-5]?[0-9])(:([0-5]?[0-9]))?", msg.args[1])
|
||||
if result2 is not None or result3 is not None:
|
||||
try:
|
||||
now = msg.date
|
||||
if result3 is None or result3.group(5) is None: sec = 0
|
||||
else: sec = int(result3.group(5))
|
||||
if result3 is None or result3.group(3) is None: minu = 0
|
||||
else: minu = int(result3.group(3))
|
||||
if result3 is None or result3.group(2) is None: hou = 0
|
||||
else: hou = int(result3.group(2))
|
||||
if result2 is None or result2.group(4) is None: yea = now.year
|
||||
else: yea = int(result2.group(4))
|
||||
if result2 is not None and result3 is not None:
|
||||
evt.end = datetime(yea, int(result2.group(3)), int(result2.group(2)), hou, minu, sec, timezone.utc)
|
||||
elif result2 is not None:
|
||||
evt.end = datetime(int(result2.group(4)), int(result2.group(3)), int(result2.group(2)), 0, 0, 0, timezone.utc)
|
||||
elif result3 is not None:
|
||||
if hou * 3600 + minu * 60 + sec > now.hour * 3600 + now.minute * 60 + now.second:
|
||||
evt.end = datetime(now.year, now.month, now.day, hou, minu, sec, timezone.utc)
|
||||
else:
|
||||
evt.end = datetime(now.year, now.month, now.day + 1, hou, minu, sec, timezone.utc)
|
||||
except:
|
||||
raise IMException("Mauvais format de date pour l'événement %s. Il n'a pas été créé." % msg.args[0])
|
||||
|
||||
elif result1 is not None and len(result1) > 0:
|
||||
evt.end = msg.date
|
||||
for (t, g) in result1:
|
||||
if g is None or g == "" or g == "m" or g == "M":
|
||||
evt.end += timedelta(minutes=int(t))
|
||||
elif g == "h" or g == "H":
|
||||
evt.end += timedelta(hours=int(t))
|
||||
elif g == "d" or g == "D" or g == "j" or g == "J":
|
||||
evt.end += timedelta(days=int(t))
|
||||
elif g == "w" or g == "W":
|
||||
evt.end += timedelta(days=int(t)*7)
|
||||
elif g == "y" or g == "Y" or g == "a" or g == "A":
|
||||
evt.end += timedelta(days=int(t)*365)
|
||||
else:
|
||||
evt.end += timedelta(seconds=int(t))
|
||||
|
||||
else:
|
||||
raise IMException("Mauvais format de date pour l'événement %s. Il n'a pas été créé." % msg.args[0])
|
||||
|
||||
context.data[msg.args[0]] = evt
|
||||
context.save()
|
||||
|
||||
if evt.end is not None:
|
||||
context.add_event(ModuleEvent(partial(fini, msg.args[0], evt),
|
||||
offset=evt.end - datetime.now(timezone.utc),
|
||||
interval=0))
|
||||
return Response("%s commencé le %s et se terminera le %s." %
|
||||
(msg.args[0], msg.date.strftime("%A %d %B %Y à %H:%M:%S"),
|
||||
evt.end.strftime("%A %d %B %Y à %H:%M:%S")),
|
||||
channel=msg.channel)
|
||||
else:
|
||||
return Response("%s commencé le %s"% (msg.args[0],
|
||||
msg.date.strftime("%A %d %B %Y à %H:%M:%S")),
|
||||
channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("end")
|
||||
@hook.command("forceend")
|
||||
def end_countdown(msg):
|
||||
if len(msg.args) < 1:
|
||||
raise IMException("quel événement terminer ?")
|
||||
|
||||
if msg.args[0] in context.data:
|
||||
if context.data[msg.args[0]].creator == msg.frm or (msg.cmd == "forceend" and msg.frm_owner):
|
||||
duration = countdown(msg.date - context.data[msg.args[0]].start)
|
||||
del context.data[msg.args[0]]
|
||||
context.save()
|
||||
return Response("%s a duré %s." % (msg.args[0], duration),
|
||||
channel=msg.channel, nick=msg.frm)
|
||||
else:
|
||||
raise IMException("Vous ne pouvez pas terminer le compteur %s, créé par %s." % (msg.args[0], context.data[msg.args[0]].creator))
|
||||
else:
|
||||
return Response("%s n'est pas un compteur connu."% (msg.args[0]), channel=msg.channel, nick=msg.frm)
|
||||
|
||||
|
||||
@hook.command("eventslist")
|
||||
def liste(msg):
|
||||
"""!eventslist: gets list of timer"""
|
||||
if len(msg.args):
|
||||
res = Response(channel=msg.channel)
|
||||
for user in msg.args:
|
||||
cmptr = [k for k in context.data if context.data[k].creator == user]
|
||||
if len(cmptr) > 0:
|
||||
res.append_message(cmptr, title="Events created by %s" % user)
|
||||
else:
|
||||
res.append_message("%s doesn't have any counting events" % user)
|
||||
return res
|
||||
else:
|
||||
return Response(list(context.data.keys()), channel=msg.channel, title="Known events")
|
||||
|
||||
|
||||
@hook.command(match=lambda msg: isinstance(msg, Command) and msg.cmd in context.data)
|
||||
def parseanswer(msg):
|
||||
res = Response(channel=msg.channel)
|
||||
|
||||
# Avoid message starting by ! which can be interpreted as command by other bots
|
||||
if msg.cmd[0] == "!":
|
||||
res.nick = msg.frm
|
||||
|
||||
if msg.cmd in context.data:
|
||||
if context.data[msg.cmd].end:
|
||||
res.append_message("%s commencé il y a %s et se terminera dans %s." % (msg.cmd, countdown(msg.date - context.data[msg.cmd].start), countdown(context.data[msg.cmd].end - msg.date)))
|
||||
else:
|
||||
res.append_message("%s commencé il y a %s." % (msg.cmd, countdown(msg.date - context.data[msg.cmd].start)))
|
||||
else:
|
||||
res.append_message(countdown_format(context.data[msg.cmd].start, context.data[msg.cmd]["msg_before"], context.data[msg.cmd]["msg_after"]))
|
||||
return res
|
||||
|
||||
|
||||
RGXP_ask = re.compile(r"^.*((create|new)\s+(a|an|a\s*new|an\s*other)?\s*(events?|commande?)|(nouvel(le)?|ajoute|cr[ée]{1,3})\s+(un)?\s*([eé]v[ée]nements?|commande?)).*$", re.I)
|
||||
|
||||
@hook.ask(match=lambda msg: RGXP_ask.match(msg.message))
|
||||
def parseask(msg):
|
||||
name = re.match("^.*!([^ \"'@!]+).*$", msg.message)
|
||||
if name is None:
|
||||
raise IMException("il faut que tu attribues une commande à l'événement.")
|
||||
if name.group(1) in context.data:
|
||||
raise IMException("un événement portant ce nom existe déjà.")
|
||||
|
||||
texts = re.match("^[^\"]*(avant|après|apres|before|after)?[^\"]*\"([^\"]+)\"[^\"]*((avant|après|apres|before|after)?.*\"([^\"]+)\".*)?$", msg.message, re.I)
|
||||
if texts is not None and texts.group(3) is not None:
|
||||
extDate = extractDate(msg.message)
|
||||
if extDate is None or extDate == "":
|
||||
raise IMException("la date de l'événement est invalide !")
|
||||
|
||||
if texts.group(1) is not None and (texts.group(1) == "après" or texts.group(1) == "apres" or texts.group(1) == "after"):
|
||||
msg_after = texts.group(2)
|
||||
msg_before = texts.group(5)
|
||||
if (texts.group(4) is not None and (texts.group(4) == "après" or texts.group(4) == "apres" or texts.group(4) == "after")) or texts.group(1) is None:
|
||||
msg_before = texts.group(2)
|
||||
msg_after = texts.group(5)
|
||||
|
||||
if msg_before.find("%s") == -1 or msg_after.find("%s") == -1:
|
||||
raise IMException("Pour que l'événement soit valide, ajouter %s à"
|
||||
" l'endroit où vous voulez que soit ajouté le"
|
||||
" compte à rebours.")
|
||||
|
||||
evt = ModuleState("event")
|
||||
evt["server"] = msg.server
|
||||
evt["channel"] = msg.channel
|
||||
evt["proprio"] = msg.frm
|
||||
evt["name"] = name.group(1)
|
||||
evt["start"] = extDate
|
||||
evt["msg_after"] = msg_after
|
||||
evt["msg_before"] = msg_before
|
||||
context.data.addChild(evt)
|
||||
context.save()
|
||||
return Response("Nouvel événement !%s ajouté avec succès." % name.group(1),
|
||||
channel=msg.channel)
|
||||
|
||||
elif texts is not None and texts.group(2) is not None:
|
||||
evt = ModuleState("event")
|
||||
evt["server"] = msg.server
|
||||
evt["channel"] = msg.channel
|
||||
evt["proprio"] = msg.frm
|
||||
evt["name"] = name.group(1)
|
||||
evt["msg_before"] = texts.group (2)
|
||||
context.data.addChild(evt)
|
||||
context.save()
|
||||
return Response("Nouvelle commande !%s ajoutée avec succès." % name.group(1),
|
||||
channel=msg.channel)
|
||||
|
||||
else:
|
||||
raise IMException("Veuillez indiquez les messages d'attente et d'après événement entre guillemets.")
|
||||
14
modules/events.xml
Normal file
14
modules/events.xml
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="events">
|
||||
<message type="cmd" name="start" call="start_countdown" />
|
||||
<message type="cmd" name="end" call="end_countdown" />
|
||||
<message type="cmd" name="forceend" call="end_countdown" />
|
||||
<message type="cmd" name="eventlist" call="liste" />
|
||||
<message type="cmd" name="eventslist" call="liste" />
|
||||
<message type="cmd" name="eventliste" call="liste" />
|
||||
<message type="cmd" name="eventsliste" call="liste" />
|
||||
<message type="cmd" name="gouter" call="cmd_gouter" />
|
||||
<message type="cmd" name="goûter" call="cmd_gouter" />
|
||||
<message type="cmd" name="week-end" call="cmd_we" />
|
||||
<message type="cmd" name="weekend" call="cmd_we" />
|
||||
</nemubotmodule>
|
||||
238
modules/events/__init__.py
Normal file
238
modules/events/__init__.py
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
# coding=utf-8
|
||||
|
||||
import imp
|
||||
import re
|
||||
import sys
|
||||
from datetime import timedelta
|
||||
from datetime import datetime
|
||||
import time
|
||||
import threading
|
||||
import traceback
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
from event import ModuleEvent
|
||||
from hooks import Hook
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "events manager"
|
||||
|
||||
def help_full ():
|
||||
return "This module store a lot of events: ny, we, vacs, " + (", ".join(DATAS.index.keys())) + "\n!eventslist: gets list of timer\n!start /something/: launch a timer"
|
||||
|
||||
CONTEXT = None
|
||||
|
||||
def load(context):
|
||||
global DATAS, CONTEXT
|
||||
CONTEXT = context
|
||||
#Define the index
|
||||
DATAS.setIndex("name")
|
||||
|
||||
for evt in DATAS.index.keys():
|
||||
if DATAS.index[evt].hasAttribute("end"):
|
||||
event = ModuleEvent(call=fini, call_data=dict(strend=DATAS.index[evt]))
|
||||
event.end = DATAS.index[evt].getDate("end")
|
||||
idt = context.add_event(event)
|
||||
if idt is not None:
|
||||
DATAS.index[evt]["id"] = idt
|
||||
|
||||
|
||||
def fini(d, strend):
|
||||
for server in CONTEXT.servers.keys():
|
||||
if not strend.hasAttribute("server") or server == strend["server"]:
|
||||
if strend["channel"] == CONTEXT.servers[server].nick:
|
||||
CONTEXT.servers[server].send_msg_usr(strend["sender"], "%s: %s arrivé à échéance." % (strend["proprio"], strend["name"]))
|
||||
else:
|
||||
CONTEXT.servers[server].send_msg(strend["channel"], "%s: %s arrivé à échéance." % (strend["proprio"], strend["name"]))
|
||||
DATAS.delChild(DATAS.index[strend["name"]])
|
||||
save()
|
||||
|
||||
def cmd_gouter(msg):
|
||||
ndate = datetime.today()
|
||||
ndate = datetime(ndate.year, ndate.month, ndate.day, 16, 42)
|
||||
return Response(msg.sender,
|
||||
msg.countdown_format(ndate,
|
||||
"Le goûter aura lieu dans %s, préparez vos biscuits !",
|
||||
"Nous avons %s de retard pour le goûter :("),
|
||||
channel=msg.channel)
|
||||
|
||||
def cmd_we(msg):
|
||||
ndate = datetime.today() + timedelta(5 - datetime.today().weekday())
|
||||
ndate = datetime(ndate.year, ndate.month, ndate.day, 0, 0, 1)
|
||||
return Response(msg.sender,
|
||||
msg.countdown_format(ndate,
|
||||
"Il reste %s avant le week-end, courage ;)",
|
||||
"Youhou, on est en week-end depuis %s."),
|
||||
channel=msg.channel)
|
||||
|
||||
def cmd_vacances(msg):
|
||||
return Response(msg.sender,
|
||||
msg.countdown_format(datetime(2013, 7, 30, 18, 0, 1),
|
||||
"Il reste %s avant les vacances :)",
|
||||
"Profitons, c'est les vacances depuis %s."),
|
||||
channel=msg.channel)
|
||||
|
||||
def start_countdown(msg):
|
||||
if msg.cmds[1] not in DATAS.index:
|
||||
|
||||
strnd = ModuleState("strend")
|
||||
strnd["server"] = msg.server
|
||||
strnd["channel"] = msg.channel
|
||||
strnd["proprio"] = msg.nick
|
||||
strnd["sender"] = msg.sender
|
||||
strnd["start"] = datetime.now()
|
||||
strnd["name"] = msg.cmds[1]
|
||||
DATAS.addChild(strnd)
|
||||
|
||||
evt = ModuleEvent(call=fini, call_data=dict(strend=strnd))
|
||||
|
||||
if len(msg.cmds) > 2:
|
||||
result1 = re.findall("([0-9]+)([smhdjwyaSMHDJWYA])?", msg.cmds[2])
|
||||
result2 = re.match("(.*[^0-9])?([0-3]?[0-9])/([0-1]?[0-9])/((19|20)?[01239][0-9])", msg.cmds[2])
|
||||
result3 = re.match("(.*[^0-9])?([0-2]?[0-9]):([0-5]?[0-9])(:([0-5]?[0-9]))?", msg.cmds[2])
|
||||
if result2 is not None or result3 is not None:
|
||||
try:
|
||||
now = datetime.now()
|
||||
if result3 is None or result3.group(5) is None: sec = 0
|
||||
else: sec = int(result3.group(5))
|
||||
if result3 is None or result3.group(3) is None: minu = 0
|
||||
else: minu = int(result3.group(3))
|
||||
if result3 is None or result3.group(2) is None: hou = 0
|
||||
else: hou = int(result3.group(2))
|
||||
|
||||
if result2 is None or result2.group(4) is None: yea = now.year
|
||||
else: yea = int(result2.group(4))
|
||||
|
||||
if result2 is not None and result3 is not None:
|
||||
strnd["end"] = datetime(yea, int(result2.group(3)), int(result2.group(2)), hou, minu, sec)
|
||||
elif result2 is not None:
|
||||
strnd["end"] = datetime(int(result2.group(4)), int(result2.group(3)), int(result2.group(2)))
|
||||
elif result3 is not None:
|
||||
if hou * 3600 + minu * 60 + sec > now.hour * 3600 + now.minute * 60 + now.second:
|
||||
strnd["end"] = datetime(now.year, now.month, now.day, hou, minu, sec)
|
||||
else:
|
||||
strnd["end"] = datetime(now.year, now.month, now.day + 1, hou, minu, sec)
|
||||
|
||||
evt.end = strnd.getDate("end")
|
||||
strnd["id"] = CONTEXT.add_event(evt)
|
||||
save()
|
||||
return Response(msg.sender, "%s commencé le %s et se terminera le %s." %
|
||||
(msg.cmds[1], datetime.now().strftime("%A %d %B %Y a %H:%M:%S"),
|
||||
strnd.getDate("end").strftime("%A %d %B %Y a %H:%M:%S")))
|
||||
except:
|
||||
DATAS.delChild(strnd)
|
||||
return Response(msg.sender,
|
||||
"Mauvais format de date pour l'evenement %s. Il n'a pas ete cree." % msg.cmds[1])
|
||||
elif result1 is not None and len(result1) > 0:
|
||||
strnd["end"] = datetime.now()
|
||||
for (t, g) in result1:
|
||||
if g is None or g == "" or g == "m" or g == "M":
|
||||
strnd["end"] += timedelta(minutes=int(t))
|
||||
elif g == "h" or g == "H":
|
||||
strnd["end"] += timedelta(hours=int(t))
|
||||
elif g == "d" or g == "D" or g == "j" or g == "J":
|
||||
strnd["end"] += timedelta(days=int(t))
|
||||
elif g == "w" or g == "W":
|
||||
strnd["end"] += timedelta(days=int(t)*7)
|
||||
elif g == "y" or g == "Y" or g == "a" or g == "A":
|
||||
strnd["end"] += timedelta(days=int(t)*365)
|
||||
else:
|
||||
strnd["end"] += timedelta(seconds=int(t))
|
||||
evt.end = strnd.getDate("end")
|
||||
strnd["id"] = CONTEXT.add_event(evt)
|
||||
save()
|
||||
return Response(msg.sender, "%s commencé le %s et se terminera le %s." %
|
||||
(msg.cmds[1], datetime.now().strftime("%A %d %B %Y a %H:%M:%S"),
|
||||
strnd.getDate("end").strftime("%A %d %B %Y a %H:%M:%S")))
|
||||
save()
|
||||
return Response(msg.sender, "%s commencé le %s"% (msg.cmds[1],
|
||||
datetime.now().strftime("%A %d %B %Y a %H:%M:%S")))
|
||||
else:
|
||||
return Response(msg.sender, "%s existe déjà."% (msg.cmds[1]))
|
||||
|
||||
def end_countdown(msg):
|
||||
if msg.cmds[1] in DATAS.index:
|
||||
res = Response(msg.sender,
|
||||
"%s a duré %s." % (msg.cmds[1],
|
||||
msg.just_countdown(datetime.now () - DATAS.index[msg.cmds[1]].getDate("start"))),
|
||||
channel=msg.channel)
|
||||
if DATAS.index[msg.cmds[1]]["proprio"] == msg.nick or (msg.cmds[0] == "forceend" and msg.is_owner):
|
||||
CONTEXT.del_event(DATAS.index[msg.cmds[1]]["id"])
|
||||
DATAS.delChild(DATAS.index[msg.cmds[1]])
|
||||
save()
|
||||
else:
|
||||
res.append_message("Vous ne pouvez pas terminer le compteur %s, créé par %s."% (msg.cmds[1], DATAS.index[msg.cmds[1]]["proprio"]))
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, "%s n'est pas un compteur connu."% (msg.cmds[1]))
|
||||
|
||||
def liste(msg):
|
||||
msg.send_snd ("Compteurs connus : %s." % ", ".join(DATAS.index.keys()))
|
||||
|
||||
def parseanswer(msg):
|
||||
if msg.cmds[0] in DATAS.index:
|
||||
if DATAS.index[msg.cmds[0]].name == "strend":
|
||||
if DATAS.index[msg.cmds[0]].hasAttribute("end"):
|
||||
return Response(msg.sender, "%s commencé il y a %s et se terminera dans %s." % (msg.cmds[0], msg.just_countdown(datetime.now() - DATAS.index[msg.cmds[0]].getDate("start")), msg.just_countdown(DATAS.index[msg.cmds[0]].getDate("end") - datetime.now())), channel=msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, "%s commencé il y a %s." % (msg.cmds[0], msg.just_countdown(datetime.now () - DATAS.index[msg.cmds[0]].getDate("start"))), channel=msg.channel)
|
||||
else:
|
||||
save()
|
||||
return Response(msg.sender, msg.countdown_format (DATAS.index[msg.cmds[0]].getDate("start"), DATAS.index[msg.cmds[0]]["msg_before"], DATAS.index[msg.cmds[0]]["msg_after"]), channel=msg.channel)
|
||||
|
||||
def parseask(msg):
|
||||
msgl = msg.content.lower()
|
||||
if re.match("^.*((create|new) +(a|an|a +new|an *other)? *(events?|commande?)|(nouvel(le)?|ajoute|cr[ée]{1,3}) +(un)? *([eé]v[ée]nements?|commande?)).*$", msgl) is not None:
|
||||
name = re.match("^.*!([^ \"'@!]+).*$", msg.content)
|
||||
if name is not None and name.group (1) not in DATAS.index:
|
||||
texts = re.match("^[^\"]*(avant|après|apres|before|after)?[^\"]*\"([^\"]+)\"[^\"]*((avant|après|apres|before|after)?.*\"([^\"]+)\".*)?$", msg.content)
|
||||
if texts is not None and texts.group (3) is not None:
|
||||
extDate = msg.extractDate ()
|
||||
if extDate is None or extDate == "":
|
||||
return Response(msg.sender, "La date de l'événement est invalide...", channel=msg.channel)
|
||||
else:
|
||||
if texts.group (1) is not None and (texts.group (1) == "après" or texts.group (1) == "apres" or texts.group (1) == "after"):
|
||||
msg_after = texts.group (2)
|
||||
msg_before = texts.group (5)
|
||||
if (texts.group (4) is not None and (texts.group (4) == "après" or texts.group (4) == "apres" or texts.group (4) == "after")) or texts.group (1) is None:
|
||||
msg_before = texts.group (2)
|
||||
msg_after = texts.group (5)
|
||||
|
||||
if msg_before.find ("%s") != -1 and msg_after.find ("%s") != -1:
|
||||
evt = ModuleState("event")
|
||||
evt["server"] = msg.server
|
||||
evt["channel"] = msg.channel
|
||||
evt["proprio"] = msg.nick
|
||||
evt["sender"] = msg.sender
|
||||
evt["name"] = name.group(1)
|
||||
evt["start"] = extDate
|
||||
evt["msg_after"] = msg_after
|
||||
evt["msg_before"] = msg_before
|
||||
DATAS.addChild(evt)
|
||||
save()
|
||||
return Response(msg.sender,
|
||||
"Nouvel événement !%s ajouté avec succès." % name.group(1),
|
||||
msg.channel)
|
||||
else:
|
||||
return Response(msg.sender,
|
||||
"Pour que l'événement soit valide, ajouter %s à"
|
||||
" l'endroit où vous voulez que soit ajouté le"
|
||||
" compte à rebours.")
|
||||
elif texts is not None and texts.group (2) is not None:
|
||||
evt = ModuleState("event")
|
||||
evt["server"] = msg.server
|
||||
evt["channel"] = msg.channel
|
||||
evt["proprio"] = msg.nick
|
||||
evt["sender"] = msg.sender
|
||||
evt["name"] = name.group(1)
|
||||
evt["msg_before"] = texts.group (2)
|
||||
DATAS.addChild(evt)
|
||||
save()
|
||||
return Response(msg.sender, "Nouvelle commande !%s ajoutée avec succès." % name.group(1))
|
||||
else:
|
||||
return Response(msg.sender, "Veuillez indiquez les messages d'attente et d'après événement entre guillemets.")
|
||||
elif name is None:
|
||||
return Response(msg.sender, "Veuillez attribuer une commande à l'événement.")
|
||||
else:
|
||||
return Response(msg.sender, "Un événement portant ce nom existe déjà.")
|
||||
|
|
@ -1,64 +0,0 @@
|
|||
"""Inform about Free Mobile tarifs"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import urllib.parse
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
ACT = {
|
||||
"ff_toFixe": "Appel vers les fixes",
|
||||
"ff_toMobile": "Appel vers les mobiles",
|
||||
"ff_smsSendedToCountry": "SMS vers le pays",
|
||||
"ff_mmsSendedToCountry": "MMS vers le pays",
|
||||
"fc_callToFrance": "Appel vers la France",
|
||||
"fc_smsToFrance": "SMS vers la france",
|
||||
"fc_mmsSended": "MMS vers la france",
|
||||
"fc_callToSameCountry": "Réception des appels",
|
||||
"fc_callReceived": "Appel dans le pays",
|
||||
"fc_smsReceived": "SMS (Réception)",
|
||||
"fc_mmsReceived": "MMS (Réception)",
|
||||
"fc_moDataFromCountry": "Data",
|
||||
}
|
||||
|
||||
def get_land_tarif(country, forfait="pkgFREE"):
|
||||
url = "http://mobile.international.free.fr/?" + urllib.parse.urlencode({'pays': country})
|
||||
page = web.getURLContent(url)
|
||||
soup = BeautifulSoup(page)
|
||||
|
||||
fact = soup.find(class_=forfait)
|
||||
|
||||
if fact is None:
|
||||
raise IMException("Country or forfait not found.")
|
||||
|
||||
res = {}
|
||||
for s in ACT.keys():
|
||||
try:
|
||||
res[s] = fact.find(attrs={"data-bind": "text: " + s}).text + " " + fact.find(attrs={"data-bind": "html: " + s + "Unit"}).text
|
||||
except AttributeError:
|
||||
res[s] = "inclus"
|
||||
|
||||
return res
|
||||
|
||||
@hook.command("freetarifs",
|
||||
help="Show Free Mobile tarifs for given contries",
|
||||
help_usage={"COUNTRY": "Show Free Mobile tarifs for given CONTRY"},
|
||||
keywords={
|
||||
"forfait=FORFAIT": "Related forfait between Free (default) and 2euro"
|
||||
})
|
||||
def get_freetarif(msg):
|
||||
res = Response(channel=msg.channel)
|
||||
|
||||
for country in msg.args:
|
||||
t = get_land_tarif(country.lower().capitalize(), "pkg" + (msg.kwargs["forfait"] if "forfait" in msg.kwargs else "FREE").upper())
|
||||
res.append_message(["\x02%s\x0F : %s" % (ACT[k], t[k]) for k in sorted(ACT.keys(), reverse=True)], title=country)
|
||||
|
||||
return res
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
"""Repositories, users or issues on GitHub"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def info_repos(repo):
|
||||
return web.getJSON("https://api.github.com/search/repositories?q=%s" %
|
||||
quote(repo))
|
||||
|
||||
|
||||
def info_user(username):
|
||||
user = web.getJSON("https://api.github.com/users/%s" % quote(username))
|
||||
|
||||
user["repos"] = web.getJSON("https://api.github.com/users/%s/"
|
||||
"repos?sort=updated" % quote(username))
|
||||
|
||||
return user
|
||||
|
||||
|
||||
def user_keys(username):
|
||||
keys = web.getURLContent("https://github.com/%s.keys" % quote(username))
|
||||
return keys.split('\n')
|
||||
|
||||
|
||||
def info_issue(repo, issue=None):
|
||||
rp = info_repos(repo)
|
||||
if rp["items"]:
|
||||
fullname = rp["items"][0]["full_name"]
|
||||
else:
|
||||
fullname = repo
|
||||
|
||||
if issue is not None:
|
||||
return [web.getJSON("https://api.github.com/repos/%s/issues/%s" %
|
||||
(quote(fullname), quote(issue)))]
|
||||
else:
|
||||
return web.getJSON("https://api.github.com/repos/%s/issues?"
|
||||
"sort=updated" % quote(fullname))
|
||||
|
||||
|
||||
def info_commit(repo, commit=None):
|
||||
rp = info_repos(repo)
|
||||
if rp["items"]:
|
||||
fullname = rp["items"][0]["full_name"]
|
||||
else:
|
||||
fullname = repo
|
||||
|
||||
if commit is not None:
|
||||
return [web.getJSON("https://api.github.com/repos/%s/commits/%s" %
|
||||
(quote(fullname), quote(commit)))]
|
||||
else:
|
||||
return web.getJSON("https://api.github.com/repos/%s/commits" %
|
||||
quote(fullname))
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("github",
|
||||
help="Display information about some repositories",
|
||||
help_usage={
|
||||
"REPO": "Display information about the repository REPO",
|
||||
})
|
||||
def cmd_github(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a repository name to search")
|
||||
|
||||
repos = info_repos(" ".join(msg.args))
|
||||
|
||||
res = Response(channel=msg.channel,
|
||||
nomore="No more repository",
|
||||
count=" (%d more repo)")
|
||||
|
||||
for repo in repos["items"]:
|
||||
homepage = ""
|
||||
if repo["homepage"] is not None:
|
||||
homepage = repo["homepage"] + " - "
|
||||
res.append_message("Repository %s: %s%s Main language: %s; %d forks; %d stars; %d watchers; %d opened_issues; view it at %s" %
|
||||
(repo["full_name"],
|
||||
homepage,
|
||||
repo["description"],
|
||||
repo["language"], repo["forks"],
|
||||
repo["stargazers_count"],
|
||||
repo["watchers_count"],
|
||||
repo["open_issues_count"],
|
||||
repo["html_url"]))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("github_user",
|
||||
help="Display information about users",
|
||||
help_usage={
|
||||
"USERNAME": "Display information about the user USERNAME",
|
||||
})
|
||||
def cmd_github_user(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a user name to search")
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more user")
|
||||
|
||||
user = info_user(" ".join(msg.args))
|
||||
|
||||
if "login" in user:
|
||||
if user["repos"]:
|
||||
kf = (" Known for: " +
|
||||
", ".join([repo["name"] for repo in user["repos"]]))
|
||||
else:
|
||||
kf = ""
|
||||
if "name" in user:
|
||||
name = user["name"]
|
||||
else:
|
||||
name = user["login"]
|
||||
res.append_message("User %s: %d public repositories; %d public gists; %d followers; %d following; view it at %s.%s" %
|
||||
(name,
|
||||
user["public_repos"],
|
||||
user["public_gists"],
|
||||
user["followers"],
|
||||
user["following"],
|
||||
user["html_url"],
|
||||
kf))
|
||||
else:
|
||||
raise IMException("User not found")
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("github_user_keys",
|
||||
help="Display user SSH keys",
|
||||
help_usage={
|
||||
"USERNAME": "Show USERNAME's SSH keys",
|
||||
})
|
||||
def cmd_github_user_keys(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a user name to search")
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more keys")
|
||||
|
||||
for k in user_keys(" ".join(msg.args)):
|
||||
res.append_message(k)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("github_issue",
|
||||
help="Display repository's issues",
|
||||
help_usage={
|
||||
"REPO": "Display latest issues created on REPO",
|
||||
"REPO #ISSUE": "Display the issue number #ISSUE for REPO",
|
||||
})
|
||||
def cmd_github_issue(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a repository to view its issues")
|
||||
|
||||
issue = None
|
||||
|
||||
li = re.match("^#?([0-9]+)$", msg.args[0])
|
||||
ri = re.match("^#?([0-9]+)$", msg.args[-1])
|
||||
if li is not None:
|
||||
issue = li.group(1)
|
||||
del msg.args[0]
|
||||
elif ri is not None:
|
||||
issue = ri.group(1)
|
||||
del msg.args[-1]
|
||||
|
||||
repo = " ".join(msg.args)
|
||||
|
||||
count = " (%d more issues)" if issue is None else None
|
||||
res = Response(channel=msg.channel, nomore="No more issue", count=count)
|
||||
|
||||
issues = info_issue(repo, issue)
|
||||
|
||||
if issues is None:
|
||||
raise IMException("Repository not found")
|
||||
|
||||
for issue in issues:
|
||||
res.append_message("%s%s issue #%d: \x03\x02%s\x03\x02 opened by %s on %s: %s" %
|
||||
(issue["state"][0].upper(),
|
||||
issue["state"][1:],
|
||||
issue["number"],
|
||||
issue["title"],
|
||||
issue["user"]["login"],
|
||||
issue["created_at"],
|
||||
issue["body"].replace("\n", " ")))
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("github_commit",
|
||||
help="Display repository's commits",
|
||||
help_usage={
|
||||
"REPO": "Display latest commits on REPO",
|
||||
"REPO COMMIT": "Display details for the COMMIT on REPO",
|
||||
})
|
||||
def cmd_github_commit(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a repository to view its commits")
|
||||
|
||||
commit = None
|
||||
if re.match("^[a-fA-F0-9]+$", msg.args[0]):
|
||||
commit = msg.args[0]
|
||||
del msg.args[0]
|
||||
elif re.match("^[a-fA-F0-9]+$", msg.args[-1]):
|
||||
commit = msg.args[-1]
|
||||
del msg.args[-1]
|
||||
|
||||
repo = " ".join(msg.args)
|
||||
|
||||
count = " (%d more commits)" if commit is None else None
|
||||
res = Response(channel=msg.channel, nomore="No more commit", count=count)
|
||||
|
||||
commits = info_commit(repo, commit)
|
||||
|
||||
if commits is None:
|
||||
raise IMException("Repository or commit not found")
|
||||
|
||||
for commit in commits:
|
||||
res.append_message("Commit %s by %s on %s: %s" %
|
||||
(commit["sha"][:10],
|
||||
commit["commit"]["author"]["name"],
|
||||
commit["commit"]["author"]["date"],
|
||||
commit["commit"]["message"].replace("\n", " ")))
|
||||
return res
|
||||
|
|
@ -1,85 +0,0 @@
|
|||
"""Filter messages, displaying lines matching a pattern"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import re
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.message import Command, Text
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def grep(fltr, cmd, msg, icase=False, only=False):
|
||||
"""Perform a grep like on known nemubot structures
|
||||
|
||||
Arguments:
|
||||
fltr -- The filter regexp
|
||||
cmd -- The subcommand to execute
|
||||
msg -- The original message
|
||||
icase -- like the --ignore-case parameter of grep
|
||||
only -- like the --only-matching parameter of grep
|
||||
"""
|
||||
|
||||
fltr = re.compile(fltr, re.I if icase else 0)
|
||||
|
||||
for r in context.subtreat(context.subparse(msg, cmd)):
|
||||
if isinstance(r, Response):
|
||||
for i in range(len(r.messages) - 1, -1, -1):
|
||||
if isinstance(r.messages[i], list):
|
||||
for j in range(len(r.messages[i]) - 1, -1, -1):
|
||||
res = fltr.match(r.messages[i][j])
|
||||
if not res:
|
||||
r.messages[i].pop(j)
|
||||
elif only:
|
||||
r.messages[i][j] = res.group(1) if fltr.groups else res.group(0)
|
||||
if len(r.messages[i]) <= 0:
|
||||
r.messages.pop(i)
|
||||
elif isinstance(r.messages[i], str):
|
||||
res = fltr.match(r.messages[i])
|
||||
if not res:
|
||||
r.messages.pop(i)
|
||||
elif only:
|
||||
r.messages[i] = res.group(1) if fltr.groups else res.group(0)
|
||||
yield r
|
||||
|
||||
elif isinstance(r, Text):
|
||||
res = fltr.match(r.message)
|
||||
if res:
|
||||
if only:
|
||||
r.message = res.group(1) if fltr.groups else res.group(0)
|
||||
yield r
|
||||
|
||||
else:
|
||||
yield r
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("grep",
|
||||
help="Display only lines from a subcommand matching the given pattern",
|
||||
help_usage={"PTRN !SUBCMD": "Filter SUBCMD command using the pattern PTRN"},
|
||||
keywords={
|
||||
"nocase": "Perform case-insensitive matching",
|
||||
"only": "Print only the matched parts of a matching line",
|
||||
})
|
||||
def cmd_grep(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("Please provide a filter and a command")
|
||||
|
||||
only = "only" in msg.kwargs
|
||||
|
||||
l = [m for m in grep(msg.args[0] if len(msg.args[0]) and msg.args[0][0] == "^" else ".*?(" + msg.args[0] + ").*?",
|
||||
" ".join(msg.args[1:]),
|
||||
msg,
|
||||
icase="nocase" in msg.kwargs,
|
||||
only=only) if m is not None]
|
||||
|
||||
if len(l) <= 0:
|
||||
raise IMException("Pattern not found in output")
|
||||
|
||||
return l
|
||||
115
modules/imdb.py
115
modules/imdb.py
|
|
@ -1,115 +0,0 @@
|
|||
"""Show many information about a movie or serie"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import re
|
||||
import urllib.parse
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def get_movie_by_id(imdbid):
|
||||
"""Returns the information about the matching movie"""
|
||||
|
||||
url = "http://www.imdb.com/title/" + urllib.parse.quote(imdbid)
|
||||
soup = BeautifulSoup(web.getURLContent(url))
|
||||
|
||||
return {
|
||||
"imdbID": imdbid,
|
||||
"Title": soup.body.find('h1').contents[0].strip(),
|
||||
"Year": soup.body.find(id="titleYear").find("a").text.strip() if soup.body.find(id="titleYear") else ", ".join([y.text.strip() for y in soup.body.find(attrs={"class": "seasons-and-year-nav"}).find_all("a")[1:]]),
|
||||
"Duration": soup.body.find(attrs={"class": "title_wrapper"}).find("time").text.strip() if soup.body.find(attrs={"class": "title_wrapper"}).find("time") else None,
|
||||
"imdbRating": soup.body.find(attrs={"class": "ratingValue"}).find("strong").text.strip() if soup.body.find(attrs={"class": "ratingValue"}) else None,
|
||||
"imdbVotes": soup.body.find(attrs={"class": "imdbRating"}).find("a").text.strip() if soup.body.find(attrs={"class": "imdbRating"}) else None,
|
||||
"Plot": re.sub(r"\s+", " ", soup.body.find(attrs={"class": "summary_text"}).text).strip(),
|
||||
|
||||
"Type": "TV Series" if soup.find(id="title-episode-widget") else "Movie",
|
||||
"Genre": ", ".join([x.text.strip() for x in soup.body.find(id="titleStoryLine").find_all("a") if x.get("href") is not None and x.get("href")[:21] == "/search/title?genres="]),
|
||||
"Country": ", ".join([x.text.strip() for x in soup.body.find(id="titleDetails").find_all("a") if x.get("href") is not None and x.get("href")[:32] == "/search/title?country_of_origin="]),
|
||||
"Credits": " ; ".join([x.find("h4").text.strip() + " " + (", ".join([y.text.strip() for y in x.find_all("a") if y.get("href") is not None and y.get("href")[:6] == "/name/"])) for x in soup.body.find_all(attrs={"class": "credit_summary_item"})]),
|
||||
}
|
||||
|
||||
|
||||
def find_movies(title, year=None):
|
||||
"""Find existing movies matching a approximate title"""
|
||||
|
||||
title = title.lower()
|
||||
|
||||
# Built URL
|
||||
url = "https://v2.sg.media-imdb.com/suggests/%s/%s.json" % (urllib.parse.quote(title[0]), urllib.parse.quote(title.replace(" ", "_")))
|
||||
|
||||
# Make the request
|
||||
data = web.getJSON(url, remove_callback=True)
|
||||
|
||||
if "d" not in data:
|
||||
return None
|
||||
elif year is None:
|
||||
return data["d"]
|
||||
else:
|
||||
return [d for d in data["d"] if "y" in d and str(d["y"]) == year]
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("imdb",
|
||||
help="View movie/serie details, using OMDB",
|
||||
help_usage={
|
||||
"TITLE": "Look for a movie titled TITLE",
|
||||
"IMDB_ID": "Look for the movie with the given IMDB_ID",
|
||||
})
|
||||
def cmd_imdb(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("precise a movie/serie title!")
|
||||
|
||||
title = ' '.join(msg.args)
|
||||
|
||||
if re.match("^tt[0-9]{7}$", title) is not None:
|
||||
data = get_movie_by_id(imdbid=title)
|
||||
else:
|
||||
rm = re.match(r"^(.+)\s\(([0-9]{4})\)$", title)
|
||||
if rm is not None:
|
||||
data = find_movies(rm.group(1), year=rm.group(2))
|
||||
else:
|
||||
data = find_movies(title)
|
||||
|
||||
if not data:
|
||||
raise IMException("Movie/series not found")
|
||||
|
||||
data = get_movie_by_id(data[0]["id"])
|
||||
|
||||
res = Response(channel=msg.channel,
|
||||
title="%s (%s)" % (data['Title'], data['Year']),
|
||||
nomore="No more information, more at http://www.imdb.com/title/%s" % data['imdbID'])
|
||||
|
||||
res.append_message("%s \x02genre:\x0F %s; \x02rating\x0F: %s (%s votes); \x02plot\x0F: %s" %
|
||||
(data['Type'], data['Genre'], data['imdbRating'], data['imdbVotes'], data['Plot']))
|
||||
res.append_message("%s \x02from\x0F %s; %s"
|
||||
% (data['Type'], data['Country'], data['Credits']))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("imdbs",
|
||||
help="Search a movie/serie by title",
|
||||
help_usage={
|
||||
"TITLE": "Search a movie/serie by TITLE",
|
||||
})
|
||||
def cmd_search(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("precise a movie/serie title!")
|
||||
|
||||
data = find_movies(' '.join(msg.args))
|
||||
|
||||
movies = list()
|
||||
for m in data:
|
||||
movies.append("\x02%s\x0F%s with %s" % (m['l'], (" (" + str(m['y']) + ")") if "y" in m else "", m['s']))
|
||||
|
||||
return Response(movies, title="Titles found", channel=msg.channel)
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
from nemubot.hooks import hook
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.tools import web
|
||||
from nemubot.module.more import Response
|
||||
import json
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
def help_full():
|
||||
return "Retrieves data from json"
|
||||
|
||||
def getRequestedTags(tags, data):
|
||||
response = ""
|
||||
if isinstance(data, list):
|
||||
for element in data:
|
||||
repdata = getRequestedTags(tags, element)
|
||||
if response:
|
||||
response = response + "\n" + repdata
|
||||
else:
|
||||
response = repdata
|
||||
else:
|
||||
for tag in tags:
|
||||
if tag in data.keys():
|
||||
if response:
|
||||
response += ", " + tag + ": " + str(data[tag])
|
||||
else:
|
||||
response = tag + ": " + str(data[tag])
|
||||
return response
|
||||
|
||||
def getJsonKeys(data):
|
||||
if isinstance(data, list):
|
||||
pkeys = []
|
||||
for element in data:
|
||||
keys = getJsonKeys(element)
|
||||
for key in keys:
|
||||
if not key in pkeys:
|
||||
pkeys.append(key)
|
||||
return pkeys
|
||||
else:
|
||||
return data.keys()
|
||||
|
||||
@hook.command("json")
|
||||
def get_json_info(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Please specify a url and a list of JSON keys.")
|
||||
|
||||
request_data = web.getURLContent(msg.args[0].replace(' ', "%20"))
|
||||
if not request_data:
|
||||
raise IMException("Please specify a valid url.")
|
||||
json_data = json.loads(request_data)
|
||||
|
||||
if len(msg.args) == 1:
|
||||
raise IMException("Please specify the keys to return (%s)" % ", ".join(getJsonKeys(json_data)))
|
||||
|
||||
tags = ','.join(msg.args[1:]).split(',')
|
||||
response = getRequestedTags(tags, json_data)
|
||||
|
||||
return Response(response, channel=msg.channel, nomore="No more content", count=" (%d more lines)")
|
||||
|
|
@ -1,78 +1,66 @@
|
|||
"""Read manual pages on IRC"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
# coding=utf-8
|
||||
|
||||
import subprocess
|
||||
import re
|
||||
import os
|
||||
|
||||
from nemubot.hooks import hook
|
||||
nemubotversion = 3.3
|
||||
|
||||
from nemubot.module.more import Response
|
||||
def load(context):
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_man, "MAN"))
|
||||
add_hook("cmd_hook", Hook(cmd_whatis, "man"))
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "Read man on IRC"
|
||||
|
||||
# GLOBALS #############################################################
|
||||
def help_full ():
|
||||
return "!man [0-9] /what/: gives informations about /what/."
|
||||
|
||||
RGXP_s = re.compile(b'\x1b\\[[0-9]+m')
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("MAN",
|
||||
help="Show man pages",
|
||||
help_usage={
|
||||
"SUBJECT": "Display the default man page for SUBJECT",
|
||||
"SECTION SUBJECT": "Display the man page in SECTION for SUBJECT"
|
||||
})
|
||||
def cmd_man(msg):
|
||||
args = ["man"]
|
||||
num = None
|
||||
if len(msg.args) == 1:
|
||||
args.append(msg.args[0])
|
||||
elif len(msg.args) >= 2:
|
||||
if len(msg.cmds) == 2:
|
||||
args.append(msg.cmds[1])
|
||||
elif len(msg.cmds) >= 3:
|
||||
try:
|
||||
num = int(msg.args[0])
|
||||
num = int(msg.cmds[1])
|
||||
args.append("%d" % num)
|
||||
args.append(msg.args[1])
|
||||
args.append(msg.cmds[2])
|
||||
except ValueError:
|
||||
args.append(msg.args[0])
|
||||
args.append(msg.cmds[1])
|
||||
|
||||
os.unsetenv("LANG")
|
||||
res = Response(channel=msg.channel)
|
||||
with subprocess.Popen(args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE) as proc:
|
||||
res = Response(msg.sender, channel=msg.channel)
|
||||
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||
for line in proc.stdout.read().split(b"\n"):
|
||||
(line, n) = RGXP_s.subn(b'', line)
|
||||
res.append_message(line.decode())
|
||||
|
||||
if len(res.messages) <= 0:
|
||||
if num is not None:
|
||||
res.append_message("There is no entry %s in section %d." %
|
||||
(msg.args[0], num))
|
||||
res.append_message("Il n'y a pas d'entrée %s dans la section %d du manuel." % (msg.cmds[1], num))
|
||||
else:
|
||||
res.append_message("There is no man page for %s." % msg.args[0])
|
||||
res.append_message("Il n'y a pas de page de manuel pour %s." % msg.cmds[1])
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("man",
|
||||
help="Show man pages synopsis (in one line)",
|
||||
help_usage={
|
||||
"SUBJECT": "Display man page synopsis for SUBJECT",
|
||||
})
|
||||
def cmd_whatis(msg):
|
||||
args = ["whatis", " ".join(msg.args)]
|
||||
args = ["whatis", " ".join(msg.cmds[1:])]
|
||||
|
||||
res = Response(channel=msg.channel)
|
||||
with subprocess.Popen(args,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE) as proc:
|
||||
res = Response(msg.sender, channel=msg.channel)
|
||||
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||
for line in proc.stdout.read().split(b"\n"):
|
||||
(line, n) = RGXP_s.subn(b'', line)
|
||||
res.append_message(" ".join(line.decode().split()))
|
||||
|
||||
if len(res.messages) <= 0:
|
||||
res.append_message("There is no man page for %s." % msg.args[0])
|
||||
if num is not None:
|
||||
res.append_message("Il n'y a pas d'entrée %s dans la section %d du manuel." % (msg.cmds[1], num))
|
||||
else:
|
||||
res.append_message("Il n'y a pas de page de manuel pour %s." % msg.cmds[1])
|
||||
|
||||
return res
|
||||
|
|
|
|||
|
|
@ -1,68 +0,0 @@
|
|||
"""Transform name location to GPS coordinates"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
URL_API = "https://open.mapquestapi.com/geocoding/v1/address?key=%s&location=%%s"
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
if not context.config or "apikey" not in context.config:
|
||||
raise ImportError("You need a MapQuest API key in order to use this "
|
||||
"module. Add it to the module configuration file:\n"
|
||||
"<module name=\"mapquest\" key=\"XXXXXXXXXXXXXXXX\" "
|
||||
"/>\nRegister at https://developer.mapquest.com/")
|
||||
global URL_API
|
||||
URL_API = URL_API % context.config["apikey"].replace("%", "%%")
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def geocode(location):
|
||||
obj = web.getJSON(URL_API % quote(location))
|
||||
|
||||
if "results" in obj and "locations" in obj["results"][0]:
|
||||
for loc in obj["results"][0]["locations"]:
|
||||
yield loc
|
||||
|
||||
|
||||
def where(loc):
|
||||
return re.sub(" +", " ",
|
||||
"{street} {adminArea5} {adminArea4} {adminArea3} "
|
||||
"{adminArea1}".format(**loc)).strip()
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("geocode",
|
||||
help="Get GPS coordinates of a place",
|
||||
help_usage={
|
||||
"PLACE": "Get GPS coordinates of PLACE"
|
||||
})
|
||||
def cmd_geocode(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a name")
|
||||
|
||||
res = Response(channel=msg.channel, nick=msg.frm,
|
||||
nomore="No more geocode", count=" (%s more geocode)")
|
||||
|
||||
for loc in geocode(' '.join(msg.args)):
|
||||
res.append_message("%s is at %s,%s (%s precision)" %
|
||||
(where(loc),
|
||||
loc["latLng"]["lat"],
|
||||
loc["latLng"]["lng"],
|
||||
loc["geocodeQuality"].lower()))
|
||||
|
||||
return res
|
||||
|
|
@ -1,249 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
"""Use MediaWiki API to get pages"""
|
||||
|
||||
import re
|
||||
import urllib.parse
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# MEDIAWIKI REQUESTS ##################################################
|
||||
|
||||
def get_namespaces(site, ssl=False, path="/w/api.php"):
|
||||
# Built URL
|
||||
url = "http%s://%s%s?format=json&action=query&meta=siteinfo&siprop=namespaces" % (
|
||||
"s" if ssl else "", site, path)
|
||||
|
||||
# Make the request
|
||||
data = web.getJSON(url)
|
||||
|
||||
namespaces = dict()
|
||||
for ns in data["query"]["namespaces"]:
|
||||
namespaces[data["query"]["namespaces"][ns]["*"]] = data["query"]["namespaces"][ns]
|
||||
return namespaces
|
||||
|
||||
|
||||
def get_raw_page(site, term, ssl=False, path="/w/api.php"):
|
||||
# Built URL
|
||||
url = "http%s://%s%s?format=json&redirects&action=query&prop=revisions&rvprop=content&titles=%s" % (
|
||||
"s" if ssl else "", site, path, urllib.parse.quote(term))
|
||||
|
||||
# Make the request
|
||||
data = web.getJSON(url)
|
||||
|
||||
for k in data["query"]["pages"]:
|
||||
try:
|
||||
return data["query"]["pages"][k]["revisions"][0]["*"]
|
||||
except:
|
||||
raise IMException("article not found")
|
||||
|
||||
|
||||
def get_unwikitextified(site, wikitext, ssl=False, path="/w/api.php"):
|
||||
# Built URL
|
||||
url = "http%s://%s%s?format=json&action=expandtemplates&text=%s" % (
|
||||
"s" if ssl else "", site, path, urllib.parse.quote(wikitext))
|
||||
|
||||
# Make the request
|
||||
data = web.getJSON(url)
|
||||
|
||||
return data["expandtemplates"]["*"]
|
||||
|
||||
|
||||
## Search
|
||||
|
||||
def opensearch(site, term, ssl=False, path="/w/api.php"):
|
||||
# Built URL
|
||||
url = "http%s://%s%s?format=json&action=opensearch&search=%s" % (
|
||||
"s" if ssl else "", site, path, urllib.parse.quote(term))
|
||||
|
||||
# Make the request
|
||||
response = web.getJSON(url)
|
||||
|
||||
if response is not None and len(response) >= 4:
|
||||
for k in range(len(response[1])):
|
||||
yield (response[1][k],
|
||||
response[2][k],
|
||||
response[3][k])
|
||||
|
||||
|
||||
def search(site, term, ssl=False, path="/w/api.php"):
|
||||
# Built URL
|
||||
url = "http%s://%s%s?format=json&action=query&list=search&srsearch=%s&srprop=titlesnippet|snippet" % (
|
||||
"s" if ssl else "", site, path, urllib.parse.quote(term))
|
||||
|
||||
# Make the request
|
||||
data = web.getJSON(url)
|
||||
|
||||
if data is not None and "query" in data and "search" in data["query"]:
|
||||
for itm in data["query"]["search"]:
|
||||
yield (web.striphtml(itm["titlesnippet"].replace("<span class='searchmatch'>", "\x03\x02").replace("</span>", "\x03\x02")),
|
||||
web.striphtml(itm["snippet"].replace("<span class='searchmatch'>", "\x03\x02").replace("</span>", "\x03\x02")))
|
||||
|
||||
|
||||
# PARSING FUNCTIONS ###################################################
|
||||
|
||||
def get_model(cnt, model="Infobox"):
|
||||
for full in re.findall(r"(\{\{" + model + " .*?(?:\{\{.*?}}.*?)*}})", cnt, flags=re.DOTALL):
|
||||
return full[3 + len(model):-2].replace("\n", " ").strip()
|
||||
|
||||
|
||||
def strip_model(cnt):
|
||||
# Strip models at begin: mostly useless
|
||||
cnt = re.sub(r"^(({{([^{]|\s|({{([^{]|\s|{{.*?}})*?}})*?)*?}}|\[\[([^[]|\s|\[\[.*?\]\])*?\]\])\s*)+", "", cnt, flags=re.DOTALL)
|
||||
|
||||
# Remove new line from models
|
||||
for full in re.findall(r"{{.*?}}", cnt, flags=re.DOTALL):
|
||||
cnt = cnt.replace(full, full.replace("\n", " "), 1)
|
||||
|
||||
# Remove new line after titles
|
||||
cnt, _ = re.subn(r"((?P<title>==+)\s*(.*?)\s*(?P=title))\n+", r"\1", cnt)
|
||||
|
||||
# Strip HTML comments
|
||||
cnt = re.sub(r"<!--.*?-->", "", cnt, flags=re.DOTALL)
|
||||
|
||||
# Strip ref
|
||||
cnt = re.sub(r"<ref.*?/ref>", "", cnt, flags=re.DOTALL)
|
||||
return cnt
|
||||
|
||||
|
||||
def parse_wikitext(site, cnt, namespaces=dict(), **kwargs):
|
||||
for i, _, _, _ in re.findall(r"({{([^{]|\s|({{(.|\s|{{.*?}})*?}})*?)*?}})", cnt):
|
||||
cnt = cnt.replace(i, get_unwikitextified(site, i, **kwargs), 1)
|
||||
|
||||
# Strip [[...]]
|
||||
for full, args, lnk in re.findall(r"(\[\[(.*?|)?([^|]*?)\]\])", cnt):
|
||||
ns = lnk.find(":")
|
||||
if lnk == "":
|
||||
cnt = cnt.replace(full, args[:-1], 1)
|
||||
elif ns > 0:
|
||||
namespace = lnk[:ns]
|
||||
if namespace in namespaces and namespaces[namespace]["canonical"] == "Category":
|
||||
cnt = cnt.replace(full, "", 1)
|
||||
continue
|
||||
cnt = cnt.replace(full, lnk, 1)
|
||||
else:
|
||||
cnt = cnt.replace(full, lnk, 1)
|
||||
|
||||
# Strip HTML tags
|
||||
cnt = web.striphtml(cnt)
|
||||
|
||||
return cnt
|
||||
|
||||
|
||||
# FORMATING FUNCTIONS #################################################
|
||||
|
||||
def irc_format(cnt):
|
||||
cnt, _ = re.subn(r"(?P<title>==+)\s*(.*?)\s*(?P=title)", "\x03\x16" + r"\2" + " :\x03\x16 ", cnt)
|
||||
return cnt.replace("'''", "\x03\x02").replace("''", "\x03\x1f")
|
||||
|
||||
|
||||
def parse_infobox(cnt):
|
||||
for v in cnt.split("|"):
|
||||
try:
|
||||
yield re.sub(r"^\s*([^=]*[^=\s])\s*=\s*(.+)\s*$", "\x03\x02" + r"\1" + ":\x03\x02 " + r"\2", v).replace("<br />", ", ").replace("<br/>", ", ").strip()
|
||||
except:
|
||||
yield re.sub(r"^\s+(.+)\s+$", "\x03\x02" + r"\1" + "\x03\x02", v).replace("<br />", ", ").replace("<br/>", ", ").strip()
|
||||
|
||||
|
||||
def get_page(site, term, subpart=None, **kwargs):
|
||||
raw = get_raw_page(site, term, **kwargs)
|
||||
|
||||
if subpart is not None:
|
||||
subpart = subpart.replace("_", " ")
|
||||
raw = re.sub(r"^.*(?P<title>==+)\s*(" + subpart + r")\s*(?P=title)", r"\1 \2 \1", raw, flags=re.DOTALL)
|
||||
|
||||
return raw
|
||||
|
||||
|
||||
# NEMUBOT #############################################################
|
||||
|
||||
def mediawiki_response(site, term, to, **kwargs):
|
||||
ns = get_namespaces(site, **kwargs)
|
||||
|
||||
terms = term.split("#", 1)
|
||||
|
||||
try:
|
||||
# Print the article if it exists
|
||||
return Response(strip_model(get_page(site, terms[0], subpart=terms[1] if len(terms) > 1 else None, **kwargs)),
|
||||
line_treat=lambda line: irc_format(parse_wikitext(site, line, ns, **kwargs)),
|
||||
channel=to)
|
||||
except:
|
||||
pass
|
||||
|
||||
# Try looking at opensearch
|
||||
os = [x for x, _, _ in opensearch(site, terms[0], **kwargs)]
|
||||
print(os)
|
||||
# Fallback to global search
|
||||
if not len(os):
|
||||
os = [x for x, _ in search(site, terms[0], **kwargs) if x is not None and x != ""]
|
||||
return Response(os,
|
||||
channel=to,
|
||||
title="Article not found, would you mean")
|
||||
|
||||
|
||||
@hook.command("mediawiki",
|
||||
help="Read an article on a MediaWiki",
|
||||
keywords={
|
||||
"ssl": "query over https instead of http",
|
||||
"path=PATH": "absolute path to the API",
|
||||
})
|
||||
def cmd_mediawiki(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("indicate a domain and a term to search")
|
||||
|
||||
return mediawiki_response(msg.args[0],
|
||||
" ".join(msg.args[1:]),
|
||||
msg.to_response,
|
||||
**msg.kwargs)
|
||||
|
||||
|
||||
@hook.command("mediawiki_search",
|
||||
help="Search an article on a MediaWiki",
|
||||
keywords={
|
||||
"ssl": "query over https instead of http",
|
||||
"path=PATH": "absolute path to the API",
|
||||
})
|
||||
def cmd_srchmediawiki(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("indicate a domain and a term to search")
|
||||
|
||||
res = Response(channel=msg.to_response, nomore="No more results", count=" (%d more results)")
|
||||
|
||||
for r in search(msg.args[0], " ".join(msg.args[1:]), **msg.kwargs):
|
||||
res.append_message("%s: %s" % r)
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("mediawiki_infobox",
|
||||
help="Highlight information from an article on a MediaWiki",
|
||||
keywords={
|
||||
"ssl": "query over https instead of http",
|
||||
"path=PATH": "absolute path to the API",
|
||||
})
|
||||
def cmd_infobox(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("indicate a domain and a term to search")
|
||||
|
||||
ns = get_namespaces(msg.args[0], **msg.kwargs)
|
||||
|
||||
return Response(", ".join([x for x in parse_infobox(get_model(get_page(msg.args[0], " ".join(msg.args[1:]), **msg.kwargs), "Infobox"))]),
|
||||
line_treat=lambda line: irc_format(parse_wikitext(msg.args[0], line, ns, **msg.kwargs)),
|
||||
channel=msg.to_response)
|
||||
|
||||
|
||||
@hook.command("wikipedia")
|
||||
def cmd_wikipedia(msg):
|
||||
if len(msg.args) < 2:
|
||||
raise IMException("indicate a lang and a term to search")
|
||||
|
||||
return mediawiki_response(msg.args[0] + ".wikipedia.org",
|
||||
" ".join(msg.args[1:]),
|
||||
msg.to_response)
|
||||
119
modules/networking.py
Normal file
119
modules/networking.py
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
# coding=utf-8
|
||||
|
||||
import http.client
|
||||
import json
|
||||
import socket
|
||||
from urllib.parse import quote
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
|
||||
from tools import web
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
def load(context):
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_traceurl, "traceurl"))
|
||||
add_hook("cmd_hook", Hook(cmd_isup, "isup"))
|
||||
add_hook("cmd_hook", Hook(cmd_curl, "curl"))
|
||||
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "The networking module"
|
||||
|
||||
def help_full ():
|
||||
return "!traceurl /url/: Follow redirections from /url/."
|
||||
|
||||
def cmd_curl(msg):
|
||||
if len(msg.cmds) > 1:
|
||||
try:
|
||||
req = web.getURLContent(" ".join(msg.cmds[1:]))
|
||||
if req is not None:
|
||||
res = Response(msg.sender, channel=msg.channel)
|
||||
for m in req.decode().split("\n"):
|
||||
res.append_message(m)
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, "Une erreur est survenue lors de l'accès à cette URL", channel=msg.channel)
|
||||
except socket.error as e:
|
||||
return Response(msg.sender, e.strerror, channel=msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, "Veuillez indiquer une URL à visiter.",
|
||||
channel=msg.channel)
|
||||
|
||||
def cmd_traceurl(msg):
|
||||
if 1 < len(msg.cmds) < 6:
|
||||
res = list()
|
||||
for url in msg.cmds[1:]:
|
||||
trace = traceURL(url)
|
||||
res.append(Response(msg.sender, trace, channel=msg.channel, title="TraceURL"))
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, "Indiquer une URL a tracer !", channel=msg.channel)
|
||||
|
||||
def cmd_isup(msg):
|
||||
if 1 < len(msg.cmds) < 6:
|
||||
res = list()
|
||||
for url in msg.cmds[1:]:
|
||||
o = urlparse(url, "http")
|
||||
if o.netloc == "":
|
||||
o = urlparse("http://" + url)
|
||||
if o.netloc != "":
|
||||
raw = urlopen("http://isitup.org/" + o.netloc + ".json", timeout=10)
|
||||
isup = json.loads(raw.read().decode())
|
||||
if "status_code" in isup and isup["status_code"] == 1:
|
||||
res.append(Response(msg.sender, "%s est accessible (temps de reponse : %ss)" % (isup["domain"], isup["response_time"]), channel=msg.channel))
|
||||
else:
|
||||
res.append(Response(msg.sender, "%s n'est pas accessible :(" % (isup["domain"]), channel=msg.channel))
|
||||
else:
|
||||
res.append(Response(msg.sender, "%s n'est pas une URL valide" % url, channel=msg.channel))
|
||||
return res
|
||||
else:
|
||||
return Response(msg.sender, "Indiquer une URL à vérifier !", channel=msg.channel)
|
||||
|
||||
def traceURL(url, timeout=5, stack=None):
|
||||
"""Follow redirections and return the redirections stack"""
|
||||
if stack is None:
|
||||
stack = list()
|
||||
stack.append(url)
|
||||
|
||||
if len(stack) > 15:
|
||||
stack.append('stack overflow :(')
|
||||
return stack
|
||||
|
||||
o = urlparse(url, "http")
|
||||
if o.netloc == "":
|
||||
return stack
|
||||
if o.scheme == "http":
|
||||
conn = http.client.HTTPConnection(o.netloc, port=o.port, timeout=timeout)
|
||||
else:
|
||||
conn = http.client.HTTPSConnection(o.netloc, port=o.port, timeout=timeout)
|
||||
try:
|
||||
conn.request("HEAD", o.path, None, {"User-agent": "Nemubot v3"})
|
||||
except socket.timeout:
|
||||
stack.append("Timeout")
|
||||
return stack
|
||||
except socket.gaierror:
|
||||
print ("<tools.web> Unable to receive page %s from %s on %d."
|
||||
% (o.path, o.netloc, o.port))
|
||||
return stack
|
||||
|
||||
try:
|
||||
res = conn.getresponse()
|
||||
except http.client.BadStatusLine:
|
||||
return stack
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
if res.status == http.client.OK:
|
||||
return stack
|
||||
elif res.status == http.client.FOUND or res.status == http.client.MOVED_PERMANENTLY or res.status == http.client.SEE_OTHER:
|
||||
url = res.getheader("Location")
|
||||
if url in stack:
|
||||
stack.append("loop on " + url)
|
||||
return stack
|
||||
else:
|
||||
return traceURL(url, timeout, stack)
|
||||
else:
|
||||
return stack
|
||||
|
|
@ -1,184 +0,0 @@
|
|||
"""Various network tools (w3m, w3c validator, curl, traceurl, ...)"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import logging
|
||||
import re
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
from . import isup
|
||||
from . import page
|
||||
from . import w3c
|
||||
from . import watchWebsite
|
||||
from . import whois
|
||||
|
||||
logger = logging.getLogger("nemubot.module.networking")
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
for mod in [isup, page, w3c, watchWebsite, whois]:
|
||||
mod.add_event = context.add_event
|
||||
mod.del_event = context.del_event
|
||||
mod.save = context.save
|
||||
mod.print = print
|
||||
mod.send_response = context.send_response
|
||||
page.load(context.config, context.add_hook)
|
||||
watchWebsite.load(context.data)
|
||||
try:
|
||||
whois.load(context.config, context.add_hook)
|
||||
except ImportError:
|
||||
logger.exception("Unable to load netwhois module")
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("title",
|
||||
help="Retrieve webpage's title",
|
||||
help_usage={"URL": "Display the title of the given URL"})
|
||||
def cmd_title(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate the URL to visit.")
|
||||
|
||||
url = " ".join(msg.args)
|
||||
res = re.search("<title>(.*?)</title>", page.fetch(" ".join(msg.args)), re.DOTALL)
|
||||
|
||||
if res is None:
|
||||
raise IMException("The page %s has no title" % url)
|
||||
else:
|
||||
return Response("%s: %s" % (url, res.group(1).replace("\n", " ")), channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("curly",
|
||||
help="Retrieve webpage's headers",
|
||||
help_usage={"URL": "Display HTTP headers of the given URL"})
|
||||
def cmd_curly(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate the URL to visit.")
|
||||
|
||||
url = " ".join(msg.args)
|
||||
version, status, reason, headers = page.headers(url)
|
||||
|
||||
return Response("Entêtes de la page %s : HTTP/%s, statut : %d %s ; headers : %s" % (url, version, status, reason, ", ".join(["\x03\x02" + h + "\x03\x02: " + v for h, v in headers])), channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("curl",
|
||||
help="Retrieve webpage's body",
|
||||
help_usage={"URL": "Display raw HTTP body of the given URL"})
|
||||
def cmd_curl(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate the URL to visit.")
|
||||
|
||||
res = Response(channel=msg.channel)
|
||||
for m in page.fetch(" ".join(msg.args)).split("\n"):
|
||||
res.append_message(m)
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("w3m",
|
||||
help="Retrieve and format webpage's content",
|
||||
help_usage={"URL": "Display and format HTTP content of the given URL"})
|
||||
def cmd_w3m(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate the URL to visit.")
|
||||
res = Response(channel=msg.channel)
|
||||
for line in page.render(" ".join(msg.args)).split("\n"):
|
||||
res.append_message(line)
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("traceurl",
|
||||
help="Follow redirections of a given URL and display each step",
|
||||
help_usage={"URL": "Display redirections steps for the given URL"})
|
||||
def cmd_traceurl(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate an URL to trace!")
|
||||
|
||||
res = list()
|
||||
for url in msg.args[:4]:
|
||||
try:
|
||||
trace = page.traceURL(url)
|
||||
res.append(Response(trace, channel=msg.channel, title="TraceURL"))
|
||||
except:
|
||||
pass
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("isup",
|
||||
help="Check if a website is up",
|
||||
help_usage={"DOMAIN": "Check if a DOMAIN is up"})
|
||||
def cmd_isup(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate an domain name to check!")
|
||||
|
||||
res = list()
|
||||
for url in msg.args[:4]:
|
||||
rep = isup.isup(url)
|
||||
if rep:
|
||||
res.append(Response("%s is up (response time: %ss)" % (url, rep), channel=msg.channel))
|
||||
else:
|
||||
res.append(Response("%s is down" % (url), channel=msg.channel))
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("w3c",
|
||||
help="Perform a w3c HTML validator check",
|
||||
help_usage={"URL": "Do W3C HTML validation on the given URL"})
|
||||
def cmd_w3c(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate an URL to validate!")
|
||||
|
||||
headers, validator = w3c.validator(msg.args[0])
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more error")
|
||||
|
||||
res.append_message("%s: status: %s, %s warning(s), %s error(s)" % (validator["url"], headers["X-W3C-Validator-Status"], headers["X-W3C-Validator-Warnings"], headers["X-W3C-Validator-Errors"]))
|
||||
|
||||
for m in validator["messages"]:
|
||||
if "lastLine" not in m:
|
||||
res.append_message("%s%s: %s" % (m["type"][0].upper(), m["type"][1:], m["message"]))
|
||||
else:
|
||||
res.append_message("%s%s on line %s, col %s: %s" % (m["type"][0].upper(), m["type"][1:], m["lastLine"], m["lastColumn"], m["message"]))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
|
||||
@hook.command("watch", data="diff",
|
||||
help="Alert on webpage change",
|
||||
help_usage={"URL": "Watch the given URL and alert when it changes"})
|
||||
@hook.command("updown", data="updown",
|
||||
help="Alert on server availability change",
|
||||
help_usage={"URL": "Watch the given domain and alert when it availability status changes"})
|
||||
def cmd_watch(msg, diffType="diff"):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate an URL to watch!")
|
||||
|
||||
return watchWebsite.add_site(msg.args[0], msg.frm, msg.channel, msg.server, diffType)
|
||||
|
||||
|
||||
@hook.command("listwatch",
|
||||
help="List URL watched for the channel",
|
||||
help_usage={None: "List URL watched for the channel"})
|
||||
def cmd_listwatch(msg):
|
||||
wl = watchWebsite.watchedon(msg.channel)
|
||||
if len(wl):
|
||||
return Response(wl, channel=msg.channel, title="URL watched on this channel")
|
||||
else:
|
||||
return Response("No URL are currently watched. Use !watch URL to watch one.", channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("unwatch",
|
||||
help="Unwatch a previously watched URL",
|
||||
help_usage={"URL": "Unwatch the given URL"})
|
||||
def cmd_unwatch(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("which URL should I stop watching?")
|
||||
|
||||
for arg in msg.args:
|
||||
return watchWebsite.del_site(arg, msg.frm, msg.channel, msg.frm_owner)
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
import urllib
|
||||
|
||||
from nemubot.tools.web import getNormalizedURL, getJSON
|
||||
|
||||
def isup(url):
|
||||
"""Determine if the given URL is up or not
|
||||
|
||||
Argument:
|
||||
url -- the URL to check
|
||||
"""
|
||||
|
||||
o = urllib.parse.urlparse(getNormalizedURL(url), "http")
|
||||
if o.netloc != "":
|
||||
isup = getJSON("https://isitup.org/%s.json" % o.netloc)
|
||||
if isup is not None and "status_code" in isup and isup["status_code"] == 1:
|
||||
return isup["response_time"]
|
||||
|
||||
return None
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
import http.client
|
||||
import socket
|
||||
import subprocess
|
||||
import tempfile
|
||||
import urllib
|
||||
|
||||
from nemubot import __version__
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.tools import web
|
||||
|
||||
|
||||
def load(CONF, add_hook):
|
||||
# TODO: check w3m exists
|
||||
pass
|
||||
|
||||
|
||||
def headers(url):
|
||||
"""Retrieve HTTP header for the given URL
|
||||
|
||||
Argument:
|
||||
url -- the page URL to get header
|
||||
"""
|
||||
|
||||
o = urllib.parse.urlparse(web.getNormalizedURL(url), "http")
|
||||
if o.netloc == "":
|
||||
raise IMException("invalid URL")
|
||||
if o.scheme == "http":
|
||||
conn = http.client.HTTPConnection(o.hostname, port=o.port, timeout=5)
|
||||
else:
|
||||
conn = http.client.HTTPSConnection(o.hostname, port=o.port, timeout=5)
|
||||
try:
|
||||
conn.request("HEAD", o.path, None, {"User-agent":
|
||||
"Nemubot v%s" % __version__})
|
||||
except ConnectionError as e:
|
||||
raise IMException(e.strerror)
|
||||
except socket.timeout:
|
||||
raise IMException("request timeout")
|
||||
except socket.gaierror:
|
||||
print ("<tools.web> Unable to receive page %s from %s on %d."
|
||||
% (o.path, o.hostname, o.port if o.port is not None else 0))
|
||||
raise IMException("an unexpected error occurs")
|
||||
|
||||
try:
|
||||
res = conn.getresponse()
|
||||
except http.client.BadStatusLine:
|
||||
raise IMException("An error occurs")
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
return (res.version, res.status, res.reason, res.getheaders())
|
||||
|
||||
|
||||
def _onNoneDefault():
|
||||
raise IMException("An error occurs when trying to access the page")
|
||||
|
||||
|
||||
def fetch(url, onNone=_onNoneDefault):
|
||||
"""Retrieve the content of the given URL
|
||||
|
||||
Argument:
|
||||
url -- the URL to fetch
|
||||
"""
|
||||
|
||||
try:
|
||||
req = web.getURLContent(url)
|
||||
if req is not None:
|
||||
return req
|
||||
else:
|
||||
if callable(onNone):
|
||||
return onNone()
|
||||
else:
|
||||
return None
|
||||
except ConnectionError as e:
|
||||
raise IMException(e.strerror)
|
||||
except socket.timeout:
|
||||
raise IMException("The request timeout when trying to access the page")
|
||||
except socket.error as e:
|
||||
raise IMException(e.strerror)
|
||||
|
||||
|
||||
def _render(cnt):
|
||||
"""Render the page contained in cnt as HTML page"""
|
||||
if cnt is None:
|
||||
return None
|
||||
|
||||
with tempfile.NamedTemporaryFile() as fp:
|
||||
fp.write(cnt.encode())
|
||||
|
||||
args = ["w3m", "-T", "text/html", "-dump"]
|
||||
args.append(fp.name)
|
||||
with subprocess.Popen(args, stdout=subprocess.PIPE, stderr=subprocess.PIPE) as proc:
|
||||
return proc.stdout.read().decode()
|
||||
|
||||
|
||||
def render(url, onNone=_onNoneDefault):
|
||||
"""Use w3m to render the given url
|
||||
|
||||
Argument:
|
||||
url -- the URL to render
|
||||
"""
|
||||
|
||||
return _render(fetch(url, onNone))
|
||||
|
||||
|
||||
def traceURL(url, stack=None):
|
||||
"""Follow redirections and return the redirections stack
|
||||
|
||||
Argument:
|
||||
url -- the URL to trace
|
||||
"""
|
||||
|
||||
if stack is None:
|
||||
stack = list()
|
||||
stack.append(url)
|
||||
|
||||
if len(stack) > 15:
|
||||
stack.append('stack overflow :(')
|
||||
return stack
|
||||
|
||||
_, status, _, heads = headers(url)
|
||||
|
||||
if status == http.client.FOUND or status == http.client.MOVED_PERMANENTLY or status == http.client.SEE_OTHER:
|
||||
for h, c in heads:
|
||||
if h == "Location":
|
||||
url = c
|
||||
if url in stack:
|
||||
stack.append("loop on " + url)
|
||||
return stack
|
||||
else:
|
||||
return traceURL(url, stack)
|
||||
return stack
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
import json
|
||||
import urllib
|
||||
|
||||
from nemubot import __version__
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.tools.web import getNormalizedURL
|
||||
|
||||
def validator(url):
|
||||
"""Run the w3c validator on the given URL
|
||||
|
||||
Argument:
|
||||
url -- the URL to validate
|
||||
"""
|
||||
|
||||
o = urllib.parse.urlparse(getNormalizedURL(url), "http")
|
||||
if o.netloc == "":
|
||||
raise IMException("Indicate a valid URL!")
|
||||
|
||||
try:
|
||||
req = urllib.request.Request("https://validator.w3.org/check?uri=%s&output=json" % (urllib.parse.quote(o.geturl())), headers={ 'User-Agent' : "Nemubot v%s" % __version__})
|
||||
raw = urllib.request.urlopen(req, timeout=10)
|
||||
except urllib.error.HTTPError as e:
|
||||
raise IMException("HTTP error occurs: %s %s" % (e.code, e.reason))
|
||||
|
||||
headers = dict()
|
||||
for Hname, Hval in raw.getheaders():
|
||||
headers[Hname] = Hval
|
||||
|
||||
if "X-W3C-Validator-Status" not in headers or (headers["X-W3C-Validator-Status"] != "Valid" and headers["X-W3C-Validator-Status"] != "Invalid"):
|
||||
raise IMException("Unexpected error on W3C servers" + (" (" + headers["X-W3C-Validator-Status"] + ")" if "X-W3C-Validator-Status" in headers else ""))
|
||||
|
||||
return headers, json.loads(raw.read().decode())
|
||||
|
|
@ -1,223 +0,0 @@
|
|||
"""Alert on changes on websites"""
|
||||
|
||||
from functools import partial
|
||||
import logging
|
||||
from random import randint
|
||||
import urllib.parse
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from nemubot.event import ModuleEvent
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.tools.web import getNormalizedURL
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
|
||||
logger = logging.getLogger("nemubot.module.networking.watchWebsite")
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
from . import page
|
||||
|
||||
DATAS = None
|
||||
|
||||
|
||||
def load(datas):
|
||||
"""Register events on watched website"""
|
||||
|
||||
global DATAS
|
||||
DATAS = datas
|
||||
|
||||
DATAS.setIndex("url", "watch")
|
||||
for site in DATAS.getNodes("watch"):
|
||||
if site.hasNode("alert"):
|
||||
start_watching(site, randint(-30, 30))
|
||||
else:
|
||||
print("No alert defined for this site: " + site["url"])
|
||||
#DATAS.delChild(site)
|
||||
|
||||
|
||||
def watchedon(channel):
|
||||
"""Get a list of currently watched URL on the given channel.
|
||||
"""
|
||||
|
||||
res = list()
|
||||
for site in DATAS.getNodes("watch"):
|
||||
if site.hasNode("alert"):
|
||||
for a in site.getNodes("alert"):
|
||||
if a["channel"] == channel:
|
||||
res.append("%s (%s)" % (site["url"], site["type"]))
|
||||
break
|
||||
return res
|
||||
|
||||
|
||||
def del_site(url, nick, channel, frm_owner):
|
||||
"""Remove a site from watching list
|
||||
|
||||
Argument:
|
||||
url -- URL to unwatch
|
||||
"""
|
||||
|
||||
o = urlparse(getNormalizedURL(url), "http")
|
||||
if o.scheme != "" and url in DATAS.index:
|
||||
site = DATAS.index[url]
|
||||
for a in site.getNodes("alert"):
|
||||
if a["channel"] == channel:
|
||||
# if not (nick == a["nick"] or frm_owner):
|
||||
# raise IMException("you cannot unwatch this URL.")
|
||||
site.delChild(a)
|
||||
if not site.hasNode("alert"):
|
||||
del_event(site["_evt_id"])
|
||||
DATAS.delChild(site)
|
||||
save()
|
||||
return Response("I don't watch this URL anymore.",
|
||||
channel=channel, nick=nick)
|
||||
raise IMException("I didn't watch this URL!")
|
||||
|
||||
|
||||
def add_site(url, nick, channel, server, diffType="diff"):
|
||||
"""Add a site to watching list
|
||||
|
||||
Argument:
|
||||
url -- URL to watch
|
||||
"""
|
||||
|
||||
o = urlparse(getNormalizedURL(url), "http")
|
||||
if o.netloc == "":
|
||||
raise IMException("sorry, I can't watch this URL :(")
|
||||
|
||||
alert = ModuleState("alert")
|
||||
alert["nick"] = nick
|
||||
alert["server"] = server
|
||||
alert["channel"] = channel
|
||||
alert["message"] = "{url} just changed!"
|
||||
|
||||
if url not in DATAS.index:
|
||||
watch = ModuleState("watch")
|
||||
watch["type"] = diffType
|
||||
watch["url"] = url
|
||||
watch["time"] = 123
|
||||
DATAS.addChild(watch)
|
||||
watch.addChild(alert)
|
||||
start_watching(watch)
|
||||
else:
|
||||
DATAS.index[url].addChild(alert)
|
||||
|
||||
save()
|
||||
return Response(channel=channel, nick=nick,
|
||||
message="this site is now under my supervision.")
|
||||
|
||||
|
||||
def format_response(site, link='%s', title='%s', categ='%s', content='%s'):
|
||||
"""Format and send response for given site
|
||||
|
||||
Argument:
|
||||
site -- DATAS structure representing a site to watch
|
||||
|
||||
Keyword arguments:
|
||||
link -- link to the content
|
||||
title -- for ATOM feed: title of the new article
|
||||
categ -- for ATOM feed: category of the new article
|
||||
content -- content of the page/new article
|
||||
"""
|
||||
|
||||
for a in site.getNodes("alert"):
|
||||
send_response(a["server"],
|
||||
Response(a["message"].format(url=site["url"],
|
||||
link=link,
|
||||
title=title,
|
||||
categ=categ,
|
||||
content=content),
|
||||
channel=a["channel"],
|
||||
server=a["server"]))
|
||||
|
||||
|
||||
def alert_change(content, site):
|
||||
"""Function called when a change is detected on a given site
|
||||
|
||||
Arguments:
|
||||
content -- The new content
|
||||
site -- DATAS structure representing a site to watch
|
||||
"""
|
||||
|
||||
if site["type"] == "updown":
|
||||
if site["lastcontent"] is None:
|
||||
site["lastcontent"] = content is not None
|
||||
|
||||
if (content is not None) != site.getBool("lastcontent"):
|
||||
format_response(site, link=site["url"])
|
||||
site["lastcontent"] = content is not None
|
||||
start_watching(site)
|
||||
return
|
||||
|
||||
if content is None:
|
||||
start_watching(site)
|
||||
return
|
||||
|
||||
if site["type"] == "atom":
|
||||
from nemubot.tools.feed import Feed
|
||||
if site["_lastpage"] is None:
|
||||
if site["lastcontent"] is None or site["lastcontent"] == "":
|
||||
site["lastcontent"] = content
|
||||
site["_lastpage"] = Feed(site["lastcontent"])
|
||||
try:
|
||||
page = Feed(content)
|
||||
except:
|
||||
print("An error occurs during Atom parsing. Restart event...")
|
||||
start_watching(site)
|
||||
return
|
||||
diff = site["_lastpage"] & page
|
||||
if len(diff) > 0:
|
||||
site["_lastpage"] = page
|
||||
diff.reverse()
|
||||
for d in diff:
|
||||
site.setIndex("term", "category")
|
||||
categories = site.index
|
||||
|
||||
if len(categories) > 0:
|
||||
if d.category is None or d.category not in categories:
|
||||
format_response(site, link=d.link, categ=categories[""]["part"], title=d.title)
|
||||
else:
|
||||
format_response(site, link=d.link, categ=categories[d.category]["part"], title=d.title)
|
||||
else:
|
||||
format_response(site, link=d.link, title=urllib.parse.unquote(d.title))
|
||||
else:
|
||||
start_watching(site)
|
||||
return # Stop here, no changes, so don't save
|
||||
|
||||
else: # Just looking for any changes
|
||||
format_response(site, link=site["url"], content=content)
|
||||
site["lastcontent"] = content
|
||||
start_watching(site)
|
||||
save()
|
||||
|
||||
|
||||
def fwatch(url):
|
||||
cnt = page.fetch(url, None)
|
||||
if cnt is not None:
|
||||
render = page._render(cnt)
|
||||
if render is None or render == "":
|
||||
return cnt
|
||||
return render
|
||||
return None
|
||||
|
||||
|
||||
def start_watching(site, offset=0):
|
||||
"""Launch the event watching given site
|
||||
|
||||
Argument:
|
||||
site -- DATAS structure representing a site to watch
|
||||
|
||||
Keyword argument:
|
||||
offset -- offset time to delay the launch of the first check
|
||||
"""
|
||||
|
||||
#o = urlparse(getNormalizedURL(site["url"]), "http")
|
||||
#print("Add %s event for site: %s" % (site["type"], o.netloc))
|
||||
|
||||
try:
|
||||
evt = ModuleEvent(func=partial(fwatch, url=site["url"]),
|
||||
cmp=site["lastcontent"],
|
||||
offset=offset, interval=site.getInt("time"),
|
||||
call=partial(alert_change, site=site))
|
||||
site["_evt_id"] = add_event(evt)
|
||||
except IMException:
|
||||
logger.exception("Unable to watch %s", site["url"])
|
||||
|
|
@ -1,136 +0,0 @@
|
|||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import datetime
|
||||
import urllib
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.tools.web import getJSON
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
URL_AVAIL = "https://www.whoisxmlapi.com/whoisserver/WhoisService?cmd=GET_DN_AVAILABILITY&domainName=%%s&outputFormat=json&username=%s&password=%s"
|
||||
URL_WHOIS = "https://www.whoisxmlapi.com/whoisserver/WhoisService?da=2&domainName=%%s&outputFormat=json&userName=%s&password=%s"
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(CONF, add_hook):
|
||||
global URL_AVAIL, URL_WHOIS
|
||||
|
||||
if not CONF or not CONF.hasNode("whoisxmlapi") or "username" not in CONF.getNode("whoisxmlapi") or "password" not in CONF.getNode("whoisxmlapi"):
|
||||
raise ImportError("You need a WhoisXML API account in order to use "
|
||||
"the !netwhois feature. Add it to the module "
|
||||
"configuration file:\n<whoisxmlapi username=\"XX\" "
|
||||
"password=\"XXX\" />\nRegister at "
|
||||
"https://www.whoisxmlapi.com/newaccount.php")
|
||||
|
||||
URL_AVAIL = URL_AVAIL % (urllib.parse.quote(CONF.getNode("whoisxmlapi")["username"]), urllib.parse.quote(CONF.getNode("whoisxmlapi")["password"]))
|
||||
URL_WHOIS = URL_WHOIS % (urllib.parse.quote(CONF.getNode("whoisxmlapi")["username"]), urllib.parse.quote(CONF.getNode("whoisxmlapi")["password"]))
|
||||
|
||||
import nemubot.hooks
|
||||
add_hook(nemubot.hooks.Command(cmd_whois, "netwhois",
|
||||
help="Get whois information about given domains",
|
||||
help_usage={"DOMAIN": "Return whois information on the given DOMAIN"}),
|
||||
"in","Command")
|
||||
add_hook(nemubot.hooks.Command(cmd_avail, "domain_available",
|
||||
help="Domain availability check using whoisxmlapi.com",
|
||||
help_usage={"DOMAIN": "Check if the given DOMAIN is available or not"}),
|
||||
"in","Command")
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def whois_entityformat(entity):
|
||||
ret = ""
|
||||
if "organization" in entity:
|
||||
ret += entity["organization"]
|
||||
if "organization" in entity and "name" in entity:
|
||||
ret += " "
|
||||
if "name" in entity:
|
||||
ret += entity["name"]
|
||||
|
||||
if "country" in entity or "city" in entity or "telephone" in entity or "email" in entity:
|
||||
ret += " (from "
|
||||
if "street1" in entity:
|
||||
ret += entity["street1"] + " "
|
||||
if "city" in entity:
|
||||
ret += entity["city"] + " "
|
||||
if "state" in entity:
|
||||
ret += entity["state"] + " "
|
||||
if "country" in entity:
|
||||
ret += entity["country"] + " "
|
||||
if "telephone" in entity:
|
||||
ret += entity["telephone"] + " "
|
||||
if "email" in entity:
|
||||
ret += entity["email"] + " "
|
||||
ret = ret.rstrip() + ")"
|
||||
|
||||
return ret.lstrip()
|
||||
|
||||
def available(dom):
|
||||
js = getJSON(URL_AVAIL % urllib.parse.quote(dom))
|
||||
|
||||
if "ErrorMessage" in js:
|
||||
raise IMException(js["ErrorMessage"]["msg"])
|
||||
|
||||
return js["DomainInfo"]["domainAvailability"] == "AVAILABLE"
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
def cmd_avail(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate a domain name for having its availability status!")
|
||||
|
||||
return Response(["%s: %s" % (dom, "available" if available(dom) else "unavailable") for dom in msg.args],
|
||||
channel=msg.channel)
|
||||
|
||||
|
||||
def cmd_whois(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indiquer un domaine ou une IP à whois !")
|
||||
|
||||
dom = msg.args[0]
|
||||
|
||||
js = getJSON(URL_WHOIS % urllib.parse.quote(dom))
|
||||
|
||||
if "ErrorMessage" in js:
|
||||
raise IMException(js["ErrorMessage"]["msg"])
|
||||
|
||||
whois = js["WhoisRecord"]
|
||||
|
||||
res = []
|
||||
|
||||
if "registrarName" in whois:
|
||||
res.append("\x03\x02registered by\x03\x02 " + whois["registrarName"])
|
||||
|
||||
if "domainAvailability" in whois:
|
||||
res.append(whois["domainAvailability"])
|
||||
|
||||
if "contactEmail" in whois:
|
||||
res.append("\x03\x02contact email\x03\x02 " + whois["contactEmail"])
|
||||
|
||||
if "audit" in whois:
|
||||
if "createdDate" in whois["audit"] and "$" in whois["audit"]["createdDate"]:
|
||||
res.append("\x03\x02created on\x03\x02 " + whois["audit"]["createdDate"]["$"])
|
||||
if "updatedDate" in whois["audit"] and "$" in whois["audit"]["updatedDate"]:
|
||||
res.append("\x03\x02updated on\x03\x02 " + whois["audit"]["updatedDate"]["$"])
|
||||
|
||||
if "registryData" in whois:
|
||||
if "expiresDateNormalized" in whois["registryData"]:
|
||||
res.append("\x03\x02expire on\x03\x02 " + whois["registryData"]["expiresDateNormalized"])
|
||||
if "registrant" in whois["registryData"]:
|
||||
res.append("\x03\x02registrant:\x03\x02 " + whois_entityformat(whois["registryData"]["registrant"]))
|
||||
if "zoneContact" in whois["registryData"]:
|
||||
res.append("\x03\x02zone contact:\x03\x02 " + whois_entityformat(whois["registryData"]["zoneContact"]))
|
||||
if "technicalContact" in whois["registryData"]:
|
||||
res.append("\x03\x02technical contact:\x03\x02 " + whois_entityformat(whois["registryData"]["technicalContact"]))
|
||||
if "administrativeContact" in whois["registryData"]:
|
||||
res.append("\x03\x02administrative contact:\x03\x02 " + whois_entityformat(whois["registryData"]["administrativeContact"]))
|
||||
if "billingContact" in whois["registryData"]:
|
||||
res.append("\x03\x02billing contact:\x03\x02 " + whois_entityformat(whois["registryData"]["billingContact"]))
|
||||
|
||||
return Response(res,
|
||||
title=whois["domainName"],
|
||||
channel=msg.channel,
|
||||
nomore="No more whois information")
|
||||
|
|
@ -1,61 +0,0 @@
|
|||
"""Display latests news from a website"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import datetime
|
||||
import re
|
||||
from urllib.parse import urljoin
|
||||
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
from nemubot.module.urlreducer import reduce_inline
|
||||
from nemubot.tools.feed import Feed, AtomEntry
|
||||
|
||||
|
||||
# HELP ################################################################
|
||||
|
||||
def help_full():
|
||||
return "Display the latests news from a given URL: !news URL"
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def find_rss_links(url):
|
||||
url = web.getNormalizedURL(url)
|
||||
soup = BeautifulSoup(web.getURLContent(url))
|
||||
for rss in soup.find_all('link', attrs={"type": re.compile("^application/(atom|rss)")}):
|
||||
yield urljoin(url, rss["href"])
|
||||
|
||||
def get_last_news(url):
|
||||
from xml.parsers.expat import ExpatError
|
||||
try:
|
||||
feed = Feed(web.getURLContent(url))
|
||||
return feed.entries
|
||||
except ExpatError:
|
||||
return []
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("news")
|
||||
def cmd_news(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Indicate the URL to visit.")
|
||||
|
||||
url = " ".join(msg.args)
|
||||
links = [x for x in find_rss_links(url)]
|
||||
if len(links) == 0: links = [ url ]
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more news from %s" % url, line_treat=reduce_inline)
|
||||
for n in get_last_news(links[0]):
|
||||
res.append_message("%s published %s: %s %s" % (("\x02" + web.striphtml(n.title) + "\x0F") if n.title else "An article without title",
|
||||
(n.updated.strftime("on %A %d. %B %Y at %H:%M") if n.updated else "someday") if isinstance(n, AtomEntry) else n.pubDate,
|
||||
web.striphtml(n.summary) if n.summary else "",
|
||||
n.link if n.link else ""))
|
||||
|
||||
return res
|
||||
4
modules/nextstop.xml
Normal file
4
modules/nextstop.xml
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="nextstop">
|
||||
<message type="cmd" name="ratp" call="ask_ratp" />
|
||||
</nemubotmodule>
|
||||
50
modules/nextstop/__init__.py
Normal file
50
modules/nextstop/__init__.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# coding=utf-8
|
||||
|
||||
import http.client
|
||||
import re
|
||||
from xml.dom.minidom import parseString
|
||||
|
||||
from .external.src import ratp
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
def load(context):
|
||||
global DATAS
|
||||
DATAS.setIndex("name", "station")
|
||||
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "Informe les usagers des prochains passages des transports en communs de la RATP"
|
||||
|
||||
def help_full ():
|
||||
return "!ratp transport line [station]: Donne des informations sur les prochains passages du transport en commun séléctionné à l'arrêt désiré. Si aucune station n'est précisée, les liste toutes."
|
||||
|
||||
|
||||
def extractInformation(msg, transport, line, station=None):
|
||||
if station is not None and station != "":
|
||||
times = ratp.getNextStopsAtStation(transport, line, station)
|
||||
if len(times) > 0:
|
||||
(time, direction, stationname) = times[0]
|
||||
return Response(msg.sender, message=["\x03\x02"+time+"\x03\x02 direction "+direction for time, direction, stationname in times], title="Prochains passages du %s ligne %s à l'arrêt %s" %
|
||||
(transport, line, stationname), channel=msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, "La station `%s' ne semble pas exister sur le %s ligne %s."
|
||||
% (station, transport, line), msg.channel)
|
||||
else:
|
||||
stations = ratp.getAllStations(transport, line)
|
||||
if len(stations) > 0:
|
||||
return Response(msg.sender, [s for s in stations], title="Stations", channel=msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, "Aucune station trouvée.", msg.channel)
|
||||
|
||||
def ask_ratp(msg):
|
||||
"""Hook entry from !ratp"""
|
||||
global DATAS
|
||||
if len(msg.cmds) == 4:
|
||||
return extractInformation(msg, msg.cmds[1], msg.cmds[2], msg.cmds[3])
|
||||
elif len(msg.cmds) == 3:
|
||||
return extractInformation(msg, msg.cmds[1], msg.cmds[2])
|
||||
else:
|
||||
return Response(msg.sender, "Mauvais usage, merci de spécifier un type de transport et une ligne, ou de consulter l'aide du module.", msg.channel, msg.nick)
|
||||
return False
|
||||
1
modules/nextstop/external
Submodule
1
modules/nextstop/external
Submodule
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e5675c631665dfbdaba55a0be66708a07d157408
|
||||
229
modules/nntp.py
229
modules/nntp.py
|
|
@ -1,229 +0,0 @@
|
|||
"""The NNTP module"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import email
|
||||
import email.policy
|
||||
from email.utils import mktime_tz, parseaddr, parsedate_tz
|
||||
from functools import partial
|
||||
from nntplib import NNTP, decode_header
|
||||
import re
|
||||
import time
|
||||
from datetime import datetime
|
||||
from zlib import adler32
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.event import ModuleEvent
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
for wn in context.data.getNodes("watched_newsgroup"):
|
||||
watch(**wn.attributes)
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def list_groups(group_pattern="*", **server):
|
||||
with NNTP(**server) as srv:
|
||||
response, l = srv.list(group_pattern)
|
||||
for i in l:
|
||||
yield i.group, srv.description(i.group), i.flag
|
||||
|
||||
def read_group(group, **server):
|
||||
with NNTP(**server) as srv:
|
||||
response, count, first, last, name = srv.group(group)
|
||||
resp, overviews = srv.over((first, last))
|
||||
for art_num, over in reversed(overviews):
|
||||
yield over
|
||||
|
||||
def read_article(msg_id, **server):
|
||||
with NNTP(**server) as srv:
|
||||
response, info = srv.article(msg_id)
|
||||
return email.message_from_bytes(b"\r\n".join(info.lines), policy=email.policy.SMTPUTF8)
|
||||
|
||||
|
||||
servers_lastcheck = dict()
|
||||
servers_lastseen = dict()
|
||||
|
||||
def whatsnew(group="*", **server):
|
||||
fill = dict()
|
||||
if "user" in server: fill["user"] = server["user"]
|
||||
if "password" in server: fill["password"] = server["password"]
|
||||
if "host" in server: fill["host"] = server["host"]
|
||||
if "port" in server: fill["port"] = server["port"]
|
||||
|
||||
idx = _indexServer(**server)
|
||||
if idx in servers_lastcheck and servers_lastcheck[idx] is not None:
|
||||
date_last_check = servers_lastcheck[idx]
|
||||
else:
|
||||
date_last_check = datetime.now()
|
||||
|
||||
if idx not in servers_lastseen:
|
||||
servers_lastseen[idx] = []
|
||||
|
||||
with NNTP(**fill) as srv:
|
||||
response, servers_lastcheck[idx] = srv.date()
|
||||
|
||||
response, groups = srv.newgroups(date_last_check)
|
||||
for g in groups:
|
||||
yield g
|
||||
|
||||
response, articles = srv.newnews(group, date_last_check)
|
||||
for msg_id in articles:
|
||||
if msg_id not in servers_lastseen[idx]:
|
||||
servers_lastseen[idx].append(msg_id)
|
||||
response, info = srv.article(msg_id)
|
||||
yield email.message_from_bytes(b"\r\n".join(info.lines))
|
||||
|
||||
# Clean huge lists
|
||||
if len(servers_lastseen[idx]) > 42:
|
||||
servers_lastseen[idx] = servers_lastseen[idx][23:]
|
||||
|
||||
|
||||
def format_article(art, **response_args):
|
||||
art["X-FromName"], art["X-FromEmail"] = parseaddr(art["From"] if "From" in art else "")
|
||||
if art["X-FromName"] == '': art["X-FromName"] = art["X-FromEmail"]
|
||||
|
||||
date = mktime_tz(parsedate_tz(art["Date"]))
|
||||
if date < time.time() - 120:
|
||||
title = "\x0314In \x0F\x03{0:02d}{Newsgroups}\x0F\x0314: on \x0F{Date}\x0314 by \x0F\x03{0:02d}{X-FromName}\x0F \x02{Subject}\x0F"
|
||||
else:
|
||||
title = "\x0314In \x0F\x03{0:02d}{Newsgroups}\x0F\x0314: by \x0F\x03{0:02d}{X-FromName}\x0F \x02{Subject}\x0F"
|
||||
|
||||
return Response(art.get_payload().replace('\n', ' '),
|
||||
title=title.format(adler32(art["Newsgroups"].encode()) & 0xf, adler32(art["X-FromEmail"].encode()) & 0xf, **{h: decode_header(i) for h,i in art.items()}),
|
||||
**response_args)
|
||||
|
||||
|
||||
watches = dict()
|
||||
|
||||
def _indexServer(**kwargs):
|
||||
if "user" not in kwargs: kwargs["user"] = ""
|
||||
if "password" not in kwargs: kwargs["password"] = ""
|
||||
if "host" not in kwargs: kwargs["host"] = ""
|
||||
if "port" not in kwargs: kwargs["port"] = 119
|
||||
return "{user}:{password}@{host}:{port}".format(**kwargs)
|
||||
|
||||
def _newevt(**args):
|
||||
context.add_event(ModuleEvent(call=partial(_ticker, **args), interval=42))
|
||||
|
||||
def _ticker(to_server, to_channel, group, server):
|
||||
_newevt(to_server=to_server, to_channel=to_channel, group=group, server=server)
|
||||
n = 0
|
||||
for art in whatsnew(group, **server):
|
||||
n += 1
|
||||
if n > 10:
|
||||
continue
|
||||
context.send_response(to_server, format_article(art, channel=to_channel))
|
||||
if n > 10:
|
||||
context.send_response(to_server, Response("... and %s others news" % (n - 10), channel=to_channel))
|
||||
|
||||
def watch(to_server, to_channel, group="*", **server):
|
||||
_newevt(to_server=to_server, to_channel=to_channel, group=group, server=server)
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
keywords_server = {
|
||||
"host=HOST": "hostname or IP of the NNTP server",
|
||||
"port=PORT": "port of the NNTP server",
|
||||
"user=USERNAME": "username to use to connect to the server",
|
||||
"password=PASSWORD": "password to use to connect to the server",
|
||||
}
|
||||
|
||||
@hook.command("nntp_groups",
|
||||
help="Show list of existing groups",
|
||||
help_usage={
|
||||
None: "Display all groups",
|
||||
"PATTERN": "Filter on group matching the PATTERN"
|
||||
},
|
||||
keywords=keywords_server)
|
||||
def cmd_groups(msg):
|
||||
if "host" not in msg.kwargs:
|
||||
raise IMException("please give a hostname in keywords")
|
||||
|
||||
return Response(["\x02\x03{0:02d}{1}\x0F: {2}".format(adler32(g[0].encode()) & 0xf, *g) for g in list_groups(msg.args[0] if len(msg.args) > 0 else "*", **msg.kwargs)],
|
||||
channel=msg.channel,
|
||||
title="Matching groups on %s" % msg.kwargs["host"])
|
||||
|
||||
|
||||
@hook.command("nntp_overview",
|
||||
help="Show an overview of articles in given group(s)",
|
||||
help_usage={
|
||||
"GROUP": "Filter on group matching the PATTERN"
|
||||
},
|
||||
keywords=keywords_server)
|
||||
def cmd_overview(msg):
|
||||
if "host" not in msg.kwargs:
|
||||
raise IMException("please give a hostname in keywords")
|
||||
|
||||
if not len(msg.args):
|
||||
raise IMException("which group would you overview?")
|
||||
|
||||
for g in msg.args:
|
||||
arts = []
|
||||
for grp in read_group(g, **msg.kwargs):
|
||||
grp["X-FromName"], grp["X-FromEmail"] = parseaddr(grp["from"] if "from" in grp else "")
|
||||
if grp["X-FromName"] == '': grp["X-FromName"] = grp["X-FromEmail"]
|
||||
|
||||
arts.append("On {date}, from \x03{0:02d}{X-FromName}\x0F \x02{subject}\x0F: \x0314{message-id}\x0F".format(adler32(grp["X-FromEmail"].encode()) & 0xf, **{h: decode_header(i) for h,i in grp.items()}))
|
||||
|
||||
if len(arts):
|
||||
yield Response(arts,
|
||||
channel=msg.channel,
|
||||
title="In \x03{0:02d}{1}\x0F".format(adler32(g[0].encode()) & 0xf, g))
|
||||
|
||||
|
||||
@hook.command("nntp_read",
|
||||
help="Read an article from a server",
|
||||
help_usage={
|
||||
"MSG_ID": "Read the given message"
|
||||
},
|
||||
keywords=keywords_server)
|
||||
def cmd_read(msg):
|
||||
if "host" not in msg.kwargs:
|
||||
raise IMException("please give a hostname in keywords")
|
||||
|
||||
for msgid in msg.args:
|
||||
if not re.match("<.*>", msgid):
|
||||
msgid = "<" + msgid + ">"
|
||||
art = read_article(msgid, **msg.kwargs)
|
||||
yield format_article(art, channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("nntp_watch",
|
||||
help="Launch an event looking for new groups and articles on a server",
|
||||
help_usage={
|
||||
None: "Watch all groups",
|
||||
"PATTERN": "Limit the watch on group matching this PATTERN"
|
||||
},
|
||||
keywords=keywords_server)
|
||||
def cmd_watch(msg):
|
||||
if "host" not in msg.kwargs:
|
||||
raise IMException("please give a hostname in keywords")
|
||||
|
||||
if not msg.frm_owner:
|
||||
raise IMException("sorry, this command is currently limited to the owner")
|
||||
|
||||
wnnode = ModuleState("watched_newsgroup")
|
||||
wnnode["id"] = _indexServer(**msg.kwargs)
|
||||
wnnode["to_server"] = msg.server
|
||||
wnnode["to_channel"] = msg.channel
|
||||
wnnode["group"] = msg.args[0] if len(msg.args) > 0 else "*"
|
||||
|
||||
wnnode["user"] = msg.kwargs["user"] if "user" in msg.kwargs else ""
|
||||
wnnode["password"] = msg.kwargs["password"] if "password" in msg.kwargs else ""
|
||||
wnnode["host"] = msg.kwargs["host"] if "host" in msg.kwargs else ""
|
||||
wnnode["port"] = msg.kwargs["port"] if "port" in msg.kwargs else 119
|
||||
|
||||
context.data.addChild(wnnode)
|
||||
watch(**wnnode.attributes)
|
||||
|
||||
return Response("Ok ok, I watch this newsgroup!", channel=msg.channel)
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
"""Perform requests to openai"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from openai import OpenAI
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
CLIENT = None
|
||||
MODEL = "gpt-4"
|
||||
ENDPOINT = None
|
||||
|
||||
def load(context):
|
||||
global CLIENT, ENDPOINT, MODEL
|
||||
if not context.config or ("apikey" not in context.config and "endpoint" not in context.config):
|
||||
raise ImportError ("You need a OpenAI API key in order to use "
|
||||
"this module. Add it to the module configuration: "
|
||||
"\n<module name=\"openai\" "
|
||||
"apikey=\"XXXXXX-XXXXXXXXXX\" endpoint=\"https://...\" model=\"gpt-4\" />")
|
||||
kwargs = {
|
||||
"api_key": context.config["apikey"] or "",
|
||||
}
|
||||
|
||||
if "endpoint" in context.config:
|
||||
ENDPOINT = context.config["endpoint"]
|
||||
kwargs["base_url"] = ENDPOINT
|
||||
|
||||
CLIENT = OpenAI(**kwargs)
|
||||
|
||||
if "model" in context.config:
|
||||
MODEL = context.config["model"]
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("list_models",
|
||||
help="list available LLM")
|
||||
def cmd_listllm(msg):
|
||||
llms = web.getJSON(ENDPOINT + "/models", timeout=6)
|
||||
return Response(message=[m for m in map(lambda i: i["id"], llms["data"])], title="Here is the available models", channel=msg.channel)
|
||||
|
||||
|
||||
@hook.command("set_model",
|
||||
help="Set the model to use when talking to nemubot")
|
||||
def cmd_setllm(msg):
|
||||
if len(msg.args) != 1:
|
||||
raise IMException("Indicate 1 model to use")
|
||||
|
||||
wanted_model = msg.args[0]
|
||||
|
||||
llms = web.getJSON(ENDPOINT + "/models", timeout=6)
|
||||
for model in llms["data"]:
|
||||
if wanted_model == model["id"]:
|
||||
break
|
||||
else:
|
||||
raise IMException("Unable to set such model: unknown")
|
||||
|
||||
MODEL = wanted_model
|
||||
return Response("New model in use: " + wanted_model, channel=msg.channel)
|
||||
|
||||
|
||||
@hook.ask()
|
||||
def parseask(msg):
|
||||
chat_completion = CLIENT.chat.completions.create(
|
||||
messages=[
|
||||
{
|
||||
"role": "system",
|
||||
"content": "You are a kind multilingual assistant. Respond to the user request in 255 characters maximum. Be conscise, go directly to the point. Never add useless terms.",
|
||||
},
|
||||
{
|
||||
"role": "user",
|
||||
"content": msg.message,
|
||||
}
|
||||
],
|
||||
model=MODEL,
|
||||
)
|
||||
|
||||
return Response(chat_completion.choices[0].message.content,
|
||||
msg.channel,
|
||||
msg.frm)
|
||||
|
|
@ -1,158 +0,0 @@
|
|||
"""Lost? use our commands to find your way!"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import re
|
||||
import urllib.parse
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
URL_DIRECTIONS_API = "https://api.openrouteservice.org/directions?api_key=%s&"
|
||||
URL_GEOCODE_API = "https://api.openrouteservice.org/geocoding?api_key=%s&"
|
||||
|
||||
waytype = [
|
||||
"unknown",
|
||||
"state road",
|
||||
"road",
|
||||
"street",
|
||||
"path",
|
||||
"track",
|
||||
"cycleway",
|
||||
"footway",
|
||||
"steps",
|
||||
"ferry",
|
||||
"construction",
|
||||
]
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
if not context.config or "apikey" not in context.config:
|
||||
raise ImportError("You need an OpenRouteService API key in order to use this "
|
||||
"module. Add it to the module configuration file:\n"
|
||||
"<module name=\"ors\" apikey=\"XXXXXXXXXXXXXXXX\" "
|
||||
"/>\nRegister at https://developers.openrouteservice.org")
|
||||
global URL_DIRECTIONS_API
|
||||
URL_DIRECTIONS_API = URL_DIRECTIONS_API % context.config["apikey"]
|
||||
global URL_GEOCODE_API
|
||||
URL_GEOCODE_API = URL_GEOCODE_API % context.config["apikey"]
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def approx_distance(lng):
|
||||
if lng > 1111:
|
||||
return "%f km" % (lng / 1000)
|
||||
else:
|
||||
return "%f m" % lng
|
||||
|
||||
|
||||
def approx_duration(sec):
|
||||
days = int(sec / 86400)
|
||||
if days > 0:
|
||||
return "%d days %f hours" % (days, (sec % 86400) / 3600)
|
||||
hours = int((sec % 86400) / 3600)
|
||||
if hours > 0:
|
||||
return "%d hours %f minutes" % (hours, (sec % 3600) / 60)
|
||||
minutes = (sec % 3600) / 60
|
||||
if minutes > 0:
|
||||
return "%d minutes" % minutes
|
||||
else:
|
||||
return "%d seconds" % sec
|
||||
|
||||
|
||||
def geocode(query, limit=7):
|
||||
obj = web.getJSON(URL_GEOCODE_API + urllib.parse.urlencode({
|
||||
'query': query,
|
||||
'limit': limit,
|
||||
}))
|
||||
|
||||
for f in obj["features"]:
|
||||
yield f["geometry"]["coordinates"], f["properties"]
|
||||
|
||||
|
||||
def firstgeocode(query):
|
||||
for g in geocode(query, limit=1):
|
||||
return g
|
||||
|
||||
|
||||
def where(loc):
|
||||
return "{name} {city} {state} {county} {country}".format(**loc)
|
||||
|
||||
|
||||
def directions(coordinates, **kwargs):
|
||||
kwargs['coordinates'] = '|'.join(coordinates)
|
||||
|
||||
print(URL_DIRECTIONS_API + urllib.parse.urlencode(kwargs))
|
||||
return web.getJSON(URL_DIRECTIONS_API + urllib.parse.urlencode(kwargs), decode_error=True)
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("geocode",
|
||||
help="Get GPS coordinates of a place",
|
||||
help_usage={
|
||||
"PLACE": "Get GPS coordinates of PLACE"
|
||||
})
|
||||
def cmd_geocode(msg):
|
||||
res = Response(channel=msg.channel, nick=msg.frm,
|
||||
nomore="No more geocode", count=" (%s more geocode)")
|
||||
|
||||
for loc in geocode(' '.join(msg.args)):
|
||||
res.append_message("%s is at %s,%s" % (
|
||||
where(loc[1]),
|
||||
loc[0][1], loc[0][0],
|
||||
))
|
||||
|
||||
return res
|
||||
|
||||
|
||||
@hook.command("directions",
|
||||
help="Get routing instructions",
|
||||
help_usage={
|
||||
"POINT1 POINT2 ...": "Get routing instructions to go from POINT1 to the last POINTX via intermediates POINTX"
|
||||
},
|
||||
keywords={
|
||||
"profile=PROF": "One of driving-car, driving-hgv, cycling-regular, cycling-road, cycling-safe, cycling-mountain, cycling-tour, cycling-electric, foot-walking, foot-hiking, wheelchair. Default: foot-walking",
|
||||
"preference=PREF": "One of fastest, shortest, recommended. Default: recommended",
|
||||
"lang=LANG": "default: en",
|
||||
})
|
||||
def cmd_directions(msg):
|
||||
drcts = directions(["{0},{1}".format(*firstgeocode(g)[0]) for g in msg.args],
|
||||
profile=msg.kwargs["profile"] if "profile" in msg.kwargs else "foot-walking",
|
||||
preference=msg.kwargs["preference"] if "preference" in msg.kwargs else "recommended",
|
||||
units="m",
|
||||
language=msg.kwargs["lang"] if "lang" in msg.kwargs else "en",
|
||||
geometry=False,
|
||||
instructions=True,
|
||||
instruction_format="text")
|
||||
if "error" in drcts and "message" in drcts["error"] and drcts["error"]["message"]:
|
||||
raise IMException(drcts["error"]["message"])
|
||||
|
||||
if "routes" not in drcts or not drcts["routes"]:
|
||||
raise IMException("No route available for this trip")
|
||||
|
||||
myway = drcts["routes"][0]
|
||||
myway["summary"]["strduration"] = approx_duration(myway["summary"]["duration"])
|
||||
myway["summary"]["strdistance"] = approx_distance(myway["summary"]["distance"])
|
||||
res = Response("Trip summary: {strdistance} in approximate {strduration}; elevation +{ascent} m -{descent} m".format(**myway["summary"]), channel=msg.channel, count=" (%d more steps)", nomore="You have arrived!")
|
||||
|
||||
def formatSegments(segments):
|
||||
for segment in segments:
|
||||
for step in segment["steps"]:
|
||||
step["strtype"] = waytype[step["type"]]
|
||||
step["strduration"] = approx_duration(step["duration"])
|
||||
step["strdistance"] = approx_distance(step["distance"])
|
||||
yield "{instruction} for {strdistance} on {strtype} (approximate time: {strduration})".format(**step)
|
||||
|
||||
if "segments" in myway:
|
||||
res.append_message([m for m in formatSegments(myway["segments"])])
|
||||
|
||||
return res
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
"""Get information about common software"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
import portage
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
DB = None
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def get_db():
|
||||
global DB
|
||||
if DB is None:
|
||||
DB = portage.db[portage.root]["porttree"].dbapi
|
||||
return DB
|
||||
|
||||
|
||||
def package_info(pkgname):
|
||||
pv = get_db().xmatch("match-all", pkgname)
|
||||
if not pv:
|
||||
raise IMException("No package named '%s' found" % pkgname)
|
||||
|
||||
bv = get_db().xmatch("bestmatch-visible", pkgname)
|
||||
pvsplit = portage.catpkgsplit(bv if bv else pv[-1])
|
||||
info = get_db().aux_get(bv if bv else pv[-1], ["DESCRIPTION", "HOMEPAGE", "LICENSE", "IUSE", "KEYWORDS"])
|
||||
|
||||
return {
|
||||
"pkgname": '/'.join(pvsplit[:2]),
|
||||
"category": pvsplit[0],
|
||||
"shortname": pvsplit[1],
|
||||
"lastvers": '-'.join(pvsplit[2:]) if pvsplit[3] != "r0" else pvsplit[2],
|
||||
"othersvers": ['-'.join(portage.catpkgsplit(p)[2:]) for p in pv if p != bv],
|
||||
"description": info[0],
|
||||
"homepage": info[1],
|
||||
"license": info[2],
|
||||
"uses": info[3],
|
||||
"keywords": info[4],
|
||||
}
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("eix",
|
||||
help="Get information about a package",
|
||||
help_usage={
|
||||
"NAME": "Get information about a software NAME"
|
||||
})
|
||||
def cmd_eix(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("please give me a package to search")
|
||||
|
||||
def srch(term):
|
||||
try:
|
||||
yield package_info(term)
|
||||
except portage.exception.AmbiguousPackageName as e:
|
||||
for i in e.args[0]:
|
||||
yield package_info(i)
|
||||
|
||||
res = Response(channel=msg.channel, count=" (%d more packages)", nomore="No more package '%s'" % msg.args[0])
|
||||
for pi in srch(msg.args[0]):
|
||||
res.append_message("\x03\x02{pkgname}:\x03\x02 {description} - {homepage} - {license} - last revisions: \x03\x02{lastvers}\x03\x02{ov}".format(ov=(", " + ', '.join(pi["othersvers"])) if pi["othersvers"] else "", **pi))
|
||||
return res
|
||||
7
modules/qcm.xml
Normal file
7
modules/qcm.xml
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="qcm">
|
||||
<file name="main" url="/var/www/nemunai.re/bot/htdocs/questions.xml"/>
|
||||
<file name="courses" url="/var/www/nemunai.re/bot/htdocs/courses.xml"/>
|
||||
<file name="users" url="/var/www/nemunai.re/bot/htdocs/users.xml"/>
|
||||
<server url="bot.nemunai.re" />
|
||||
</nemubotmodule>
|
||||
31
modules/qcm/Course.py
Normal file
31
modules/qcm/Course.py
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
# coding=utf-8
|
||||
|
||||
COURSES = None
|
||||
|
||||
class Course:
|
||||
def __init__(self, iden):
|
||||
global COURSES
|
||||
if iden in COURSES.index:
|
||||
self.node = COURSES.index[iden]
|
||||
else:
|
||||
self.node = { "code":"N/A", "name":"N/A", "branch":"N/A" }
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.node["xml:id"]
|
||||
|
||||
@property
|
||||
def code(self):
|
||||
return self.node["code"]
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return self.node["name"]
|
||||
|
||||
@property
|
||||
def branch(self):
|
||||
return self.node["branch"]
|
||||
|
||||
@property
|
||||
def validated(self):
|
||||
return int(self.node["validated"]) > 0
|
||||
93
modules/qcm/Question.py
Normal file
93
modules/qcm/Question.py
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
import hashlib
|
||||
import http.client
|
||||
import socket
|
||||
from urllib.parse import quote
|
||||
|
||||
from .Course import Course
|
||||
from .User import User
|
||||
|
||||
QUESTIONS = None
|
||||
|
||||
class Question:
|
||||
def __init__(self, node):
|
||||
self.node = node
|
||||
|
||||
@property
|
||||
def ident(self):
|
||||
return self.node["xml:id"]
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.node["xml:id"]
|
||||
|
||||
@property
|
||||
def question(self):
|
||||
return self.node["question"]
|
||||
|
||||
@property
|
||||
def course(self):
|
||||
return Course(self.node["course"])
|
||||
|
||||
@property
|
||||
def answers(self):
|
||||
return self.node.getNodes("answer")
|
||||
|
||||
@property
|
||||
def validator(self):
|
||||
return User(self.node["validator"])
|
||||
|
||||
@property
|
||||
def writer(self):
|
||||
return User(self.node["writer"])
|
||||
|
||||
@property
|
||||
def validated(self):
|
||||
return self.node["validated"]
|
||||
|
||||
@property
|
||||
def addedtime(self):
|
||||
return datetime.fromtimestamp(float(self.node["addedtime"]))
|
||||
|
||||
@property
|
||||
def author(self):
|
||||
return User(self.node["writer"])
|
||||
|
||||
def report(self, raison="Sans raison"):
|
||||
conn = http.client.HTTPConnection(CONF.getNode("server")["url"], timeout=10)
|
||||
try:
|
||||
conn.request("GET", "report.php?id=" + hashlib.md5(self.id.encode()).hexdigest() + "&raison=" + quote(raison))
|
||||
except socket.gaierror:
|
||||
print ("[%s] impossible de récupérer la page %s."%(s, p))
|
||||
return False
|
||||
res = conn.getresponse()
|
||||
conn.close()
|
||||
return (res.status == http.client.OK)
|
||||
|
||||
@property
|
||||
def tupleInfo(self):
|
||||
return (self.author.username, self.validator.username, self.addedtime)
|
||||
|
||||
@property
|
||||
def bestAnswer(self):
|
||||
best = self.answers[0]
|
||||
for answer in self.answers:
|
||||
if best.getInt("score") < answer.getInt("score"):
|
||||
best = answer
|
||||
return best["answer"]
|
||||
|
||||
def isCorrect(self, msg):
|
||||
msg = msg.lower().replace(" ", "")
|
||||
for answer in self.answers:
|
||||
if msg == answer["answer"].lower().replace(" ", ""):
|
||||
return True
|
||||
return False
|
||||
|
||||
def getScore(self, msg):
|
||||
msg = msg.lower().replace(" ", "")
|
||||
for answer in self.answers:
|
||||
if msg == answer["answer"].lower().replace(" ", ""):
|
||||
return answer.getInt("score")
|
||||
return 0
|
||||
16
modules/qcm/QuestionFile.py
Normal file
16
modules/qcm/QuestionFile.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# coding=utf-8
|
||||
|
||||
import module_states_file as xmlparser
|
||||
|
||||
from .Question import Question
|
||||
|
||||
class QuestionFile:
|
||||
def __init__(self, filename):
|
||||
self.questions = xmlparser.parse_file(filename)
|
||||
self.questions.setIndex("xml:id")
|
||||
|
||||
def getQuestion(self, ident):
|
||||
if ident in self.questions.index:
|
||||
return Question(self.questions.index[ident])
|
||||
else:
|
||||
return None
|
||||
67
modules/qcm/Session.py
Normal file
67
modules/qcm/Session.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
# coding=utf-8
|
||||
|
||||
import threading
|
||||
|
||||
SESSIONS = dict()
|
||||
|
||||
from . import Question
|
||||
|
||||
from response import Response
|
||||
|
||||
class Session:
|
||||
def __init__(self, srv, chan, sender):
|
||||
self.questions = list()
|
||||
self.current = -1
|
||||
self.score = 0
|
||||
self.good = 0
|
||||
self.bad = 0
|
||||
self.trys = 0
|
||||
self.timer = None
|
||||
self.server = srv
|
||||
self.channel = chan
|
||||
self.sender = sender
|
||||
|
||||
def addQuestion(self, ident):
|
||||
if ident not in self.questions:
|
||||
self.questions.append(ident)
|
||||
return True
|
||||
return False
|
||||
|
||||
def next_question(self):
|
||||
self.trys = 0
|
||||
self.current += 1
|
||||
return self.question
|
||||
|
||||
@property
|
||||
def question(self):
|
||||
if self.current >= 0 and self.current < len(self.questions):
|
||||
return Question.Question(Question.QUESTIONS.index[self.questions[self.current]])
|
||||
else:
|
||||
return None
|
||||
|
||||
def askNext(self, bfr = ""):
|
||||
global SESSIONS
|
||||
self.timer = None
|
||||
nextQ = self.next_question()
|
||||
if nextQ is not None:
|
||||
if self.sender.split("!")[0] != self.channel:
|
||||
self.server.send_response(Response(self.sender, "%s%s" % (bfr, nextQ.question), self.channel, nick=self.sender.split("!")[0]))
|
||||
else:
|
||||
self.server.send_response(Response(self.sender, "%s%s" % (bfr, nextQ.question), self.channel))
|
||||
else:
|
||||
if self.good > 1:
|
||||
goodS = "s"
|
||||
else:
|
||||
goodS = ""
|
||||
|
||||
if self.sender.split("!")[0] != self.channel:
|
||||
self.server.send_response(Response(self.sender, "%sFini, tu as donné %d bonne%s réponse%s sur %d questions." % (self.sender, bfr, self.good, goodS, goodS, len(self.questions)), self.channel, nick=self.sender.split("!")[0]))
|
||||
else:
|
||||
self.server.send_response(Response(self.sender, "%sFini, tu as donné %d bonne%s réponse%s sur %d questions." % (self.sender, bfr, self.good, goodS, goodS, len(self.questions)), self.channel))
|
||||
del SESSIONS[self.sender]
|
||||
|
||||
def prepareNext(self, lag = 3):
|
||||
if self.timer is None:
|
||||
self.timer = threading.Timer(lag, self.askNext)
|
||||
self.timer.start()
|
||||
|
||||
27
modules/qcm/User.py
Normal file
27
modules/qcm/User.py
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
# coding=utf-8
|
||||
|
||||
USERS = None
|
||||
|
||||
class User:
|
||||
def __init__(self, iden):
|
||||
global USERS
|
||||
if iden in USERS.index:
|
||||
self.node = USERS.index[iden]
|
||||
else:
|
||||
self.node = { "username":"N/A", "email":"N/A" }
|
||||
|
||||
@property
|
||||
def id(self):
|
||||
return self.node["xml:id"]
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self.node["username"]
|
||||
|
||||
@property
|
||||
def email(self):
|
||||
return self.node["email"]
|
||||
|
||||
@property
|
||||
def validated(self):
|
||||
return int(self.node["validated"]) > 0
|
||||
197
modules/qcm/__init__.py
Normal file
197
modules/qcm/__init__.py
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
import http.client
|
||||
import re
|
||||
import random
|
||||
import sys
|
||||
import time
|
||||
|
||||
import xmlparser
|
||||
|
||||
nemubotversion = 3.2
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "MCQ module, working with http://bot.nemunai.re/"
|
||||
|
||||
def help_full ():
|
||||
return "!qcm [<nbQuest>] [<theme>]"
|
||||
|
||||
from . import Question
|
||||
from . import Course
|
||||
from . import Session
|
||||
|
||||
def load(context):
|
||||
CONF.setIndex("name", "file")
|
||||
|
||||
def buildSession(msg, categ = None, nbQuest = 10, channel = False):
|
||||
if Question.QUESTIONS is None:
|
||||
Question.QUESTIONS = xmlparser.parse_file(CONF.index["main"]["url"])
|
||||
Question.QUESTIONS.setIndex("xml:id")
|
||||
Course.COURSES = xmlparser.parse_file(CONF.index["courses"]["url"])
|
||||
Course.COURSES.setIndex("xml:id")
|
||||
User.USERS = xmlparser.parse_file(CONF.index["users"]["url"])
|
||||
User.USERS.setIndex("xml:id")
|
||||
#Remove no validated questions
|
||||
keys = list()
|
||||
for k in Question.QUESTIONS.index.keys():
|
||||
keys.append(k)
|
||||
for ques in keys:
|
||||
if Question.QUESTIONS.index[ques]["validated"] != "1" or Question.QUESTIONS.index[ques]["reported"] == "1":
|
||||
del Question.QUESTIONS.index[ques]
|
||||
|
||||
#Apply filter
|
||||
QS = list()
|
||||
if categ is not None and len(categ) > 0:
|
||||
#Find course id corresponding to categ
|
||||
courses = list()
|
||||
for c in Course.COURSES.childs:
|
||||
if c["code"] in categ:
|
||||
courses.append(c["xml:id"])
|
||||
|
||||
#Keep only questions matching course or branch
|
||||
for q in Question.QUESTIONS.index.keys():
|
||||
if (Question.QUESTIONS.index[q]["branch"] is not None and Question.QUESTIONS.index[q]["branch"].find(categ)) or Question.QUESTIONS.index[q]["course"] in courses:
|
||||
QS.append(q)
|
||||
else:
|
||||
for q in Question.QUESTIONS.index.keys():
|
||||
QS.append(q)
|
||||
|
||||
nbQuest = min(nbQuest, len(QS))
|
||||
|
||||
if channel:
|
||||
sess = Session.Session(msg.srv, msg.channel, msg.channel)
|
||||
else:
|
||||
sess = Session.Session(msg.srv, msg.channel, msg.sender)
|
||||
maxQuest = len(QS) - 1
|
||||
for i in range(0, nbQuest):
|
||||
while True:
|
||||
q = QS[random.randint(0, maxQuest)]
|
||||
if sess.addQuestion(q):
|
||||
break
|
||||
if channel:
|
||||
Session.SESSIONS[msg.channel] = sess
|
||||
else:
|
||||
Session.SESSIONS[msg.realname] = sess
|
||||
|
||||
|
||||
def askQuestion(msg, bfr = ""):
|
||||
return Session.SESSIONS[msg.realname].askNext(bfr)
|
||||
|
||||
def parseanswer(msg):
|
||||
global DATAS
|
||||
if msg.cmd[0] == "qcm" or msg.cmd[0] == "qcmchan" or msg.cmd[0] == "simulateqcm":
|
||||
if msg.realname in Session.SESSIONS:
|
||||
if len(msg.cmd) > 1:
|
||||
if msg.cmd[1] == "stop" or msg.cmd[1] == "end":
|
||||
sess = Session.SESSIONS[msg.realname]
|
||||
if sess.good > 1: goodS = "s"
|
||||
else: goodS = ""
|
||||
del Session.SESSIONS[msg.realname]
|
||||
return Response(msg.sender,
|
||||
"Fini, tu as donné %d bonne%s réponse%s sur %d questions." % (sess.good, goodS, goodS, sess.current),
|
||||
msg.channel, nick=msg.nick)
|
||||
elif msg.cmd[1] == "next" or msg.cmd[1] == "suivant" or msg.cmd[1] == "suivante":
|
||||
return askQuestion(msg)
|
||||
return Response(msg.sender, "tu as déjà une session de QCM en cours, finis-la avant d'en commencer une nouvelle.", msg.channel, msg.nick)
|
||||
elif msg.channel in Session.SESSIONS:
|
||||
if len(msg.cmd) > 1:
|
||||
if msg.cmd[1] == "stop" or msg.cmd[1] == "end":
|
||||
sess = Session.SESSIONS[msg.channel]
|
||||
if sess.good > 1: goodS = "s"
|
||||
else: goodS = ""
|
||||
del Session.SESSIONS[msg.channel]
|
||||
return Response(msg.sender, "Fini, vous avez donné %d bonne%s réponse%s sur %d questions." % (sess.good, goodS, goodS, sess.current), msg.channel)
|
||||
elif msg.cmd[1] == "next" or msg.cmd[1] == "suivant" or msg.cmd[1] == "suivante":
|
||||
Session.SESSIONS[msg.channel].prepareNext(1)
|
||||
return True
|
||||
else:
|
||||
nbQuest = 10
|
||||
filtre = list()
|
||||
if len(msg.cmd) > 1:
|
||||
for cmd in msg.cmd[1:]:
|
||||
try:
|
||||
tmp = int(cmd)
|
||||
nbQuest = tmp
|
||||
except ValueError:
|
||||
filtre.append(cmd.upper())
|
||||
if len(filtre) == 0:
|
||||
filtre = None
|
||||
if msg.channel in Session.SESSIONS:
|
||||
return Response(msg.sender, "Il y a deja une session de QCM sur ce chan.")
|
||||
else:
|
||||
buildSession(msg, filtre, nbQuest, msg.cmd[0] == "qcmchan")
|
||||
if msg.cmd[0] == "qcm":
|
||||
return askQuestion(msg)
|
||||
elif msg.cmd[0] == "qcmchan":
|
||||
return Session.SESSIONS[msg.channel].askNext()
|
||||
else:
|
||||
del Session.SESSIONS[msg.realname]
|
||||
return Response(msg.sender, "QCM de %d questions" % len(Session.SESSIONS[msg.realname].questions), msg.channel)
|
||||
return True
|
||||
elif msg.realname in Session.SESSIONS:
|
||||
if msg.cmd[0] == "info" or msg.cmd[0] == "infoquestion":
|
||||
return Response(msg.sender, "Cette question a été écrite par %s et validée par %s, le %s" % Session.SESSIONS[msg.realname].question.tupleInfo, msg.channel)
|
||||
elif msg.cmd[0] == "report" or msg.cmd[0] == "reportquestion":
|
||||
if len(msg.cmd) == 1:
|
||||
return Response(msg.sender, "Veuillez indiquer une raison de report", msg.channel)
|
||||
elif Session.SESSIONS[msg.realname].question.report(' '.join(msg.cmd[1:])):
|
||||
return Response(msg.sender, "Cette question vient d'être signalée.", msg.channel)
|
||||
Session.SESSIONS[msg.realname].askNext()
|
||||
else:
|
||||
return Response(msg.sender, "Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.", msg.channel)
|
||||
elif msg.channel in Session.SESSIONS:
|
||||
if msg.cmd[0] == "info" or msg.cmd[0] == "infoquestion":
|
||||
return Response(msg.sender, "Cette question a été écrite par %s et validée par %s, le %s" % Session.SESSIONS[msg.channel].question.tupleInfo, msg.channel)
|
||||
elif msg.cmd[0] == "report" or msg.cmd[0] == "reportquestion":
|
||||
if len(msg.cmd) == 1:
|
||||
return Response(msg.sender, "Veuillez indiquer une raison de report", msg.channel)
|
||||
elif Session.SESSIONS[msg.channel].question.report(' '.join(msg.cmd[1:])):
|
||||
Session.SESSIONS[msg.channel].prepareNext()
|
||||
return Response(msg.sender, "Cette question vient d'être signalée.", msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, "Une erreur s'est produite lors du signalement de la question, veuillez recommencer plus tard.", msg.channel)
|
||||
else:
|
||||
if msg.cmd[0] == "listecours":
|
||||
if Course.COURSES is None:
|
||||
return Response(msg.sender, "La liste de cours n'est pas encore construite, lancez un QCM pour la construire.", msg.channel)
|
||||
else:
|
||||
res = Response(msg.sender, channel=msg.channel, title="Liste des cours existants : ")
|
||||
res.append_message([cours["code"] + " (" + cours["name"] + ")" for cours in Course.COURSES.getNodes("course")])
|
||||
return res
|
||||
elif msg.cmd[0] == "refreshqcm":
|
||||
Question.QUESTIONS = None
|
||||
Course.COURSES = None
|
||||
User.USERS = None
|
||||
return True
|
||||
return False
|
||||
|
||||
def parseask(msg):
|
||||
if msg.realname in Session.SESSIONS:
|
||||
dest = msg.realname
|
||||
|
||||
if Session.SESSIONS[dest].question.isCorrect(msg.content):
|
||||
Session.SESSIONS[dest].good += 1
|
||||
Session.SESSIONS[dest].score += Session.SESSIONS[dest].question.getScore(msg.content)
|
||||
return askQuestion(msg, "correct ; ")
|
||||
else:
|
||||
Session.SESSIONS[dest].bad += 1
|
||||
if Session.SESSIONS[dest].trys == 0:
|
||||
Session.SESSIONS[dest].trys = 1
|
||||
return Response(msg.sender, "non, essaie encore :p", msg.channel, msg.nick)
|
||||
else:
|
||||
return askQuestion(msg, "non, la bonne reponse était : %s ; " % Session.SESSIONS[dest].question.bestAnswer)
|
||||
|
||||
elif msg.channel in Session.SESSIONS:
|
||||
dest = msg.channel
|
||||
|
||||
if Session.SESSIONS[dest].question.isCorrect(msg.content):
|
||||
Session.SESSIONS[dest].good += 1
|
||||
Session.SESSIONS[dest].score += Session.SESSIONS[dest].question.getScore(msg.content)
|
||||
Session.SESSIONS[dest].prepareNext()
|
||||
return Response(msg.sender, "correct :)", msg.channel, nick=msg.nick)
|
||||
else:
|
||||
Session.SESSIONS[dest].bad += 1
|
||||
return Response(msg.sender, "non, essaie encore :p", msg.channel, nick=msg.nick)
|
||||
return False
|
||||
32
modules/qd/DelayedTuple.py
Normal file
32
modules/qd/DelayedTuple.py
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
# coding=utf-8
|
||||
|
||||
import re
|
||||
import threading
|
||||
|
||||
class DelayedTuple:
|
||||
def __init__(self, regexp, great):
|
||||
self.delayEvnt = threading.Event()
|
||||
self.msg = None
|
||||
self.regexp = regexp
|
||||
self.great = great
|
||||
|
||||
def triche(self, res):
|
||||
if res is not None:
|
||||
return re.match(".*" + self.regexp + ".*", res.lower() + " ") is None
|
||||
else:
|
||||
return True
|
||||
|
||||
def perfect(self, res):
|
||||
if res is not None:
|
||||
return re.match(".*" + self.great + ".*", res.lower() + " ") is not None
|
||||
else:
|
||||
return False
|
||||
|
||||
def good(self, res):
|
||||
if res is not None:
|
||||
return re.match(".*" + self.regexp + ".*", res.lower() + " ") is not None
|
||||
else:
|
||||
return False
|
||||
|
||||
def wait(self, timeout):
|
||||
self.delayEvnt.wait(timeout)
|
||||
60
modules/qd/GameUpdater.py
Normal file
60
modules/qd/GameUpdater.py
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
import random
|
||||
import threading
|
||||
from .DelayedTuple import DelayedTuple
|
||||
|
||||
DELAYED = dict()
|
||||
|
||||
LASTQUESTION = 99999
|
||||
|
||||
class GameUpdater(threading.Thread):
|
||||
def __init__(self, msg, bfrseen):
|
||||
self.msg = msg
|
||||
self.bfrseen = bfrseen
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def run(self):
|
||||
global DELAYED, LASTQUESTION
|
||||
|
||||
if self.bfrseen is not None:
|
||||
seen = datetime.now() - self.bfrseen
|
||||
rnd = random.randint(0, int(seen.seconds/90))
|
||||
else:
|
||||
rnd = 1
|
||||
|
||||
if rnd != 0:
|
||||
QUESTIONS = CONF.getNodes("question")
|
||||
|
||||
if self.msg.channel == "#nemutest":
|
||||
quest = 9
|
||||
else:
|
||||
if LASTQUESTION >= len(QUESTIONS):
|
||||
print (QUESTIONS)
|
||||
random.shuffle(QUESTIONS)
|
||||
LASTQUESTION = 0
|
||||
quest = LASTQUESTION
|
||||
LASTQUESTION += 1
|
||||
|
||||
question = QUESTIONS[quest]["question"]
|
||||
regexp = QUESTIONS[quest]["regexp"]
|
||||
great = QUESTIONS[quest]["great"]
|
||||
self.msg.send_chn("%s: %s" % (self.msg.nick, question))
|
||||
|
||||
DELAYED[self.msg.nick] = DelayedTuple(regexp, great)
|
||||
|
||||
DELAYED[self.msg.nick].wait(20)
|
||||
|
||||
if DELAYED[self.msg.nick].triche(DELAYED[self.msg.nick].msg):
|
||||
getUser(self.msg.nick).playTriche()
|
||||
self.msg.send_chn("%s: Tricheur !" % self.msg.nick)
|
||||
elif DELAYED[self.msg.nick].perfect(DELAYED[self.msg.nick].msg):
|
||||
if random.randint(0, 10) == 1:
|
||||
getUser(self.msg.nick).bonusQuestion()
|
||||
self.msg.send_chn("%s: Correct !" % self.msg.nick)
|
||||
else:
|
||||
self.msg.send_chn("%s: J'accepte" % self.msg.nick)
|
||||
del DELAYED[self.msg.nick]
|
||||
SCORES.save(self.msg.nick)
|
||||
save()
|
||||
20
modules/qd/QDWrapper.py
Normal file
20
modules/qd/QDWrapper.py
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
# coding=utf-8
|
||||
|
||||
from tools.wrapper import Wrapper
|
||||
from .Score import Score
|
||||
|
||||
class QDWrapper(Wrapper):
|
||||
def __init__(self, datas):
|
||||
Wrapper.__init__(self)
|
||||
self.DATAS = datas
|
||||
self.stateName = "player"
|
||||
self.attName = "name"
|
||||
|
||||
def __getitem__(self, i):
|
||||
if i in self.cache:
|
||||
return self.cache[i]
|
||||
else:
|
||||
sc = Score()
|
||||
sc.parse(Wrapper.__getitem__(self, i))
|
||||
self.cache[i] = sc
|
||||
return sc
|
||||
126
modules/qd/Score.py
Normal file
126
modules/qd/Score.py
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
class Score:
|
||||
"""Manage the user's scores"""
|
||||
def __init__(self):
|
||||
#FourtyTwo
|
||||
self.ftt = 0
|
||||
#TwentyThree
|
||||
self.twt = 0
|
||||
self.pi = 0
|
||||
self.notfound = 0
|
||||
self.tententen = 0
|
||||
self.leet = 0
|
||||
self.great = 0
|
||||
self.bad = 0
|
||||
self.triche = 0
|
||||
self.last = None
|
||||
self.changed = False
|
||||
|
||||
def parse(self, item):
|
||||
self.ftt = item.getInt("fourtytwo")
|
||||
self.twt = item.getInt("twentythree")
|
||||
self.pi = item.getInt("pi")
|
||||
self.notfound = item.getInt("notfound")
|
||||
self.tententen = item.getInt("tententen")
|
||||
self.leet = item.getInt("leet")
|
||||
self.great = item.getInt("great")
|
||||
self.bad = item.getInt("bad")
|
||||
self.triche = item.getInt("triche")
|
||||
|
||||
def save(self, state):
|
||||
state.setAttribute("fourtytwo", self.ftt)
|
||||
state.setAttribute("twentythree", self.twt)
|
||||
state.setAttribute("pi", self.pi)
|
||||
state.setAttribute("notfound", self.notfound)
|
||||
state.setAttribute("tententen", self.tententen)
|
||||
state.setAttribute("leet", self.leet)
|
||||
state.setAttribute("great", self.great)
|
||||
state.setAttribute("bad", self.bad)
|
||||
state.setAttribute("triche", self.triche)
|
||||
|
||||
def merge(self, other):
|
||||
self.ftt += other.ftt
|
||||
self.twt += other.twt
|
||||
self.pi += other.pi
|
||||
self.notfound += other.notfound
|
||||
self.tententen += other.tententen
|
||||
self.leet += other.leet
|
||||
self.great += other.great
|
||||
self.bad += other.bad
|
||||
self.triche += other.triche
|
||||
|
||||
def newWinner(self):
|
||||
self.ftt = 0
|
||||
self.twt = 0
|
||||
self.pi = 1
|
||||
self.notfound = 1
|
||||
self.tententen = 0
|
||||
self.leet = 1
|
||||
self.great = -1
|
||||
self.bad = -4
|
||||
self.triche = 0
|
||||
|
||||
def isWinner(self):
|
||||
return self.great >= 42
|
||||
|
||||
def playFtt(self):
|
||||
if self.canPlay():
|
||||
self.ftt += 1
|
||||
def playTwt(self):
|
||||
if self.canPlay():
|
||||
self.twt += 1
|
||||
def playSuite(self):
|
||||
self.canPlay()
|
||||
self.twt += 1
|
||||
self.great += 1
|
||||
def playPi(self):
|
||||
if self.canPlay():
|
||||
self.pi += 1
|
||||
def playNotfound(self):
|
||||
if self.canPlay():
|
||||
self.notfound += 1
|
||||
def playTen(self):
|
||||
if self.canPlay():
|
||||
self.tententen += 1
|
||||
def playLeet(self):
|
||||
if self.canPlay():
|
||||
self.leet += 1
|
||||
def playGreat(self):
|
||||
if self.canPlay():
|
||||
self.great += 1
|
||||
def playBad(self):
|
||||
if self.canPlay():
|
||||
self.bad += 1
|
||||
self.great += 1
|
||||
def playTriche(self):
|
||||
self.triche += 1
|
||||
def oupsTriche(self):
|
||||
self.triche -= 1
|
||||
def bonusQuestion(self):
|
||||
return
|
||||
|
||||
def toTuple(self):
|
||||
return (self.ftt, self.twt, self.pi, self.notfound, self.tententen, self.leet, self.great, self.bad, self.triche)
|
||||
|
||||
def canPlay(self):
|
||||
now = datetime.now()
|
||||
ret = self.last == None or self.last.minute != now.minute or self.last.hour != now.hour or self.last.day != now.day
|
||||
self.changed = self.changed or ret
|
||||
return ret
|
||||
|
||||
def hasChanged(self):
|
||||
if self.changed:
|
||||
self.changed = False
|
||||
self.last = datetime.now()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def score(self):
|
||||
return (self.ftt * 2 + self.great * 5 + self.leet * 13.37 + (self.pi + 1) * 3.1415 * (self.notfound + 1) + self.tententen * 10 + self.twt - (self.bad + 1) * 10 * (self.triche * 5 + 1) + 7)
|
||||
|
||||
def details(self):
|
||||
return "42: %d, 23: %d, leet: %d, pi: %d, 404: %d, 10: %d, great: %d, bad: %d, triche: %d = %d."%(self.ftt, self.twt, self.leet, self.pi, self.notfound, self.tententen, self.great, self.bad, self.triche, self.score())
|
||||
224
modules/qd/__init__.py
Normal file
224
modules/qd/__init__.py
Normal file
|
|
@ -0,0 +1,224 @@
|
|||
# coding=utf-8
|
||||
|
||||
import re
|
||||
import imp
|
||||
from datetime import datetime
|
||||
|
||||
nemubotversion = 3.0
|
||||
|
||||
from . import GameUpdater
|
||||
from . import QDWrapper
|
||||
from . import Score
|
||||
|
||||
channels = "#nemutest #42sh #ykar #epitagueule"
|
||||
LASTSEEN = dict ()
|
||||
temps = dict ()
|
||||
|
||||
SCORES = None
|
||||
|
||||
def load(context):
|
||||
global DATAS, SCORES, CONF
|
||||
DATAS.setIndex("name", "player")
|
||||
SCORES = QDWrapper.QDWrapper(DATAS)
|
||||
GameUpdater.SCORES = SCORES
|
||||
GameUpdater.CONF = CONF
|
||||
GameUpdater.save = save
|
||||
GameUpdater.getUser = getUser
|
||||
|
||||
def reload():
|
||||
imp.reload(GameUpdater)
|
||||
imp.reload(QDWrapper)
|
||||
imp.reload(Score)
|
||||
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "42 game!"
|
||||
|
||||
def help_full ():
|
||||
return "!42: display scores\n!42 help: display the performed calculate\n!42 manche: display information about current round\n!42 /who/: show the /who/'s scores"
|
||||
|
||||
|
||||
def parseanswer (msg):
|
||||
if msg.cmd[0] == "42" or msg.cmd[0] == "score" or msg.cmd[0] == "scores":
|
||||
global SCORES
|
||||
if len(msg.cmd) > 2 and msg.is_owner and ((msg.cmd[1] == "merge" and len(msg.cmd) > 3) or msg.cmd[1] == "oupstriche"):
|
||||
if msg.cmd[2] in SCORES and (len(msg.cmd) <= 3 or msg.cmd[3] in SCORES):
|
||||
if msg.cmd[1] == "merge":
|
||||
SCORES[msg.cmd[2]].merge (SCORES[msg.cmd[3]])
|
||||
del SCORES[msg.cmd[3]]
|
||||
msg.send_chn ("%s a été correctement fusionné avec %s."%(msg.cmd[3], msg.cmd[2]))
|
||||
elif msg.cmd[1] == "oupstriche":
|
||||
SCORES[msg.cmd[2]].oupsTriche()
|
||||
else:
|
||||
if msg.cmd[2] not in SCORES:
|
||||
msg.send_chn ("%s n'est pas un joueur connu."%msg.cmd[2])
|
||||
elif msg.cmd[3] not in SCORES:
|
||||
msg.send_chn ("%s n'est pas un joueur connu."%msg.cmd[3])
|
||||
elif len(msg.cmd) > 1 and (msg.cmd[1] == "help" or msg.cmd[1] == "aide"):
|
||||
msg.send_chn ("Formule : \"42\" * 2 + great * 5 + leet * 13.37 + (pi + 1) * 3.1415 * (not_found + 1) + tententen * 10 + \"23\" - (bad + 1) * 10 * (triche * 5 + 1) + 7")
|
||||
elif len(msg.cmd) > 1 and (msg.cmd[1] == "manche" or msg.cmd[1] == "round"):
|
||||
manche = DATAS.getNode("manche")
|
||||
msg.send_chn ("Nous sommes dans la %de manche, gagnée par %s avec %d points et commencée par %s le %s." % (manche.getInt("number"), manche["winner"], manche.getInt("winner_score"), manche["who"], manche.getDate("date")))
|
||||
#elif msg.channel == "#nemutest":
|
||||
else:
|
||||
phrase = ""
|
||||
|
||||
if len(msg.cmd) > 1:
|
||||
if msg.cmd[1] in SCORES:
|
||||
phrase += " " + msg.cmd[1] + ": " + SCORES[msg.cmd[1]].details()
|
||||
else:
|
||||
phrase = " %s n'a encore jamais joué,"%(msg.cmd[1])
|
||||
else:
|
||||
for nom, scr in sorted(SCORES.items(), key=rev, reverse=True):
|
||||
score = scr.score()
|
||||
if score != 0:
|
||||
if phrase == "":
|
||||
phrase = " *%s.%s: %d*,"%(nom[0:1], nom[1:len(nom)], score)
|
||||
else:
|
||||
phrase += " %s.%s: %d,"%(nom[0:1], nom[1:len(nom)], score)
|
||||
|
||||
msg.send_chn ("Scores :%s" % (phrase[0:len(phrase)-1]))
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def win(msg):
|
||||
global SCORES
|
||||
who = msg.nick
|
||||
|
||||
manche = DATAS.getNode("manche")
|
||||
|
||||
maxi_scor = 0
|
||||
maxi_name = None
|
||||
|
||||
for player in DATAS.index.keys():
|
||||
scr = SCORES[player].score()
|
||||
if scr > maxi_scor:
|
||||
maxi_scor = scr
|
||||
maxi_name = player
|
||||
|
||||
for player in DATAS.index.keys():
|
||||
scr = SCORES[player].score()
|
||||
if scr > maxi_scor / 3:
|
||||
del SCORES[player]
|
||||
else:
|
||||
DATAS.index[player]["great"] = 0
|
||||
SCORES.flush()
|
||||
|
||||
if who != maxi_name:
|
||||
msg.send_chn ("Félicitations %s, tu remportes cette manche terminée par %s, avec un score de %d !"%(maxi_name, who, maxi_scor))
|
||||
else:
|
||||
msg.send_chn ("Félicitations %s, tu remportes cette manche avec %d points !"%(maxi_name, maxi_scor))
|
||||
|
||||
manche.setAttribute("number", manche.getInt("number") + 1)
|
||||
manche.setAttribute("winner", maxi_name)
|
||||
manche.setAttribute("winner_score", maxi_scor)
|
||||
manche.setAttribute("who", who)
|
||||
manche.setAttribute("date", datetime.now())
|
||||
|
||||
print ("Nouvelle manche !")
|
||||
save()
|
||||
|
||||
|
||||
def parseask (msg):
|
||||
if len(GameUpdater.DELAYED) > 0:
|
||||
if msg.nick in GameUpdater.DELAYED:
|
||||
GameUpdater.DELAYED[msg.nick].msg = msg.content
|
||||
GameUpdater.DELAYED[msg.nick].delayEvnt.set()
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
|
||||
def rev (tupl):
|
||||
(k, v) = tupl
|
||||
return (v.score(), k)
|
||||
|
||||
|
||||
def getUser(name):
|
||||
global SCORES
|
||||
if name not in SCORES:
|
||||
SCORES[name] = Score.Score()
|
||||
return SCORES[name]
|
||||
|
||||
|
||||
def parselisten (msg):
|
||||
if len(GameUpdater.DELAYED) > 0 and msg.nick in GameUpdater.DELAYED and GameUpdater.DELAYED[msg.nick].good(msg.content):
|
||||
msg.send_chn("%s: n'oublie pas le nemubot: devant ta réponse pour qu'elle soit prise en compte !" % msg.nick)
|
||||
|
||||
bfrseen = None
|
||||
if msg.realname in LASTSEEN:
|
||||
bfrseen = LASTSEEN[msg.realname]
|
||||
LASTSEEN[msg.realname] = datetime.now()
|
||||
|
||||
# if msg.channel == "#nemutest" and msg.nick not in GameUpdater.DELAYED:
|
||||
if msg.channel != "#nemutest" and msg.nick not in GameUpdater.DELAYED:
|
||||
|
||||
if re.match("^(42|quarante[- ]?deux).{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.minute == 10 and msg.time.second == 10 and msg.time.hour == 10:
|
||||
getUser(msg.nick).playTen()
|
||||
getUser(msg.nick).playGreat()
|
||||
elif msg.time.minute == 42:
|
||||
if msg.time.second == 0:
|
||||
getUser(msg.nick).playGreat()
|
||||
getUser(msg.nick).playFtt()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if re.match("^(23|vingt[ -]?trois).{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.minute == 23:
|
||||
if msg.time.second == 0:
|
||||
getUser(msg.nick).playGreat()
|
||||
getUser(msg.nick).playTwt()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if re.match("^(10){3}.{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.minute == 10 and msg.time.hour == 10:
|
||||
if msg.time.second == 10:
|
||||
getUser(msg.nick).playGreat()
|
||||
getUser(msg.nick).playTen()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if re.match("^0?12345.{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.hour == 1 and msg.time.minute == 23 and (msg.time.second == 45 or (msg.time.second == 46 and msg.time.microsecond < 330000)):
|
||||
getUser(msg.nick).playSuite()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if re.match("^[1l][e3]{2}[t7] ?t?ime.{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.hour == 13 and msg.time.minute == 37:
|
||||
if msg.time.second == 0:
|
||||
getUser(msg.nick).playGreat()
|
||||
getUser(msg.nick).playLeet()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if re.match("^(pi|3.14) ?time.{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.hour == 3 and msg.time.minute == 14:
|
||||
if msg.time.second == 15 or msg.time.second == 16:
|
||||
getUser(msg.nick).playGreat()
|
||||
getUser(msg.nick).playPi()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if re.match("^(404( ?time)?|time ?not ?found).{,2}$", msg.content.strip().lower()):
|
||||
if msg.time.hour == 4 and msg.time.minute == 4:
|
||||
if msg.time.second == 0 or msg.time.second == 4:
|
||||
getUser(msg.nick).playGreat()
|
||||
getUser(msg.nick).playNotfound()
|
||||
else:
|
||||
getUser(msg.nick).playBad()
|
||||
|
||||
if getUser(msg.nick).isWinner():
|
||||
print ("Nous avons un vainqueur ! Nouvelle manche :p")
|
||||
win(msg)
|
||||
return True
|
||||
elif getUser(msg.nick).hasChanged():
|
||||
gu = GameUpdater.GameUpdater(msg, bfrseen)
|
||||
gu.start()
|
||||
return True
|
||||
return False
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
"""Informe les usagers des prochains passages des transports en communs de la RATP"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.module.more import Response
|
||||
|
||||
from nextstop import ratp
|
||||
|
||||
@hook.command("ratp",
|
||||
help="Affiche les prochains horaires de passage",
|
||||
help_usage={
|
||||
"TRANSPORT": "Affiche les lignes du moyen de transport donné",
|
||||
"TRANSPORT LINE": "Affiche les stations sur la ligne de transport donnée",
|
||||
"TRANSPORT LINE STATION": "Affiche les prochains horaires de passage à l'arrêt donné",
|
||||
"TRANSPORT LINE STATION DESTINATION": "Affiche les prochains horaires de passage dans la direction donnée",
|
||||
})
|
||||
def ask_ratp(msg):
|
||||
l = len(msg.args)
|
||||
|
||||
transport = msg.args[0] if l > 0 else None
|
||||
line = msg.args[1] if l > 1 else None
|
||||
station = msg.args[2] if l > 2 else None
|
||||
direction = msg.args[3] if l > 3 else None
|
||||
|
||||
if station is not None:
|
||||
times = sorted(ratp.getNextStopsAtStation(transport, line, station, direction), key=lambda i: i[0])
|
||||
|
||||
if len(times) == 0:
|
||||
raise IMException("la station %s n'existe pas sur le %s ligne %s." % (station, transport, line))
|
||||
|
||||
(time, direction, stationname) = times[0]
|
||||
return Response(message=["\x03\x02%s\x03\x02 direction %s" % (time, direction) for time, direction, stationname in times],
|
||||
title="Prochains passages du %s ligne %s à l'arrêt %s" % (transport, line, stationname),
|
||||
channel=msg.channel)
|
||||
|
||||
elif line is not None:
|
||||
stations = ratp.getAllStations(transport, line)
|
||||
|
||||
if len(stations) == 0:
|
||||
raise IMException("aucune station trouvée.")
|
||||
return Response(stations, title="Stations", channel=msg.channel)
|
||||
|
||||
elif transport is not None:
|
||||
lines = ratp.getTransportLines(transport)
|
||||
if len(lines) == 0:
|
||||
raise IMException("aucune ligne trouvée.")
|
||||
return Response(lines, title="Lignes", channel=msg.channel)
|
||||
|
||||
else:
|
||||
raise IMException("précise au moins un moyen de transport.")
|
||||
|
||||
|
||||
@hook.command("ratp_alert",
|
||||
help="Affiche les perturbations en cours sur le réseau")
|
||||
def ratp_alert(msg):
|
||||
if len(msg.args) == 0:
|
||||
raise IMException("précise au moins un moyen de transport.")
|
||||
|
||||
l = len(msg.args)
|
||||
transport = msg.args[0] if l > 0 else None
|
||||
line = msg.args[1] if l > 1 else None
|
||||
|
||||
if line is not None:
|
||||
d = ratp.getDisturbanceFromLine(transport, line)
|
||||
if "date" in d and d["date"] is not None:
|
||||
incidents = "Au {date[date]}, {title}: {message}".format(**d)
|
||||
else:
|
||||
incidents = "{title}: {message}".format(**d)
|
||||
else:
|
||||
incidents = ratp.getDisturbance(None, transport)
|
||||
|
||||
return Response(incidents, channel=msg.channel, nomore="No more incidents", count=" (%d more incidents)")
|
||||
|
|
@ -1,97 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
"""Get information about subreddit"""
|
||||
|
||||
import re
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
def help_full():
|
||||
return "!subreddit /subreddit/: Display information on the subreddit."
|
||||
|
||||
LAST_SUBS = dict()
|
||||
|
||||
|
||||
@hook.command("subreddit")
|
||||
def cmd_subreddit(msg):
|
||||
global LAST_SUBS
|
||||
if not len(msg.args):
|
||||
if msg.channel in LAST_SUBS and len(LAST_SUBS[msg.channel]) > 0:
|
||||
subs = [LAST_SUBS[msg.channel].pop()]
|
||||
else:
|
||||
raise IMException("Which subreddit? Need inspiration? "
|
||||
"type !horny or !bored")
|
||||
else:
|
||||
subs = msg.args
|
||||
|
||||
all_res = list()
|
||||
for osub in subs:
|
||||
sub = re.match(r"^/?(?:(\w)/)?(\w+)/?$", osub)
|
||||
if sub is not None:
|
||||
if sub.group(1) is not None and sub.group(1) != "":
|
||||
where = sub.group(1)
|
||||
else:
|
||||
where = "r"
|
||||
|
||||
sbr = web.getJSON("https://www.reddit.com/%s/%s/about.json" %
|
||||
(where, sub.group(2)))
|
||||
|
||||
if sbr is None:
|
||||
raise IMException("subreddit not found")
|
||||
|
||||
if "title" in sbr["data"]:
|
||||
res = Response(channel=msg.channel,
|
||||
nomore="No more information")
|
||||
res.append_message(
|
||||
("[NSFW] " if sbr["data"]["over18"] else "") +
|
||||
sbr["data"]["url"] + " " + sbr["data"]["title"] + ": " +
|
||||
sbr["data"]["public_description" if sbr["data"]["public_description"] != "" else "description"].replace("\n", " ") +
|
||||
" %s subscriber(s)" % sbr["data"]["subscribers"])
|
||||
if sbr["data"]["public_description"] != "":
|
||||
res.append_message(
|
||||
sbr["data"]["description"].replace("\n", " "))
|
||||
all_res.append(res)
|
||||
else:
|
||||
all_res.append(Response("/%s/%s doesn't exist" %
|
||||
(where, sub.group(2)),
|
||||
channel=msg.channel))
|
||||
else:
|
||||
all_res.append(Response("%s is not a valid subreddit" % osub,
|
||||
channel=msg.channel, nick=msg.frm))
|
||||
|
||||
return all_res
|
||||
|
||||
|
||||
@hook.message()
|
||||
def parselisten(msg):
|
||||
global LAST_SUBS
|
||||
|
||||
if hasattr(msg, "message") and msg.message and type(msg.message) == str:
|
||||
urls = re.findall("www.reddit.com(/\w/\w+/?)", msg.message)
|
||||
for url in urls:
|
||||
for recv in msg.to:
|
||||
if recv not in LAST_SUBS:
|
||||
LAST_SUBS[recv] = list()
|
||||
LAST_SUBS[recv].append(url)
|
||||
|
||||
|
||||
@hook.post()
|
||||
def parseresponse(msg):
|
||||
global LAST_SUBS
|
||||
|
||||
if hasattr(msg, "text") and msg.text and type(msg.text) == str:
|
||||
urls = re.findall("www.reddit.com(/\w/\w+/?)", msg.text)
|
||||
for url in urls:
|
||||
for recv in msg.to:
|
||||
if recv not in LAST_SUBS:
|
||||
LAST_SUBS[recv] = list()
|
||||
LAST_SUBS[recv].append(url)
|
||||
|
||||
return msg
|
||||
|
|
@ -1,94 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
"""Repology.org module: the packaging hub"""
|
||||
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
|
||||
nemubotversion = 4.0
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
URL_REPOAPI = "https://repology.org/api/v1/project/%s"
|
||||
|
||||
def get_json_project(project):
|
||||
prj = web.getJSON(URL_REPOAPI % (project))
|
||||
|
||||
return prj
|
||||
|
||||
|
||||
@hook.command("repology",
|
||||
help="Display version information about a package",
|
||||
help_usage={
|
||||
"PACKAGE_NAME": "Retrieve informations about PACKAGE_NAME",
|
||||
},
|
||||
keywords={
|
||||
"distro=DISTRO": "filter by disto",
|
||||
"status=STATUS[,STATUS...]": "filter by status",
|
||||
})
|
||||
def cmd_repology(msg):
|
||||
if len(msg.args) == 0:
|
||||
raise IMException("Please provide at least a package name")
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more information on package")
|
||||
|
||||
for project in msg.args:
|
||||
prj = get_json_project(project)
|
||||
if len(prj) == 0:
|
||||
raise IMException("Unable to find package " + project)
|
||||
|
||||
pkg_versions = {}
|
||||
pkg_maintainers = {}
|
||||
pkg_licenses = {}
|
||||
summary = None
|
||||
|
||||
for repo in prj:
|
||||
# Apply filters
|
||||
if "distro" in msg.kwargs and repo["repo"].find(msg.kwargs["distro"]) < 0:
|
||||
continue
|
||||
if "status" in msg.kwargs and repo["status"] not in msg.kwargs["status"].split(","):
|
||||
continue
|
||||
|
||||
name = repo["visiblename"] if "visiblename" in repo else repo["name"]
|
||||
status = repo["status"] if "status" in repo else "unknown"
|
||||
if name not in pkg_versions:
|
||||
pkg_versions[name] = {}
|
||||
if status not in pkg_versions[name]:
|
||||
pkg_versions[name][status] = []
|
||||
if repo["version"] not in pkg_versions[name][status]:
|
||||
pkg_versions[name][status].append(repo["version"])
|
||||
|
||||
if "maintainers" in repo:
|
||||
if name not in pkg_maintainers:
|
||||
pkg_maintainers[name] = []
|
||||
for maintainer in repo["maintainers"]:
|
||||
if maintainer not in pkg_maintainers[name]:
|
||||
pkg_maintainers[name].append(maintainer)
|
||||
|
||||
if "licenses" in repo:
|
||||
if name not in pkg_licenses:
|
||||
pkg_licenses[name] = []
|
||||
for lic in repo["licenses"]:
|
||||
if lic not in pkg_licenses[name]:
|
||||
pkg_licenses[name].append(lic)
|
||||
|
||||
if "summary" in repo and summary is None:
|
||||
summary = repo["summary"]
|
||||
|
||||
for pkgname in sorted(pkg_versions.keys()):
|
||||
m = "Package " + pkgname + " (" + summary + ")"
|
||||
if pkgname in pkg_licenses:
|
||||
m += " under " + ", ".join(pkg_licenses[pkgname])
|
||||
m += ": " + " - ".join([status + ": " + ", ".join(pkg_versions[pkgname][status]) for status in ["newest", "devel", "unique", "outdated", "legacy", "rolling", "noscheme", "untrusted", "ignored"] if status in pkg_versions[pkgname]])
|
||||
if "distro" in msg.kwargs and pkgname in pkg_maintainers:
|
||||
m += " - Maintained by " + ", ".join(pkg_maintainers[pkgname])
|
||||
|
||||
res.append_message(m)
|
||||
|
||||
return res
|
||||
|
|
@ -1,54 +1,12 @@
|
|||
"""Help to make choice"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
# coding=utf-8
|
||||
|
||||
import random
|
||||
import shlex
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
nemubotversion = 3.3
|
||||
|
||||
from nemubot.module.more import Response
|
||||
def load(context):
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_choice, "choice"))
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("choice")
|
||||
def cmd_choice(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate some terms to pick!")
|
||||
|
||||
return Response(random.choice(msg.args),
|
||||
channel=msg.channel,
|
||||
nick=msg.frm)
|
||||
|
||||
|
||||
@hook.command("choicecmd")
|
||||
def cmd_choicecmd(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate some command to pick!")
|
||||
|
||||
choice = shlex.split(random.choice(msg.args))
|
||||
|
||||
return [x for x in context.subtreat(context.subparse(msg, choice))]
|
||||
|
||||
|
||||
@hook.command("choiceres")
|
||||
def cmd_choiceres(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate some command to pick a message from!")
|
||||
|
||||
rl = [x for x in context.subtreat(context.subparse(msg, " ".join(msg.args)))]
|
||||
if len(rl) <= 0:
|
||||
return rl
|
||||
|
||||
r = random.choice(rl)
|
||||
|
||||
if isinstance(r, Response):
|
||||
for i in range(len(r.messages) - 1, -1, -1):
|
||||
if isinstance(r.messages[i], list):
|
||||
r.messages = [ random.choice(random.choice(r.messages)) ]
|
||||
elif isinstance(r.messages[i], str):
|
||||
r.messages = [ random.choice(r.messages) ]
|
||||
return r
|
||||
return Response(msg.sender, random.choice(msg.cmds[1:]), channel=msg.channel)
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
"""Find information about an SAP transaction codes"""
|
||||
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
from bs4 import BeautifulSoup
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
nemubotversion = 4.0
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
def help_full():
|
||||
return "Retrieve SAP transaction codes and details using tcodes or keywords: !tcode <transaction code|keywords>"
|
||||
|
||||
|
||||
@hook.command("tcode")
|
||||
def cmd_tcode(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate a transaction code or "
|
||||
"a keyword to search!")
|
||||
|
||||
url = ("https://www.tcodesearch.com/tcodes/search?q=%s" %
|
||||
urllib.parse.quote(msg.args[0]))
|
||||
|
||||
page = web.getURLContent(url)
|
||||
soup = BeautifulSoup(page)
|
||||
|
||||
res = Response(channel=msg.channel,
|
||||
nomore="No more transaction code",
|
||||
count=" (%d more tcodes)")
|
||||
|
||||
|
||||
search_res = soup.find("", {'id':'searchresults'})
|
||||
for item in search_res.find_all('dd'):
|
||||
res.append_message(item.get_text().split('\n')[1].strip())
|
||||
|
||||
return res
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
"""Search engine for IoT"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from datetime import datetime
|
||||
import ipaddress
|
||||
import urllib.parse
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
BASEURL = "https://api.shodan.io/shodan/"
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
if not context.config or "apikey" not in context.config:
|
||||
raise ImportError("You need a Shodan API key in order to use this "
|
||||
"module. Add it to the module configuration file:\n"
|
||||
"<module name=\"shodan\" apikey=\"XXXXXXXXXXXXXXXX\" "
|
||||
"/>\nRegister at https://account.shodan.io/register")
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def host_lookup(ip):
|
||||
url = BASEURL + "host/" + urllib.parse.quote(ip) + "?" + urllib.parse.urlencode({'key': context.config["apikey"]})
|
||||
return web.getJSON(url)
|
||||
|
||||
|
||||
def search_hosts(query):
|
||||
url = BASEURL + "host/search?" + urllib.parse.urlencode({'query': query, 'key': context.config["apikey"]})
|
||||
return web.getJSON(url, max_size=4194304)
|
||||
|
||||
|
||||
def print_ssl(ssl):
|
||||
return (
|
||||
"SSL: " +
|
||||
" ".join([v for v in ssl["versions"] if v[0] != "-"]) +
|
||||
"; cipher used: " + ssl["cipher"]["name"] +
|
||||
("; certificate: " + ssl["cert"]["sig_alg"] +
|
||||
" issued by: " + ssl["cert"]["issuer"]["CN"] +
|
||||
" expires on: " + str(datetime.strptime(ssl["cert"]["expires"], "%Y%m%d%H%M%SZ")) if "cert" in ssl else "")
|
||||
)
|
||||
|
||||
def print_service(svc):
|
||||
ip = ipaddress.ip_address(svc["ip_str"])
|
||||
return ((svc["ip_str"] if ip.version == 4 else "[%s]" % svc["ip_str"]) +
|
||||
":{port}/{transport} ({module}):" +
|
||||
(" {os}" if svc["os"] else "") +
|
||||
(" {product}" if "product" in svc else "") +
|
||||
(" {version}" if "version" in svc else "") +
|
||||
(" {info}" if "info" in svc else "") +
|
||||
(" Vulns: " + ", ".join(svc["opts"]["vulns"]) if "opts" in svc and "vulns" in svc["opts"] else "") +
|
||||
(" " + print_ssl(svc["ssl"]) if "ssl" in svc else "") +
|
||||
(" \x03\x1D" + svc["data"].replace("\r\n", "\n").split("\n")[0] + "\x03\x1D" if "data" in svc else "") +
|
||||
(" " + svc["title"] if "title" in svc else "")
|
||||
).format(module=svc["_shodan"]["module"], **svc)
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("shodan",
|
||||
help="Use shodan.io to get information on machines connected to Internet",
|
||||
help_usage={
|
||||
"IP": "retrieve information about the given IP (can be v4 or v6)",
|
||||
"TERM": "retrieve all hosts matching TERM somewhere in their exposed stuff"
|
||||
})
|
||||
def shodan(msg):
|
||||
if not msg.args:
|
||||
raise IMException("indicate an IP or a term to search!")
|
||||
|
||||
terms = " ".join(msg.args)
|
||||
|
||||
try:
|
||||
ip = ipaddress.ip_address(terms)
|
||||
except ValueError:
|
||||
ip = None
|
||||
|
||||
if ip:
|
||||
h = host_lookup(terms)
|
||||
res = Response(channel=msg.channel,
|
||||
title="%s" % ((h["ip_str"] if ip.version == 4 else "[%s]" % h["ip_str"]) + (" (" + ", ".join(h["hostnames"]) + ")") if h["hostnames"] else ""))
|
||||
res.append_message("{isp} ({asn}) -> {city} ({country_code}), running {os}. Vulns: {vulns_str}. Open ports: {open_ports}. Last update: {last_update}".format(
|
||||
open_ports=", ".join(map(lambda a: str(a), h["ports"])), vulns_str=", ".join(h["vulns"]) if "vulns" in h else None, **h).strip())
|
||||
for d in h["data"]:
|
||||
res.append_message(print_service(d))
|
||||
|
||||
else:
|
||||
q = search_hosts(terms)
|
||||
res = Response(channel=msg.channel,
|
||||
count=" (%%s/%s results)" % q["total"])
|
||||
for r in q["matches"]:
|
||||
res.append_message(print_service(r))
|
||||
|
||||
return res
|
||||
|
|
@ -1,50 +1,52 @@
|
|||
# coding=utf-8
|
||||
|
||||
"""as http://sleepyti.me/, give you the best time to go to bed"""
|
||||
|
||||
import re
|
||||
import imp
|
||||
from datetime import datetime, timedelta, timezone
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
|
||||
from nemubot.hooks import hook
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
from nemubot.module.more import Response
|
||||
nemubotversion = 3.3
|
||||
|
||||
def help_tiny ():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "as http://sleepyti.me/, give you the best time to go to bed"
|
||||
|
||||
def help_full ():
|
||||
return ("If you would like to sleep soon, use !sleepytime to know the best"
|
||||
" time to wake up; use !sleepytime hh:mm if you want to wake up at"
|
||||
" hh:mm")
|
||||
return "If you would like to sleep soon, use !sleepytime to know the best time to wake up; use !sleepytime hh:mm if you want to wake up at hh:mm"
|
||||
|
||||
def load(context):
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_sleep, "sleeptime"))
|
||||
add_hook("cmd_hook", Hook(cmd_sleep, "sleepytime"))
|
||||
|
||||
|
||||
@hook.command("sleepytime")
|
||||
def cmd_sleep(msg):
|
||||
if len(msg.args) and re.match("[0-9]{1,2}[h':.,-]([0-9]{1,2})?[m'\":.,-]?",
|
||||
msg.args[0]) is not None:
|
||||
if len (msg.cmds) > 1 and re.match("[0-9]{1,2}[h':.,-]([0-9]{1,2})?[m'\":.,-]?",
|
||||
msg.cmds[1]) is not None:
|
||||
# First, parse the hour
|
||||
p = re.match("([0-9]{1,2})[h':.,-]([0-9]{1,2})?[m':.,-]?", msg.args[0])
|
||||
f = [datetime(datetime.now(timezone.utc).year,
|
||||
datetime.now(timezone.utc).month,
|
||||
datetime.now(timezone.utc).day,
|
||||
p = re.match("([0-9]{1,2})[h':.,-]([0-9]{1,2})?[m':.,-]?", msg.cmds[1])
|
||||
f = [datetime(datetime.today().year,
|
||||
datetime.today().month,
|
||||
datetime.today().day,
|
||||
hour=int(p.group(1)))]
|
||||
if p.group(2) is not None:
|
||||
f[0] += timedelta(minutes=int(p.group(2)))
|
||||
g = list()
|
||||
for i in range(6):
|
||||
for i in range(0,6):
|
||||
f.append(f[i] - timedelta(hours=1,minutes=30))
|
||||
g.append(f[i+1].strftime("%H:%M"))
|
||||
return Response("You should try to fall asleep at one of the following"
|
||||
" times: %s" % ', '.join(g), channel=msg.channel)
|
||||
return Response(msg.sender,
|
||||
"You should try to fall asleep at one of the following"
|
||||
" times: %s" % ', '.join(g), msg.channel)
|
||||
|
||||
# Just get awake times
|
||||
else:
|
||||
f = [datetime.now(timezone.utc) + timedelta(minutes=15)]
|
||||
f = [datetime.now() + timedelta(minutes=15)]
|
||||
g = list()
|
||||
for i in range(6):
|
||||
for i in range(0,6):
|
||||
f.append(f[i] + timedelta(hours=1,minutes=30))
|
||||
g.append(f[i+1].strftime("%H:%M"))
|
||||
return Response("If you head to bed right now, you should try to wake"
|
||||
return Response(msg.sender,
|
||||
"If you head to bed right now, you should try to wake"
|
||||
" up at one of the following times: %s" %
|
||||
', '.join(g), channel=msg.channel)
|
||||
', '.join(g), msg.channel)
|
||||
|
|
|
|||
116
modules/smmry.py
116
modules/smmry.py
|
|
@ -1,116 +0,0 @@
|
|||
"""Summarize texts"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
from nemubot.module.urlreducer import LAST_URLS
|
||||
|
||||
|
||||
# GLOBALS #############################################################
|
||||
|
||||
URL_API = "https://api.smmry.com/?SM_API_KEY=%s"
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
if not context.config or "apikey" not in context.config:
|
||||
raise ImportError("You need a Smmry API key in order to use this "
|
||||
"module. Add it to the module configuration file:\n"
|
||||
"<module name=\"smmry\" apikey=\"XXXXXXXXXXXXXXXX\" "
|
||||
"/>\nRegister at https://smmry.com/partner")
|
||||
global URL_API
|
||||
URL_API = URL_API % context.config["apikey"]
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("smmry",
|
||||
help="Summarize the following words/command return",
|
||||
help_usage={
|
||||
"WORDS/CMD": ""
|
||||
},
|
||||
keywords={
|
||||
"keywords?=X": "Returns keywords instead of summary (count optional)",
|
||||
"length=7": "The number of sentences returned, default 7",
|
||||
"break": "inserts the string [BREAK] between sentences",
|
||||
"ignore_length": "returns summary regardless of quality or length",
|
||||
"quote_avoid": "sentences with quotations will be excluded",
|
||||
"question_avoid": "sentences with question will be excluded",
|
||||
"exclamation_avoid": "sentences with exclamation marks will be excluded",
|
||||
})
|
||||
def cmd_smmry(msg):
|
||||
if not len(msg.args):
|
||||
global LAST_URLS
|
||||
if msg.channel in LAST_URLS and len(LAST_URLS[msg.channel]) > 0:
|
||||
msg.args.append(LAST_URLS[msg.channel].pop())
|
||||
else:
|
||||
raise IMException("I have no more URL to sum up.")
|
||||
|
||||
URL = URL_API
|
||||
if "length" in msg.kwargs:
|
||||
if int(msg.kwargs["length"]) > 0 :
|
||||
URL += "&SM_LENGTH=" + msg.kwargs["length"]
|
||||
else:
|
||||
msg.kwargs["ignore_length"] = True
|
||||
if "break" in msg.kwargs: URL += "&SM_WITH_BREAK"
|
||||
if "ignore_length" in msg.kwargs: URL += "&SM_IGNORE_LENGTH"
|
||||
if "quote_avoid" in msg.kwargs: URL += "&SM_QUOTE_AVOID"
|
||||
if "question_avoid" in msg.kwargs: URL += "&SM_QUESTION_AVOID"
|
||||
if "exclamation_avoid" in msg.kwargs: URL += "&SM_EXCLAMATION_AVOID"
|
||||
if "keywords" in msg.kwargs and msg.kwargs["keywords"] is not None and int(msg.kwargs["keywords"]) > 0: URL += "&SM_KEYWORD_COUNT=" + msg.kwargs["keywords"]
|
||||
|
||||
res = Response(channel=msg.channel)
|
||||
|
||||
if web.isURL(" ".join(msg.args)):
|
||||
smmry = web.getJSON(URL + "&SM_URL=" + quote(" ".join(msg.args)), timeout=23)
|
||||
else:
|
||||
cnt = ""
|
||||
for r in context.subtreat(context.subparse(msg, " ".join(msg.args))):
|
||||
if isinstance(r, Response):
|
||||
for i in range(len(r.messages) - 1, -1, -1):
|
||||
if isinstance(r.messages[i], list):
|
||||
for j in range(len(r.messages[i]) - 1, -1, -1):
|
||||
cnt += r.messages[i][j] + "\n"
|
||||
elif isinstance(r.messages[i], str):
|
||||
cnt += r.messages[i] + "\n"
|
||||
else:
|
||||
cnt += str(r.messages) + "\n"
|
||||
|
||||
elif isinstance(r, Text):
|
||||
cnt += r.message + "\n"
|
||||
|
||||
else:
|
||||
cnt += str(r) + "\n"
|
||||
|
||||
smmry = web.getJSON(URL, body="sm_api_input=" + quote(cnt), timeout=23)
|
||||
|
||||
if "sm_api_error" in smmry:
|
||||
if smmry["sm_api_error"] == 0:
|
||||
title = "Internal server problem (not your fault)"
|
||||
elif smmry["sm_api_error"] == 1:
|
||||
title = "Incorrect submission variables"
|
||||
elif smmry["sm_api_error"] == 2:
|
||||
title = "Intentional restriction (low credits?)"
|
||||
elif smmry["sm_api_error"] == 3:
|
||||
title = "Summarization error"
|
||||
else:
|
||||
title = "Unknown error"
|
||||
raise IMException(title + ": " + smmry['sm_api_message'].lower())
|
||||
|
||||
if "keywords" in msg.kwargs:
|
||||
smmry["sm_api_content"] = ", ".join(smmry["sm_api_keyword_array"])
|
||||
|
||||
if "sm_api_title" in smmry and smmry["sm_api_title"] != "":
|
||||
res.append_message(smmry["sm_api_content"], title=smmry["sm_api_title"])
|
||||
else:
|
||||
res.append_message(smmry["sm_api_content"])
|
||||
|
||||
return res
|
||||
153
modules/sms.py
153
modules/sms.py
|
|
@ -1,153 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
"""Send SMS using SMS API (currently only Free Mobile)"""
|
||||
|
||||
import re
|
||||
import socket
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import urllib.parse
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
def load(context):
|
||||
context.data.setIndex("name", "phone")
|
||||
|
||||
def help_full():
|
||||
return "!sms /who/[,/who/[,...]] message: send a SMS to /who/."
|
||||
|
||||
def send_sms(frm, api_usr, api_key, content):
|
||||
content = "<%s> %s" % (frm, content)
|
||||
|
||||
try:
|
||||
req = urllib.request.Request("https://smsapi.free-mobile.fr/sendmsg?user=%s&pass=%s&msg=%s" % (api_usr, api_key, urllib.parse.quote(content)))
|
||||
res = urllib.request.urlopen(req, timeout=5)
|
||||
except socket.timeout:
|
||||
return "timeout"
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 400:
|
||||
return "paramètre manquant"
|
||||
elif e.code == 402:
|
||||
return "paiement requis"
|
||||
elif e.code == 403 or e.code == 404:
|
||||
return "clef incorrecte"
|
||||
elif e.code != 200:
|
||||
return "erreur inconnue (%d)" % status
|
||||
except:
|
||||
return "unknown error"
|
||||
|
||||
return None
|
||||
|
||||
def check_sms_dests(dests, cur_epoch):
|
||||
"""Raise exception if one of the dest is not known or has already receive a SMS recently
|
||||
"""
|
||||
for u in dests:
|
||||
if u not in context.data.index:
|
||||
raise IMException("Désolé, je sais pas comment envoyer de SMS à %s." % u)
|
||||
elif cur_epoch - float(context.data.index[u]["lastuse"]) < 42:
|
||||
raise IMException("Un peu de calme, %s a déjà reçu un SMS il n'y a pas si longtemps." % u)
|
||||
return True
|
||||
|
||||
|
||||
def send_sms_to_list(msg, frm, dests, content, cur_epoch):
|
||||
fails = list()
|
||||
for u in dests:
|
||||
context.data.index[u]["lastuse"] = cur_epoch
|
||||
test = send_sms(frm, context.data.index[u]["user"], context.data.index[u]["key"], content)
|
||||
if test is not None:
|
||||
fails.append( "%s: %s" % (u, test) )
|
||||
|
||||
if len(fails) > 0:
|
||||
return Response("quelque chose ne s'est pas bien passé durant l'envoi du SMS : " + ", ".join(fails), msg.channel, msg.frm)
|
||||
else:
|
||||
return Response("le SMS a bien été envoyé", msg.channel, msg.frm)
|
||||
|
||||
|
||||
@hook.command("sms")
|
||||
def cmd_sms(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("À qui veux-tu envoyer ce SMS ?")
|
||||
|
||||
cur_epoch = time.mktime(time.localtime())
|
||||
dests = msg.args[0].split(",")
|
||||
frm = msg.frm if msg.to_response[0] == msg.frm else msg.frm + "@" + msg.to[0]
|
||||
content = " ".join(msg.args[1:])
|
||||
|
||||
check_sms_dests(dests, cur_epoch)
|
||||
return send_sms_to_list(msg, frm, dests, content, cur_epoch)
|
||||
|
||||
|
||||
@hook.command("smscmd")
|
||||
def cmd_smscmd(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("À qui veux-tu envoyer ce SMS ?")
|
||||
|
||||
cur_epoch = time.mktime(time.localtime())
|
||||
dests = msg.args[0].split(",")
|
||||
frm = msg.frm if msg.to_response[0] == msg.frm else msg.frm + "@" + msg.to[0]
|
||||
cmd = " ".join(msg.args[1:])
|
||||
|
||||
content = None
|
||||
for r in context.subtreat(context.subparse(msg, cmd)):
|
||||
if isinstance(r, Response):
|
||||
for m in r.messages:
|
||||
if isinstance(m, list):
|
||||
for n in m:
|
||||
content = n
|
||||
break
|
||||
if content is not None:
|
||||
break
|
||||
elif isinstance(m, str):
|
||||
content = m
|
||||
break
|
||||
|
||||
elif isinstance(r, Text):
|
||||
content = r.message
|
||||
|
||||
if content is None:
|
||||
raise IMException("Aucun SMS envoyé : le résultat de la commande n'a pas retourné de contenu.")
|
||||
|
||||
check_sms_dests(dests, cur_epoch)
|
||||
return send_sms_to_list(msg, frm, dests, content, cur_epoch)
|
||||
|
||||
|
||||
apiuser_ask = re.compile(r"(utilisateur|user|numéro|numero|compte|abonne|abone|abonné|account)\s+(est|is)\s+(?P<user>[0-9]{7,})", re.IGNORECASE)
|
||||
apikey_ask = re.compile(r"(clef|key|password|mot de passe?)\s+(?:est|is)?\s+(?P<key>[a-zA-Z0-9]{10,})", re.IGNORECASE)
|
||||
|
||||
@hook.ask()
|
||||
def parseask(msg):
|
||||
if msg.message.find("Free") >= 0 and (
|
||||
msg.message.find("API") >= 0 or msg.message.find("api") >= 0) and (
|
||||
msg.message.find("SMS") >= 0 or msg.message.find("sms") >= 0):
|
||||
resuser = apiuser_ask.search(msg.message)
|
||||
reskey = apikey_ask.search(msg.message)
|
||||
if resuser is not None and reskey is not None:
|
||||
apiuser = resuser.group("user")
|
||||
apikey = reskey.group("key")
|
||||
|
||||
test = send_sms("nemubot", apiuser, apikey,
|
||||
"Vous avez enregistré vos codes d'authentification dans nemubot, félicitation !")
|
||||
if test is not None:
|
||||
return Response("je n'ai pas pu enregistrer tes identifiants : %s" % test, msg.channel, msg.frm)
|
||||
|
||||
if msg.frm in context.data.index:
|
||||
context.data.index[msg.frm]["user"] = apiuser
|
||||
context.data.index[msg.frm]["key"] = apikey
|
||||
else:
|
||||
ms = ModuleState("phone")
|
||||
ms.setAttribute("name", msg.frm)
|
||||
ms.setAttribute("user", apiuser)
|
||||
ms.setAttribute("key", apikey)
|
||||
ms.setAttribute("lastuse", 0)
|
||||
context.data.addChild(ms)
|
||||
context.save()
|
||||
return Response("ok, c'est noté. Je t'ai envoyé un SMS pour tester ;)",
|
||||
msg.channel, msg.frm)
|
||||
5
modules/soutenance.xml
Normal file
5
modules/soutenance.xml
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
<?xml version="1.0" ?>
|
||||
<nemubotmodule name="soutenance">
|
||||
<server ip="www.acu.epita.fr" url="/intra/sout_liste.html" />
|
||||
<message type="cmd" name="soutenance" call="ask_soutenance" />
|
||||
</nemubotmodule>
|
||||
13
modules/soutenance/Delayed.py
Normal file
13
modules/soutenance/Delayed.py
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
# coding=utf-8
|
||||
|
||||
import threading
|
||||
|
||||
class Delayed:
|
||||
def __init__(self, name):
|
||||
self.name = name
|
||||
self.res = None
|
||||
self.evt = threading.Event()
|
||||
|
||||
def wait(self, timeout):
|
||||
self.evt.clear()
|
||||
self.evt.wait(timeout)
|
||||
179
modules/soutenance/SiteSoutenances.py
Normal file
179
modules/soutenance/SiteSoutenances.py
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import datetime
|
||||
from datetime import timedelta
|
||||
import http.client
|
||||
import re
|
||||
import threading
|
||||
import time
|
||||
|
||||
from response import Response
|
||||
|
||||
from .Soutenance import Soutenance
|
||||
|
||||
class SiteSoutenances(threading.Thread):
|
||||
def __init__(self, datas):
|
||||
self.souts = list()
|
||||
self.updated = datetime.now()
|
||||
self.datas = datas
|
||||
threading.Thread.__init__(self)
|
||||
|
||||
def getPage(self):
|
||||
conn = http.client.HTTPSConnection(CONF.getNode("server")["ip"], timeout=10)
|
||||
try:
|
||||
conn.request("GET", CONF.getNode("server")["url"])
|
||||
|
||||
res = conn.getresponse()
|
||||
page = res.read()
|
||||
except:
|
||||
print ("[%s] impossible de récupérer la page %s."%(s, p))
|
||||
return ""
|
||||
conn.close()
|
||||
return page
|
||||
|
||||
def parsePage(self, page):
|
||||
save = False
|
||||
for line in page.split("\n"):
|
||||
if re.match("</tr>", line) is not None:
|
||||
save = False
|
||||
elif re.match("<tr.*>", line) is not None:
|
||||
save = True
|
||||
last = Soutenance()
|
||||
self.souts.append(last)
|
||||
elif save:
|
||||
result = re.match("<td[^>]+>(.*)</td>", line)
|
||||
if last.hour is None:
|
||||
try:
|
||||
last.hour = datetime.fromtimestamp(time.mktime(time.strptime(result.group(1), "%Y-%m-%d %H:%M")))
|
||||
except ValueError:
|
||||
continue
|
||||
elif last.rank == 0:
|
||||
last.rank = int (result.group(1))
|
||||
elif last.login == None:
|
||||
last.login = result.group(1)
|
||||
elif last.state == None:
|
||||
last.state = result.group(1)
|
||||
elif last.assistant == None:
|
||||
last.assistant = result.group(1)
|
||||
elif last.start == None:
|
||||
try:
|
||||
last.start = datetime.fromtimestamp(time.mktime(time.strptime(result.group(1), "%Y-%m-%d %H:%M")))
|
||||
except ValueError:
|
||||
last.start = None
|
||||
elif last.end == None:
|
||||
try:
|
||||
last.end = datetime.fromtimestamp(time.mktime(time.strptime(result.group(1), "%Y-%m-%d %H:%M")))
|
||||
except ValueError:
|
||||
last.end = None
|
||||
|
||||
def gen_response(self, req, msg):
|
||||
"""Generate a text response on right server and channel"""
|
||||
return Response(req["sender"], msg, req["channel"], server=req["server"])
|
||||
|
||||
def res_next(self, req):
|
||||
soutenance = self.findLast()
|
||||
if soutenance is None:
|
||||
return self.gen_response(req, "Il ne semble pas y avoir de soutenance pour le moment.")
|
||||
else:
|
||||
if soutenance.start > soutenance.hour:
|
||||
avre = "%s de *retard*"%msg.just_countdown(soutenance.start - soutenance.hour, 4)
|
||||
else:
|
||||
avre = "%s *d'avance*"%msg.just_countdown(soutenance.hour - soutenance.start, 4)
|
||||
self.gen_response(req, "Actuellement à la soutenance numéro %d, commencée il y a %s avec %s."%(soutenance.rank, msg.just_countdown(datetime.now () - soutenance.start, 4), avre))
|
||||
|
||||
def res_assistants(self, req):
|
||||
assistants = self.findAssistants()
|
||||
if len(assistants) > 0:
|
||||
return self.gen_response(req, "Les %d assistants faisant passer les soutenances sont : %s." % (len(assistants), ', '.join(assistants.keys())))
|
||||
else:
|
||||
return self.gen_response(req, "Il ne semble pas y avoir de soutenance pour le moment.")
|
||||
|
||||
def res_soutenance(self, req):
|
||||
name = req["user"]
|
||||
|
||||
if name == "acu" or name == "yaka" or name == "acus" or name == "yakas" or name == "assistant" or name == "assistants":
|
||||
return self.res_assistants(req)
|
||||
elif name == "next":
|
||||
return self.res_next(req)
|
||||
|
||||
soutenance = self.findClose(name)
|
||||
if soutenance is None:
|
||||
return self.gen_response(req, "Pas d'horaire de soutenance pour %s."%name)
|
||||
else:
|
||||
if soutenance.state == "En cours":
|
||||
return self.gen_response(req, "%s est actuellement en soutenance avec %s. Elle était prévue à %s, position %d."%(name, soutenance.assistant, soutenance.hour, soutenance.rank))
|
||||
elif soutenance.state == "Effectue":
|
||||
return self.gen_response(req, "%s a passé sa soutenance avec %s. Elle a duré %s."%(name, soutenance.assistant, msg.just_countdown(soutenance.end - soutenance.start, 4)))
|
||||
elif soutenance.state == "Retard":
|
||||
return self.gen_response(req, "%s était en retard à sa soutenance de %s."%(name, soutenance.hour))
|
||||
else:
|
||||
last = self.findLast()
|
||||
if last is not None:
|
||||
if soutenance.hour + (last.start - last.hour) > datetime.now ():
|
||||
return self.gen_response(req, "Soutenance de %s : %s, position %d ; estimation du passage : dans %s."%(name, soutenance.hour, soutenance.rank, msg.just_countdown((soutenance.hour - datetime.now ()) + (last.start - last.hour))))
|
||||
else:
|
||||
return self.gen_response(req, "Soutenance de %s : %s, position %d ; passage imminent."%(name, soutenance.hour, soutenance.rank))
|
||||
else:
|
||||
return self.gen_response(req, "Soutenance de %s : %s, position %d."%(name, soutenance.hour, soutenance.rank))
|
||||
|
||||
def res_list(self, req):
|
||||
name = req["user"]
|
||||
|
||||
souts = self.findAll(name)
|
||||
if souts is None:
|
||||
self.gen_response(req, "Pas de soutenance prévues pour %s."%name)
|
||||
else:
|
||||
first = True
|
||||
for s in souts:
|
||||
if first:
|
||||
self.gen_response(req, "Soutenance(s) de %s : - %s (position %d) ;"%(name, s.hour, s.rank))
|
||||
first = False
|
||||
else:
|
||||
self.gen_response(req, " %s - %s (position %d) ;"%(len(name)*' ', s.hour, s.rank))
|
||||
|
||||
def run(self):
|
||||
self.parsePage(self.getPage().decode())
|
||||
res = list()
|
||||
for u in self.datas.getNodes("request"):
|
||||
res.append(self.res_soutenance(u))
|
||||
return res
|
||||
|
||||
def needUpdate(self):
|
||||
if self.findLast() is not None and datetime.now () - self.updated > timedelta(minutes=2):
|
||||
return True
|
||||
elif datetime.now () - self.updated < timedelta(hours=1):
|
||||
return False
|
||||
else:
|
||||
return True
|
||||
|
||||
def findAssistants(self):
|
||||
h = dict()
|
||||
for s in self.souts:
|
||||
if s.assistant is not None and s.assistant != "":
|
||||
h[s.assistant] = (s.start, s.end)
|
||||
return h
|
||||
|
||||
def findLast(self):
|
||||
close = None
|
||||
for s in self.souts:
|
||||
if (s.state != "En attente" and s.start is not None and (close is None or close.rank < s.rank or close.hour.day > s.hour.day)) and (close is None or s.hour - close.hour < timedelta(seconds=2499)):
|
||||
close = s
|
||||
return close
|
||||
|
||||
def findAll(self, login):
|
||||
ss = list()
|
||||
for s in self.souts:
|
||||
if s.login == login:
|
||||
ss.append(s)
|
||||
return ss
|
||||
|
||||
def findClose(self, login):
|
||||
ss = self.findAll(login)
|
||||
close = None
|
||||
for s in ss:
|
||||
if close is not None:
|
||||
print (close.hour)
|
||||
print (s.hour)
|
||||
if close is None or (close.hour < s.hour and close.hour.day >= datetime.datetime().day):
|
||||
close = s
|
||||
return close
|
||||
11
modules/soutenance/Soutenance.py
Normal file
11
modules/soutenance/Soutenance.py
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
# coding=utf-8
|
||||
|
||||
class Soutenance:
|
||||
def __init__(self):
|
||||
self.hour = None
|
||||
self.rank = 0
|
||||
self.login = None
|
||||
self.state = None
|
||||
self.assistant = None
|
||||
self.start = None
|
||||
self.end = None
|
||||
48
modules/soutenance/__init__.py
Normal file
48
modules/soutenance/__init__.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# coding=utf-8
|
||||
|
||||
import time
|
||||
import re
|
||||
import threading
|
||||
from datetime import date
|
||||
from datetime import datetime
|
||||
|
||||
from . import SiteSoutenances
|
||||
|
||||
nemubotversion = 3.3
|
||||
|
||||
def help_tiny():
|
||||
"""Line inserted in the response to the command !help"""
|
||||
return "EPITA ING1 defenses module"
|
||||
|
||||
def help_full():
|
||||
return "!soutenance: gives information about current defenses state\n!soutenance <who>: gives the date of the next defense of /who/.\n!soutenances <who>: gives all defense dates of /who/"
|
||||
|
||||
def load(context):
|
||||
global CONF
|
||||
SiteSoutenances.CONF = CONF
|
||||
|
||||
def ask_soutenance(msg):
|
||||
req = ModuleState("request")
|
||||
if len(msg.cmds) > 1:
|
||||
req.setAttribute("user", msg.cmds[1])
|
||||
else:
|
||||
req.setAttribute("user", "next")
|
||||
req.setAttribute("server", msg.server)
|
||||
req.setAttribute("channel", msg.channel)
|
||||
req.setAttribute("sender", msg.sender)
|
||||
|
||||
#An instance of this module is already running?
|
||||
if not DATAS.hasAttribute("_running") or DATAS["_running"].needUpdate():
|
||||
DATAS.addChild(req)
|
||||
site = SiteSoutenances.SiteSoutenances(DATAS)
|
||||
DATAS.setAttribute("_running", site)
|
||||
|
||||
res = site.run()
|
||||
|
||||
for n in DATAS.getNodes("request"):
|
||||
DATAS.delChild(n)
|
||||
|
||||
return res
|
||||
else:
|
||||
site = DATAS["_running"]
|
||||
return site.res_soutenance(req)
|
||||
133
modules/speak.py
133
modules/speak.py
|
|
@ -1,133 +0,0 @@
|
|||
# coding=utf-8
|
||||
|
||||
from datetime import timedelta
|
||||
from queue import Queue
|
||||
import re
|
||||
import subprocess
|
||||
from threading import Thread
|
||||
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.message import Text
|
||||
from nemubot.message.visitor import AbstractVisitor
|
||||
|
||||
nemubotversion = 3.4
|
||||
|
||||
queue = Queue()
|
||||
spk_th = None
|
||||
last = None
|
||||
|
||||
SMILEY = list()
|
||||
CORRECTIONS = list()
|
||||
|
||||
def load(context):
|
||||
for smiley in context.config.getNodes("smiley"):
|
||||
if smiley.hasAttribute("txt") and smiley.hasAttribute("mood"):
|
||||
SMILEY.append((smiley.getAttribute("txt"), smiley.getAttribute("mood")))
|
||||
print ("%d smileys loaded" % len(SMILEY))
|
||||
|
||||
for correct in context.config.getNodes("correction"):
|
||||
if correct.hasAttribute("bad") and correct.hasAttribute("good"):
|
||||
CORRECTIONS.append((" " + (correct.getAttribute("bad") + " "), (" " + correct.getAttribute("good") + " ")))
|
||||
print ("%d corrections loaded" % len(CORRECTIONS))
|
||||
|
||||
|
||||
class Speaker(Thread):
|
||||
|
||||
def run(self):
|
||||
global queue, spk_th
|
||||
while not queue.empty():
|
||||
sentence = queue.get_nowait()
|
||||
lang = "fr"
|
||||
subprocess.call(["espeak", "-v", lang, "--", sentence])
|
||||
queue.task_done()
|
||||
|
||||
spk_th = None
|
||||
|
||||
|
||||
class SpeakerVisitor(AbstractVisitor):
|
||||
|
||||
def __init__(self, last):
|
||||
self.pp = ""
|
||||
self.last = last
|
||||
|
||||
|
||||
def visit_Text(self, msg):
|
||||
force = (self.last is None)
|
||||
|
||||
if force or msg.date - self.last.date > timedelta(0, 500):
|
||||
self.pp += "A %d heure %d : " % (msg.date.hour, msg.date.minute)
|
||||
force = True
|
||||
|
||||
if force or msg.channel != self.last.channel:
|
||||
if msg.to_response == msg.to:
|
||||
self.pp += "sur %s. " % (", ".join(msg.to))
|
||||
else:
|
||||
self.pp += "en message priver. "
|
||||
|
||||
action = False
|
||||
if msg.message.find("ACTION ") == 0:
|
||||
self.pp += "%s " % msg.frm
|
||||
msg.message = msg.message.replace("ACTION ", "")
|
||||
action = True
|
||||
for (txt, mood) in SMILEY:
|
||||
if msg.message.find(txt) >= 0:
|
||||
self.pp += "%s %s : " % (msg.frm, mood)
|
||||
msg.message = msg.message.replace(txt, "")
|
||||
action = True
|
||||
break
|
||||
|
||||
if not action and (force or msg.frm != self.last.frm):
|
||||
self.pp += "%s dit : " % msg.frm
|
||||
|
||||
if re.match(".*https?://.*", msg.message) is not None:
|
||||
msg.message = re.sub(r'https?://([^/]+)[^ ]*', " U.R.L \\1", msg.message)
|
||||
|
||||
self.pp += msg.message
|
||||
|
||||
|
||||
def visit_DirectAsk(self, msg):
|
||||
res = Text("%s: %s" % (msg.designated, msg.message),
|
||||
server=msg.server, date=msg.date,
|
||||
to=msg.to, frm=msg.frm)
|
||||
res.accept(self)
|
||||
|
||||
|
||||
def visit_Command(self, msg):
|
||||
res = Text("Bang %s%s%s" % (msg.cmd,
|
||||
" " if len(msg.args) else "",
|
||||
" ".join(msg.args)),
|
||||
server=msg.server, date=msg.date,
|
||||
to=msg.to, frm=msg.frm)
|
||||
res.accept(self)
|
||||
|
||||
|
||||
def visit_OwnerCommand(self, msg):
|
||||
res = Text("Owner Bang %s%s%s" % (msg.cmd,
|
||||
" " if len(msg.args) else "",
|
||||
" ".join(msg.args)),
|
||||
server=msg.server, date=msg.date,
|
||||
to=msg.to, frm=msg.frm)
|
||||
res.accept(self)
|
||||
|
||||
|
||||
@hook("in")
|
||||
def treat_for_speak(msg):
|
||||
if not msg.frm_owner:
|
||||
append_message(msg)
|
||||
|
||||
def append_message(msg):
|
||||
global last, spk_th
|
||||
|
||||
if hasattr(msg, "message") and msg.message.find("TYPING ") == 0:
|
||||
return
|
||||
if last is not None and last.message == msg.message:
|
||||
return
|
||||
|
||||
vprnt = SpeakerVisitor(last)
|
||||
msg.accept(vprnt)
|
||||
queue.put_nowait(vprnt.pp)
|
||||
last = msg
|
||||
|
||||
if spk_th is None:
|
||||
spk_th = Speaker()
|
||||
spk_th.start()
|
||||
|
|
@ -1,97 +1,89 @@
|
|||
"""Check words spelling"""
|
||||
# coding=utf-8
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
|
||||
from nemubot import context
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools.xmlparser.node import ModuleState
|
||||
import re
|
||||
from urllib.parse import quote
|
||||
|
||||
from .pyaspell import Aspell
|
||||
from .pyaspell import AspellError
|
||||
|
||||
from nemubot.module.more import Response
|
||||
nemubotversion = 3.3
|
||||
|
||||
def help_tiny ():
|
||||
return "Check words spelling"
|
||||
|
||||
# LOADING #############################################################
|
||||
def help_full ():
|
||||
return "!spell [<lang>] <word>: give the correct spelling of <word> in <lang=fr>."
|
||||
|
||||
def load(context):
|
||||
context.data.setIndex("name", "score")
|
||||
global DATAS
|
||||
DATAS.setIndex("name", "score")
|
||||
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_spell, "spell"))
|
||||
add_hook("cmd_hook", Hook(cmd_spell, "orthographe"))
|
||||
add_hook("cmd_hook", Hook(cmd_score, "spellscore"))
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def add_score(nick, t):
|
||||
if nick not in context.data.index:
|
||||
st = ModuleState("score")
|
||||
st["name"] = nick
|
||||
context.data.addChild(st)
|
||||
|
||||
if context.data.index[nick].hasAttribute(t):
|
||||
context.data.index[nick][t] = context.data.index[nick].getInt(t) + 1
|
||||
else:
|
||||
context.data.index[nick][t] = 1
|
||||
context.save()
|
||||
|
||||
|
||||
def check_spell(word, lang='fr'):
|
||||
a = Aspell([("lang", lang)])
|
||||
if a.check(word.encode("utf-8")):
|
||||
ret = True
|
||||
else:
|
||||
ret = a.suggest(word.encode("utf-8"))
|
||||
a.close()
|
||||
return ret
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("spell",
|
||||
help="give the correct spelling of given words",
|
||||
help_usage={"WORD": "give the correct spelling of the WORD."},
|
||||
keywords={"lang=": "change the language use for checking, default fr"})
|
||||
def cmd_spell(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indique une orthographe approximative du mot dont tu veux vérifier l'orthographe.")
|
||||
if len(msg.cmds) < 2:
|
||||
return Response(msg.sender, "Indiquer une orthographe approximative du mot dont vous voulez vérifier l'orthographe.", msg.channel)
|
||||
|
||||
lang = msg.kwargs["lang"] if "lang" in msg.kwargs else "fr"
|
||||
|
||||
res = Response(channel=msg.channel)
|
||||
for word in msg.args:
|
||||
lang = "fr"
|
||||
strRes = list()
|
||||
for word in msg.cmds[1:]:
|
||||
if len(word) <= 2 and len(msg.cmds) > 2:
|
||||
lang = word
|
||||
else:
|
||||
try:
|
||||
r = check_spell(word, lang)
|
||||
except AspellError:
|
||||
raise IMException("Je n'ai pas le dictionnaire `%s' :(" % lang)
|
||||
|
||||
return Response(msg.sender, "Je n'ai pas le dictionnaire `%s' :(" % lang, msg.channel)
|
||||
if r == True:
|
||||
add_score(msg.frm, "correct")
|
||||
res.append_message("l'orthographe de `%s' est correcte" % word)
|
||||
|
||||
add_score(msg.nick, "correct")
|
||||
strRes.append("l'orthographe de `%s' est correcte" % word)
|
||||
elif len(r) > 0:
|
||||
add_score(msg.frm, "bad")
|
||||
res.append_message(r, title="suggestions pour `%s'" % word)
|
||||
|
||||
add_score(msg.nick, "bad")
|
||||
strRes.append("suggestions pour `%s' : %s" % (word, ", ".join(r)))
|
||||
else:
|
||||
add_score(msg.frm, "bad")
|
||||
res.append_message("aucune suggestion pour `%s'" % word)
|
||||
add_score(msg.nick, "bad")
|
||||
strRes.append("aucune suggestion pour `%s'" % word)
|
||||
return Response(msg.sender, strRes, channel=msg.channel)
|
||||
|
||||
return res
|
||||
def add_score(nick, t):
|
||||
global DATAS
|
||||
if nick not in DATAS.index:
|
||||
st = ModuleState("score")
|
||||
st["name"] = nick
|
||||
DATAS.addChild(st)
|
||||
|
||||
if DATAS.index[nick].hasAttribute(t):
|
||||
DATAS.index[nick][t] = DATAS.index[nick].getInt(t) + 1
|
||||
else:
|
||||
DATAS.index[nick][t] = 1
|
||||
save()
|
||||
|
||||
@hook.command("spellscore",
|
||||
help="Show spell score (tests, mistakes, ...) for someone",
|
||||
help_usage={"USER": "Display score of USER"})
|
||||
def cmd_score(msg):
|
||||
global DATAS
|
||||
res = list()
|
||||
unknown = list()
|
||||
if not len(msg.args):
|
||||
raise IMException("De qui veux-tu voir les scores ?")
|
||||
for cmd in msg.args:
|
||||
if cmd in context.data.index:
|
||||
res.append(Response("%s: %s" % (cmd, " ; ".join(["%s: %d" % (a, context.data.index[cmd].getInt(a)) for a in context.data.index[cmd].attributes.keys() if a != "name"])), channel=msg.channel))
|
||||
if len(msg.cmds) > 1:
|
||||
for cmd in msg.cmds[1:]:
|
||||
if cmd in DATAS.index:
|
||||
res.append(Response(msg.sender, "%s: %s" % (cmd, " ; ".join(["%s: %d" % (a, DATAS.index[cmd].getInt(a)) for a in DATAS.index[cmd].attributes.keys() if a != "name"])), channel=msg.channel))
|
||||
else:
|
||||
unknown.append(cmd)
|
||||
else:
|
||||
return Response(msg.sender, "De qui veux-tu voir les scores ?", channel=msg.channel, nick=msg.nick)
|
||||
if len(unknown) > 0:
|
||||
res.append(Response("%s inconnus" % ", ".join(unknown), channel=msg.channel))
|
||||
res.append(Response(msg.sender, "%s inconnus" % ", ".join(unknown), channel=msg.channel))
|
||||
|
||||
return res
|
||||
|
||||
def check_spell(word, lang='fr'):
|
||||
a = Aspell([("lang", lang), ("lang", "fr")])
|
||||
if a.check(word.encode("iso-8859-15")):
|
||||
ret = True
|
||||
else:
|
||||
ret = a.suggest(word.encode("iso-8859-15"))
|
||||
a.close()
|
||||
return ret
|
||||
|
|
|
|||
332
modules/suivi.py
332
modules/suivi.py
|
|
@ -1,332 +0,0 @@
|
|||
"""Postal tracking module"""
|
||||
|
||||
# PYTHON STUFF ############################################
|
||||
|
||||
import json
|
||||
import urllib.parse
|
||||
from bs4 import BeautifulSoup
|
||||
import re
|
||||
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.tools.web import getURLContent, getURLHeaders, getJSON
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# POSTAGE SERVICE PARSERS ############################################
|
||||
|
||||
def get_tnt_info(track_id):
|
||||
values = []
|
||||
data = getURLContent('https://www.tnt.fr/public/suivi_colis/recherche/visubontransport.do?bonTransport=%s' % track_id)
|
||||
soup = BeautifulSoup(data)
|
||||
status_list = soup.find('div', class_='result__content')
|
||||
if not status_list:
|
||||
return None
|
||||
last_status = status_list.find('div', class_='roster')
|
||||
if last_status:
|
||||
for info in last_status.find_all('div', class_='roster__item'):
|
||||
values.append(info.get_text().strip())
|
||||
if len(values) == 3:
|
||||
return (values[0], values[1], values[2])
|
||||
|
||||
|
||||
def get_colissimo_info(colissimo_id):
|
||||
colissimo_data = getURLContent("https://www.laposte.fr/particulier/outils/suivre-vos-envois?code=%s" % colissimo_id)
|
||||
soup = BeautifulSoup(colissimo_data)
|
||||
|
||||
dataArray = soup.find(class_='results-suivi')
|
||||
if dataArray and dataArray.table and dataArray.table.tbody and dataArray.table.tbody.tr:
|
||||
td = dataArray.table.tbody.tr.find_all('td')
|
||||
if len(td) > 2:
|
||||
date = td[0].get_text()
|
||||
libelle = re.sub(r'[\n\t\r]', '', td[1].get_text())
|
||||
site = td[2].get_text().strip()
|
||||
return (date, libelle, site.strip())
|
||||
|
||||
|
||||
def get_chronopost_info(track_id):
|
||||
data = urllib.parse.urlencode({'listeNumeros': track_id})
|
||||
track_baseurl = "https://www.chronopost.fr/expedier/inputLTNumbersNoJahia.do?lang=fr_FR"
|
||||
track_data = getURLContent(track_baseurl, data.encode('utf-8'))
|
||||
soup = BeautifulSoup(track_data)
|
||||
|
||||
infoClass = soup.find(class_='numeroColi2')
|
||||
if infoClass and infoClass.get_text():
|
||||
info = infoClass.get_text().split("\n")
|
||||
if len(info) >= 1:
|
||||
info = info[1].strip().split("\"")
|
||||
if len(info) >= 2:
|
||||
date = info[2]
|
||||
libelle = info[1]
|
||||
return (date, libelle)
|
||||
|
||||
|
||||
def get_colisprive_info(track_id):
|
||||
data = urllib.parse.urlencode({'numColis': track_id})
|
||||
track_baseurl = "https://www.colisprive.com/moncolis/pages/detailColis.aspx"
|
||||
track_data = getURLContent(track_baseurl, data.encode('utf-8'))
|
||||
soup = BeautifulSoup(track_data)
|
||||
|
||||
dataArray = soup.find(class_='BandeauInfoColis')
|
||||
if (dataArray and dataArray.find(class_='divStatut')
|
||||
and dataArray.find(class_='divStatut').find(class_='tdText')):
|
||||
status = dataArray.find(class_='divStatut') \
|
||||
.find(class_='tdText').get_text()
|
||||
return status
|
||||
|
||||
|
||||
def get_ups_info(track_id):
|
||||
data = json.dumps({'Locale': "en_US", 'TrackingNumber': [track_id]})
|
||||
track_baseurl = "https://www.ups.com/track/api/Track/GetStatus?loc=en_US"
|
||||
track_data = getJSON(track_baseurl, data.encode('utf-8'), header={"Content-Type": "application/json"})
|
||||
return (track_data["trackDetails"][0]["trackingNumber"],
|
||||
track_data["trackDetails"][0]["packageStatus"],
|
||||
track_data["trackDetails"][0]["shipmentProgressActivities"][0]["date"] + " " + track_data["trackDetails"][0]["shipmentProgressActivities"][0]["time"],
|
||||
track_data["trackDetails"][0]["shipmentProgressActivities"][0]["location"],
|
||||
track_data["trackDetails"][0]["shipmentProgressActivities"][0]["activityScan"])
|
||||
|
||||
|
||||
def get_laposte_info(laposte_id):
|
||||
status, laposte_headers = getURLHeaders("https://www.laposte.fr/outils/suivre-vos-envois?" + urllib.parse.urlencode({'code': laposte_id}))
|
||||
|
||||
laposte_cookie = None
|
||||
for k,v in laposte_headers:
|
||||
if k.lower() == "set-cookie" and v.find("access_token") >= 0:
|
||||
laposte_cookie = v.split(";")[0]
|
||||
|
||||
laposte_data = getJSON("https://api.laposte.fr/ssu/v1/suivi-unifie/idship/%s?lang=fr_FR" % urllib.parse.quote(laposte_id), header={"Accept": "application/json", "Cookie": laposte_cookie})
|
||||
|
||||
shipment = laposte_data["shipment"]
|
||||
return (shipment["product"], shipment["idShip"], shipment["event"][0]["label"], shipment["event"][0]["date"])
|
||||
|
||||
|
||||
def get_postnl_info(postnl_id):
|
||||
data = urllib.parse.urlencode({'barcodes': postnl_id})
|
||||
postnl_baseurl = "http://www.postnl.post/details/"
|
||||
|
||||
postnl_data = getURLContent(postnl_baseurl, data.encode('utf-8'))
|
||||
soup = BeautifulSoup(postnl_data)
|
||||
if (soup.find(id='datatables')
|
||||
and soup.find(id='datatables').tbody
|
||||
and soup.find(id='datatables').tbody.tr):
|
||||
search_res = soup.find(id='datatables').tbody.tr
|
||||
if len(search_res.find_all('td')) >= 3:
|
||||
field = field.find_next('td')
|
||||
post_date = field.get_text()
|
||||
|
||||
field = field.find_next('td')
|
||||
post_status = field.get_text()
|
||||
|
||||
field = field.find_next('td')
|
||||
post_destination = field.get_text()
|
||||
|
||||
return (post_status.lower(), post_destination, post_date)
|
||||
|
||||
|
||||
def get_usps_info(usps_id):
|
||||
usps_parcelurl = "https://tools.usps.com/go/TrackConfirmAction_input?" + urllib.parse.urlencode({'qtc_tLabels1': usps_id})
|
||||
|
||||
usps_data = getURLContent(usps_parcelurl)
|
||||
soup = BeautifulSoup(usps_data)
|
||||
if (soup.find(id="trackingHistory_1")
|
||||
and soup.find(class_="tracking_history").find(class_="row_notification")
|
||||
and soup.find(class_="tracking_history").find(class_="row_top").find_all("td")):
|
||||
notification = soup.find(class_="tracking_history").find(class_="row_notification").text.strip()
|
||||
date = re.sub(r"\s+", " ", soup.find(class_="tracking_history").find(class_="row_top").find_all("td")[0].text.strip())
|
||||
status = soup.find(class_="tracking_history").find(class_="row_top").find_all("td")[1].text.strip()
|
||||
last_location = soup.find(class_="tracking_history").find(class_="row_top").find_all("td")[2].text.strip()
|
||||
|
||||
print(notification)
|
||||
|
||||
return (notification, date, status, last_location)
|
||||
|
||||
|
||||
def get_fedex_info(fedex_id, lang="en_US"):
|
||||
data = urllib.parse.urlencode({
|
||||
'data': json.dumps({
|
||||
"TrackPackagesRequest": {
|
||||
"appType": "WTRK",
|
||||
"appDeviceType": "DESKTOP",
|
||||
"uniqueKey": "",
|
||||
"processingParameters": {},
|
||||
"trackingInfoList": [
|
||||
{
|
||||
"trackNumberInfo": {
|
||||
"trackingNumber": str(fedex_id),
|
||||
"trackingQualifier": "",
|
||||
"trackingCarrier": ""
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}),
|
||||
'action': "trackpackages",
|
||||
'locale': lang,
|
||||
'version': 1,
|
||||
'format': "json"
|
||||
})
|
||||
fedex_baseurl = "https://www.fedex.com/trackingCal/track"
|
||||
|
||||
fedex_data = getJSON(fedex_baseurl, data.encode('utf-8'))
|
||||
|
||||
if ("TrackPackagesResponse" in fedex_data and
|
||||
"packageList" in fedex_data["TrackPackagesResponse"] and
|
||||
len(fedex_data["TrackPackagesResponse"]["packageList"]) and
|
||||
(not fedex_data["TrackPackagesResponse"]["errorList"][0]["code"] or
|
||||
fedex_data["TrackPackagesResponse"]["errorList"][0]["code"] == '0') and
|
||||
not fedex_data["TrackPackagesResponse"]["packageList"][0]["errorList"][0]["code"]
|
||||
):
|
||||
return fedex_data["TrackPackagesResponse"]["packageList"][0]
|
||||
|
||||
|
||||
def get_dhl_info(dhl_id, lang="en"):
|
||||
dhl_parcelurl = "http://www.dhl.com/shipmentTracking?" + urllib.parse.urlencode({'AWB': dhl_id})
|
||||
|
||||
dhl_data = getJSON(dhl_parcelurl)
|
||||
|
||||
if "results" in dhl_data and dhl_data["results"]:
|
||||
return dhl_data["results"][0]
|
||||
|
||||
|
||||
# TRACKING HANDLERS ###################################################
|
||||
|
||||
def handle_tnt(tracknum):
|
||||
info = get_tnt_info(tracknum)
|
||||
if info:
|
||||
status, date, place = info
|
||||
placestr = ''
|
||||
if place:
|
||||
placestr = ' à \x02{place}\x0f'
|
||||
return ('Le colis \x02{trackid}\x0f a actuellement le status: '
|
||||
'\x02{status}\x0F mis à jour le \x02{date}\x0f{place}.'
|
||||
.format(trackid=tracknum, status=status,
|
||||
date=re.sub(r'\s+', ' ', date), place=placestr))
|
||||
|
||||
|
||||
def handle_laposte(tracknum):
|
||||
info = get_laposte_info(tracknum)
|
||||
if info:
|
||||
poste_type, poste_id, poste_status, poste_date = info
|
||||
return ("\x02%s\x0F : \x02%s\x0F est actuellement "
|
||||
"\x02%s\x0F (Mis à jour le \x02%s\x0F"
|
||||
")." % (poste_type, poste_id, poste_status, poste_date))
|
||||
|
||||
|
||||
def handle_postnl(tracknum):
|
||||
info = get_postnl_info(tracknum)
|
||||
if info:
|
||||
post_status, post_destination, post_date = info
|
||||
return ("PostNL \x02%s\x0F est actuellement "
|
||||
"\x02%s\x0F vers le pays \x02%s\x0F (Mis à jour le \x02%s\x0F"
|
||||
")." % (tracknum, post_status, post_destination, post_date))
|
||||
|
||||
|
||||
def handle_usps(tracknum):
|
||||
info = get_usps_info(tracknum)
|
||||
if info:
|
||||
notif, last_date, last_status, last_location = info
|
||||
return ("USPS \x02{tracknum}\x0F: {last_status} in \x02{last_location}\x0F as of {last_date}: {notif}".format(tracknum=tracknum, notif=notif, last_date=last_date, last_status=last_status.lower(), last_location=last_location))
|
||||
|
||||
|
||||
def handle_ups(tracknum):
|
||||
info = get_ups_info(tracknum)
|
||||
if info:
|
||||
tracknum, status, last_date, last_location, last_status = info
|
||||
return ("UPS \x02{tracknum}\x0F: {status}: in \x02{last_location}\x0F as of {last_date}: {last_status}".format(tracknum=tracknum, status=status, last_date=last_date, last_status=last_status.lower(), last_location=last_location))
|
||||
|
||||
|
||||
def handle_colissimo(tracknum):
|
||||
info = get_colissimo_info(tracknum)
|
||||
if info:
|
||||
date, libelle, site = info
|
||||
return ("Colissimo: \x02%s\x0F : \x02%s\x0F Dernière mise à jour le "
|
||||
"\x02%s\x0F au site \x02%s\x0F."
|
||||
% (tracknum, libelle, date, site))
|
||||
|
||||
|
||||
def handle_chronopost(tracknum):
|
||||
info = get_chronopost_info(tracknum)
|
||||
if info:
|
||||
date, libelle = info
|
||||
return ("Colis Chronopost: \x02%s\x0F : \x02%s\x0F. Dernière mise à "
|
||||
"jour \x02%s\x0F." % (tracknum, libelle, date))
|
||||
|
||||
|
||||
def handle_coliprive(tracknum):
|
||||
info = get_colisprive_info(tracknum)
|
||||
if info:
|
||||
return ("Colis Privé: \x02%s\x0F : \x02%s\x0F." % (tracknum, info))
|
||||
|
||||
|
||||
def handle_fedex(tracknum):
|
||||
info = get_fedex_info(tracknum)
|
||||
if info:
|
||||
if info["displayActDeliveryDateTime"] != "":
|
||||
return ("{trackingCarrierDesc}: \x02{statusWithDetails}\x0F: in \x02{statusLocationCity}, {statusLocationCntryCD}\x0F, delivered on: {displayActDeliveryDateTime}.".format(**info))
|
||||
elif info["statusLocationCity"] != "":
|
||||
return ("{trackingCarrierDesc}: \x02{statusWithDetails}\x0F: estimated delivery: {displayEstDeliveryDateTime}.".format(**info))
|
||||
else:
|
||||
return ("{trackingCarrierDesc}: \x02{statusWithDetails}\x0F: in \x02{statusLocationCity}, {statusLocationCntryCD}\x0F, estimated delivery: {displayEstDeliveryDateTime}.".format(**info))
|
||||
|
||||
|
||||
def handle_dhl(tracknum):
|
||||
info = get_dhl_info(tracknum)
|
||||
if info:
|
||||
return "DHL {label} {id}: \x02{description}\x0F".format(**info)
|
||||
|
||||
|
||||
TRACKING_HANDLERS = {
|
||||
'laposte': handle_laposte,
|
||||
'postnl': handle_postnl,
|
||||
'colissimo': handle_colissimo,
|
||||
'chronopost': handle_chronopost,
|
||||
'coliprive': handle_coliprive,
|
||||
'tnt': handle_tnt,
|
||||
'fedex': handle_fedex,
|
||||
'dhl': handle_dhl,
|
||||
'usps': handle_usps,
|
||||
'ups': handle_ups,
|
||||
}
|
||||
|
||||
|
||||
# HOOKS ##############################################################
|
||||
|
||||
@hook.command("track",
|
||||
help="Track postage delivery",
|
||||
help_usage={
|
||||
"TRACKING_ID [...]": "Track the specified postage IDs on various tracking services."
|
||||
},
|
||||
keywords={
|
||||
"tracker=TRK": "Precise the tracker (default: all) among: " + ', '.join(TRACKING_HANDLERS)
|
||||
})
|
||||
def get_tracking_info(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("Renseignez un identifiant d'envoi.")
|
||||
|
||||
res = Response(channel=msg.channel, count=" (%d suivis supplémentaires)")
|
||||
|
||||
if 'tracker' in msg.kwargs:
|
||||
if msg.kwargs['tracker'] in TRACKING_HANDLERS:
|
||||
trackers = {
|
||||
msg.kwargs['tracker']: TRACKING_HANDLERS[msg.kwargs['tracker']]
|
||||
}
|
||||
else:
|
||||
raise IMException("No tracker named \x02{tracker}\x0F, please use"
|
||||
" one of the following: \x02{trackers}\x0F"
|
||||
.format(tracker=msg.kwargs['tracker'],
|
||||
trackers=', '
|
||||
.join(TRACKING_HANDLERS.keys())))
|
||||
else:
|
||||
trackers = TRACKING_HANDLERS
|
||||
|
||||
for tracknum in msg.args:
|
||||
for name, tracker in trackers.items():
|
||||
ret = tracker(tracknum)
|
||||
if ret:
|
||||
res.append_message(ret)
|
||||
break
|
||||
if not ret:
|
||||
res.append_message("L'identifiant \x02{id}\x0F semble incorrect,"
|
||||
" merci de vérifier son exactitude."
|
||||
.format(id=tracknum))
|
||||
return res
|
||||
142
modules/syno.py
142
modules/syno.py
|
|
@ -1,117 +1,61 @@
|
|||
"""Find synonyms"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
# coding=utf-8
|
||||
|
||||
import re
|
||||
import traceback
|
||||
import sys
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
from tools import web
|
||||
|
||||
from nemubot.module.more import Response
|
||||
nemubotversion = 3.3
|
||||
|
||||
def help_tiny ():
|
||||
return "Find french synonyms"
|
||||
|
||||
# LOADING #############################################################
|
||||
def help_full ():
|
||||
return "!syno <word>: give a list of synonyms for <word>."
|
||||
|
||||
def load(context):
|
||||
global lang_binding
|
||||
|
||||
if not context.config or not "bighugelabskey" in context.config:
|
||||
logger.error("You need a NigHugeLabs API key in order to have english "
|
||||
"theasorus. Add it to the module configuration file:\n"
|
||||
"<module name=\"syno\" bighugelabskey=\"XXXXXXXXXXXXXXXX\""
|
||||
" />\nRegister at https://words.bighugelabs.com/getkey.php")
|
||||
else:
|
||||
lang_binding["en"] = lambda word: get_english_synos(context.config["bighugelabskey"], word)
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_syno, "syno"))
|
||||
add_hook("cmd_hook", Hook(cmd_syno, "synonyme"))
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def get_french_synos(word):
|
||||
url = "https://crisco.unicaen.fr/des/synonymes/" + quote(word)
|
||||
page = web.getURLContent(url)
|
||||
|
||||
best = list(); synos = list(); anton = list()
|
||||
|
||||
if page is not None:
|
||||
for line in page.split("\n"):
|
||||
|
||||
if line.find("!-- Fin liste des antonymes --") > 0:
|
||||
for elt in re.finditer(">([^<>]+)</a>", line):
|
||||
anton.append(elt.group(1))
|
||||
|
||||
elif line.find("!--Fin liste des synonymes--") > 0:
|
||||
for elt in re.finditer(">([^<>]+)</a>", line):
|
||||
synos.append(elt.group(1))
|
||||
|
||||
elif re.match("[ \t]*<tr[^>]*>.*</tr>[ \t]*</table>.*", line) is not None:
|
||||
for elt in re.finditer(">&[^;]+;([^&]*)&[^;]+;<", line):
|
||||
best.append(elt.group(1))
|
||||
|
||||
return (best, synos, anton)
|
||||
|
||||
|
||||
def get_english_synos(key, word):
|
||||
cnt = web.getJSON("https://words.bighugelabs.com/api/2/%s/%s/json" %
|
||||
(quote(key), quote(word.encode("ISO-8859-1"))))
|
||||
|
||||
best = list(); synos = list(); anton = list()
|
||||
|
||||
if cnt is not None:
|
||||
for k, c in cnt.items():
|
||||
if "syn" in c: best += c["syn"]
|
||||
if "rel" in c: synos += c["rel"]
|
||||
if "ant" in c: anton += c["ant"]
|
||||
|
||||
return (best, synos, anton)
|
||||
|
||||
|
||||
lang_binding = { 'fr': get_french_synos }
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("synonymes", data="synonymes",
|
||||
help="give a list of synonyms",
|
||||
help_usage={"WORD": "give synonyms of the given WORD"},
|
||||
keywords={
|
||||
"lang=LANG": "change the dictionnary language: default fr, available: " + ", ".join(lang_binding)
|
||||
})
|
||||
@hook.command("antonymes", data="antonymes",
|
||||
help="give a list of antonyms",
|
||||
help_usage={"WORD": "give antonyms of the given WORD"},
|
||||
keywords={
|
||||
"lang=LANG": "change the dictionnary language: default fr, available: " + ", ".join(lang_binding)
|
||||
})
|
||||
def go(msg, what):
|
||||
if not len(msg.args):
|
||||
raise IMException("de quel mot veux-tu connaître la liste des synonymes ?")
|
||||
|
||||
lang = msg.kwargs["lang"] if "lang" in msg.kwargs else "fr"
|
||||
word = ' '.join(msg.args)
|
||||
|
||||
def cmd_syno(msg):
|
||||
if 1 < len(msg.cmds) < 6:
|
||||
for word in msg.cmds[1:]:
|
||||
try:
|
||||
best, synos, anton = lang_binding[lang](word)
|
||||
synos = get_synos(word)
|
||||
except:
|
||||
best, synos, anton = (list(), list(), list())
|
||||
synos = None
|
||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
||||
traceback.print_exception(exc_type, exc_value,
|
||||
exc_traceback)
|
||||
|
||||
if what == "synonymes":
|
||||
if len(synos) > 0 or len(best) > 0:
|
||||
res = Response(channel=msg.channel, title="Synonymes de %s" % word)
|
||||
if len(best) > 0: res.append_message(best)
|
||||
if len(synos) > 0: res.append_message(synos)
|
||||
return res
|
||||
if synos is None:
|
||||
return Response(msg.sender,
|
||||
"Une erreur s'est produite durant la recherche"
|
||||
" d'un synonyme de %s" % word, msg.channel)
|
||||
elif len(synos) > 0:
|
||||
return Response(msg.sender, synos, msg.channel,
|
||||
title="Synonymes de %s" % word)
|
||||
else:
|
||||
raise IMException("Aucun synonyme de %s n'a été trouvé" % word)
|
||||
return Response(msg.sender,
|
||||
"Aucun synonymes de %s n'a été trouvé" % word,
|
||||
msg.channel)
|
||||
return False
|
||||
|
||||
elif what == "antonymes":
|
||||
if len(anton) > 0:
|
||||
res = Response(anton, channel=msg.channel,
|
||||
title="Antonymes de %s" % word)
|
||||
return res
|
||||
else:
|
||||
raise IMException("Aucun antonyme de %s n'a été trouvé" % word)
|
||||
|
||||
def get_synos(word):
|
||||
url = "http://www.crisco.unicaen.fr/des/synonymes/" + quote(word.encode("ISO-8859-1"))
|
||||
print_debug (url)
|
||||
page = web.getURLContent(url)
|
||||
if page is not None:
|
||||
synos = list()
|
||||
for line in page.decode().split("\n"):
|
||||
if re.match("[ \t]*<tr[^>]*>.*</tr>[ \t]*</table>.*", line) is not None:
|
||||
for elt in re.finditer(">&[^;]+;([^&]*)&[^;]+;<", line):
|
||||
synos.append(elt.group(1))
|
||||
return synos
|
||||
else:
|
||||
raise IMException("WHAT?!")
|
||||
return None
|
||||
|
|
|
|||
|
|
@ -1,40 +0,0 @@
|
|||
from datetime import datetime
|
||||
import urllib
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import human
|
||||
from nemubot.tools.web import getJSON
|
||||
|
||||
nemubotversion = 4.0
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
URL_TPBAPI = None
|
||||
|
||||
def load(context):
|
||||
if not context.config or "url" not in context.config:
|
||||
raise ImportError("You need a TPB API in order to use the !tpb feature"
|
||||
". Add it to the module configuration file:\n<module"
|
||||
"name=\"tpb\" url=\"http://tpbapi.org/\" />\nSample "
|
||||
"API: "
|
||||
"https://gist.github.com/colona/07a925f183cfb47d5f20")
|
||||
global URL_TPBAPI
|
||||
URL_TPBAPI = context.config["url"]
|
||||
|
||||
@hook.command("tpb")
|
||||
def cmd_tpb(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("indicate an item to search!")
|
||||
|
||||
torrents = getJSON(URL_TPBAPI + urllib.parse.quote(" ".join(msg.args)))
|
||||
|
||||
res = Response(channel=msg.channel, nomore="No more torrents", count=" (%d more torrents)")
|
||||
|
||||
if torrents:
|
||||
for t in torrents:
|
||||
t["sizeH"] = human.size(t["size"])
|
||||
t["dateH"] = datetime.fromtimestamp(t["date"]).strftime('%Y-%m-%d %H:%M:%S')
|
||||
res.append_message("\x03\x02{title}\x03\x02 in {category}, {sizeH}; added at {dateH}; id: {id}; magnet:?xt=urn:btih:{magnet}&tr=udp%3A%2F%2Ftracker.openbittorrent.com%3A80&tr=udp%3A%2F%2Ftracker.publicbt.com%3A80&tr=udp%3A%2F%2Ftracker.istole.it%3A6969&tr=udp%3A%2F%2Fopen.demonii.com%3A1337".format(**t))
|
||||
|
||||
return res
|
||||
|
|
@ -1,111 +1,97 @@
|
|||
"""Translation module"""
|
||||
|
||||
# PYTHON STUFFS #######################################################
|
||||
# coding=utf-8
|
||||
|
||||
import http.client
|
||||
import re
|
||||
import socket
|
||||
import json
|
||||
from urllib.parse import quote
|
||||
|
||||
from nemubot.exception import IMException
|
||||
from nemubot.hooks import hook
|
||||
from nemubot.tools import web
|
||||
nemubotversion = 3.3
|
||||
|
||||
from nemubot.module.more import Response
|
||||
|
||||
|
||||
# GLOBALS #############################################################
|
||||
import xmlparser
|
||||
|
||||
LANG = ["ar", "zh", "cz", "en", "fr", "gr", "it",
|
||||
"ja", "ko", "pl", "pt", "ro", "es", "tr"]
|
||||
URL = "http://api.wordreference.com/0.8/%s/json/%%s%%s/%%s"
|
||||
|
||||
|
||||
# LOADING #############################################################
|
||||
|
||||
def load(context):
|
||||
if not context.config or "wrapikey" not in context.config:
|
||||
raise ImportError("You need a WordReference API key in order to use "
|
||||
"this module. Add it to the module configuration "
|
||||
"file:\n<module name=\"translate\" wrapikey=\"XXXXX\""
|
||||
" />\nRegister at http://"
|
||||
"www.wordreference.com/docs/APIregistration.aspx")
|
||||
global URL
|
||||
URL = URL % context.config["wrapikey"]
|
||||
from hooks import Hook
|
||||
add_hook("cmd_hook", Hook(cmd_translate, "translate"))
|
||||
add_hook("cmd_hook", Hook(cmd_translate, "traduction"))
|
||||
add_hook("cmd_hook", Hook(cmd_translate, "traduit"))
|
||||
add_hook("cmd_hook", Hook(cmd_translate, "traduire"))
|
||||
|
||||
|
||||
# MODULE CORE #########################################################
|
||||
|
||||
def meaning(entry):
|
||||
ret = list()
|
||||
if "sense" in entry and len(entry["sense"]) > 0:
|
||||
ret.append('« %s »' % entry["sense"])
|
||||
if "usage" in entry and len(entry["usage"]) > 0:
|
||||
ret.append(entry["usage"])
|
||||
if len(ret) > 0:
|
||||
return " as " + "/".join(ret)
|
||||
else:
|
||||
return ""
|
||||
|
||||
|
||||
def extract_traslation(entry):
|
||||
ret = list()
|
||||
for i in [ "FirstTranslation", "SecondTranslation", "ThirdTranslation", "FourthTranslation" ]:
|
||||
if i in entry:
|
||||
ret.append("\x03\x02%s\x03\x02%s" % (entry[i]["term"], meaning(entry[i])))
|
||||
if "Note" in entry and entry["Note"]:
|
||||
ret.append("note: %s" % entry["Note"])
|
||||
return ", ".join(ret)
|
||||
|
||||
|
||||
def translate(term, langFrom="en", langTo="fr"):
|
||||
wres = web.getJSON(URL % (langFrom, langTo, quote(term)))
|
||||
|
||||
if "Error" in wres:
|
||||
raise IMException(wres["Note"])
|
||||
|
||||
else:
|
||||
for k in sorted(wres.keys()):
|
||||
t = wres[k]
|
||||
if len(k) > 4 and k[:4] == "term":
|
||||
if "Entries" in t:
|
||||
ent = t["Entries"]
|
||||
else:
|
||||
ent = t["PrincipalTranslations"]
|
||||
|
||||
for i in sorted(ent.keys()):
|
||||
yield "Translation of %s%s: %s" % (
|
||||
ent[i]["OriginalTerm"]["term"],
|
||||
meaning(ent[i]["OriginalTerm"]),
|
||||
extract_traslation(ent[i]))
|
||||
|
||||
|
||||
# MODULE INTERFACE ####################################################
|
||||
|
||||
@hook.command("translate",
|
||||
help="Word translation using WordReference.com",
|
||||
help_usage={
|
||||
"TERM": "Found translation of TERM from/to english to/from <lang>."
|
||||
},
|
||||
keywords={
|
||||
"from=LANG": "language of the term you asked for translation between: en, " + ", ".join(LANG),
|
||||
"to=LANG": "language of the translated terms between: en, " + ", ".join(LANG),
|
||||
})
|
||||
def cmd_translate(msg):
|
||||
if not len(msg.args):
|
||||
raise IMException("which word would you translate?")
|
||||
|
||||
langFrom = msg.kwargs["from"] if "from" in msg.kwargs else "en"
|
||||
if "to" in msg.kwargs:
|
||||
langTo = msg.kwargs["to"]
|
||||
global LANG
|
||||
startWord = 1
|
||||
if msg.cmds[startWord] in LANG:
|
||||
langTo = msg.cmds[startWord]
|
||||
startWord += 1
|
||||
else:
|
||||
langTo = "fr" if langFrom == "en" else "en"
|
||||
langTo = "fr"
|
||||
if msg.cmds[startWord] in LANG:
|
||||
langFrom = langTo
|
||||
langTo = msg.cmds[startWord]
|
||||
startWord += 1
|
||||
else:
|
||||
if langTo == "en":
|
||||
langFrom = "fr"
|
||||
else:
|
||||
langFrom = "en"
|
||||
|
||||
if langFrom not in LANG or langTo not in LANG:
|
||||
raise IMException("sorry, I can only translate to or from: " + ", ".join(LANG))
|
||||
if langFrom != "en" and langTo != "en":
|
||||
raise IMException("sorry, I can only translate to or from english")
|
||||
(res, page) = getPage(' '.join(msg.cmds[startWord:]), langFrom, langTo)
|
||||
if res == http.client.OK:
|
||||
wres = json.loads(page.decode())
|
||||
if "Error" in wres:
|
||||
return Response(msg.sender, wres["Note"], msg.channel)
|
||||
else:
|
||||
start = "Traduction de %s : "%' '.join(msg.cmds[startWord:])
|
||||
if "Entries" in wres["term0"]:
|
||||
if "SecondTranslation" in wres["term0"]["Entries"]["0"]:
|
||||
return Response(msg.sender, start +
|
||||
wres["term0"]["Entries"]["0"]["FirstTranslation"]["term"] +
|
||||
" ; " +
|
||||
wres["term0"]["Entries"]["0"]["SecondTranslation"]["term"],
|
||||
msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, start +
|
||||
wres["term0"]["Entries"]["0"]["FirstTranslation"]["term"],
|
||||
msg.channel)
|
||||
elif "PrincipalTranslations" in wres["term0"]:
|
||||
if "1" in wres["term0"]["PrincipalTranslations"]:
|
||||
return Response(msg.sender, start +
|
||||
wres["term0"]["PrincipalTranslations"]["0"]["FirstTranslation"]["term"] +
|
||||
" ; " +
|
||||
wres["term0"]["PrincipalTranslations"]["1"]["FirstTranslation"]["term"],
|
||||
msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, start +
|
||||
wres["term0"]["PrincipalTranslations"]["0"]["FirstTranslation"]["term"],
|
||||
msg.channel)
|
||||
else:
|
||||
return Response(msg.sender, "Une erreur s'est produite durant la recherche"
|
||||
" d'une traduction de %s"
|
||||
% ' '.join(msg.cmds[startWord:]),
|
||||
msg.channel)
|
||||
|
||||
res = Response(channel=msg.channel,
|
||||
count=" (%d more meanings)",
|
||||
nomore="No more translation")
|
||||
for t in translate(" ".join(msg.args), langFrom=langFrom, langTo=langTo):
|
||||
res.append_message(t)
|
||||
return res
|
||||
|
||||
def getPage(terms, langfrom="fr", langto="en"):
|
||||
conn = http.client.HTTPConnection("api.wordreference.com", timeout=5)
|
||||
try:
|
||||
conn.request("GET", "/0.8/%s/json/%s%s/%s" % (
|
||||
CONF.getNode("wrapi")["key"], langfrom, langto, quote(terms)))
|
||||
except socket.gaierror:
|
||||
print ("impossible de récupérer la page WordReference.")
|
||||
return (http.client.INTERNAL_SERVER_ERROR, None)
|
||||
except (TypeError, KeyError):
|
||||
print ("You need a WordReference API key in order to use this module."
|
||||
" Add it to the module configuration file:\n<wrapi key=\"XXXXX\""
|
||||
" />\nRegister at "
|
||||
"http://www.wordreference.com/docs/APIregistration.aspx")
|
||||
return (http.client.INTERNAL_SERVER_ERROR, None)
|
||||
|
||||
res = conn.getresponse()
|
||||
data = res.read()
|
||||
|
||||
conn.close()
|
||||
return (res.status, data)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue