Compare commits
177 commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 27bd0c50c1 | |||
| 9d7c278d1a | |||
| 310f933091 | |||
| 26282cb81d | |||
| de2c37a54a | |||
| 622159f6b5 | |||
| 32ebe42f41 | |||
| ea0ec42a4b | |||
| 23f043673f | |||
| ac432fabcc | |||
| 38b5b1eabd | |||
| 84fef789b5 | |||
| 45a27b477d | |||
| a8472ecc29 | |||
| 861ca0afdd | |||
| 9f83e5b178 | |||
| 68c61f40d3 | |||
| 60a9ec92b7 | |||
| 8dd6b9d471 | |||
| 13c643fc19 | |||
| 5ec2f2997b | |||
| faa5759645 | |||
| 37a230e70e | |||
| 904ad72316 | |||
| f17f8b9dfa | |||
| d56c2396c0 | |||
| b369683914 | |||
| aee2da4122 | |||
| 1f2d297ea1 | |||
|
|
b72871a8c2 | ||
| 644a641b13 | |||
| 4499677d55 | |||
| 87b5ce842d | |||
| 144551a232 | |||
| 7854e8628f | |||
| 85c418bd06 | |||
| 20c19a72bc | |||
| 9417e2ba93 | |||
| 517bf21d25 | |||
| fa0f2e93ef | |||
| b349d22370 | |||
| 8605932702 | |||
| 10b8ce8940 | |||
| 445a66ea90 | |||
| 46541cb35e | |||
| 8224d11207 | |||
| 15064204d8 | |||
| 67dee382a6 | |||
| f1da640a5b | |||
| 2fd20d9002 | |||
| 31abcc97cf | |||
| 53fe00ed58 | |||
| 4a636b2b11 | |||
| 72bc8d3839 | |||
| 5578e8b86e | |||
| 3b99099b52 | |||
| cd6750154c | |||
| b8741bb1f7 | |||
| 125ae6ad0b | |||
| 015fb47d90 | |||
| 8a25ebb45b | |||
| 1887e481d2 | |||
| 342bb9acdc | |||
| 2af56e606a | |||
| 4275009dea | |||
| f520c67c89 | |||
| c3c7484792 | |||
| 4cd099e087 | |||
| d528746cb5 | |||
|
|
5cbad96492 | ||
| 226ee4e34e | |||
|
|
e0d7ef1314 | ||
|
|
99384ad6f7 | ||
|
|
ef4c119f1f | ||
|
|
a7f4ccc959 | ||
|
|
c23dc22ce2 | ||
| bb0e958118 | |||
| b15d18b3a5 | |||
| 5646850df1 | |||
| 30ec912162 | |||
| 28d4e507eb | |||
| 62cd92e1cb | |||
| 12ddf40ef4 | |||
| 7a4b27510c | |||
| 05d20ed6ee | |||
| f60de818f2 | |||
| 45fe5b2156 | |||
| e49312e63e | |||
| a11ccb2e39 | |||
| fde459c3ff | |||
| 694c54a6bc | |||
| 1dae3c713a | |||
| 89772ebce0 | |||
| aa81aa4e96 | |||
| 3c7ed176c0 | |||
| 6dda142188 | |||
| dcb44ca3f2 | |||
| 0a576410c7 | |||
| 128afb5914 | |||
| 39b7b1ae2f | |||
| 09462d0d90 | |||
| b8f4560780 | |||
| 281d81acc4 | |||
| 29817ba1c1 | |||
| b517cac4cf | |||
| f81349bbfd | |||
| ce012b7017 | |||
|
|
e3b6c3b85e | ||
| 39056cf358 | |||
| f16dedb320 | |||
| 171297b581 | |||
| 3267c3e2e1 | |||
| aad777058e | |||
| f633a3effe | |||
| bbfecdfced | |||
| 94ff951b2e | |||
| a5479d7b0d | |||
| 0a3744577d | |||
| 67cb3caa95 | |||
| 2265e1a096 | |||
| b6945cf81c | |||
| e8809b77d2 | |||
| 9d446cbd14 | |||
| cde4ee05f7 | |||
| ac0cf729f1 | |||
| 35e0890563 | |||
| 58c349eb2c | |||
| bcd57e61ea | |||
| 0be6ebcd4b | |||
| b4218478bd | |||
| 6ac9fc4857 | |||
| 8a96f7bee9 | |||
| 7791f24423 | |||
| b809451be2 | |||
| dbcc7c664f | |||
| 8de31d784b | |||
| f4216af7c7 | |||
| cf8e1cffc5 | |||
| 97a1385903 | |||
| 764e6f070b | |||
| 6d8dca211d | |||
| 1c21231f31 | |||
| 2a3cd07c63 | |||
| b5d5a67b2d | |||
| 4c11c5e215 | |||
| a0c3f6d2b3 | |||
| 7cf73fb84a | |||
| 6cd299ab60 | |||
| fc14c76b6d | |||
| 24eb9a6911 | |||
| 38fd9e5091 | |||
| a7d7013639 | |||
| 9dc385a32a | |||
| 150d069dfb | |||
| f160411f71 | |||
| b0678ceb84 | |||
| 57275f5735 | |||
| ec512fc540 | |||
| 7bc37617b0 | |||
| 1858a045cc | |||
| 1b108428c2 | |||
| 08a7701238 | |||
| be9492c151 | |||
| 679f50b730 | |||
| 2334bc502a | |||
| c3f2c89c7c | |||
| 920506c702 | |||
| aefc0bb534 | |||
| 52b3bfa945 | |||
| c8c9112b0f | |||
| 7a48dc2cef | |||
| 0efee0cb83 | |||
| 3301fb87c2 | |||
| 3f2b18cae8 | |||
| e9cea5d010 | |||
|
|
f2c44a1108 | ||
|
|
f15ebd7c02 |
119 changed files with 4256 additions and 2931 deletions
26
.drone.yml
Normal file
26
.drone.yml
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
3
.gitmodules
vendored
3
.gitmodules
vendored
|
|
@ -1,3 +0,0 @@
|
||||||
[submodule "modules/nextstop/external"]
|
|
||||||
path = modules/nextstop/external
|
|
||||||
url = git://github.com/nbr23/NextStop.git
|
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
language: python
|
language: python
|
||||||
python:
|
python:
|
||||||
- 3.3
|
|
||||||
- 3.4
|
- 3.4
|
||||||
- 3.5
|
- 3.5
|
||||||
|
- 3.6
|
||||||
|
- 3.7
|
||||||
- nightly
|
- nightly
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
|
|
|
||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
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" ]
|
||||||
|
|
@ -10,7 +10,7 @@ Requirements
|
||||||
*nemubot* requires at least Python 3.3 to work.
|
*nemubot* requires at least Python 3.3 to work.
|
||||||
|
|
||||||
Some modules (like `cve`, `nextstop` or `laposte`) require the
|
Some modules (like `cve`, `nextstop` or `laposte`) require the
|
||||||
[BeautifulSoup module](http://www.crummy.com/software/BeautifulSoup/),
|
[BeautifulSoup module](https://www.crummy.com/software/BeautifulSoup/),
|
||||||
but the core and framework has no dependency.
|
but the core and framework has no dependency.
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from nemubot.message import Command
|
||||||
from nemubot.tools.human import guess
|
from nemubot.tools.human import guess
|
||||||
from nemubot.tools.xmlparser.node import ModuleState
|
from nemubot.tools.xmlparser.node import ModuleState
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# LOADING #############################################################
|
# LOADING #############################################################
|
||||||
|
|
@ -76,7 +76,7 @@ def get_variable(name, msg=None):
|
||||||
elif name in context.data.getNode("variables").index:
|
elif name in context.data.getNode("variables").index:
|
||||||
return context.data.getNode("variables").index[name]["value"]
|
return context.data.getNode("variables").index[name]["value"]
|
||||||
else:
|
else:
|
||||||
return ""
|
return None
|
||||||
|
|
||||||
|
|
||||||
def list_variables(user=None):
|
def list_variables(user=None):
|
||||||
|
|
@ -108,12 +108,12 @@ def set_variable(name, value, creator):
|
||||||
context.save()
|
context.save()
|
||||||
|
|
||||||
|
|
||||||
def replace_variables(cnts, msg=None):
|
def replace_variables(cnts, msg):
|
||||||
"""Replace variables contained in the content
|
"""Replace variables contained in the content
|
||||||
|
|
||||||
Arguments:
|
Arguments:
|
||||||
cnt -- content where search variables
|
cnt -- content where search variables
|
||||||
msg -- optional message where pick some variables
|
msg -- Message where pick some variables
|
||||||
"""
|
"""
|
||||||
|
|
||||||
unsetCnt = list()
|
unsetCnt = list()
|
||||||
|
|
@ -122,12 +122,12 @@ def replace_variables(cnts, msg=None):
|
||||||
resultCnt = list()
|
resultCnt = list()
|
||||||
|
|
||||||
for cnt in cnts:
|
for cnt in cnts:
|
||||||
for res in re.findall("\\$\{(?P<name>[a-zA-Z0-9:]+)\}", cnt):
|
for res, name, default in re.findall("\\$\{(([a-zA-Z0-9:]+)(?:-([^}]+))?)\}", cnt):
|
||||||
rv = re.match("([0-9]+)(:([0-9]*))?", res)
|
rv = re.match("([0-9]+)(:([0-9]*))?", name)
|
||||||
if rv is not None:
|
if rv is not None:
|
||||||
varI = int(rv.group(1)) - 1
|
varI = int(rv.group(1)) - 1
|
||||||
if varI > len(msg.args):
|
if varI >= len(msg.args):
|
||||||
cnt = cnt.replace("${%s}" % res, "", 1)
|
cnt = cnt.replace("${%s}" % res, default, 1)
|
||||||
elif rv.group(2) is not None:
|
elif rv.group(2) is not None:
|
||||||
if rv.group(3) is not None and len(rv.group(3)):
|
if rv.group(3) is not None and len(rv.group(3)):
|
||||||
varJ = int(rv.group(3)) - 1
|
varJ = int(rv.group(3)) - 1
|
||||||
|
|
@ -142,9 +142,10 @@ def replace_variables(cnts, msg=None):
|
||||||
cnt = cnt.replace("${%s}" % res, msg.args[varI], 1)
|
cnt = cnt.replace("${%s}" % res, msg.args[varI], 1)
|
||||||
unsetCnt.append(varI)
|
unsetCnt.append(varI)
|
||||||
else:
|
else:
|
||||||
cnt = cnt.replace("${%s}" % res, get_variable(res), 1)
|
cnt = cnt.replace("${%s}" % res, get_variable(name) or default, 1)
|
||||||
resultCnt.append(cnt)
|
resultCnt.append(cnt)
|
||||||
|
|
||||||
|
# Remove used content
|
||||||
for u in sorted(set(unsetCnt), reverse=True):
|
for u in sorted(set(unsetCnt), reverse=True):
|
||||||
msg.args.pop(u)
|
msg.args.pop(u)
|
||||||
|
|
||||||
|
|
@ -184,7 +185,7 @@ def cmd_listvars(msg):
|
||||||
def cmd_set(msg):
|
def cmd_set(msg):
|
||||||
if len(msg.args) < 2:
|
if len(msg.args) < 2:
|
||||||
raise IMException("!set take two args: the key and the value.")
|
raise IMException("!set take two args: the key and the value.")
|
||||||
set_variable(msg.args[0], " ".join(msg.args[1:]), msg.nick)
|
set_variable(msg.args[0], " ".join(msg.args[1:]), msg.frm)
|
||||||
return Response("Variable $%s successfully defined." % msg.args[0],
|
return Response("Variable $%s successfully defined." % msg.args[0],
|
||||||
channel=msg.channel)
|
channel=msg.channel)
|
||||||
|
|
||||||
|
|
@ -221,13 +222,13 @@ def cmd_alias(msg):
|
||||||
|
|
||||||
if alias.cmd in context.data.getNode("aliases").index:
|
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"]),
|
return Response("%s corresponds to %s" % (alias.cmd, context.data.getNode("aliases").index[alias.cmd]["origin"]),
|
||||||
channel=msg.channel, nick=msg.nick)
|
channel=msg.channel, nick=msg.frm)
|
||||||
|
|
||||||
elif len(msg.args) > 1:
|
elif len(msg.args) > 1:
|
||||||
create_alias(alias.cmd,
|
create_alias(alias.cmd,
|
||||||
" ".join(msg.args[1:]),
|
" ".join(msg.args[1:]),
|
||||||
channel=msg.channel,
|
channel=msg.channel,
|
||||||
creator=msg.nick)
|
creator=msg.frm)
|
||||||
return Response("New alias %s successfully registered." % alias.cmd,
|
return Response("New alias %s successfully registered." % alias.cmd,
|
||||||
channel=msg.channel)
|
channel=msg.channel)
|
||||||
|
|
||||||
|
|
@ -259,19 +260,18 @@ def cmd_unalias(msg):
|
||||||
|
|
||||||
@hook.add(["pre","Command"])
|
@hook.add(["pre","Command"])
|
||||||
def treat_alias(msg):
|
def treat_alias(msg):
|
||||||
if msg.cmd in context.data.getNode("aliases").index:
|
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"]
|
origin = context.data.getNode("aliases").index[msg.cmd]["origin"]
|
||||||
rpl_cmd = context.subparse(msg, origin)
|
rpl_msg = context.subparse(msg, origin)
|
||||||
if isinstance(rpl_cmd, Command):
|
if isinstance(rpl_msg, Command):
|
||||||
rpl_cmd.args = replace_variables(rpl_cmd.args, msg)
|
rpl_msg.args = replace_variables(rpl_msg.args, msg)
|
||||||
rpl_cmd.args += msg.args
|
rpl_msg.args += msg.args
|
||||||
rpl_cmd.kwargs.update(msg.kwargs)
|
rpl_msg.kwargs.update(msg.kwargs)
|
||||||
elif len(msg.args) or len(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 '!'?).")
|
raise IMException("This kind of alias doesn't take any argument (haven't you forgotten the '!'?).")
|
||||||
|
|
||||||
# Avoid infinite recursion
|
# Avoid infinite recursion
|
||||||
if msg.cmd != rpl_cmd.cmd:
|
if not isinstance(rpl_msg, Command) or msg.cmd != rpl_msg.cmd:
|
||||||
# Also return origin message, if it can be treated as well
|
return rpl_msg
|
||||||
return [msg, rpl_cmd]
|
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from nemubot.tools.countdown import countdown_format
|
||||||
from nemubot.tools.date import extractDate
|
from nemubot.tools.date import extractDate
|
||||||
from nemubot.tools.xmlparser.node import ModuleState
|
from nemubot.tools.xmlparser.node import ModuleState
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# LOADING #############################################################
|
# LOADING #############################################################
|
||||||
|
|
@ -27,7 +27,7 @@ def load(context):
|
||||||
def findName(msg):
|
def findName(msg):
|
||||||
if (not len(msg.args) or msg.args[0].lower() == "moi" or
|
if (not len(msg.args) or msg.args[0].lower() == "moi" or
|
||||||
msg.args[0].lower() == "me"):
|
msg.args[0].lower() == "me"):
|
||||||
name = msg.nick.lower()
|
name = msg.frm.lower()
|
||||||
else:
|
else:
|
||||||
name = msg.args[0].lower()
|
name = msg.args[0].lower()
|
||||||
|
|
||||||
|
|
@ -77,7 +77,7 @@ def cmd_anniv(msg):
|
||||||
else:
|
else:
|
||||||
return Response("désolé, je ne connais pas la date d'anniversaire"
|
return Response("désolé, je ne connais pas la date d'anniversaire"
|
||||||
" de %s. Quand est-il né ?" % name,
|
" de %s. Quand est-il né ?" % name,
|
||||||
msg.channel, msg.nick)
|
msg.channel, msg.frm)
|
||||||
|
|
||||||
|
|
||||||
@hook.command("age",
|
@hook.command("age",
|
||||||
|
|
@ -98,7 +98,7 @@ def cmd_age(msg):
|
||||||
msg.channel)
|
msg.channel)
|
||||||
else:
|
else:
|
||||||
return Response("désolé, je ne connais pas l'âge de %s."
|
return Response("désolé, je ne connais pas l'âge de %s."
|
||||||
" Quand est-il né ?" % name, msg.channel, msg.nick)
|
" Quand est-il né ?" % name, msg.channel, msg.frm)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -106,18 +106,18 @@ def cmd_age(msg):
|
||||||
|
|
||||||
@hook.ask()
|
@hook.ask()
|
||||||
def parseask(msg):
|
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.text, re.I)
|
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:
|
if res is not None:
|
||||||
try:
|
try:
|
||||||
extDate = extractDate(msg.text)
|
extDate = extractDate(msg.message)
|
||||||
if extDate is None or extDate.year > datetime.now().year:
|
if extDate is None or extDate.year > datetime.now().year:
|
||||||
return Response("la date de naissance ne paraît pas valide...",
|
return Response("la date de naissance ne paraît pas valide...",
|
||||||
msg.channel,
|
msg.channel,
|
||||||
msg.nick)
|
msg.frm)
|
||||||
else:
|
else:
|
||||||
nick = res.group(1)
|
nick = res.group(1)
|
||||||
if nick == "my" or nick == "I" or nick == "i" or nick == "je" or nick == "mon" or nick == "ma":
|
if nick == "my" or nick == "I" or nick == "i" or nick == "je" or nick == "mon" or nick == "ma":
|
||||||
nick = msg.nick
|
nick = msg.frm
|
||||||
if nick.lower() in context.data.index:
|
if nick.lower() in context.data.index:
|
||||||
context.data.index[nick.lower()]["born"] = extDate
|
context.data.index[nick.lower()]["born"] = extDate
|
||||||
else:
|
else:
|
||||||
|
|
@ -129,6 +129,6 @@ def parseask(msg):
|
||||||
return Response("ok, c'est noté, %s est né le %s"
|
return Response("ok, c'est noté, %s est né le %s"
|
||||||
% (nick, extDate.strftime("%A %d %B %Y à %H:%M")),
|
% (nick, extDate.strftime("%A %d %B %Y à %H:%M")),
|
||||||
msg.channel,
|
msg.channel,
|
||||||
msg.nick)
|
msg.frm)
|
||||||
except:
|
except:
|
||||||
raise IMException("la date de naissance ne paraît pas valide.")
|
raise IMException("la date de naissance ne paraît pas valide.")
|
||||||
|
|
|
||||||
|
|
@ -4,12 +4,11 @@
|
||||||
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
from nemubot import context
|
|
||||||
from nemubot.event import ModuleEvent
|
from nemubot.event import ModuleEvent
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools.countdown import countdown_format
|
from nemubot.tools.countdown import countdown_format
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# GLOBALS #############################################################
|
# GLOBALS #############################################################
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# LOADING #############################################################
|
# LOADING #############################################################
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.message import Command, DirectAsk, Text
|
from nemubot.message import Command, DirectAsk, Text
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# MODULE CORE #########################################################
|
# MODULE CORE #########################################################
|
||||||
|
|
|
||||||
|
|
@ -1,202 +0,0 @@
|
||||||
# Nemubot is a smart and modulable IM bot.
|
|
||||||
# Copyright (C) 2012-2015 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 traceback
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from nemubot.hooks import hook
|
|
||||||
|
|
||||||
nemubotversion = 3.4
|
|
||||||
NODATA = True
|
|
||||||
|
|
||||||
|
|
||||||
def getserver(toks, context, prompt, mandatory=False, **kwargs):
|
|
||||||
"""Choose the server in toks or prompt.
|
|
||||||
This function modify the tokens list passed as argument"""
|
|
||||||
|
|
||||||
if len(toks) > 1 and toks[1] in context.servers:
|
|
||||||
return context.servers[toks.pop(1)]
|
|
||||||
elif not mandatory or prompt.selectedServer:
|
|
||||||
return prompt.selectedServer
|
|
||||||
else:
|
|
||||||
from nemubot.prompt.error import PromptError
|
|
||||||
raise PromptError("Please SELECT a server or give its name in argument.")
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "close")
|
|
||||||
def close(toks, context, **kwargs):
|
|
||||||
"""Disconnect and forget (remove from the servers list) the server"""
|
|
||||||
srv = getserver(toks, context=context, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
if srv.close():
|
|
||||||
del context.servers[srv.id]
|
|
||||||
return 0
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "connect")
|
|
||||||
def connect(toks, **kwargs):
|
|
||||||
"""Make the connexion to a server"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
return not srv.open()
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "disconnect")
|
|
||||||
def disconnect(toks, **kwargs):
|
|
||||||
"""Close the connection to a server"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
return not srv.close()
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "discover")
|
|
||||||
def discover(toks, context, **kwargs):
|
|
||||||
"""Discover a new bot on a server"""
|
|
||||||
srv = getserver(toks, context=context, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
if len(toks) > 1 and "!" in toks[1]:
|
|
||||||
bot = context.add_networkbot(srv, name)
|
|
||||||
return not bot.connect()
|
|
||||||
else:
|
|
||||||
print(" %s is not a valid fullname, for example: "
|
|
||||||
"nemubot!nemubotV3@bot.nemunai.re" % ''.join(toks[1:1]))
|
|
||||||
return 1
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "join")
|
|
||||||
@hook("prompt_cmd", "leave")
|
|
||||||
@hook("prompt_cmd", "part")
|
|
||||||
def join(toks, **kwargs):
|
|
||||||
"""Join or leave a channel"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
if len(toks) <= 2:
|
|
||||||
print("%s: not enough arguments." % toks[0])
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if toks[0] == "join":
|
|
||||||
if len(toks) > 2:
|
|
||||||
srv.write("JOIN %s %s" % (toks[1], toks[2]))
|
|
||||||
else:
|
|
||||||
srv.write("JOIN %s" % toks[1])
|
|
||||||
|
|
||||||
elif toks[0] == "leave" or toks[0] == "part":
|
|
||||||
if len(toks) > 2:
|
|
||||||
srv.write("PART %s :%s" % (toks[1], " ".join(toks[2:])))
|
|
||||||
else:
|
|
||||||
srv.write("PART %s" % toks[1])
|
|
||||||
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "save")
|
|
||||||
def save_mod(toks, context, **kwargs):
|
|
||||||
"""Force save module data"""
|
|
||||||
if len(toks) < 2:
|
|
||||||
print("save: not enough arguments.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
wrn = 0
|
|
||||||
for mod in toks[1:]:
|
|
||||||
if mod in context.modules:
|
|
||||||
context.modules[mod].save()
|
|
||||||
print("save: module `%s´ saved successfully" % mod)
|
|
||||||
else:
|
|
||||||
wrn += 1
|
|
||||||
print("save: no module named `%s´" % mod)
|
|
||||||
return wrn
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "send")
|
|
||||||
def send(toks, **kwargs):
|
|
||||||
"""Send a message on a channel"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
# Check the server is connected
|
|
||||||
if not srv.connected:
|
|
||||||
print ("send: server `%s' not connected." % srv.id)
|
|
||||||
return 2
|
|
||||||
|
|
||||||
if len(toks) <= 3:
|
|
||||||
print ("send: not enough arguments.")
|
|
||||||
return 1
|
|
||||||
|
|
||||||
if toks[1] not in srv.channels:
|
|
||||||
print ("send: channel `%s' not authorized in server `%s'."
|
|
||||||
% (toks[1], srv.id))
|
|
||||||
return 3
|
|
||||||
|
|
||||||
from nemubot.message import Text
|
|
||||||
srv.send_response(Text(" ".join(toks[2:]), server=None,
|
|
||||||
to=[toks[1]]))
|
|
||||||
return 0
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "zap")
|
|
||||||
def zap(toks, **kwargs):
|
|
||||||
"""Hard change connexion state"""
|
|
||||||
srv = getserver(toks, mandatory=True, **kwargs)
|
|
||||||
|
|
||||||
srv.connected = not srv.connected
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "top")
|
|
||||||
def top(toks, context, **kwargs):
|
|
||||||
"""Display consumers load information"""
|
|
||||||
print("Queue size: %d, %d thread(s) running (counter: %d)" %
|
|
||||||
(context.cnsr_queue.qsize(),
|
|
||||||
len(context.cnsr_thrd),
|
|
||||||
context.cnsr_thrd_size))
|
|
||||||
if len(context.events) > 0:
|
|
||||||
print("Events registered: %d, next in %d seconds" %
|
|
||||||
(len(context.events),
|
|
||||||
context.events[0].time_left.seconds))
|
|
||||||
else:
|
|
||||||
print("No events registered")
|
|
||||||
|
|
||||||
for th in context.cnsr_thrd:
|
|
||||||
if th.is_alive():
|
|
||||||
print(("#" * 15 + " Stack trace for thread %u " + "#" * 15) %
|
|
||||||
th.ident)
|
|
||||||
traceback.print_stack(sys._current_frames()[th.ident])
|
|
||||||
|
|
||||||
|
|
||||||
@hook("prompt_cmd", "netstat")
|
|
||||||
def netstat(toks, context, **kwargs):
|
|
||||||
"""Display sockets in use and many other things"""
|
|
||||||
if len(context.network) > 0:
|
|
||||||
print("Distant bots connected: %d:" % len(context.network))
|
|
||||||
for name, bot in context.network.items():
|
|
||||||
print("# %s:" % name)
|
|
||||||
print(" * Declared hooks:")
|
|
||||||
lvl = 0
|
|
||||||
for hlvl in bot.hooks:
|
|
||||||
lvl += 1
|
|
||||||
for hook in (hlvl.all_pre + hlvl.all_post + hlvl.cmd_rgxp +
|
|
||||||
hlvl.cmd_default + hlvl.ask_rgxp +
|
|
||||||
hlvl.ask_default + hlvl.msg_rgxp +
|
|
||||||
hlvl.msg_default):
|
|
||||||
print(" %s- %s" % (' ' * lvl * 2, hook))
|
|
||||||
for kind in ["irc_hook", "cmd_hook", "ask_hook", "msg_hook"]:
|
|
||||||
print(" %s- <%s> %s" % (' ' * lvl * 2, kind,
|
|
||||||
", ".join(hlvl.__dict__[kind].keys())))
|
|
||||||
print(" * My tag: %d" % bot.my_tag)
|
|
||||||
print(" * Tags in use (%d):" % bot.inc_tag)
|
|
||||||
for tag, (cmd, data) in bot.tags.items():
|
|
||||||
print(" - %11s: %s « %s »" % (tag, cmd, data))
|
|
||||||
else:
|
|
||||||
print("No distant bot connected")
|
|
||||||
|
|
@ -11,7 +11,7 @@ from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
from nemubot.tools.web import striphtml
|
from nemubot.tools.web import striphtml
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# GLOBALS #############################################################
|
# GLOBALS #############################################################
|
||||||
|
|
@ -36,7 +36,7 @@ for k, v in s:
|
||||||
# MODULE CORE #########################################################
|
# MODULE CORE #########################################################
|
||||||
|
|
||||||
def get_conjug(verb, stringTens):
|
def get_conjug(verb, stringTens):
|
||||||
url = ("http://leconjugueur.lefigaro.fr/conjugaison/verbe/%s.html" %
|
url = ("https://leconjugueur.lefigaro.fr/conjugaison/verbe/%s.html" %
|
||||||
quote(verb.encode("ISO-8859-1")))
|
quote(verb.encode("ISO-8859-1")))
|
||||||
page = web.getURLContent(url)
|
page = web.getURLContent(url)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools.web import getURLContent, striphtml
|
from nemubot.tools.web import getURLContent, striphtml
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# GLOBALS #############################################################
|
# GLOBALS #############################################################
|
||||||
|
|
@ -25,10 +25,8 @@ def get_info_yt(msg):
|
||||||
|
|
||||||
for line in soup.body.find_all('tr'):
|
for line in soup.body.find_all('tr'):
|
||||||
n = line.find_all('td')
|
n = line.find_all('td')
|
||||||
if len(n) == 5:
|
if len(n) == 7:
|
||||||
try:
|
res.append_message("\x02%s:\x0F from %s type %s at %s. Weight: %s. %s%s" %
|
||||||
res.append_message("\x02%s:\x0F from %s type %s at %s. %s" %
|
tuple([striphtml(x.text).strip() for x in n]))
|
||||||
tuple([striphtml(x.text) for x in n]))
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -5,29 +5,67 @@
|
||||||
from bs4 import BeautifulSoup
|
from bs4 import BeautifulSoup
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools.web import getURLContent, striphtml
|
from nemubot.tools.web import getURLContent, striphtml
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
BASEURL_NIST = 'https://web.nvd.nist.gov/view/vuln/detail?vulnId='
|
BASEURL_NIST = 'https://nvd.nist.gov/vuln/detail/'
|
||||||
|
|
||||||
|
|
||||||
# MODULE CORE #########################################################
|
# 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):
|
def get_cve(cve_id):
|
||||||
search_url = BASEURL_NIST + quote(cve_id.upper())
|
search_url = BASEURL_NIST + quote(cve_id.upper())
|
||||||
|
|
||||||
soup = BeautifulSoup(getURLContent(search_url))
|
soup = BeautifulSoup(getURLContent(search_url))
|
||||||
vuln = soup.body.find(class_="vuln-detail")
|
|
||||||
cvss = vuln.findAll('div')[4]
|
|
||||||
|
|
||||||
return [
|
vuln = {}
|
||||||
"Base score: " + cvss.findAll('div')[0].findAll('a')[0].text.strip(),
|
|
||||||
vuln.findAll('p')[0].text, # description
|
for vd in VULN_DATAS:
|
||||||
striphtml(vuln.findAll('div')[0].text).strip(), # publication date
|
r = soup.body.find(attrs={"data-testid": VULN_DATAS[vd]})
|
||||||
striphtml(vuln.findAll('div')[1].text).strip(), # last revised
|
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 ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
@ -42,6 +80,20 @@ def get_cve_desc(msg):
|
||||||
if cve_id[:3].lower() != 'cve':
|
if cve_id[:3].lower() != 'cve':
|
||||||
cve_id = 'cve-' + cve_id
|
cve_id = 'cve-' + cve_id
|
||||||
|
|
||||||
res.append_message(get_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
|
return res
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
# MODULE CORE #########################################################
|
# MODULE CORE #########################################################
|
||||||
|
|
||||||
|
|
|
||||||
94
modules/dig.py
Normal file
94
modules/dig.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""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
|
||||||
89
modules/disas.py
Normal file
89
modules/disas.py
Normal file
|
|
@ -0,0 +1,89 @@
|
||||||
|
"""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,7 +1,9 @@
|
||||||
"""Create countdowns and reminders"""
|
"""Create countdowns and reminders"""
|
||||||
|
|
||||||
import re
|
import calendar
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
|
from functools import partial
|
||||||
|
import re
|
||||||
|
|
||||||
from nemubot import context
|
from nemubot import context
|
||||||
from nemubot.exception import IMException
|
from nemubot.exception import IMException
|
||||||
|
|
@ -10,31 +12,84 @@ from nemubot.hooks import hook
|
||||||
from nemubot.message import Command
|
from nemubot.message import Command
|
||||||
from nemubot.tools.countdown import countdown_format, countdown
|
from nemubot.tools.countdown import countdown_format, countdown
|
||||||
from nemubot.tools.date import extractDate
|
from nemubot.tools.date import extractDate
|
||||||
from nemubot.tools.xmlparser.node import ModuleState
|
from nemubot.tools.xmlparser.basic import DictNode
|
||||||
|
|
||||||
from more import Response
|
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 ():
|
def help_full ():
|
||||||
return "This module store a lot of events: ny, we, " + (", ".join(context.datas.index.keys())) + "\n!eventslist: gets list of timer\n!start /something/: launch a timer"
|
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):
|
def load(context):
|
||||||
#Define the index
|
context.set_knodes({
|
||||||
context.data.setIndex("name")
|
"dict": DictNode,
|
||||||
|
"event": Event,
|
||||||
|
})
|
||||||
|
|
||||||
for evt in context.data.index.keys():
|
if context.data is None:
|
||||||
if context.data.index[evt].hasAttribute("end"):
|
context.set_default(DictNode())
|
||||||
event = ModuleEvent(call=fini, call_data=dict(strend=context.data.index[evt]))
|
|
||||||
event._end = context.data.index[evt].getDate("end")
|
# Relaunch all timers
|
||||||
idt = context.add_event(event)
|
for kevt in context.data:
|
||||||
if idt is not None:
|
if context.data[kevt].end:
|
||||||
context.data.index[evt]["_id"] = idt
|
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(d, strend):
|
def fini(name, evt):
|
||||||
context.send_response(strend["server"], Response("%s arrivé à échéance." % strend["name"], channel=strend["channel"], nick=strend["proprio"]))
|
context.send_response(evt._server, Response("%s arrivé à échéance." % name, channel=evt._channel, nick=evt.creator))
|
||||||
context.data.delChild(context.data.index[strend["name"]])
|
evt._evt = None
|
||||||
|
del context.data[name]
|
||||||
context.save()
|
context.save()
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -63,18 +118,10 @@ def start_countdown(msg):
|
||||||
"""!start /something/: launch a timer"""
|
"""!start /something/: launch a timer"""
|
||||||
if len(msg.args) < 1:
|
if len(msg.args) < 1:
|
||||||
raise IMException("indique le nom d'un événement à chronométrer")
|
raise IMException("indique le nom d'un événement à chronométrer")
|
||||||
if msg.args[0] in context.data.index:
|
if msg.args[0] in context.data:
|
||||||
raise IMException("%s existe déjà." % msg.args[0])
|
raise IMException("%s existe déjà." % msg.args[0])
|
||||||
|
|
||||||
strnd = ModuleState("strend")
|
evt = Event(server=msg.server, channel=msg.channel, creator=msg.frm, start_time=msg.date)
|
||||||
strnd["server"] = msg.server
|
|
||||||
strnd["channel"] = msg.channel
|
|
||||||
strnd["proprio"] = msg.nick
|
|
||||||
strnd["start"] = msg.date
|
|
||||||
strnd["name"] = msg.args[0]
|
|
||||||
context.data.addChild(strnd)
|
|
||||||
|
|
||||||
evt = ModuleEvent(call=fini, call_data=dict(strend=strnd))
|
|
||||||
|
|
||||||
if len(msg.args) > 1:
|
if len(msg.args) > 1:
|
||||||
result1 = re.findall("([0-9]+)([smhdjwyaSMHDJWYA])?", msg.args[1])
|
result1 = re.findall("([0-9]+)([smhdjwyaSMHDJWYA])?", msg.args[1])
|
||||||
|
|
@ -92,50 +139,51 @@ def start_countdown(msg):
|
||||||
if result2 is None or result2.group(4) is None: yea = now.year
|
if result2 is None or result2.group(4) is None: yea = now.year
|
||||||
else: yea = int(result2.group(4))
|
else: yea = int(result2.group(4))
|
||||||
if result2 is not None and result3 is not None:
|
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, timezone.utc)
|
evt.end = datetime(yea, int(result2.group(3)), int(result2.group(2)), hou, minu, sec, timezone.utc)
|
||||||
elif result2 is not None:
|
elif result2 is not None:
|
||||||
strnd["end"] = datetime(int(result2.group(4)), int(result2.group(3)), int(result2.group(2)), 0, 0, 0, timezone.utc)
|
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:
|
elif result3 is not None:
|
||||||
if hou * 3600 + minu * 60 + sec > now.hour * 3600 + now.minute * 60 + now.second:
|
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, timezone.utc)
|
evt.end = datetime(now.year, now.month, now.day, hou, minu, sec, timezone.utc)
|
||||||
else:
|
else:
|
||||||
strnd["end"] = datetime(now.year, now.month, now.day + 1, hou, minu, sec, timezone.utc)
|
evt.end = datetime(now.year, now.month, now.day + 1, hou, minu, sec, timezone.utc)
|
||||||
evt._end = strnd.getDate("end")
|
|
||||||
strnd["_id"] = context.add_event(evt)
|
|
||||||
except:
|
except:
|
||||||
context.data.delChild(strnd)
|
|
||||||
raise IMException("Mauvais format de date pour l'événement %s. Il n'a pas été créé." % msg.args[0])
|
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:
|
elif result1 is not None and len(result1) > 0:
|
||||||
strnd["end"] = msg.date
|
evt.end = msg.date
|
||||||
for (t, g) in result1:
|
for (t, g) in result1:
|
||||||
if g is None or g == "" or g == "m" or g == "M":
|
if g is None or g == "" or g == "m" or g == "M":
|
||||||
strnd["end"] += timedelta(minutes=int(t))
|
evt.end += timedelta(minutes=int(t))
|
||||||
elif g == "h" or g == "H":
|
elif g == "h" or g == "H":
|
||||||
strnd["end"] += timedelta(hours=int(t))
|
evt.end += timedelta(hours=int(t))
|
||||||
elif g == "d" or g == "D" or g == "j" or g == "J":
|
elif g == "d" or g == "D" or g == "j" or g == "J":
|
||||||
strnd["end"] += timedelta(days=int(t))
|
evt.end += timedelta(days=int(t))
|
||||||
elif g == "w" or g == "W":
|
elif g == "w" or g == "W":
|
||||||
strnd["end"] += timedelta(days=int(t)*7)
|
evt.end += timedelta(days=int(t)*7)
|
||||||
elif g == "y" or g == "Y" or g == "a" or g == "A":
|
elif g == "y" or g == "Y" or g == "a" or g == "A":
|
||||||
strnd["end"] += timedelta(days=int(t)*365)
|
evt.end += timedelta(days=int(t)*365)
|
||||||
else:
|
else:
|
||||||
strnd["end"] += timedelta(seconds=int(t))
|
evt.end += timedelta(seconds=int(t))
|
||||||
evt._end = strnd.getDate("end")
|
|
||||||
eid = context.add_event(evt)
|
|
||||||
if eid is not None:
|
|
||||||
strnd["_id"] = eid
|
|
||||||
|
|
||||||
|
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()
|
context.save()
|
||||||
if "end" in strnd:
|
|
||||||
|
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." %
|
return Response("%s commencé le %s et se terminera le %s." %
|
||||||
(msg.args[0], msg.date.strftime("%A %d %B %Y à %H:%M:%S"),
|
(msg.args[0], msg.date.strftime("%A %d %B %Y à %H:%M:%S"),
|
||||||
strnd.getDate("end").strftime("%A %d %B %Y à %H:%M:%S")),
|
evt.end.strftime("%A %d %B %Y à %H:%M:%S")),
|
||||||
nick=msg.frm)
|
channel=msg.channel)
|
||||||
else:
|
else:
|
||||||
return Response("%s commencé le %s"% (msg.args[0],
|
return Response("%s commencé le %s"% (msg.args[0],
|
||||||
msg.date.strftime("%A %d %B %Y à %H:%M:%S")),
|
msg.date.strftime("%A %d %B %Y à %H:%M:%S")),
|
||||||
nick=msg.frm)
|
channel=msg.channel)
|
||||||
|
|
||||||
|
|
||||||
@hook.command("end")
|
@hook.command("end")
|
||||||
|
|
@ -144,67 +192,66 @@ def end_countdown(msg):
|
||||||
if len(msg.args) < 1:
|
if len(msg.args) < 1:
|
||||||
raise IMException("quel événement terminer ?")
|
raise IMException("quel événement terminer ?")
|
||||||
|
|
||||||
if msg.args[0] in context.data.index:
|
if msg.args[0] in context.data:
|
||||||
if context.data.index[msg.args[0]]["proprio"] == msg.nick or (msg.cmd == "forceend" and msg.frm_owner):
|
if context.data[msg.args[0]].creator == msg.frm or (msg.cmd == "forceend" and msg.frm_owner):
|
||||||
duration = countdown(msg.date - context.data.index[msg.args[0]].getDate("start"))
|
duration = countdown(msg.date - context.data[msg.args[0]].start)
|
||||||
context.del_event(context.data.index[msg.args[0]]["_id"])
|
del context.data[msg.args[0]]
|
||||||
context.data.delChild(context.data.index[msg.args[0]])
|
|
||||||
context.save()
|
context.save()
|
||||||
return Response("%s a duré %s." % (msg.args[0], duration),
|
return Response("%s a duré %s." % (msg.args[0], duration),
|
||||||
channel=msg.channel, nick=msg.nick)
|
channel=msg.channel, nick=msg.frm)
|
||||||
else:
|
else:
|
||||||
raise IMException("Vous ne pouvez pas terminer le compteur %s, créé par %s." % (msg.args[0], context.data.index[msg.args[0]]["proprio"]))
|
raise IMException("Vous ne pouvez pas terminer le compteur %s, créé par %s." % (msg.args[0], context.data[msg.args[0]].creator))
|
||||||
else:
|
else:
|
||||||
return Response("%s n'est pas un compteur connu."% (msg.args[0]), channel=msg.channel, nick=msg.nick)
|
return Response("%s n'est pas un compteur connu."% (msg.args[0]), channel=msg.channel, nick=msg.frm)
|
||||||
|
|
||||||
|
|
||||||
@hook.command("eventslist")
|
@hook.command("eventslist")
|
||||||
def liste(msg):
|
def liste(msg):
|
||||||
"""!eventslist: gets list of timer"""
|
"""!eventslist: gets list of timer"""
|
||||||
if len(msg.args):
|
if len(msg.args):
|
||||||
res = list()
|
res = Response(channel=msg.channel)
|
||||||
for user in msg.args:
|
for user in msg.args:
|
||||||
cmptr = [x["name"] for x in context.data.index.values() if x["proprio"] == user]
|
cmptr = [k for k in context.data if context.data[k].creator == user]
|
||||||
if len(cmptr) > 0:
|
if len(cmptr) > 0:
|
||||||
res.append("Compteurs créés par %s : %s" % (user, ", ".join(cmptr)))
|
res.append_message(cmptr, title="Events created by %s" % user)
|
||||||
else:
|
else:
|
||||||
res.append("%s n'a pas créé de compteur" % user)
|
res.append_message("%s doesn't have any counting events" % user)
|
||||||
return Response(" ; ".join(res), channel=msg.channel)
|
return res
|
||||||
else:
|
else:
|
||||||
return Response("Compteurs connus : %s." % ", ".join(context.data.index.keys()), channel=msg.channel)
|
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.index)
|
@hook.command(match=lambda msg: isinstance(msg, Command) and msg.cmd in context.data)
|
||||||
def parseanswer(msg):
|
def parseanswer(msg):
|
||||||
res = Response(channel=msg.channel)
|
res = Response(channel=msg.channel)
|
||||||
|
|
||||||
# Avoid message starting by ! which can be interpreted as command by other bots
|
# Avoid message starting by ! which can be interpreted as command by other bots
|
||||||
if msg.cmd[0] == "!":
|
if msg.cmd[0] == "!":
|
||||||
res.nick = msg.nick
|
res.nick = msg.frm
|
||||||
|
|
||||||
if context.data.index[msg.cmd].name == "strend":
|
if msg.cmd in context.data:
|
||||||
if context.data.index[msg.cmd].hasAttribute("end"):
|
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.index[msg.cmd].getDate("start")), countdown(context.data.index[msg.cmd].getDate("end") - msg.date)))
|
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:
|
else:
|
||||||
res.append_message("%s commencé il y a %s." % (msg.cmd, countdown(msg.date - context.data.index[msg.cmd].getDate("start"))))
|
res.append_message("%s commencé il y a %s." % (msg.cmd, countdown(msg.date - context.data[msg.cmd].start)))
|
||||||
else:
|
else:
|
||||||
res.append_message(countdown_format(context.data.index[msg.cmd].getDate("start"), context.data.index[msg.cmd]["msg_before"], context.data.index[msg.cmd]["msg_after"]))
|
res.append_message(countdown_format(context.data[msg.cmd].start, context.data[msg.cmd]["msg_before"], context.data[msg.cmd]["msg_after"]))
|
||||||
return res
|
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)
|
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.text))
|
@hook.ask(match=lambda msg: RGXP_ask.match(msg.message))
|
||||||
def parseask(msg):
|
def parseask(msg):
|
||||||
name = re.match("^.*!([^ \"'@!]+).*$", msg.text)
|
name = re.match("^.*!([^ \"'@!]+).*$", msg.message)
|
||||||
if name is None:
|
if name is None:
|
||||||
raise IMException("il faut que tu attribues une commande à l'événement.")
|
raise IMException("il faut que tu attribues une commande à l'événement.")
|
||||||
if name.group(1) in context.data.index:
|
if name.group(1) in context.data:
|
||||||
raise IMException("un événement portant ce nom existe déjà.")
|
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.text, re.I)
|
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:
|
if texts is not None and texts.group(3) is not None:
|
||||||
extDate = extractDate(msg.text)
|
extDate = extractDate(msg.message)
|
||||||
if extDate is None or extDate == "":
|
if extDate is None or extDate == "":
|
||||||
raise IMException("la date de l'événement est invalide !")
|
raise IMException("la date de l'événement est invalide !")
|
||||||
|
|
||||||
|
|
@ -223,7 +270,7 @@ def parseask(msg):
|
||||||
evt = ModuleState("event")
|
evt = ModuleState("event")
|
||||||
evt["server"] = msg.server
|
evt["server"] = msg.server
|
||||||
evt["channel"] = msg.channel
|
evt["channel"] = msg.channel
|
||||||
evt["proprio"] = msg.nick
|
evt["proprio"] = msg.frm
|
||||||
evt["name"] = name.group(1)
|
evt["name"] = name.group(1)
|
||||||
evt["start"] = extDate
|
evt["start"] = extDate
|
||||||
evt["msg_after"] = msg_after
|
evt["msg_after"] = msg_after
|
||||||
|
|
@ -237,7 +284,7 @@ def parseask(msg):
|
||||||
evt = ModuleState("event")
|
evt = ModuleState("event")
|
||||||
evt["server"] = msg.server
|
evt["server"] = msg.server
|
||||||
evt["channel"] = msg.channel
|
evt["channel"] = msg.channel
|
||||||
evt["proprio"] = msg.nick
|
evt["proprio"] = msg.frm
|
||||||
evt["name"] = name.group(1)
|
evt["name"] = name.group(1)
|
||||||
evt["msg_before"] = texts.group (2)
|
evt["msg_before"] = texts.group (2)
|
||||||
context.data.addChild(evt)
|
context.data.addChild(evt)
|
||||||
|
|
|
||||||
64
modules/freetarifs.py
Normal file
64
modules/freetarifs.py
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
"""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
|
||||||
|
|
@ -9,7 +9,7 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# MODULE CORE #########################################################
|
# MODULE CORE #########################################################
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.message import Command, Text
|
from nemubot.message import Command, Text
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# MODULE CORE #########################################################
|
# MODULE CORE #########################################################
|
||||||
|
|
@ -73,7 +73,7 @@ def cmd_grep(msg):
|
||||||
|
|
||||||
only = "only" in msg.kwargs
|
only = "only" in msg.kwargs
|
||||||
|
|
||||||
l = [m for m in grep(msg.args[0] if msg.args[0][0] == "^" else ".*?(" + msg.args[0] + ").*?",
|
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:]),
|
" ".join(msg.args[1:]),
|
||||||
msg,
|
msg,
|
||||||
icase="nocase" in msg.kwargs,
|
icase="nocase" in msg.kwargs,
|
||||||
|
|
|
||||||
|
|
@ -5,63 +5,56 @@
|
||||||
import re
|
import re
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from nemubot.exception import IMException
|
from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# MODULE CORE #########################################################
|
# MODULE CORE #########################################################
|
||||||
|
|
||||||
def get_movie(title=None, year=None, imdbid=None, fullplot=True, tomatoes=False):
|
def get_movie_by_id(imdbid):
|
||||||
"""Returns the information about the matching movie"""
|
"""Returns the information about the matching movie"""
|
||||||
|
|
||||||
# Built URL
|
url = "http://www.imdb.com/title/" + urllib.parse.quote(imdbid)
|
||||||
url = "http://www.omdbapi.com/?"
|
soup = BeautifulSoup(web.getURLContent(url))
|
||||||
if title is not None:
|
|
||||||
url += "t=%s&" % urllib.parse.quote(title)
|
|
||||||
if year is not None:
|
|
||||||
url += "y=%s&" % urllib.parse.quote(year)
|
|
||||||
if imdbid is not None:
|
|
||||||
url += "i=%s&" % urllib.parse.quote(imdbid)
|
|
||||||
if fullplot:
|
|
||||||
url += "plot=full&"
|
|
||||||
if tomatoes:
|
|
||||||
url += "tomatoes=true&"
|
|
||||||
|
|
||||||
# Make the request
|
return {
|
||||||
data = web.getJSON(url)
|
"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(),
|
||||||
|
|
||||||
# Return data
|
"Type": "TV Series" if soup.find(id="title-episode-widget") else "Movie",
|
||||||
if "Error" in data:
|
"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="]),
|
||||||
raise IMException(data["Error"])
|
"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"})]),
|
||||||
elif "Response" in data and data["Response"] == "True":
|
}
|
||||||
return data
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise IMException("An error occurs during movie search")
|
|
||||||
|
|
||||||
|
|
||||||
def find_movies(title):
|
def find_movies(title, year=None):
|
||||||
"""Find existing movies matching a approximate title"""
|
"""Find existing movies matching a approximate title"""
|
||||||
|
|
||||||
|
title = title.lower()
|
||||||
|
|
||||||
# Built URL
|
# Built URL
|
||||||
url = "http://www.omdbapi.com/?s=%s" % urllib.parse.quote(title)
|
url = "https://v2.sg.media-imdb.com/suggests/%s/%s.json" % (urllib.parse.quote(title[0]), urllib.parse.quote(title.replace(" ", "_")))
|
||||||
|
|
||||||
# Make the request
|
# Make the request
|
||||||
data = web.getJSON(url)
|
data = web.getJSON(url, remove_callback=True)
|
||||||
|
|
||||||
# Return data
|
|
||||||
if "Error" in data:
|
|
||||||
raise IMException(data["Error"])
|
|
||||||
|
|
||||||
elif "Search" in data:
|
|
||||||
return data
|
|
||||||
|
|
||||||
|
if "d" not in data:
|
||||||
|
return None
|
||||||
|
elif year is None:
|
||||||
|
return data["d"]
|
||||||
else:
|
else:
|
||||||
raise IMException("An error occurs during movie search")
|
return [d for d in data["d"] if "y" in d and str(d["y"]) == year]
|
||||||
|
|
||||||
|
|
||||||
# MODULE INTERFACE ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
@ -79,23 +72,28 @@ def cmd_imdb(msg):
|
||||||
title = ' '.join(msg.args)
|
title = ' '.join(msg.args)
|
||||||
|
|
||||||
if re.match("^tt[0-9]{7}$", title) is not None:
|
if re.match("^tt[0-9]{7}$", title) is not None:
|
||||||
data = get_movie(imdbid=title)
|
data = get_movie_by_id(imdbid=title)
|
||||||
else:
|
else:
|
||||||
rm = re.match(r"^(.+)\s\(([0-9]{4})\)$", title)
|
rm = re.match(r"^(.+)\s\(([0-9]{4})\)$", title)
|
||||||
if rm is not None:
|
if rm is not None:
|
||||||
data = get_movie(title=rm.group(1), year=rm.group(2))
|
data = find_movies(rm.group(1), year=rm.group(2))
|
||||||
else:
|
else:
|
||||||
data = get_movie(title=title)
|
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,
|
res = Response(channel=msg.channel,
|
||||||
title="%s (%s)" % (data['Title'], data['Year']),
|
title="%s (%s)" % (data['Title'], data['Year']),
|
||||||
nomore="No more information, more at http://www.imdb.com/title/%s" % data['imdbID'])
|
nomore="No more information, more at http://www.imdb.com/title/%s" % data['imdbID'])
|
||||||
|
|
||||||
res.append_message("\x02rating\x0F: %s (%s votes); \x02plot\x0F: %s" %
|
res.append_message("%s \x02genre:\x0F %s; \x02rating\x0F: %s (%s votes); \x02plot\x0F: %s" %
|
||||||
(data['imdbRating'], data['imdbVotes'], data['Plot']))
|
(data['Type'], data['Genre'], data['imdbRating'], data['imdbVotes'], data['Plot']))
|
||||||
|
res.append_message("%s \x02from\x0F %s; %s"
|
||||||
|
% (data['Type'], data['Country'], data['Credits']))
|
||||||
|
|
||||||
res.append_message("%s \x02from\x0F %s \x02released on\x0F %s; \x02genre:\x0F %s; \x02directed by:\x0F %s; \x02written by:\x0F %s; \x02main actors:\x0F %s"
|
|
||||||
% (data['Type'], data['Country'], data['Released'], data['Genre'], data['Director'], data['Writer'], data['Actors']))
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -111,7 +109,7 @@ def cmd_search(msg):
|
||||||
data = find_movies(' '.join(msg.args))
|
data = find_movies(' '.join(msg.args))
|
||||||
|
|
||||||
movies = list()
|
movies = list()
|
||||||
for m in data['Search']:
|
for m in data:
|
||||||
movies.append("\x02%s\x0F (%s of %s)" % (m['Title'], m['Type'], m['Year']))
|
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)
|
return Response(movies, title="Titles found", channel=msg.channel)
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.exception import IMException
|
from nemubot.exception import IMException
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
import json
|
import json
|
||||||
|
|
||||||
nemubotversion = 3.4
|
nemubotversion = 3.4
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import os
|
||||||
|
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# GLOBALS #############################################################
|
# GLOBALS #############################################################
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
# GLOBALS #############################################################
|
# GLOBALS #############################################################
|
||||||
|
|
||||||
URL_API = "http://open.mapquestapi.com/geocoding/v1/address?key=%s&location=%%s"
|
URL_API = "https://open.mapquestapi.com/geocoding/v1/address?key=%s&location=%%s"
|
||||||
|
|
||||||
|
|
||||||
# LOADING #############################################################
|
# LOADING #############################################################
|
||||||
|
|
@ -23,7 +23,7 @@ def load(context):
|
||||||
raise ImportError("You need a MapQuest API key in order to use this "
|
raise ImportError("You need a MapQuest API key in order to use this "
|
||||||
"module. Add it to the module configuration file:\n"
|
"module. Add it to the module configuration file:\n"
|
||||||
"<module name=\"mapquest\" key=\"XXXXXXXXXXXXXXXX\" "
|
"<module name=\"mapquest\" key=\"XXXXXXXXXXXXXXXX\" "
|
||||||
"/>\nRegister at http://developer.mapquest.com/")
|
"/>\nRegister at https://developer.mapquest.com/")
|
||||||
global URL_API
|
global URL_API
|
||||||
URL_API = URL_API % context.config["apikey"].replace("%", "%%")
|
URL_API = URL_API % context.config["apikey"].replace("%", "%%")
|
||||||
|
|
||||||
|
|
@ -55,7 +55,7 @@ def cmd_geocode(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IMException("indicate a name")
|
raise IMException("indicate a name")
|
||||||
|
|
||||||
res = Response(channel=msg.channel, nick=msg.nick,
|
res = Response(channel=msg.channel, nick=msg.frm,
|
||||||
nomore="No more geocode", count=" (%s more geocode)")
|
nomore="No more geocode", count=" (%s more geocode)")
|
||||||
|
|
||||||
for loc in geocode(' '.join(msg.args)):
|
for loc in geocode(' '.join(msg.args)):
|
||||||
|
|
|
||||||
|
|
@ -11,15 +11,15 @@ from nemubot.tools import web
|
||||||
|
|
||||||
nemubotversion = 3.4
|
nemubotversion = 3.4
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# MEDIAWIKI REQUESTS ##################################################
|
# MEDIAWIKI REQUESTS ##################################################
|
||||||
|
|
||||||
def get_namespaces(site, ssl=False):
|
def get_namespaces(site, ssl=False, path="/w/api.php"):
|
||||||
# Built URL
|
# Built URL
|
||||||
url = "http%s://%s/w/api.php?format=json&action=query&meta=siteinfo&siprop=namespaces" % (
|
url = "http%s://%s%s?format=json&action=query&meta=siteinfo&siprop=namespaces" % (
|
||||||
"s" if ssl else "", site)
|
"s" if ssl else "", site, path)
|
||||||
|
|
||||||
# Make the request
|
# Make the request
|
||||||
data = web.getJSON(url)
|
data = web.getJSON(url)
|
||||||
|
|
@ -30,10 +30,10 @@ def get_namespaces(site, ssl=False):
|
||||||
return namespaces
|
return namespaces
|
||||||
|
|
||||||
|
|
||||||
def get_raw_page(site, term, ssl=False):
|
def get_raw_page(site, term, ssl=False, path="/w/api.php"):
|
||||||
# Built URL
|
# Built URL
|
||||||
url = "http%s://%s/w/api.php?format=json&redirects&action=query&prop=revisions&rvprop=content&titles=%s" % (
|
url = "http%s://%s%s?format=json&redirects&action=query&prop=revisions&rvprop=content&titles=%s" % (
|
||||||
"s" if ssl else "", site, urllib.parse.quote(term))
|
"s" if ssl else "", site, path, urllib.parse.quote(term))
|
||||||
|
|
||||||
# Make the request
|
# Make the request
|
||||||
data = web.getJSON(url)
|
data = web.getJSON(url)
|
||||||
|
|
@ -45,10 +45,10 @@ def get_raw_page(site, term, ssl=False):
|
||||||
raise IMException("article not found")
|
raise IMException("article not found")
|
||||||
|
|
||||||
|
|
||||||
def get_unwikitextified(site, wikitext, ssl=False):
|
def get_unwikitextified(site, wikitext, ssl=False, path="/w/api.php"):
|
||||||
# Built URL
|
# Built URL
|
||||||
url = "http%s://%s/w/api.php?format=json&action=expandtemplates&text=%s" % (
|
url = "http%s://%s%s?format=json&action=expandtemplates&text=%s" % (
|
||||||
"s" if ssl else "", site, urllib.parse.quote(wikitext))
|
"s" if ssl else "", site, path, urllib.parse.quote(wikitext))
|
||||||
|
|
||||||
# Make the request
|
# Make the request
|
||||||
data = web.getJSON(url)
|
data = web.getJSON(url)
|
||||||
|
|
@ -58,10 +58,10 @@ def get_unwikitextified(site, wikitext, ssl=False):
|
||||||
|
|
||||||
## Search
|
## Search
|
||||||
|
|
||||||
def opensearch(site, term, ssl=False):
|
def opensearch(site, term, ssl=False, path="/w/api.php"):
|
||||||
# Built URL
|
# Built URL
|
||||||
url = "http%s://%s/w/api.php?format=json&action=opensearch&search=%s" % (
|
url = "http%s://%s%s?format=json&action=opensearch&search=%s" % (
|
||||||
"s" if ssl else "", site, urllib.parse.quote(term))
|
"s" if ssl else "", site, path, urllib.parse.quote(term))
|
||||||
|
|
||||||
# Make the request
|
# Make the request
|
||||||
response = web.getJSON(url)
|
response = web.getJSON(url)
|
||||||
|
|
@ -73,10 +73,10 @@ def opensearch(site, term, ssl=False):
|
||||||
response[3][k])
|
response[3][k])
|
||||||
|
|
||||||
|
|
||||||
def search(site, term, ssl=False):
|
def search(site, term, ssl=False, path="/w/api.php"):
|
||||||
# Built URL
|
# Built URL
|
||||||
url = "http%s://%s/w/api.php?format=json&action=query&list=search&srsearch=%s&srprop=titlesnippet|snippet" % (
|
url = "http%s://%s%s?format=json&action=query&list=search&srsearch=%s&srprop=titlesnippet|snippet" % (
|
||||||
"s" if ssl else "", site, urllib.parse.quote(term))
|
"s" if ssl else "", site, path, urllib.parse.quote(term))
|
||||||
|
|
||||||
# Make the request
|
# Make the request
|
||||||
data = web.getJSON(url)
|
data = web.getJSON(url)
|
||||||
|
|
@ -89,6 +89,11 @@ def search(site, term, ssl=False):
|
||||||
|
|
||||||
# PARSING FUNCTIONS ###################################################
|
# 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):
|
def strip_model(cnt):
|
||||||
# Strip models at begin: mostly useless
|
# Strip models at begin: mostly useless
|
||||||
cnt = re.sub(r"^(({{([^{]|\s|({{([^{]|\s|{{.*?}})*?}})*?)*?}}|\[\[([^[]|\s|\[\[.*?\]\])*?\]\])\s*)+", "", cnt, flags=re.DOTALL)
|
cnt = re.sub(r"^(({{([^{]|\s|({{([^{]|\s|{{.*?}})*?}})*?)*?}}|\[\[([^[]|\s|\[\[.*?\]\])*?\]\])\s*)+", "", cnt, flags=re.DOTALL)
|
||||||
|
|
@ -108,9 +113,9 @@ def strip_model(cnt):
|
||||||
return cnt
|
return cnt
|
||||||
|
|
||||||
|
|
||||||
def parse_wikitext(site, cnt, namespaces=dict(), ssl=False):
|
def parse_wikitext(site, cnt, namespaces=dict(), **kwargs):
|
||||||
for i, _, _, _ in re.findall(r"({{([^{]|\s|({{(.|\s|{{.*?}})*?}})*?)*?}})", cnt):
|
for i, _, _, _ in re.findall(r"({{([^{]|\s|({{(.|\s|{{.*?}})*?}})*?)*?}})", cnt):
|
||||||
cnt = cnt.replace(i, get_unwikitextified(site, i, ssl), 1)
|
cnt = cnt.replace(i, get_unwikitextified(site, i, **kwargs), 1)
|
||||||
|
|
||||||
# Strip [[...]]
|
# Strip [[...]]
|
||||||
for full, args, lnk in re.findall(r"(\[\[(.*?|)?([^|]*?)\]\])", cnt):
|
for full, args, lnk in re.findall(r"(\[\[(.*?|)?([^|]*?)\]\])", cnt):
|
||||||
|
|
@ -139,67 +144,101 @@ def irc_format(cnt):
|
||||||
return cnt.replace("'''", "\x03\x02").replace("''", "\x03\x1f")
|
return cnt.replace("'''", "\x03\x02").replace("''", "\x03\x1f")
|
||||||
|
|
||||||
|
|
||||||
def get_page(site, term, ssl=False, subpart=None):
|
def parse_infobox(cnt):
|
||||||
raw = get_raw_page(site, term, ssl)
|
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:
|
if subpart is not None:
|
||||||
subpart = subpart.replace("_", " ")
|
subpart = subpart.replace("_", " ")
|
||||||
raw = re.sub(r"^.*(?P<title>==+)\s*(" + subpart + r")\s*(?P=title)", r"\1 \2 \1", raw, flags=re.DOTALL)
|
raw = re.sub(r"^.*(?P<title>==+)\s*(" + subpart + r")\s*(?P=title)", r"\1 \2 \1", raw, flags=re.DOTALL)
|
||||||
|
|
||||||
return strip_model(raw)
|
return raw
|
||||||
|
|
||||||
|
|
||||||
# NEMUBOT #############################################################
|
# NEMUBOT #############################################################
|
||||||
|
|
||||||
def mediawiki_response(site, term, to):
|
def mediawiki_response(site, term, to, **kwargs):
|
||||||
ns = get_namespaces(site)
|
ns = get_namespaces(site, **kwargs)
|
||||||
|
|
||||||
terms = term.split("#", 1)
|
terms = term.split("#", 1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Print the article if it exists
|
# Print the article if it exists
|
||||||
return Response(get_page(site, terms[0], subpart=terms[1] if len(terms) > 1 else None),
|
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)),
|
line_treat=lambda line: irc_format(parse_wikitext(site, line, ns, **kwargs)),
|
||||||
channel=to)
|
channel=to)
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
# Try looking at opensearch
|
# Try looking at opensearch
|
||||||
os = [x for x, _, _ in opensearch(site, terms[0])]
|
os = [x for x, _, _ in opensearch(site, terms[0], **kwargs)]
|
||||||
print(os)
|
print(os)
|
||||||
# Fallback to global search
|
# Fallback to global search
|
||||||
if not len(os):
|
if not len(os):
|
||||||
os = [x for x, _ in search(site, terms[0]) if x is not None and x != ""]
|
os = [x for x, _ in search(site, terms[0], **kwargs) if x is not None and x != ""]
|
||||||
return Response(os,
|
return Response(os,
|
||||||
channel=to,
|
channel=to,
|
||||||
title="Article not found, would you mean")
|
title="Article not found, would you mean")
|
||||||
|
|
||||||
|
|
||||||
@hook.command("mediawiki")
|
@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):
|
def cmd_mediawiki(msg):
|
||||||
"""Read an article on a MediaWiki"""
|
|
||||||
if len(msg.args) < 2:
|
if len(msg.args) < 2:
|
||||||
raise IMException("indicate a domain and a term to search")
|
raise IMException("indicate a domain and a term to search")
|
||||||
|
|
||||||
return mediawiki_response(msg.args[0],
|
return mediawiki_response(msg.args[0],
|
||||||
" ".join(msg.args[1:]),
|
" ".join(msg.args[1:]),
|
||||||
msg.to_response)
|
msg.to_response,
|
||||||
|
**msg.kwargs)
|
||||||
|
|
||||||
|
|
||||||
@hook.command("search_mediawiki")
|
@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):
|
def cmd_srchmediawiki(msg):
|
||||||
"""Search an article on a MediaWiki"""
|
|
||||||
if len(msg.args) < 2:
|
if len(msg.args) < 2:
|
||||||
raise IMException("indicate a domain and a term to search")
|
raise IMException("indicate a domain and a term to search")
|
||||||
|
|
||||||
res = Response(channel=msg.to_response, nomore="No more results", count=" (%d more results)")
|
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:])):
|
for r in search(msg.args[0], " ".join(msg.args[1:]), **msg.kwargs):
|
||||||
res.append_message("%s: %s" % r)
|
res.append_message("%s: %s" % r)
|
||||||
|
|
||||||
return res
|
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")
|
@hook.command("wikipedia")
|
||||||
def cmd_wikipedia(msg):
|
def cmd_wikipedia(msg):
|
||||||
if len(msg.args) < 2:
|
if len(msg.args) < 2:
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ import re
|
||||||
from nemubot.exception import IMException
|
from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
from . import isup
|
from . import isup
|
||||||
from . import page
|
from . import page
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ def isup(url):
|
||||||
|
|
||||||
o = urllib.parse.urlparse(getNormalizedURL(url), "http")
|
o = urllib.parse.urlparse(getNormalizedURL(url), "http")
|
||||||
if o.netloc != "":
|
if o.netloc != "":
|
||||||
isup = getJSON("http://isitup.org/%s.json" % 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:
|
if isup is not None and "status_code" in isup and isup["status_code"] == 1:
|
||||||
return isup["response_time"]
|
return isup["response_time"]
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ def validator(url):
|
||||||
raise IMException("Indicate a valid URL!")
|
raise IMException("Indicate a valid URL!")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
req = urllib.request.Request("http://validator.w3.org/check?uri=%s&output=json" % (urllib.parse.quote(o.geturl())), headers={ 'User-Agent' : "Nemubot v%s" % __version__})
|
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)
|
raw = urllib.request.urlopen(req, timeout=10)
|
||||||
except urllib.error.HTTPError as e:
|
except urllib.error.HTTPError as e:
|
||||||
raise IMException("HTTP error occurs: %s %s" % (e.code, e.reason))
|
raise IMException("HTTP error occurs: %s %s" % (e.code, e.reason))
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
"""Alert on changes on websites"""
|
"""Alert on changes on websites"""
|
||||||
|
|
||||||
|
from functools import partial
|
||||||
import logging
|
import logging
|
||||||
from random import randint
|
from random import randint
|
||||||
import urllib.parse
|
import urllib.parse
|
||||||
|
|
@ -12,7 +13,7 @@ from nemubot.tools.xmlparser.node import ModuleState
|
||||||
|
|
||||||
logger = logging.getLogger("nemubot.module.networking.watchWebsite")
|
logger = logging.getLogger("nemubot.module.networking.watchWebsite")
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
from . import page
|
from . import page
|
||||||
|
|
||||||
|
|
@ -209,15 +210,14 @@ def start_watching(site, offset=0):
|
||||||
offset -- offset time to delay the launch of the first check
|
offset -- offset time to delay the launch of the first check
|
||||||
"""
|
"""
|
||||||
|
|
||||||
o = urlparse(getNormalizedURL(site["url"]), "http")
|
#o = urlparse(getNormalizedURL(site["url"]), "http")
|
||||||
#print_debug("Add %s event for site: %s" % (site["type"], o.netloc))
|
#print("Add %s event for site: %s" % (site["type"], o.netloc))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
evt = ModuleEvent(func=fwatch,
|
evt = ModuleEvent(func=partial(fwatch, url=site["url"]),
|
||||||
cmp_data=site["lastcontent"],
|
cmp=site["lastcontent"],
|
||||||
func_data=site["url"], offset=offset,
|
offset=offset, interval=site.getInt("time"),
|
||||||
interval=site.getInt("time"),
|
call=partial(alert_change, site=site))
|
||||||
call=alert_change, call_data=site)
|
|
||||||
site["_evt_id"] = add_event(evt)
|
site["_evt_id"] = add_event(evt)
|
||||||
except IMException:
|
except IMException:
|
||||||
logger.exception("Unable to watch %s", site["url"])
|
logger.exception("Unable to watch %s", site["url"])
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,10 @@ import urllib
|
||||||
from nemubot.exception import IMException
|
from nemubot.exception import IMException
|
||||||
from nemubot.tools.web import getJSON
|
from nemubot.tools.web import getJSON
|
||||||
|
|
||||||
from more import Response
|
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_AVAIL = "https://www.whoisxmlapi.com/whoisserver/WhoisService?cmd=GET_DN_AVAILABILITY&domainName=%%s&outputFormat=json&username=%s&password=%s"
|
||||||
URL_WHOIS = "http://www.whoisxmlapi.com/whoisserver/WhoisService?da=2&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 #############################################################
|
# LOADING #############################################################
|
||||||
|
|
@ -22,7 +22,7 @@ def load(CONF, add_hook):
|
||||||
"the !netwhois feature. Add it to the module "
|
"the !netwhois feature. Add it to the module "
|
||||||
"configuration file:\n<whoisxmlapi username=\"XX\" "
|
"configuration file:\n<whoisxmlapi username=\"XX\" "
|
||||||
"password=\"XXX\" />\nRegister at "
|
"password=\"XXX\" />\nRegister at "
|
||||||
"http://www.whoisxmlapi.com/newaccount.php")
|
"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_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"]))
|
URL_WHOIS = URL_WHOIS % (urllib.parse.quote(CONF.getNode("whoisxmlapi")["username"]), urllib.parse.quote(CONF.getNode("whoisxmlapi")["password"]))
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,8 @@ from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.tools import web
|
from nemubot.tools import web
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
from nemubot.module.urlreducer import reduce_inline
|
||||||
from nemubot.tools.feed import Feed, AtomEntry
|
from nemubot.tools.feed import Feed, AtomEntry
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -50,10 +51,11 @@ def cmd_news(msg):
|
||||||
links = [x for x in find_rss_links(url)]
|
links = [x for x in find_rss_links(url)]
|
||||||
if len(links) == 0: links = [ url ]
|
if len(links) == 0: links = [ url ]
|
||||||
|
|
||||||
res = Response(channel=msg.channel, nomore="No more news from %s" % 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]):
|
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",
|
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,
|
(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 "",
|
web.striphtml(n.summary) if n.summary else "",
|
||||||
n.link if n.link else ""))
|
n.link if n.link else ""))
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
|
|
||||||
|
|
@ -1,4 +0,0 @@
|
||||||
<?xml version="1.0" ?>
|
|
||||||
<nemubotmodule name="nextstop">
|
|
||||||
<message type="cmd" name="ratp" call="ask_ratp" />
|
|
||||||
</nemubotmodule>
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
"""Informe les usagers des prochains passages des transports en communs de la RATP"""
|
|
||||||
|
|
||||||
from nemubot.exception import IMException
|
|
||||||
from nemubot.hooks import hook
|
|
||||||
from more import Response
|
|
||||||
|
|
||||||
nemubotversion = 3.4
|
|
||||||
|
|
||||||
from .external.src import 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."
|
|
||||||
|
|
||||||
|
|
||||||
@hook.command("ratp")
|
|
||||||
def ask_ratp(msg):
|
|
||||||
"""Hook entry from !ratp"""
|
|
||||||
if len(msg.args) >= 3:
|
|
||||||
transport = msg.args[0]
|
|
||||||
line = msg.args[1]
|
|
||||||
station = msg.args[2]
|
|
||||||
if len(msg.args) == 4:
|
|
||||||
times = ratp.getNextStopsAtStation(transport, line, station, msg.args[3])
|
|
||||||
else:
|
|
||||||
times = ratp.getNextStopsAtStation(transport, line, station)
|
|
||||||
|
|
||||||
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 len(msg.args) == 2:
|
|
||||||
stations = ratp.getAllStations(msg.args[0], msg.args[1])
|
|
||||||
|
|
||||||
if len(stations) == 0:
|
|
||||||
raise IMException("aucune station trouvée.")
|
|
||||||
return Response([s for s in stations], title="Stations", channel=msg.channel)
|
|
||||||
|
|
||||||
else:
|
|
||||||
raise IMException("Mauvais usage, merci de spécifier un type de transport et une ligne, ou de consulter l'aide du module.")
|
|
||||||
|
|
||||||
@hook.command("ratp_alert")
|
|
||||||
def ratp_alert(msg):
|
|
||||||
if len(msg.args) == 2:
|
|
||||||
transport = msg.args[0]
|
|
||||||
cause = msg.args[1]
|
|
||||||
incidents = ratp.getDisturbance(cause, transport)
|
|
||||||
return Response(incidents, channel=msg.channel, nomore="No more incidents", count=" (%d more incidents)")
|
|
||||||
else:
|
|
||||||
raise IMException("Mauvais usage, merci de spécifier un type de transport et un type d'alerte (alerte, manif, travaux), ou de consulter l'aide du module.")
|
|
||||||
|
|
@ -1 +0,0 @@
|
||||||
Subproject commit 3d5c9b2d52fbd214f5aaad00e5f3952de919b3e5
|
|
||||||
229
modules/nntp.py
Normal file
229
modules/nntp.py
Normal file
|
|
@ -0,0 +1,229 @@
|
||||||
|
"""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)
|
||||||
87
modules/openai.py
Normal file
87
modules/openai.py
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
"""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)
|
||||||
158
modules/openroute.py
Normal file
158
modules/openroute.py
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
"""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
|
||||||
68
modules/pkgs.py
Normal file
68
modules/pkgs.py
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
"""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
|
||||||
74
modules/ratp.py
Normal file
74
modules/ratp.py
Normal file
|
|
@ -0,0 +1,74 @@
|
||||||
|
"""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)")
|
||||||
|
|
@ -10,7 +10,7 @@ from nemubot.tools import web
|
||||||
|
|
||||||
nemubotversion = 3.4
|
nemubotversion = 3.4
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
def help_full():
|
def help_full():
|
||||||
|
|
@ -40,7 +40,7 @@ def cmd_subreddit(msg):
|
||||||
else:
|
else:
|
||||||
where = "r"
|
where = "r"
|
||||||
|
|
||||||
sbr = web.getJSON("http://www.reddit.com/%s/%s/about.json" %
|
sbr = web.getJSON("https://www.reddit.com/%s/%s/about.json" %
|
||||||
(where, sub.group(2)))
|
(where, sub.group(2)))
|
||||||
|
|
||||||
if sbr is None:
|
if sbr is None:
|
||||||
|
|
@ -64,22 +64,29 @@ def cmd_subreddit(msg):
|
||||||
channel=msg.channel))
|
channel=msg.channel))
|
||||||
else:
|
else:
|
||||||
all_res.append(Response("%s is not a valid subreddit" % osub,
|
all_res.append(Response("%s is not a valid subreddit" % osub,
|
||||||
channel=msg.channel, nick=msg.nick))
|
channel=msg.channel, nick=msg.frm))
|
||||||
|
|
||||||
return all_res
|
return all_res
|
||||||
|
|
||||||
|
|
||||||
@hook.message()
|
@hook.message()
|
||||||
def parselisten(msg):
|
def parselisten(msg):
|
||||||
parseresponse(msg)
|
global LAST_SUBS
|
||||||
return None
|
|
||||||
|
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()
|
@hook.post()
|
||||||
def parseresponse(msg):
|
def parseresponse(msg):
|
||||||
global LAST_SUBS
|
global LAST_SUBS
|
||||||
|
|
||||||
if hasattr(msg, "text") and msg.text:
|
if hasattr(msg, "text") and msg.text and type(msg.text) == str:
|
||||||
urls = re.findall("www.reddit.com(/\w/\w+/?)", msg.text)
|
urls = re.findall("www.reddit.com(/\w/\w+/?)", msg.text)
|
||||||
for url in urls:
|
for url in urls:
|
||||||
for recv in msg.to:
|
for recv in msg.to:
|
||||||
|
|
|
||||||
94
modules/repology.py
Normal file
94
modules/repology.py
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
# 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
|
||||||
|
|
@ -8,9 +8,8 @@ import shlex
|
||||||
from nemubot import context
|
from nemubot import context
|
||||||
from nemubot.exception import IMException
|
from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.message import Command
|
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
# MODULE INTERFACE ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
@ -22,7 +21,7 @@ def cmd_choice(msg):
|
||||||
|
|
||||||
return Response(random.choice(msg.args),
|
return Response(random.choice(msg.args),
|
||||||
channel=msg.channel,
|
channel=msg.channel,
|
||||||
nick=msg.nick)
|
nick=msg.frm)
|
||||||
|
|
||||||
|
|
||||||
@hook.command("choicecmd")
|
@hook.command("choicecmd")
|
||||||
|
|
@ -32,8 +31,24 @@ def cmd_choicecmd(msg):
|
||||||
|
|
||||||
choice = shlex.split(random.choice(msg.args))
|
choice = shlex.split(random.choice(msg.args))
|
||||||
|
|
||||||
return [x for x in context.subtreat(Command(choice[0][1:],
|
return [x for x in context.subtreat(context.subparse(msg, choice))]
|
||||||
choice[1:],
|
|
||||||
to_response=msg.to_response,
|
|
||||||
frm=msg.frm,
|
@hook.command("choiceres")
|
||||||
server=msg.server))]
|
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
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ from nemubot.tools import web
|
||||||
|
|
||||||
nemubotversion = 4.0
|
nemubotversion = 4.0
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
def help_full():
|
def help_full():
|
||||||
|
|
@ -25,7 +25,7 @@ def cmd_tcode(msg):
|
||||||
raise IMException("indicate a transaction code or "
|
raise IMException("indicate a transaction code or "
|
||||||
"a keyword to search!")
|
"a keyword to search!")
|
||||||
|
|
||||||
url = ("http://www.tcodesearch.com/tcodes/search?q=%s" %
|
url = ("https://www.tcodesearch.com/tcodes/search?q=%s" %
|
||||||
urllib.parse.quote(msg.args[0]))
|
urllib.parse.quote(msg.args[0]))
|
||||||
|
|
||||||
page = web.getURLContent(url)
|
page = web.getURLContent(url)
|
||||||
|
|
|
||||||
104
modules/shodan.py
Normal file
104
modules/shodan.py
Normal file
|
|
@ -0,0 +1,104 @@
|
||||||
|
"""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
|
||||||
|
|
@ -10,7 +10,7 @@ from nemubot.hooks import hook
|
||||||
|
|
||||||
nemubotversion = 3.4
|
nemubotversion = 3.4
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
def help_full():
|
def help_full():
|
||||||
|
|
|
||||||
116
modules/smmry.py
Normal file
116
modules/smmry.py
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
"""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
|
||||||
110
modules/sms.py
110
modules/sms.py
|
|
@ -16,7 +16,7 @@ from nemubot.tools.xmlparser.node import ModuleState
|
||||||
|
|
||||||
nemubotversion = 3.4
|
nemubotversion = 3.4
|
||||||
|
|
||||||
from more import Response
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
def load(context):
|
def load(context):
|
||||||
context.data.setIndex("name", "phone")
|
context.data.setIndex("name", "phone")
|
||||||
|
|
@ -46,47 +46,89 @@ def send_sms(frm, api_usr, api_key, content):
|
||||||
|
|
||||||
return None
|
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")
|
@hook.command("sms")
|
||||||
def cmd_sms(msg):
|
def cmd_sms(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IMException("À qui veux-tu envoyer ce SMS ?")
|
raise IMException("À qui veux-tu envoyer ce SMS ?")
|
||||||
|
|
||||||
# Check dests
|
cur_epoch = time.mktime(time.localtime())
|
||||||
cur_epoch = time.mktime(time.localtime());
|
dests = msg.args[0].split(",")
|
||||||
for u in msg.args[0].split(","):
|
frm = msg.frm if msg.to_response[0] == msg.frm else msg.frm + "@" + msg.to[0]
|
||||||
if u not in context.data.index:
|
content = " ".join(msg.args[1:])
|
||||||
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)
|
|
||||||
|
|
||||||
# Go!
|
check_sms_dests(dests, cur_epoch)
|
||||||
fails = list()
|
return send_sms_to_list(msg, frm, dests, content, cur_epoch)
|
||||||
for u in msg.args[0].split(","):
|
|
||||||
context.data.index[u]["lastuse"] = cur_epoch
|
|
||||||
if msg.to_response[0] == msg.frm:
|
@hook.command("smscmd")
|
||||||
frm = msg.frm
|
def cmd_smscmd(msg):
|
||||||
else:
|
if not len(msg.args):
|
||||||
frm = msg.frm + "@" + msg.to[0]
|
raise IMException("À qui veux-tu envoyer ce SMS ?")
|
||||||
test = send_sms(frm, context.data.index[u]["user"], context.data.index[u]["key"], " ".join(msg.args[1:]))
|
|
||||||
if test is not None:
|
cur_epoch = time.mktime(time.localtime())
|
||||||
fails.append( "%s: %s" % (u, test) )
|
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)
|
||||||
|
|
||||||
if len(fails) > 0:
|
|
||||||
return Response("quelque chose ne s'est pas bien passé durant l'envoi du SMS : " + ", ".join(fails), msg.channel, msg.nick)
|
|
||||||
else:
|
|
||||||
return Response("le SMS a bien été envoyé", msg.channel, msg.nick)
|
|
||||||
|
|
||||||
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)
|
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)
|
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()
|
@hook.ask()
|
||||||
def parseask(msg):
|
def parseask(msg):
|
||||||
if msg.text.find("Free") >= 0 and (
|
if msg.message.find("Free") >= 0 and (
|
||||||
msg.text.find("API") >= 0 or msg.text.find("api") >= 0) and (
|
msg.message.find("API") >= 0 or msg.message.find("api") >= 0) and (
|
||||||
msg.text.find("SMS") >= 0 or msg.text.find("sms") >= 0):
|
msg.message.find("SMS") >= 0 or msg.message.find("sms") >= 0):
|
||||||
resuser = apiuser_ask.search(msg.text)
|
resuser = apiuser_ask.search(msg.message)
|
||||||
reskey = apikey_ask.search(msg.text)
|
reskey = apikey_ask.search(msg.message)
|
||||||
if resuser is not None and reskey is not None:
|
if resuser is not None and reskey is not None:
|
||||||
apiuser = resuser.group("user")
|
apiuser = resuser.group("user")
|
||||||
apikey = reskey.group("key")
|
apikey = reskey.group("key")
|
||||||
|
|
@ -94,18 +136,18 @@ def parseask(msg):
|
||||||
test = send_sms("nemubot", apiuser, apikey,
|
test = send_sms("nemubot", apiuser, apikey,
|
||||||
"Vous avez enregistré vos codes d'authentification dans nemubot, félicitation !")
|
"Vous avez enregistré vos codes d'authentification dans nemubot, félicitation !")
|
||||||
if test is not None:
|
if test is not None:
|
||||||
return Response("je n'ai pas pu enregistrer tes identifiants : %s" % test, msg.channel, msg.nick)
|
return Response("je n'ai pas pu enregistrer tes identifiants : %s" % test, msg.channel, msg.frm)
|
||||||
|
|
||||||
if msg.nick in context.data.index:
|
if msg.frm in context.data.index:
|
||||||
context.data.index[msg.nick]["user"] = apiuser
|
context.data.index[msg.frm]["user"] = apiuser
|
||||||