Compare commits
259 commits
feature/ev
...
master
| 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 | ||
| 91b550754f | |||
| abf810209e | |||
| 358499e6d5 | |||
| 5fae67255b | |||
| 2c3d61495f | |||
| a05821620d | |||
| 26668c81b1 | |||
| 663e5e7207 | |||
| 6ad979a5eb | |||
| 09e3b082c1 | |||
| ff6460b92e | |||
| d028afd09e | |||
| bd2eff83b7 | |||
| 645c18c981 | |||
| 1d13d56dce | |||
| 277d55d521 | |||
| d705d351c0 | |||
| 9ff8a3a02b | |||
| 1d18305870 | |||
| 009ab08821 | |||
| 313c693d48 | |||
| c9801ee2f7 | |||
| a089efff1a | |||
| d4b6283e23 | |||
| a3236cd67a | |||
| 274836e39a | |||
| 707131023a | |||
| 57c460fc9c | |||
| cd0dbc4cc2 | |||
| d59f629dd9 | |||
| 1e29061bc9 | |||
| e03d803ae0 | |||
| f47aa8c478 | |||
| 6fc6561186 | |||
| ea8656ce0d | |||
| 0ba763f8b1 | |||
| 43c42e1397 | |||
| 926648517f | |||
| 31d93734a6 | |||
| e83c4091bf | |||
| 7ae7e381c3 | |||
| f27347f028 | |||
| 38412c1c16 | |||
| 2ebd86b80f | |||
| 0f4a904a77 | |||
| 36cfdd8861 | |||
| 11bdf8d0a1 | |||
| 00fa139e54 | |||
| 1ef54426bc | |||
| 6aef54910e | |||
| a4e6e4ce84 | |||
| c4a7df7a6f | |||
| 3a1ce6c9e8 | |||
| c06fb69c8b | |||
| f39a0eac56 | |||
| 49d7e4ced6 | |||
| de2e1d6216 | |||
| ea9829b341 | |||
| 70b52d5567 | |||
| 8ff0b626a2 | |||
| 979f1d0c55 | |||
| 9790954dfc | |||
| c6aa38147b | |||
| 8b4f08c5bd | |||
| ac33ceb579 | |||
| 9935e038fc | |||
| f496c31d1c | |||
|
|
c6e1e9acb2 | ||
|
|
1e36846265 | ||
|
|
04d5be04fa | ||
|
|
3cb9a54cee | ||
|
|
497263eaf7 | ||
| e4d67ec345 | |||
| 2fdef0afe4 | |||
| c560e13f24 | |||
| 92530ef1b2 | |||
| 59ea2e971b | |||
| 2b96c32063 | |||
|
|
aca073faff | ||
|
|
7ce9b2bb4c | ||
| 5141a0dc17 | |||
| 56c43179f3 |
138 changed files with 6680 additions and 4053 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" ]
|
||||||
43
README.md
43
README.md
|
|
@ -1,17 +1,50 @@
|
||||||
# *nemubot*
|
nemubot
|
||||||
|
=======
|
||||||
|
|
||||||
An extremely modulable IRC bot, built around XML configuration files!
|
An extremely modulable IRC bot, built around XML configuration files!
|
||||||
|
|
||||||
|
|
||||||
## Requirements
|
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.
|
||||||
|
|
||||||
|
|
||||||
## Documentation
|
Installation
|
||||||
|
------------
|
||||||
|
|
||||||
Have a look to the wiki at https://github.com/nemunaire/nemubot/wiki
|
Use the `setup.py` file: `python setup.py install`.
|
||||||
|
|
||||||
|
### VirtualEnv setup
|
||||||
|
|
||||||
|
The easiest way to do this is through a virtualenv:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
virtualenv venv
|
||||||
|
. venv/bin/activate
|
||||||
|
python setup.py install
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create a new configuration file
|
||||||
|
|
||||||
|
There is a sample configuration file, called `bot_sample.xml`. You can
|
||||||
|
create your own configuration file from it.
|
||||||
|
|
||||||
|
|
||||||
|
Usage
|
||||||
|
-----
|
||||||
|
|
||||||
|
Don't forget to activate your virtualenv in further terminals, if you
|
||||||
|
use it.
|
||||||
|
|
||||||
|
To launch the bot, run:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
nemubot bot.xml
|
||||||
|
```
|
||||||
|
|
||||||
|
Where `bot.xml` is your configuration file.
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
#!/usr/bin/env python3.3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# Nemubot is a smart and modulable IM bot.
|
# Nemubot is a smart and modulable IM bot.
|
||||||
# Copyright (C) 2012-2015 Mercier Pierre-Olivier
|
# Copyright (C) 2012-2016 Mercier Pierre-Olivier
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# 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
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
<nemubotconfig nick="nemubot" realname="nemubot clone" owner="someone">
|
<nemubotconfig nick="nemubot" realname="nemubot clone" owner="someone">
|
||||||
|
|
||||||
<server host="irc.rezosup.org" port="6667" autoconnect="true" caps="znc.in/server-time-iso">
|
<server uri="irc://irc.rezosup.org:6667" autoconnect="true" caps="znc.in/server-time-iso">
|
||||||
<channel name="#nemutest" />
|
<channel name="#nemutest" />
|
||||||
</server>
|
</server>
|
||||||
|
|
||||||
<!--
|
<!--
|
||||||
<server host="my_host.local" port="6667" password="secret" autoconnect="true" ip="10.69.42.23" ssl="on">
|
<server host="ircs://my_host.local:6667" password="secret" autoconnect="true">
|
||||||
<channel name="#nemutest" />
|
<channel name="#nemutest" />
|
||||||
</server>
|
</server>
|
||||||
-->
|
-->
|
||||||
|
|
|
||||||
120
modules/alias.py
120
modules/alias.py
|
|
@ -3,17 +3,16 @@
|
||||||
# PYTHON STUFFS #######################################################
|
# PYTHON STUFFS #######################################################
|
||||||
|
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from datetime import datetime, timezone
|
from datetime import datetime, timezone
|
||||||
import shlex
|
|
||||||
|
|
||||||
from nemubot import context
|
from nemubot import context
|
||||||
from nemubot.exception import IRCException
|
from nemubot.exception import IMException
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
from nemubot.message import Command
|
from nemubot.message import Command
|
||||||
|
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 #############################################################
|
||||||
|
|
@ -77,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):
|
||||||
|
|
@ -109,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()
|
||||||
|
|
@ -123,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
|
||||||
|
|
@ -143,11 +142,12 @@ 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):
|
||||||
k = msg.args.pop(u)
|
msg.args.pop(u)
|
||||||
|
|
||||||
return resultCnt
|
return resultCnt
|
||||||
|
|
||||||
|
|
@ -156,7 +156,7 @@ def replace_variables(cnts, msg=None):
|
||||||
|
|
||||||
## Variables management
|
## Variables management
|
||||||
|
|
||||||
@hook("cmd_hook", "listvars",
|
@hook.command("listvars",
|
||||||
help="list defined variables for substitution in input commands",
|
help="list defined variables for substitution in input commands",
|
||||||
help_usage={
|
help_usage={
|
||||||
None: "List all known variables",
|
None: "List all known variables",
|
||||||
|
|
@ -179,20 +179,20 @@ def cmd_listvars(msg):
|
||||||
return Response("There is currently no variable stored.", channel=msg.channel)
|
return Response("There is currently no variable stored.", channel=msg.channel)
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "set",
|
@hook.command("set",
|
||||||
help="Create or set variables for substitution in input commands",
|
help="Create or set variables for substitution in input commands",
|
||||||
help_usage={"KEY VALUE": "Define the variable named KEY and fill it with VALUE as content"})
|
help_usage={"KEY VALUE": "Define the variable named KEY and fill it with VALUE as content"})
|
||||||
def cmd_set(msg):
|
def cmd_set(msg):
|
||||||
if len(msg.args) < 2:
|
if len(msg.args) < 2:
|
||||||
raise IRCException("!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)
|
||||||
|
|
||||||
|
|
||||||
## Alias management
|
## Alias management
|
||||||
|
|
||||||
@hook("cmd_hook", "listalias",
|
@hook.command("listalias",
|
||||||
help="List registered aliases",
|
help="List registered aliases",
|
||||||
help_usage={
|
help_usage={
|
||||||
None: "List all registered aliases",
|
None: "List all registered aliases",
|
||||||
|
|
@ -206,27 +206,42 @@ def cmd_listalias(msg):
|
||||||
return Response("There is no alias currently.", channel=msg.channel)
|
return Response("There is no alias currently.", channel=msg.channel)
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "alias",
|
@hook.command("alias",
|
||||||
help="Display the replacement command for a given alias")
|
help="Display or define the replacement command for a given alias",
|
||||||
|
help_usage={
|
||||||
|
"ALIAS": "Extends the given alias",
|
||||||
|
"ALIAS COMMAND [ARGS ...]": "Create a new alias named ALIAS as replacement to the given COMMAND and ARGS",
|
||||||
|
})
|
||||||
def cmd_alias(msg):
|
def cmd_alias(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IRCException("!alias takes as argument an alias to extend.")
|
raise IMException("!alias takes as argument an alias to extend.")
|
||||||
res = list()
|
|
||||||
for alias in msg.args:
|
alias = context.subparse(msg, msg.args[0])
|
||||||
if alias[0] == "!":
|
if alias is None or not isinstance(alias, Command):
|
||||||
alias = alias[1:]
|
raise IMException("%s is not a valid alias" % msg.args[0])
|
||||||
if alias in context.data.getNode("aliases").index:
|
|
||||||
res.append("!%s correspond to %s" % (alias, context.data.getNode("aliases").index[alias]["origin"]))
|
if alias.cmd in context.data.getNode("aliases").index:
|
||||||
|
return Response("%s corresponds to %s" % (alias.cmd, context.data.getNode("aliases").index[alias.cmd]["origin"]),
|
||||||
|
channel=msg.channel, nick=msg.frm)
|
||||||
|
|
||||||
|
elif len(msg.args) > 1:
|
||||||
|
create_alias(alias.cmd,
|
||||||
|
" ".join(msg.args[1:]),
|
||||||
|
channel=msg.channel,
|
||||||
|
creator=msg.frm)
|
||||||
|
return Response("New alias %s successfully registered." % alias.cmd,
|
||||||
|
channel=msg.channel)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
res.append("!%s is not an alias" % alias)
|
wym = [m for m in guess(alias.cmd, context.data.getNode("aliases").index)]
|
||||||
return Response(res, channel=msg.channel, nick=msg.nick)
|
raise IMException(msg.args[0] + " is not an alias." + (" Would you mean: %s?" % ", ".join(wym) if len(wym) else ""))
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "unalias",
|
@hook.command("unalias",
|
||||||
help="Remove a previously created alias")
|
help="Remove a previously created alias")
|
||||||
def cmd_unalias(msg):
|
def cmd_unalias(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IRCException("Which alias would you want to remove?")
|
raise IMException("Which alias would you want to remove?")
|
||||||
res = list()
|
res = list()
|
||||||
for alias in msg.args:
|
for alias in msg.args:
|
||||||
if alias[0] == "!" and len(alias) > 1:
|
if alias[0] == "!" and len(alias) > 1:
|
||||||
|
|
@ -243,39 +258,20 @@ def cmd_unalias(msg):
|
||||||
|
|
||||||
## Alias replacement
|
## Alias replacement
|
||||||
|
|
||||||
@hook("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:
|
||||||
txt = context.data.getNode("aliases").index[msg.cmd]["origin"]
|
origin = context.data.getNode("aliases").index[msg.cmd]["origin"]
|
||||||
# TODO: for legacy compatibility
|
rpl_msg = context.subparse(msg, origin)
|
||||||
if txt[0] == "!":
|
if isinstance(rpl_msg, Command):
|
||||||
txt = txt[1:]
|
rpl_msg.args = replace_variables(rpl_msg.args, msg)
|
||||||
try:
|
rpl_msg.args += msg.args
|
||||||
args = shlex.split(txt)
|
rpl_msg.kwargs.update(msg.kwargs)
|
||||||
except ValueError:
|
elif len(msg.args) or len(msg.kwargs):
|
||||||
args = txt.split(' ')
|
raise IMException("This kind of alias doesn't take any argument (haven't you forgotten the '!'?).")
|
||||||
nmsg = Command(args[0], replace_variables(args[1:], msg) + msg.args, **msg.export_args())
|
|
||||||
|
|
||||||
# Avoid infinite recursion
|
# Avoid infinite recursion
|
||||||
if msg.cmd != nmsg.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, nmsg]
|
|
||||||
|
|
||||||
return msg
|
return msg
|
||||||
|
|
||||||
|
|
||||||
@hook("ask_default")
|
|
||||||
def parseask(msg):
|
|
||||||
if re.match(".*(register|set|cr[ée]{2}|new|nouvel(le)?) alias.*", msg.text) is not None:
|
|
||||||
result = re.match(".*alias !?([^ ]+) ?(pour|for|=|:) ?(.+)$", msg.text)
|
|
||||||
if result.group(1) in context.data.getNode("aliases").index:
|
|
||||||
raise IRCException("this alias is already defined.")
|
|
||||||
else:
|
|
||||||
create_alias(result.group(1),
|
|
||||||
result.group(3),
|
|
||||||
channel=msg.channel,
|
|
||||||
creator=msg.nick)
|
|
||||||
res = Response("New alias %s successfully registered." %
|
|
||||||
result.group(1), channel=msg.channel)
|
|
||||||
return res
|
|
||||||
return None
|
|
||||||
|
|
|
||||||
|
|
@ -7,13 +7,13 @@ import sys
|
||||||
from datetime import date, datetime
|
from datetime import date, datetime
|
||||||
|
|
||||||
from nemubot import context
|
from nemubot import context
|
||||||
from nemubot.exception import IRCException
|
from nemubot.exception import IMException
|
||||||
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 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()
|
||||||
|
|
||||||
|
|
@ -46,7 +46,7 @@ def findName(msg):
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|
||||||
@hook("cmd_hook", "anniv",
|
@hook.command("anniv",
|
||||||
help="gives the remaining time before the anniversary of known people",
|
help="gives the remaining time before the anniversary of known people",
|
||||||
help_usage={
|
help_usage={
|
||||||
None: "Calculate the time remaining before your birthday",
|
None: "Calculate the time remaining before your birthday",
|
||||||
|
|
@ -77,10 +77,10 @@ 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("cmd_hook", "age",
|
@hook.command("age",
|
||||||
help="Calculate age of known people",
|
help="Calculate age of known people",
|
||||||
help_usage={
|
help_usage={
|
||||||
None: "Calculate your age",
|
None: "Calculate your age",
|
||||||
|
|
@ -98,26 +98,26 @@ 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
|
||||||
|
|
||||||
|
|
||||||
## Input parsing
|
## Input parsing
|
||||||
|
|
||||||
@hook("ask_default")
|
@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 IRCException("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 #############################################################
|
||||||
|
|
@ -47,9 +46,9 @@ def load(context):
|
||||||
|
|
||||||
# MODULE INTERFACE ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
@hook("cmd_hook", "newyear",
|
@hook.command("newyear",
|
||||||
help="Display the remaining time before the next new year")
|
help="Display the remaining time before the next new year")
|
||||||
@hook("cmd_hook", str(yrn),
|
@hook.command(str(yrn),
|
||||||
help="Display the remaining time before %d" % yrn)
|
help="Display the remaining time before %d" % yrn)
|
||||||
def cmd_newyear(msg):
|
def cmd_newyear(msg):
|
||||||
return Response(countdown_format(datetime(yrn, 1, 1, 0, 0, 1, 0,
|
return Response(countdown_format(datetime(yrn, 1, 1, 0, 0, 1, 0,
|
||||||
|
|
@ -59,7 +58,7 @@ def cmd_newyear(msg):
|
||||||
channel=msg.channel)
|
channel=msg.channel)
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_rgxp", data=yrn, regexp="^[0-9]{4}$",
|
@hook.command(data=yrn, regexp="^[0-9]{4}$",
|
||||||
help="Calculate time remaining/passed before/since the requested year")
|
help="Calculate time remaining/passed before/since the requested year")
|
||||||
def cmd_timetoyear(msg, cur):
|
def cmd_timetoyear(msg, cur):
|
||||||
yr = int(msg.cmd)
|
yr = int(msg.cmd)
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,17 @@
|
||||||
import urllib
|
import urllib
|
||||||
|
|
||||||
from nemubot import context
|
from nemubot import context
|
||||||
from nemubot.exception import IRCException
|
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 #############################################################
|
||||||
|
|
||||||
def load(context):
|
def load(context):
|
||||||
if not context.config or not context.config.getAttribute("goodreadskey"):
|
if not context.config or "goodreadskey" not in context.config:
|
||||||
raise ImportError("You need a Goodreads API key in order to use this "
|
raise ImportError("You need a Goodreads 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=\"books\" goodreadskey=\"XXXXXX\" />\n"
|
"<module name=\"books\" goodreadskey=\"XXXXXX\" />\n"
|
||||||
|
|
@ -28,8 +28,8 @@ def get_book(title):
|
||||||
"""Retrieve a book from its title"""
|
"""Retrieve a book from its title"""
|
||||||
response = web.getXML("https://www.goodreads.com/book/title.xml?key=%s&title=%s" %
|
response = web.getXML("https://www.goodreads.com/book/title.xml?key=%s&title=%s" %
|
||||||
(context.config["goodreadskey"], urllib.parse.quote(title)))
|
(context.config["goodreadskey"], urllib.parse.quote(title)))
|
||||||
if response is not None and response.hasNode("book"):
|
if response is not None and len(response.getElementsByTagName("book")):
|
||||||
return response.getNode("book")
|
return response.getElementsByTagName("book")[0]
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
@ -38,8 +38,8 @@ def search_books(title):
|
||||||
"""Get a list of book matching given title"""
|
"""Get a list of book matching given title"""
|
||||||
response = web.getXML("https://www.goodreads.com/search.xml?key=%s&q=%s" %
|
response = web.getXML("https://www.goodreads.com/search.xml?key=%s&q=%s" %
|
||||||
(context.config["goodreadskey"], urllib.parse.quote(title)))
|
(context.config["goodreadskey"], urllib.parse.quote(title)))
|
||||||
if response is not None and response.hasNode("search"):
|
if response is not None and len(response.getElementsByTagName("search")):
|
||||||
return response.getNode("search").getNode("results").getNodes("work")
|
return response.getElementsByTagName("search")[0].getElementsByTagName("results")[0].getElementsByTagName("work")
|
||||||
else:
|
else:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
@ -48,43 +48,43 @@ def search_author(name):
|
||||||
"""Looking for an author"""
|
"""Looking for an author"""
|
||||||
response = web.getXML("https://www.goodreads.com/api/author_url/%s?key=%s" %
|
response = web.getXML("https://www.goodreads.com/api/author_url/%s?key=%s" %
|
||||||
(urllib.parse.quote(name), context.config["goodreadskey"]))
|
(urllib.parse.quote(name), context.config["goodreadskey"]))
|
||||||
if response is not None and response.hasNode("author") and response.getNode("author").hasAttribute("id"):
|
if response is not None and len(response.getElementsByTagName("author")) and response.getElementsByTagName("author")[0].hasAttribute("id"):
|
||||||
response = web.getXML("https://www.goodreads.com/author/show/%s.xml?key=%s" %
|
response = web.getXML("https://www.goodreads.com/author/show/%s.xml?key=%s" %
|
||||||
(urllib.parse.quote(response.getNode("author")["id"]), context.config["goodreadskey"]))
|
(urllib.parse.quote(response.getElementsByTagName("author")[0].getAttribute("id")), context.config["goodreadskey"]))
|
||||||
if response is not None and response.hasNode("author"):
|
if response is not None and len(response.getElementsByTagName("author")):
|
||||||
return response.getNode("author")
|
return response.getElementsByTagName("author")[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
# MODULE INTERFACE ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
@hook("cmd_hook", "book",
|
@hook.command("book",
|
||||||
help="Get information about a book from its title",
|
help="Get information about a book from its title",
|
||||||
help_usage={
|
help_usage={
|
||||||
"TITLE": "Get information about a book titled TITLE"
|
"TITLE": "Get information about a book titled TITLE"
|
||||||
})
|
})
|
||||||
def cmd_book(msg):
|
def cmd_book(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IRCException("please give me a title to search")
|
raise IMException("please give me a title to search")
|
||||||
|
|
||||||
book = get_book(" ".join(msg.args))
|
book = get_book(" ".join(msg.args))
|
||||||
if book is None:
|
if book is None:
|
||||||
raise IRCException("unable to find book named like this")
|
raise IMException("unable to find book named like this")
|
||||||
res = Response(channel=msg.channel)
|
res = Response(channel=msg.channel)
|
||||||
res.append_message("%s, writed by %s: %s" % (book.getNode("title").getContent(),
|
res.append_message("%s, written by %s: %s" % (book.getElementsByTagName("title")[0].firstChild.nodeValue,
|
||||||
book.getNode("authors").getNode("author").getNode("name").getContent(),
|
book.getElementsByTagName("author")[0].getElementsByTagName("name")[0].firstChild.nodeValue,
|
||||||
web.striphtml(book.getNode("description").getContent())))
|
web.striphtml(book.getElementsByTagName("description")[0].firstChild.nodeValue if book.getElementsByTagName("description")[0].firstChild else "")))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "search_books",
|
@hook.command("search_books",
|
||||||
help="Search book's title",
|
help="Search book's title",
|
||||||
help_usage={
|
help_usage={
|
||||||
"APPROX_TITLE": "Search for a book approximately titled APPROX_TITLE"
|
"APPROX_TITLE": "Search for a book approximately titled APPROX_TITLE"
|
||||||
})
|
})
|
||||||
def cmd_books(msg):
|
def cmd_books(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IRCException("please give me a title to search")
|
raise IMException("please give me a title to search")
|
||||||
|
|
||||||
title = " ".join(msg.args)
|
title = " ".join(msg.args)
|
||||||
res = Response(channel=msg.channel,
|
res = Response(channel=msg.channel,
|
||||||
|
|
@ -92,21 +92,24 @@ def cmd_books(msg):
|
||||||
count=" (%d more books)")
|
count=" (%d more books)")
|
||||||
|
|
||||||
for book in search_books(title):
|
for book in search_books(title):
|
||||||
res.append_message("%s, writed by %s" % (book.getNode("best_book").getNode("title").getContent(),
|
res.append_message("%s, writed by %s" % (book.getElementsByTagName("best_book")[0].getElementsByTagName("title")[0].firstChild.nodeValue,
|
||||||
book.getNode("best_book").getNode("author").getNode("name").getContent()))
|
book.getElementsByTagName("best_book")[0].getElementsByTagName("author")[0].getElementsByTagName("name")[0].firstChild.nodeValue))
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "author_books",
|
@hook.command("author_books",
|
||||||
help="Looking for books writen by a given author",
|
help="Looking for books writen by a given author",
|
||||||
help_usage={
|
help_usage={
|
||||||
"AUTHOR": "Looking for books writen by AUTHOR"
|
"AUTHOR": "Looking for books writen by AUTHOR"
|
||||||
})
|
})
|
||||||
def cmd_author(msg):
|
def cmd_author(msg):
|
||||||
if not len(msg.args):
|
if not len(msg.args):
|
||||||
raise IRCException("please give me an author to search")
|
raise IMException("please give me an author to search")
|
||||||
|
|
||||||
ath = search_author(" ".join(msg.args))
|
name = " ".join(msg.args)
|
||||||
return Response([b.getNode("title").getContent() for b in ath.getNode("books").getNodes("book")],
|
ath = search_author(name)
|
||||||
|
if ath is None:
|
||||||
|
raise IMException("%s does not appear to be a published author." % name)
|
||||||
|
return Response([b.getElementsByTagName("title")[0].firstChild.nodeValue for b in ath.getElementsByTagName("book")],
|
||||||
channel=msg.channel,
|
channel=msg.channel,
|
||||||
title=ath.getNode("name").getContent())
|
title=ath.getElementsByTagName("name")[0].firstChild.nodeValue)
|
||||||
|
|
|
||||||
55
modules/cat.py
Normal file
55
modules/cat.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
"""Concatenate commands"""
|
||||||
|
|
||||||
|
# PYTHON STUFFS #######################################################
|
||||||
|
|
||||||
|
from nemubot import context
|
||||||
|
from nemubot.exception import IMException
|
||||||
|
from nemubot.hooks import hook
|
||||||
|
from nemubot.message import Command, DirectAsk, Text
|
||||||
|
|
||||||
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
|
||||||
|
# MODULE CORE #########################################################
|
||||||
|
|
||||||
|
def cat(msg, *terms):
|
||||||
|
res = Response(channel=msg.to_response, server=msg.server)
|
||||||
|
for term in terms:
|
||||||
|
m = context.subparse(msg, term)
|
||||||
|
if isinstance(m, Command) or isinstance(m, DirectAsk):
|
||||||
|
for r in context.subtreat(m):
|
||||||
|
if isinstance(r, Response):
|
||||||
|
for t in range(len(r.messages)):
|
||||||
|
res.append_message(r.messages[t],
|
||||||
|
title=r.rawtitle if not isinstance(r.rawtitle, list) else r.rawtitle[t])
|
||||||
|
|
||||||
|
elif isinstance(r, Text):
|
||||||
|
res.append_message(r.message)
|
||||||
|
|
||||||
|
elif isinstance(r, str):
|
||||||
|
res.append_message(r)
|
||||||
|
|
||||||
|
else:
|
||||||
|
res.append_message(term)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
|
@hook.command("cat",
|
||||||
|
help="Concatenate responses of commands given as argument",
|
||||||
|
help_usage={"!SUBCMD [!SUBCMD [...]]": "Concatenate response of subcommands"},
|
||||||
|
keywords={
|
||||||
|
"merge": "Merge messages into the same",
|
||||||
|
})
|
||||||
|
def cmd_cat(msg):
|
||||||
|
if len(msg.args) < 1:
|
||||||
|
raise IMException("No subcommand to concatenate")
|
||||||
|
|
||||||
|
r = cat(msg, *msg.args)
|
||||||
|
|
||||||
|
if "merge" in msg.kwargs and len(r.messages) > 1:
|
||||||
|
r.messages = [ r.messages ]
|
||||||
|
|
||||||
|
return r
|
||||||
|
|
@ -1,204 +0,0 @@
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
# 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")
|
|
||||||
|
|
@ -6,12 +6,12 @@ from collections import defaultdict
|
||||||
import re
|
import re
|
||||||
from urllib.parse import quote
|
from urllib.parse import quote
|
||||||
|
|
||||||
from nemubot.exception import IRCException
|
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 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)
|
||||||
|
|
||||||
|
|
@ -51,10 +51,10 @@ def compute_line(line, stringTens):
|
||||||
try:
|
try:
|
||||||
idTemps = d[stringTens]
|
idTemps = d[stringTens]
|
||||||
except:
|
except:
|
||||||
raise IRCException("le temps demandé n'existe pas")
|
raise IMException("le temps demandé n'existe pas")
|
||||||
|
|
||||||
if len(idTemps) == 0:
|
if len(idTemps) == 0:
|
||||||
raise IRCException("le temps demandé n'existe pas")
|
raise IMException("le temps demandé n'existe pas")
|
||||||
|
|
||||||
index = line.index('<div id="temps' + idTemps[0] + '\"')
|
index = line.index('<div id="temps' + idTemps[0] + '\"')
|
||||||
endIndex = line[index:].index('<div class=\"conjugBloc\"')
|
endIndex = line[index:].index('<div class=\"conjugBloc\"')
|
||||||
|
|
@ -72,13 +72,13 @@ def compute_line(line, stringTens):
|
||||||
|
|
||||||
# MODULE INTERFACE ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
@hook("cmd_hook", "conjugaison",
|
@hook.command("conjugaison",
|
||||||
help_usage={
|
help_usage={
|
||||||
"TENS VERB": "give the conjugaison for VERB in TENS."
|
"TENS VERB": "give the conjugaison for VERB in TENS."
|
||||||
})
|
})
|
||||||
def cmd_conjug(msg):
|
def cmd_conjug(msg):
|
||||||
if len(msg.args) < 2:
|
if len(msg.args) < 2:
|
||||||
raise IRCException("donne moi un temps et un verbe, et je te donnerai "
|
raise IMException("donne moi un temps et un verbe, et je te donnerai "
|
||||||
"sa conjugaison!")
|
"sa conjugaison!")
|
||||||
|
|
||||||
tens = ' '.join(msg.args[:-1])
|
tens = ' '.join(msg.args[:-1])
|
||||||
|
|
@ -91,4 +91,4 @@ def cmd_conjug(msg):
|
||||||
return Response(conjug, channel=msg.channel,
|
return Response(conjug, channel=msg.channel,
|
||||||
title="Conjugaison de %s" % verb)
|
title="Conjugaison de %s" % verb)
|
||||||
else:
|
else:
|
||||||
raise IRCException("aucune conjugaison de '%s' n'a été trouvé" % verb)
|
raise IMException("aucune conjugaison de '%s' n'a été trouvé" % verb)
|
||||||
|
|
|
||||||
|
|
@ -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 #############################################################
|
||||||
|
|
@ -16,7 +16,7 @@ URL = 'https://ctftime.org/event/list/upcoming'
|
||||||
|
|
||||||
# MODULE INTERFACE ####################################################
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
@hook("cmd_hook", "ctfs",
|
@hook.command("ctfs",
|
||||||
help="Display the upcoming CTFs")
|
help="Display the upcoming CTFs")
|
||||||
def get_info_yt(msg):
|
def get_info_yt(msg):
|
||||||
soup = BeautifulSoup(getURLContent(URL))
|
soup = BeautifulSoup(getURLContent(URL))
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,78 @@
|
||||||
|
"""Read CVE in your IM client"""
|
||||||
|
|
||||||
|
# PYTHON STUFFS #######################################################
|
||||||
|
|
||||||
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
|
from nemubot.tools.web import getURLContent, striphtml
|
||||||
from more import Response
|
|
||||||
|
|
||||||
"""CVE description"""
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
nemubotversion = 4.0
|
BASEURL_NIST = 'https://nvd.nist.gov/vuln/detail/'
|
||||||
|
|
||||||
BASEURL_MITRE = 'http://cve.mitre.org/cgi-bin/cvename.cgi?name='
|
|
||||||
|
# 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_MITRE + quote(cve_id.upper())
|
search_url = BASEURL_NIST + quote(cve_id.upper())
|
||||||
|
|
||||||
soup = BeautifulSoup(getURLContent(search_url))
|
soup = BeautifulSoup(getURLContent(search_url))
|
||||||
desc = soup.body.findAll('td')
|
|
||||||
|
|
||||||
return desc[17].text.replace("\n", " ") + " Moar at " + search_url
|
vuln = {}
|
||||||
|
|
||||||
@hook("cmd_hook", "cve")
|
for vd in VULN_DATAS:
|
||||||
|
r = soup.body.find(attrs={"data-testid": VULN_DATAS[vd]})
|
||||||
|
if r:
|
||||||
|
vuln[vd] = r.text.strip()
|
||||||
|
|
||||||
|
return vuln
|
||||||
|
|
||||||
|
|
||||||
|
def display_metrics(av, ac, pr, ui, s, c, i, a, **kwargs):
|
||||||
|
ret = []
|
||||||
|
if av != "None": ret.append("Attack Vector: \x02%s\x0F" % av)
|
||||||
|
if ac != "None": ret.append("Attack Complexity: \x02%s\x0F" % ac)
|
||||||
|
if pr != "None": ret.append("Privileges Required: \x02%s\x0F" % pr)
|
||||||
|
if ui != "None": ret.append("User Interaction: \x02%s\x0F" % ui)
|
||||||
|
if s != "Unchanged": ret.append("Scope: \x02%s\x0F" % s)
|
||||||
|
if c != "None": ret.append("Confidentiality: \x02%s\x0F" % c)
|
||||||
|
if i != "None": ret.append("Integrity: \x02%s\x0F" % i)
|
||||||
|
if a != "None": ret.append("Availability: \x02%s\x0F" % a)
|
||||||
|
return ', '.join(ret)
|
||||||
|
|
||||||
|
|
||||||
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
|
@hook.command("cve",
|
||||||
|
help="Display given CVE",
|
||||||
|
help_usage={"CVE_ID": "Display the description of the given CVE"})
|
||||||
def get_cve_desc(msg):
|
def get_cve_desc(msg):
|
||||||
res = Response(channel=msg.channel)
|
res = Response(channel=msg.channel)
|
||||||
|
|
||||||
|
|
@ -28,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
|
||||||
|
|
|
||||||
138
modules/ddg.py
Normal file
138
modules/ddg.py
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
"""Search around DuckDuckGo search engine"""
|
||||||
|
|
||||||
|
# PYTHON STUFFS #######################################################
|
||||||
|
|
||||||
|
from urllib.parse import quote
|
||||||
|
|
||||||
|
from nemubot.exception import IMException
|
||||||
|
from nemubot.hooks import hook
|
||||||
|
from nemubot.tools import web
|
||||||
|
|
||||||
|
from nemubot.module.more import Response
|
||||||
|
|
||||||
|
# MODULE CORE #########################################################
|
||||||
|
|
||||||
|
def do_search(terms):
|
||||||
|
if "!safeoff" in terms:
|
||||||
|
terms.remove("!safeoff")
|
||||||
|
safeoff = True
|
||||||
|
else:
|
||||||
|
safeoff = False
|
||||||
|
|
||||||
|
sterm = " ".join(terms)
|
||||||
|
return DDGResult(sterm, web.getJSON(
|
||||||
|
"https://api.duckduckgo.com/?q=%s&format=json&no_redirect=1%s" %
|
||||||
|
(quote(sterm), "&kp=-1" if safeoff else "")))
|
||||||
|
|
||||||
|
|
||||||
|
class DDGResult:
|
||||||
|
|
||||||
|
def __init__(self, terms, res):
|
||||||
|
if res is None:
|
||||||
|
raise IMException("An error occurs during search")
|
||||||
|
|
||||||
|
self.terms = terms
|
||||||
|
self.ddgres = res
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def type(self):
|
||||||
|
if not self.ddgres or "Type" not in self.ddgres:
|
||||||
|
return ""
|
||||||
|
return self.ddgres["Type"]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def definition(self):
|
||||||
|
if "Definition" not in self.ddgres or not self.ddgres["Definition"]:
|
||||||
|
return None
|
||||||
|
return self.ddgres["Definition"] + " <" + self.ddgres["DefinitionURL"] + "> from " + self.ddgres["DefinitionSource"]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def relatedTopics(self):
|
||||||
|
if "RelatedTopics" in self.ddgres:
|
||||||
|
for rt in self.ddgres["RelatedTopics"]:
|
||||||
|
if "Text" in rt:
|
||||||
|
yield rt["Text"] + " <" + rt["FirstURL"] + ">"
|
||||||
|
elif "Topics" in rt:
|
||||||
|
yield rt["Name"] + ": " + "; ".join([srt["Text"] + " <" + srt["FirstURL"] + ">" for srt in rt["Topics"]])
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def redirect(self):
|
||||||
|
if "Redirect" not in self.ddgres or not self.ddgres["Redirect"]:
|
||||||
|
return None
|
||||||
|
return self.ddgres["Redirect"]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def entity(self):
|
||||||
|
if "Entity" not in self.ddgres or not self.ddgres["Entity"]:
|
||||||
|
return None
|
||||||
|
return self.ddgres["Entity"]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def heading(self):
|
||||||
|
if "Heading" not in self.ddgres or not self.ddgres["Heading"]:
|
||||||
|
return " ".join(self.terms)
|
||||||
|
return self.ddgres["Heading"]
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def result(self):
|
||||||
|
if "Results" in self.ddgres:
|
||||||
|
for res in self.ddgres["Results"]:
|
||||||
|
yield res["Text"] + " <" + res["FirstURL"] + ">"
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def answer(self):
|
||||||
|
if "Answer" not in self.ddgres or not self.ddgres["Answer"]:
|
||||||
|
return None
|
||||||
|
return web.striphtml(self.ddgres["Answer"])
|
||||||
|
|
||||||
|
|
||||||
|
@property
|
||||||
|
def abstract(self):
|
||||||
|
if "Abstract" not in self.ddgres or not self.ddgres["Abstract"]:
|
||||||
|
return None
|
||||||
|
return self.ddgres["AbstractText"] + " <" + self.ddgres["AbstractURL"] + "> from " + self.ddgres["AbstractSource"]
|
||||||
|
|
||||||
|
|
||||||
|
# MODULE INTERFACE ####################################################
|
||||||
|
|
||||||
|
@hook.command("define")
|
||||||
|
def define(msg):
|
||||||
|
if not len(msg.args):
|
||||||
|
raise IMException("Indicate a term to define")
|
||||||
|
|
||||||
|
s = do_search(msg.args)
|
||||||
|
|
||||||
|
if not s.definition:
|
||||||
|
raise IMException("no definition found for '%s'." % " ".join(msg.args))
|
||||||
|
|
||||||
|
return Response(s.definition, channel=msg.channel)
|
||||||
|
|
||||||
|
@hook.command("search")
|
||||||
|
def search(msg):
|
||||||
|
if not len(msg.args):
|
||||||
|
raise IMException("Indicate a term to search")
|
||||||
|
|
||||||
|
s = do_search(msg.args)
|
||||||
|
|
||||||
|
res = Response(channel=msg.channel, nomore="No more results",
|
||||||
|
count=" (%d more results)")
|
||||||
|
|
||||||
|
res.append_message(s.redirect)
|
||||||
|
res.append_message(s.answer)
|
||||||
|
res.append_message(s.abstract)
|
||||||
|
res.append_message([r for r in s.result])
|
||||||
|
|
||||||
|
for rt in s.relatedTopics:
|
||||||
|
res.append_message(rt)
|
||||||
|
|
||||||
|
res.append_message(s.definition)
|
||||||
|
|
||||||
|
return res
|
||||||
|
|
@ -1,71 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
from nemubot.tools import web
|
|
||||||
from nemubot.tools.xmlparser import parse_string
|
|
||||||
|
|
||||||
|
|
||||||
class DDGSearch:
|
|
||||||
|
|
||||||
def __init__(self, terms, safeoff=False):
|
|
||||||
self.terms = terms
|
|
||||||
|
|
||||||
self.ddgres = web.getXML(
|
|
||||||
"https://api.duckduckgo.com/?q=%s&format=xml&no_redirect=1%s" %
|
|
||||||
(quote(terms), "&kp=-1" if safeoff else ""),
|
|
||||||
timeout=10)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def type(self):
|
|
||||||
if self.ddgres and self.ddgres.hasNode("Type"):
|
|
||||||
return self.ddgres.getFirstNode("Type").getContent()
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def definition(self):
|
|
||||||
if self.ddgres.hasNode("Definition"):
|
|
||||||
return self.ddgres.getFirstNode("Definition").getContent()
|
|
||||||
else:
|
|
||||||
return "Sorry, no definition found for %s" % self.terms
|
|
||||||
|
|
||||||
@property
|
|
||||||
def relatedTopics(self):
|
|
||||||
try:
|
|
||||||
for rt in self.ddgres.getFirstNode("RelatedTopics").getNodes("RelatedTopic"):
|
|
||||||
yield rt.getFirstNode("Text").getContent()
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@property
|
|
||||||
def redirect(self):
|
|
||||||
try:
|
|
||||||
return self.ddgres.getFirstNode("Redirect").getContent()
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def result(self):
|
|
||||||
try:
|
|
||||||
node = self.ddgres.getFirstNode("Results").getFirstNode("Result")
|
|
||||||
return node.getFirstNode("Text").getContent() + ": " + node.getFirstNode("FirstURL").getContent()
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def answer(self):
|
|
||||||
try:
|
|
||||||
return web.striphtml(self.ddgres.getFirstNode("Answer").getContent())
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
||||||
@property
|
|
||||||
def abstract(self):
|
|
||||||
try:
|
|
||||||
if self.ddgres.getNode("Abstract").getContent() != "":
|
|
||||||
return self.ddgres.getNode("Abstract").getContent() + " <" + self.ddgres.getNode("AbstractURL").getContent() + ">"
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
except:
|
|
||||||
return None
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
from urllib.parse import quote
|
|
||||||
|
|
||||||
from nemubot.tools import web
|
|
||||||
|
|
||||||
|
|
||||||
class UrbanDictionnary:
|
|
||||||
|
|
||||||
def __init__(self, terms):
|
|
||||||
self.terms = terms
|
|
||||||
|
|
||||||
self.udres = web.getJSON(
|
|
||||||
"http://api.urbandictionary.com/v0/define?term=%s" % quote(terms),
|
|
||||||
timeout=10)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def result_type(self):
|
|
||||||
if self.udres and "result_type" in self.udres:
|
|
||||||
return self.udres["result_type"]
|
|
||||||
else:
|
|
||||||
return ""
|
|
||||||
|
|
||||||
@property
|
|
||||||
def definitions(self):
|
|
||||||
if self.udres and "list" in self.udres:
|
|
||||||
for d in self.udres["list"]:
|
|
||||||
yield d["definition"] + "\n" + d["example"]
|
|
||||||
else:
|
|
||||||
yield "Sorry, no definition found for %s" % self.terms
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
"""Search around various search engine or knowledges database"""
|
|
||||||
|
|
||||||
import imp
|
|
||||||
|
|
||||||
from nemubot import context
|
|
||||||
from nemubot.exception import IRCException
|
|
||||||
from nemubot.hooks import hook
|
|
||||||
|
|
||||||
nemubotversion = 3.4
|
|
||||||
|
|
||||||
from more import Response
|
|
||||||
|
|
||||||
from . import DDGSearch
|
|
||||||
from . import UrbanDictionnary
|
|
||||||
|
|
||||||
@hook("cmd_hook", "define")
|
|
||||||
def define(msg):
|
|
||||||
if not len(msg.args):
|
|
||||||
raise IRCException("Indicate a term to define")
|
|
||||||
|
|
||||||
s = DDGSearch.DDGSearch(' '.join(msg.args))
|
|
||||||
|
|
||||||
return Response(s.definition, channel=msg.channel)
|
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "search")
|
|
||||||
def search(msg):
|
|
||||||
if not len(msg.args):
|
|
||||||
raise IRCException("Indicate a term to search")
|
|
||||||
|
|
||||||
if "!safeoff" in msg.args:
|
|
||||||
msg.args.remove("!safeoff")
|
|
||||||
safeoff = True
|
|
||||||
else:
|
|
||||||
safeoff = False
|
|
||||||
|
|
||||||
s = DDGSearch.DDGSearch(' '.join(msg.args), safeoff)
|
|
||||||
|
|
||||||
res = Response(channel=msg.channel, nomore="No more results",
|
|
||||||
count=" (%d more results)")
|
|
||||||
|
|
||||||
res.append_message(s.redirect)
|
|
||||||
res.append_message(s.abstract)
|
|
||||||
res.append_message(s.result)
|
|
||||||
res.append_message(s.answer)
|
|
||||||
|
|
||||||
for rt in s.relatedTopics:
|
|
||||||
res.append_message(rt)
|
|
||||||
|
|
||||||
res.append_message(s.definition)
|
|
||||||
|
|
||||||
return res
|
|
||||||
|
|
||||||
|
|
||||||
@hook("cmd_hook", "urbandictionnary")
|
|
||||||
def udsearch(msg):
|
|
||||||
if not len(msg.args):
|
|
||||||
raise IRCException("Indicate a term to search")
|
|
||||||
|
|
||||||
s = UrbanDictionnary.UrbanDictionnary(' '.join(msg.args))
|
|
||||||
|
|
||||||
res = Response(channel=msg.channel, nomore="No more results",
|
|
||||||
count=" (%d more definitions)")
|
|
||||||
|
|
||||||
for d in s.definitions:
|
|
||||||
res.append_message(d.replace("\n", " "))
|
|
||||||
|
|
||||||
return res
|
|
||||||
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,49 +1,99 @@
|
||||||
# coding=utf-8
|
|
||||||
|
|
||||||
"""Create countdowns and reminders"""
|
"""Create countdowns and reminders"""
|
||||||
|
|
||||||
import imp
|
import calendar
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
from datetime import datetime, timedelta, timezone
|
from datetime import datetime, timedelta, timezone
|
||||||
import time
|
from functools import partial
|
||||||
import threading
|
import re
|
||||||
import traceback
|
|
||||||
|
|
||||||
from nemubot import context
|
from nemubot import context
|
||||||
from nemubot.exception import IRCException
|
from nemubot.exception import IMException
|
||||||
from nemubot.event import ModuleEvent
|
from nemubot.event import ModuleEvent
|
||||||
from nemubot.hooks import hook
|
from nemubot.hooks import hook
|
||||||
|
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
|
||||||
|
|
||||||
nemubotversion = 3.4
|
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
|
||||||
|
|
||||||
from more import Response
|
|
||||||
|
|
||||||
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()
|
||||||
|
|
||||||
@hook("cmd_hook", "goûter")
|
|
||||||
|
@hook.command("goûter")
|
||||||
def cmd_gouter(msg):
|
def cmd_gouter(msg):
|
||||||
ndate = datetime.now(timezone.utc)
|
ndate = datetime.now(timezone.utc)
|
||||||
ndate = datetime(ndate.year, ndate.month, ndate.day, 16, 42, 0, 0, timezone.utc)
|
ndate = datetime(ndate.year, ndate.month, ndate.day, 16, 42, 0, 0, timezone.utc)
|
||||||
|
|
@ -52,7 +102,8 @@ def cmd_gouter(msg):
|
||||||
"Nous avons %s de retard pour le goûter :("),
|
"Nous avons %s de retard pour le goûter :("),
|
||||||
channel=msg.channel)
|
channel=msg.channel)
|
||||||
|
|
||||||
@hook("cmd_hook", "week-end")
|
|
||||||
|
@hook.command("week-end")
|
||||||
def cmd_we(msg):
|
def cmd_we(msg):
|
||||||
ndate = datetime.now(timezone.utc) + timedelta(5 - datetime.today().weekday())
|
ndate = datetime.now(timezone.utc) + timedelta(5 - datetime.today().weekday())
|
||||||
ndate = datetime(ndate.year, ndate.month, ndate.day, 0, 0, 1, 0, timezone.utc)
|
ndate = datetime(ndate.year, ndate.month, ndate.day, 0, 0, 1, 0, timezone.utc)
|
||||||
|
|
@ -61,23 +112,16 @@ def cmd_we(msg):
|
||||||
"Youhou, on est en week-end depuis %s."),
|
"Youhou, on est en week-end depuis %s."),
|
||||||
channel=msg.channel)
|
channel=msg.channel)
|
||||||
|
|
||||||
@hook("cmd_hook", "start")
|
|
||||||
|
@hook.command("start")
|
||||||
def start_countdown(msg):
|
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 IRCException("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 IRCException("%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])
|
||||||
|
|
@ -95,136 +139,138 @@ 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 IRCException("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( | ||||||