Compare commits

..

39 Commits

Author SHA1 Message Date
Ville Ranki 68b4fa4772
Merge pull request #242 from Cantido/feature/d20
Add !roll module for dice rolling
2023-06-22 09:49:28 +03:00
Rosa Richter f62e2164da
Add roll module 2023-06-20 19:48:50 -06:00
Ville Ranki bf3e4a7c7c
Merge pull request #235 from Aciid/wikipedia
wikipedia.py: fixes smart trim, redirects, canonical urls
2023-03-10 10:28:26 +02:00
Aciid 3c3eae0c39
wikipedia.py: add smart trimming for extract, use redirects, use canonical URL in content, convert spaces to underscore in urls. 2023-03-08 14:57:26 +02:00
Ville Ranki 075e94ccb6
Merge pull request #234 from Aciid/twitter
Twitter module initial commit
2023-03-06 12:01:05 +02:00
Ville Ranki fea9be54fa
Merge branch 'master' into twitter 2023-03-06 12:00:57 +02:00
Aciid 3d17818737
Rename Twitter module to Nitter 2023-03-06 11:58:48 +02:00
Ville Ranki 34dfe5e878
Merge pull request #229 from vranki/dependabot/github_actions/docker/build-push-action-4.0.0
Bump docker/build-push-action from 3.2.0 to 4.0.0
2023-03-06 09:22:06 +02:00
Ville Ranki dda525a22c
Merge pull request #227 from vranki/dependabot/github_actions/docker/metadata-action-4.3.0
Bump docker/metadata-action from 4.1.1 to 4.3.0
2023-03-06 09:21:49 +02:00
Ville Ranki 2e6ff51121
Merge pull request #233 from Aciid/wikipedia
New module Wikipedia
2023-03-06 09:18:16 +02:00
Aciid 3bdf703639
Twitter module initial commit 2023-03-04 16:58:34 +02:00
Aciid 9af7a4228b
Wikipedia module initial commit 2023-03-04 15:38:53 +02:00
Aciid bd8f574572
Wikipedia module initial commit 2023-03-04 15:38:38 +02:00
Ville Ranki 5da7e36e94
Merge pull request #232 from european-epc-competence-center/status_module
improved status list formatting (html) and added save state on clear
2023-02-01 13:57:12 +02:00
Sebastian Schmittner 9dff166f15
improved status list formatting (html) and added save state on clear
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-02-01 12:10:32 +01:00
Ville Ranki 3f4c0d7d9b
Merge pull request #231 from european-epc-competence-center/status_module
Status module
2023-02-01 09:25:06 +02:00
Ville Ranki 6243ac8da6
Merge pull request #230 from european-epc-competence-center/xkcd_module
Xkcd module
2023-02-01 09:23:38 +02:00
Sebastian Schmittner 72acb78fba
flake8
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-01-31 21:57:48 +01:00
Sebastian Schmittner 3c3a6535a5
added user status module
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-01-31 21:57:10 +01:00
Sebastian Schmittner 91dfcf8328
flake
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-01-31 21:04:26 +01:00
Sebastian Schmittner 656f0748ea
read me
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-01-31 21:00:09 +01:00
Sebastian Schmittner 43e7dca53b
fixing minor bugs in xkcd bot
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-01-31 20:44:26 +01:00
Sebastian Schmittner 0b4b02e144
added module to query comics from xkcd. This module is derived from the apod module.
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2023-01-31 20:26:27 +01:00
dependabot[bot] 6389df651e
Bump docker/build-push-action from 3.2.0 to 4.0.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.2.0 to 4.0.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](c56af95754...3b5e8027fc)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-31 13:03:31 +00:00
dependabot[bot] d31cccf2fe
Bump docker/metadata-action from 4.1.1 to 4.3.0
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.1.1 to 4.3.0.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](57396166ad...507c2f2dc5)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-01-16 13:07:45 +00:00
Ville Ranki ad7124d140
Merge pull request #219 from vranki/dependabot/github_actions/docker/build-push-action-3.2.0
Bump docker/build-push-action from 3.1.1 to 3.2.0
2022-12-02 21:12:54 +02:00
Ville Ranki 3b117419f0
Merge pull request #221 from vranki/dependabot/github_actions/docker/login-action-2.1.0
Bump docker/login-action from 2.0.0 to 2.1.0
2022-12-02 21:12:41 +02:00
Ville Ranki d7263357d3
Merge pull request #222 from vranki/dependabot/github_actions/docker/metadata-action-4.1.1
Bump docker/metadata-action from 4.0.1 to 4.1.1
2022-12-02 21:12:26 +02:00
Ville Ranki 4746c7fada
Merge pull request #223 from european-epc-competence-center/homeserver_only
add homeserver_only option
2022-12-02 21:01:01 +02:00
Ville Ranki 2bd0703df3
Merge pull request #225 from european-epc-competence-center/inspiro_bot_module
Adding a simple module to query Inspirobot
2022-12-02 20:59:54 +02:00
F-Node-Karlsruhe 2c16ca5062
Fix boolean in statement 2022-11-24 08:32:38 +01:00
F-Node-Karlsruhe a19e124cdf use whitelist for invites instead of rooms 2022-11-23 12:08:04 +01:00
Sebastian Schmittner d72ea71d71
removed old code snipped
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2022-11-23 10:42:18 +01:00
Sebastian Schmittner 56594e6e13
Adding a simple module to query randomly generated pictures from inspirobot api
Signed-off-by: Sebastian Schmittner <sebastian.schmittner@eecc.de>
2022-11-23 10:34:30 +01:00
F-Node-Karlsruhe b5c9c3874f actually add the variable to the statement 2022-11-20 12:57:42 +01:00
F-Node-Karlsruhe 9811e2328f add homeserver_only option 2022-11-20 12:39:58 +01:00
dependabot[bot] 45106fa647
Bump docker/metadata-action from 4.0.1 to 4.1.1
Bumps [docker/metadata-action](https://github.com/docker/metadata-action) from 4.0.1 to 4.1.1.
- [Release notes](https://github.com/docker/metadata-action/releases)
- [Commits](69f6fc9d46...57396166ad)

---
updated-dependencies:
- dependency-name: docker/metadata-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-18 13:15:27 +00:00
dependabot[bot] ff51fe2ffc
Bump docker/login-action from 2.0.0 to 2.1.0
Bumps [docker/login-action](https://github.com/docker/login-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/docker/login-action/releases)
- [Commits](49ed152c8e...f4ef78c080)

---
updated-dependencies:
- dependency-name: docker/login-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 13:17:54 +00:00
dependabot[bot] 02468cf72e
Bump docker/build-push-action from 3.1.1 to 3.2.0
Bumps [docker/build-push-action](https://github.com/docker/build-push-action) from 3.1.1 to 3.2.0.
- [Release notes](https://github.com/docker/build-push-action/releases)
- [Commits](c84f382811...c56af95754)

---
updated-dependencies:
- dependency-name: docker/build-push-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
2022-10-12 13:17:32 +00:00
11 changed files with 511 additions and 21 deletions

View File

@ -37,7 +37,7 @@ jobs:
# https://github.com/docker/login-action # https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }} - name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request' if: github.event_name != 'pull_request'
uses: docker/login-action@49ed152c8eca782a232dede0303416e8f356c37b uses: docker/login-action@f4ef78c080cd8ba55a85445d5b36e214a81df20a
with: with:
registry: ${{ env.REGISTRY }} registry: ${{ env.REGISTRY }}
username: ${{ github.actor }} username: ${{ github.actor }}
@ -47,7 +47,7 @@ jobs:
# https://github.com/docker/metadata-action # https://github.com/docker/metadata-action
- name: Extract Docker metadata - name: Extract Docker metadata
id: meta id: meta
uses: docker/metadata-action@69f6fc9d46f2f8bf0d5491e4aabe0bb8c6a4678a uses: docker/metadata-action@507c2f2dc502c992ad446e3d7a5dfbe311567a96
with: with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: latest=true flavor: latest=true
@ -55,7 +55,7 @@ jobs:
# Build and push Docker image with Buildx (don't push on PR) # Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action # https://github.com/docker/build-push-action
- name: Build and push Docker image - name: Build and push Docker image
uses: docker/build-push-action@c84f38281176d4c9cdb1626ffafcd6b3911b5d94 uses: docker/build-push-action@3b5e8027fcad23fda98b2e3ac259d8d67585f671
with: with:
context: . context: .
push: ${{ github.event_name != 'pull_request' }} push: ${{ github.event_name != 'pull_request' }}

View File

@ -24,6 +24,7 @@ pillow = "*"
giphypop = "*" giphypop = "*"
tzlocal = "*" tzlocal = "*"
nest_asyncio = "*" nest_asyncio = "*"
d20 = "*"
[dev-packages] [dev-packages]
pylint = "*" pylint = "*"

View File

@ -279,14 +279,14 @@ mxma requires all commands to be run as bot owner.
#### SpaceAPI #### SpaceAPI
Polls the status of Hack- and Makerspaces that provide an endpoint Polls the status of Hack- and Makerspaces that provide an endpoint
that conforms to the [SpaceAPI](https://spaceapi.io/) protocol and notifies that conforms to the [SpaceAPI](https://spaceapi.io/) protocol and notifies
about changes of the opening status. about changes of the opening status.
To add a new endpoint simply use To add a new endpoint simply use
`!spaceapi add https://hackspace.example.org/status` `!spaceapi add https://hackspace.example.org/status`
For Admins: A template and I18N can be configured via settings of For Admins: A template and I18N can be configured via settings of
the module. Use `!bot export spacepi`, then change the the module. Use `!bot export spacepi`, then change the
settings and import again with `!bot import spacepi SETTINGS`. settings and import again with `!bot import spacepi SETTINGS`.
### Url ### Url
@ -371,6 +371,40 @@ The module uses a demo API Key which can be replaced by your own api key by sett
You can create one at https://api.nasa.gov/#signUp You can create one at https://api.nasa.gov/#signUp
### XKCD
Fetch comic strips from [XKCD](https://xkcd.com) (A webcomic of romance,
sarcasm, math, and language) and post them to the room.
Command:
* !xkcd - Sends latest XKCD comic strip to the room
* !xkcd ID - Sends xkcd number ID to the room
* !xkcd help - show command help
### Inspirobot
Query a randomly generated inspirational poster from https://inspirobot.me/ upload and send to the room.
Command:
* !inspire - Generate inspiration and post to the room
* !inspire help - Show this help
### User Status
This is a substitute for matrix' (element's?) missing user status feature.
Save a custom (status) message for users and allows to query them.
Commands:
* !status clear - clear my status
* !status show [user] - show the status of the given user. If no user is given, show all status messages
* !status help - show this text
* !status [status] - set your status (your status must not begin with another command word.)
### Wolfram Alpha ### Wolfram Alpha
Make queries to Wolfram Alpha Make queries to Wolfram Alpha
@ -518,8 +552,8 @@ Read the documentation to create one at https://developers.giphy.com/docs/api
Commands: Commands:
* !giphy apikey [apikey] - Set api key (Must be done as bot owner) * !giphy apikey [apikey] - Set api key (Must be done as bot owner)
* !giphy [query] - Post the first image result from giphy for the given [query] * !giphy [query] - Post the first image result from giphy for the given [query]
Example: Example:
@ -533,7 +567,7 @@ Can be used to post a picture from Gfycat given a query string.
Commands: Commands:
* !gfycat [query] - Post the first image result from gfycat for the given [query] * !gfycat [query] - Post the first image result from gfycat for the given [query]
Example: Example:
@ -572,7 +606,7 @@ Environ variables seen by command:
Docker environment: Docker environment:
Since the module needs access to the source of the running Tautulli instance volumes on both Docker (hemppa and Tautulli) should be defined and being visible each other. Since the module needs access to the source of the running Tautulli instance volumes on both Docker (hemppa and Tautulli) should be defined and being visible each other.
When running on Docker the env variables seen by command should be defined for the bot instance. When running on Docker the env variables seen by command should be defined for the bot instance.
Example: Example:
@ -590,8 +624,8 @@ environment but can be extended to any purpose.
* Create labels to github that represent for example different machines and spaces. * Create labels to github that represent for example different machines and spaces.
You can create any number of them. You can create any number of them.
* Define label colors for each type of asset. These are called domains in this module. * Define label colors for each type of asset. These are called domains in this module.
For example set all machine labels to be #B60205 and space labels to be #0E8A16. These For example set all machine labels to be #B60205 and space labels to be #0E8A16. These
can be easily picked from color chooser. can be easily picked from color chooser.
* Edit the repository description and add a json block describing the * Edit the repository description and add a json block describing the
label domains and their colors (array format supports multiple colors per domain). For example: label domains and their colors (array format supports multiple colors per domain). For example:
@ -674,6 +708,35 @@ including version, how many users are connected, and ping time.
* !mumble - Show info about the configured mumble server * !mumble - Show info about the configured mumble server
- !mumble (set|setserver) [host] ([port]) - Set the configured mumble server - !mumble (set|setserver) [host] ([port]) - Set the configured mumble server
### Nitter
Reads Twitter links from room, replaces domain with nitter, removes query parameters and posts link to room.
#### Usage
* !nitter enable - enable converting twitter links to nitter links in this room (must be done as room admin)
* !nitter disable - disable converting twitter links to nitter links in this room (must be done as room admin)
### Wikipedia
Searches Wikipedia for a given query and returns the first result summary and link.
#### Usage
* !wikipedia [query] - Search Wikipedia for query
### Dice Roll
Rolls dice in XdY format.
* !roll 1d20 - roll a single d20
* !roll 1d20+4 - a skill check or attack roll
* !roll 1d20+1 adv - a skill check or attack roll with advantage
* !roll 1d20-1 dis - a skill check or attack roll with disadvantage
* !roll help - show help info
For more syntax help, see <https://d20.readthedocs.io/en/latest/start.html#dice-syntax>.
## Bot setup ## Bot setup
* Create a Matrix user * Create a Matrix user
@ -741,7 +804,7 @@ docker-compose up
## Env variables ## Env variables
`MATRIX_USER` is the full MXID (not just username) of the Matrix user. `MATRIX_ACCESS_TOKEN` `MATRIX_USER` is the full MXID (not just username) of the Matrix user. `MATRIX_ACCESS_TOKEN`
and `MATRIX_SERVER` should be url to the user's server (non-delegated server). Set `JOIN_ON_INVITE` (default true) and `MATRIX_SERVER` should be url to the user's server (non-delegated server). Set `JOIN_ON_INVITE` (default true)
to false if you don't want the bot automatically joining rooms. to false if you don't want the bot automatically joining rooms.
You can get access token by logging in with Element Android and looking from Settings / Help & About. You can get access token by logging in with Element Android and looking from Settings / Help & About.
@ -752,6 +815,8 @@ Typically set your own id into it.
`OWNERS_ONLY` is an optional variable once defined only the owners can operate the bot (this is a form of whitelisting) `OWNERS_ONLY` is an optional variable once defined only the owners can operate the bot (this is a form of whitelisting)
`INVITE_WHITELIST` (default empty) is an optional comma-separated list of matrix id's to restrict the acceptance of invites into rooms of the bot to users or servers. It supports wild cards: Example value: `@user:matrix.org,@*:myserver.org`
`LEAVE_EMPTY_ROOMS` (default true) if this is set to false, the bot will stay in empty rooms `LEAVE_EMPTY_ROOMS` (default true) if this is set to false, the bot will stay in empty rooms
__*ATTENTION:*__ Don't include bot itself in `BOT_OWNERS` if cron or any other module that can cause bot to send custom commands is used, as it could potentially be used to run owner commands as the bot itself. __*ATTENTION:*__ Don't include bot itself in `BOT_OWNERS` if cron or any other module that can cause bot to send custom commands is used, as it could potentially be used to run owner commands as the bot itself.
@ -829,7 +894,7 @@ class Bot:
:param bot_ignore: Flag to mark the message to be ignored by the bot :param bot_ignore: Flag to mark the message to be ignored by the bot
:return: :return:
""" """
async def send_image(self, room, url, body, event=None, mimetype=None, width=None, height=None, size=None): async def send_image(self, room, url, body, event=None, mimetype=None, width=None, height=None, size=None):
""" """
@ -843,17 +908,17 @@ class Bot:
:param size: Size in bytes of the image :param size: Size in bytes of the image
:return: :return:
""" """
async def upload_image(self, url, blob=False, blob_content_type="image/png"): async def upload_image(self, url, blob=False, blob_content_type="image/png"):
""" """
:param url: Url of binary content of the image to upload :param url: Url of binary content of the image to upload
:param blob: Flag to indicate if the first param is an url or a binary content :param blob: Flag to indicate if the first param is an url or a binary content
:param blob_content_type: Content type of the image in case of binary content :param blob_content_type: Content type of the image in case of binary content
:return: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri, Content type, Width, Height, Image size in bytes :return: A MXC-Uri https://matrix.org/docs/spec/client_server/r0.6.0#mxc-uri, Content type, Width, Height, Image size in bytes
""" """
async def upload_and_send_image(self, room, url, event=None, text=None, blob=False, blob_content_type="image/png"): async def upload_and_send_image(self, room, url, event=None, text=None, blob=False, blob_content_type="image/png"):
""" """
@ -861,7 +926,7 @@ class Bot:
:param url: Url of binary content of the image to upload :param url: Url of binary content of the image to upload
:param event: The event to reply to :param event: The event to reply to
:param text: A textual representation of the image :param text: A textual representation of the image
:param blob: Flag to indicate if the second param is an url or a binary content :param blob: Flag to indicate if the second param is an url or a binary content
:param blob_content_type: Content type of the image in case of binary content :param blob_content_type: Content type of the image in case of binary content
:return: :return:
""" """

19
bot.py
View File

@ -35,6 +35,7 @@ class Bot:
self.version = '1.5' self.version = '1.5'
self.client = None self.client = None
self.join_on_invite = False self.join_on_invite = False
self.invite_whitelist = []
self.modules = dict() self.modules = dict()
self.module_aliases = dict() self.module_aliases = dict()
self.leave_empty_rooms = True self.leave_empty_rooms = True
@ -458,11 +459,25 @@ class Bot:
def starts_with_command(body): def starts_with_command(body):
"""Checks if body starts with ! and has one or more letters after it""" """Checks if body starts with ! and has one or more letters after it"""
return re.match(r"^!\w.*", body) is not None return re.match(r"^!\w.*", body) is not None
def on_invite_whitelist(self, sender):
for entry in self.invite_whitelist:
if entry == sender:
return True
controll_value = entry.split(':')
if controll_value[0] == '@*' and controll_value[1] == sender.split(':')[1]:
return True
return False
async def invite_cb(self, room, event): async def invite_cb(self, room, event):
room: MatrixRoom room: MatrixRoom
event: InviteEvent event: InviteEvent
if len(self.invite_whitelist) > 0 and not self.on_invite_whitelist(event.sender):
self.logger.error(f'Cannot join room {room.display_name}, as {event.sender} is not whitelisted for invites!')
return
if self.join_on_invite or self.is_owner(event): if self.join_on_invite or self.is_owner(event):
for attempt in range(3): for attempt in range(3):
self.jointime = datetime.datetime.now() self.jointime = datetime.datetime.now()
@ -558,6 +573,7 @@ class Bot:
bot_owners = os.getenv('BOT_OWNERS') bot_owners = os.getenv('BOT_OWNERS')
access_token = os.getenv('MATRIX_ACCESS_TOKEN') access_token = os.getenv('MATRIX_ACCESS_TOKEN')
join_on_invite = os.getenv('JOIN_ON_INVITE') join_on_invite = os.getenv('JOIN_ON_INVITE')
invite_whitelist = os.getenv('INVITE_WHITELIST')
owners_only = os.getenv('OWNERS_ONLY') is not None owners_only = os.getenv('OWNERS_ONLY') is not None
leave_empty_rooms = os.getenv('LEAVE_EMPTY_ROOMS') leave_empty_rooms = os.getenv('LEAVE_EMPTY_ROOMS')
@ -565,6 +581,7 @@ class Bot:
self.client = AsyncClient(matrix_server, self.matrix_user, ssl = matrix_server.startswith("https://")) self.client = AsyncClient(matrix_server, self.matrix_user, ssl = matrix_server.startswith("https://"))
self.client.access_token = access_token self.client.access_token = access_token
self.join_on_invite = (join_on_invite or '').lower() == 'true' self.join_on_invite = (join_on_invite or '').lower() == 'true'
self.invite_whitelist = invite_whitelist.split(',') if invite_whitelist is not None else []
self.leave_empty_rooms = (leave_empty_rooms or 'true').lower() == 'true' self.leave_empty_rooms = (leave_empty_rooms or 'true').lower() == 'true'
self.owners = bot_owners.split(',') self.owners = bot_owners.split(',')
self.owners_only = owners_only self.owners_only = owners_only
@ -616,6 +633,8 @@ class Bot:
if self.join_on_invite: if self.join_on_invite:
self.logger.info('Note: Bot will join rooms if invited') self.logger.info('Note: Bot will join rooms if invited')
if len(self.invite_whitelist) > 0:
self.logger.info(f'Note: Bot will only join rooms when the inviting user is contained in {self.invite_whitelist}')
self.logger.info('Bot running as %s, owners %s', self.client.user, self.owners) self.logger.info('Bot running as %s, owners %s', self.client.user, self.owners)
self.bot_task = asyncio.create_task(self.client.sync_forever(timeout=30000)) self.bot_task = asyncio.create_task(self.client.sync_forever(timeout=30000))
await self.bot_task await self.bot_task

View File

@ -6,7 +6,6 @@ class MatrixModule(BotModule):
def __init__(self, name): def __init__(self, name):
super().__init__(name) super().__init__(name)
self.msg_users = False self.msg_users = False
self.info = "More information at https://github.com/vranki/hemppa"
def get_settings(self): def get_settings(self):
data = super().get_settings() data = super().get_settings()

54
modules/inspire.py Normal file
View File

@ -0,0 +1,54 @@
import html
import requests
from modules.common.module import BotModule
class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
self.url_generator_url = "https://inspirobot.me/api?generate=true"
self.matrix_uri_cache = dict()
async def matrix_message(self, bot, room, event):
self.logger.debug(f"room: {room.name} sender: {event.sender} wants to be inspired!")
args = event.body.split()
if len(args) == 1:
await self.send_inspiration(bot, room, self.url_generator_url)
return
elif len(args) == 2:
if args[1] == "help":
await self.command_help(bot, room)
return
await bot.send_text(room, f"unknown command: {args}")
await self.command_help(bot, room)
async def send_inspiration(self, bot, room, url_generator_url):
self.logger.debug(f"Asking inspirobot for pic url at {url_generator_url}")
response = requests.get(url_generator_url)
if response.status_code != 200:
self.logger.error("unable to request inspirobot api. response: [status: %d text: %s]", response.status_code, response.text)
return await bot.send_text(room, f"sorry. something went wrong accessing inspirobot: {response.status_code}: {response.text}")
pic_url = response.text
self.logger.debug("Sending image with src='%s'", pic_url)
await bot.upload_and_send_image(room, pic_url)
def help(self):
return """I'm InspiroBot.
I am an artificial intelligence dedicated to generating unlimited amounts of unique inspirational quotes
for endless enrichment of pointless human existence.
https://inspirobot.me/
"""
async def command_help(self, bot, room):
msg = """usage: !inspire [command]
No command to generate an inspirational poster just for you!
- help - show this. Useful, isn't it?
"""
await bot.send_html(room, f"<b>{html.escape(self.help())}</b>", self.help())
await bot.send_text(room, msg)

87
modules/nitter.py Normal file
View File

@ -0,0 +1,87 @@
import re
from modules.common.module import BotModule
from nio import RoomMessageText
# This module reads matrix messages and converts twitter.com links to nitter.nl
# Module will only target messages that contain only the twitter link
# Additionally module will target only profile links or post links, query parameters are removed
class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
self.regex = re.compile(r'https://twitter.com/([^?]*)')
self.bot = None
self.enabled_rooms = []
def matrix_start(self, bot):
"""
Register callback for all RoomMessageText events on startup
"""
super().matrix_start(bot)
self.bot = bot
bot.client.add_event_callback(self.text_cb, RoomMessageText)
def matrix_stop(self, bot):
super().matrix_stop(bot)
bot.remove_callback(self.text_cb)
async def text_cb(self, room, event):
"""
Handle client callbacks for all room text events
"""
if room.room_id not in self.enabled_rooms:
return
if self.bot.should_ignore_event(event):
return
# no content at all?
if len(event.body) < 1:
return
if event.body.startswith('!'):
return
if len(event.body.split()) <= 1:
if event.body.startswith('https://twitter.com/'):
for link in self.regex.findall(event.body):
await self.bot.send_text(room, f'https://nitter.net/{link}')
return
async def matrix_message(self, bot, room, event):
"""
Required for initialization of the module
"""
args = event.body.split()
args.pop(0)
if len(args) == 0:
await bot.send_text(room, 'Usage: !nitter <enable|disable>')
return
if len(args) == 1:
if args[0] == 'enable':
bot.must_be_admin(room, event)
self.enabled_rooms.append(room.room_id)
self.enabled_rooms = list(dict.fromkeys(self.enabled_rooms)) # Deduplicate
await bot.send_text(room, "Ok, enabling conversion of twitter links to nitter links here")
bot.save_settings()
return
if args[0] == 'disable':
bot.must_be_admin(room, event)
self.enabled_rooms.remove(room.room_id)
await bot.send_text(room, "Ok, disabling conversion of twitter links to nitter links here")
bot.save_settings()
return
def help(self):
return 'Converts Twitter links to nitter links.'
def get_settings(self):
data = super().get_settings()
data["enabled_rooms"] = self.enabled_rooms
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("enabled_rooms"):
self.enabled_rooms = data["enabled_rooms"]

30
modules/roll.py Normal file
View File

@ -0,0 +1,30 @@
from modules.common.module import BotModule
import d20
class MatrixModule(BotModule):
async def matrix_message(self, bot, room, event):
args = event.body.split()
args.pop(0)
if args[0] == 'help':
await bot.send_text(room, self.long_help())
else:
try:
result = d20.roll(' '.join(args), stringifier=d20.SimpleStringifier())
await bot.send_text(room, str(result), event=event)
except:
await bot.send_text(room, 'Invalid roll syntax', event=event)
def help(self):
return 'Rolls dice in XdY format'
def long_help(self, bot=None, event=None, **kwargs):
text = self.help() + (
'\n- "!roll 1d20": roll a single d20'
'\n- "!roll 1d20+4": A skill check or attack roll'
'\n- "!roll 1d20+1 adv": A skill check or attack roll with advantage'
'\n- "!roll 1d20-1 dis": A skill check or attack roll with disadvantage'
'\n- "!roll help": show this help'
'\n'
'\nFor more syntax help, see https://d20.readthedocs.io/en/latest/start.html#dice-syntax')
return text

64
modules/status.py Normal file
View File

@ -0,0 +1,64 @@
import html
from modules.common.module import BotModule
class MatrixModule(BotModule):
"""
This is a substitute for matrix' (element's?) missing user status feature.
Save a custom (status) message for users and allows to query them.
"""
def __init__(self, name):
super().__init__(name)
self.status = dict()
async def matrix_message(self, bot, room, event):
args = event.body.split()
args.pop(0)
if len(args) < 1 or args[0] == "help":
await bot.send_text(room, self.help())
elif args[0] == "show":
if len(args) > 1:
await self.send_status(bot=bot, room=room, user=args[1])
else:
await self.send_status(bot=bot, room=room)
elif args[0] == "clear":
self.status.pop(event.sender)
bot.save_settings()
await bot.send_text(room, f"Cleared status of {event.sender}")
else:
self.status[event.sender] = " ".join(args)
bot.save_settings()
await self.send_status(bot=bot, room=room, user=event.sender)
async def send_status(self, bot, room, user=None):
if user:
if user in self.status:
await bot.send_text(room, f"Status message of {user}: {self.status[user]}")
else:
await bot.send_text(room, f"No status known for {user}")
else:
await bot.send_html(room, "<b>All status messages:</b><br/><ul><li>" +
"</li><li>".join([f"<b>{html.escape(key)}:</b> {html.escape(value)}" for key, value in self.status.items()]) +
"</li></ul>", f"All status messages:\n{self.status}")
def get_settings(self):
data = super().get_settings()
data["user_status_list"] = self.status
return data
def set_settings(self, data):
super().set_settings(data)
if data.get("user_status_list"):
self.status = data["user_status_list"]
def help(self):
return """
Store a status message per user and display them.
Usage:
!status clear - clear my status
!status show [user] - show the status of user. If no user is given, show all status messages
!status help - show this text
!status [status] - set your status
"""

79
modules/wikipedia.py Normal file
View File

@ -0,0 +1,79 @@
import re
import requests
from modules.common.module import BotModule
# This module searches wikipedia for query, returns page summary and link.
class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
self.api_url = 'https://en.wikipedia.org/w/api.php'
async def matrix_message(self, bot, room, event):
args = event.body.split()
if len(args) > 1:
query = event.body[len(args[0]) + 1:]
try:
response = requests.get(self.api_url, params={
'action': 'query',
'format': 'json',
'exintro': True,
'explaintext': True,
'prop': 'extracts',
'redirects': 1,
'titles': query,
})
response.raise_for_status()
data = response.json()
# Get the page id
page_id = list(data['query']['pages'].keys())[0]
if page_id == '-1':
await bot.send_text(room, 'No results found')
return
# Get the page title
title = data['query']['pages'][page_id]['title']
# Get the page summary
summary = data['query']['pages'][page_id]['extract']
# Remove all html tags
extract = re.sub('<[^<]+?>', '', summary)
# Remove any multiple spaces
extract = re.sub(' +', ' ', extract)
# Remove any new lines
extract = re.sub('', '', extract)
# Remove any tabs
extract = re.sub('\t', '', extract)
# Truncate the extract, Element URL preview contains nonsense Wikipedia meta content
if len(extract) <= 256:
pass
else:
extract = ' '.join(extract[:256 + 1].split(' ')[0:-1]) + '...'
# Get the page url
url = f'https://en.wikipedia.org/wiki/{title}'
# Convert all spaces to underscores in url
url = re.sub(r'\s', '_', url)
# Format the response
response = f'{title}: {extract} \n{url}'
# Send the response
await bot.send_text(room, response)
return
except Exception as exc:
await bot.send_text(room, str(exc))
else:
await bot.send_text(room, 'Usage: !wikipedia <query>')
def help(self):
return ('Wikipedia bot')

92
modules/xkcd.py Normal file
View File

@ -0,0 +1,92 @@
import re
import html
import requests
from modules.common.module import BotModule
from modules.common.exceptions import UploadFailed
class Xkcd:
"""
Uses the XKCD (json) api https://xkcd.com/json.html to fetch web comics and metadata and display them in chats.
"""
def __init__(self, title, img, alt, num):
self.title = title
self.img = img
self.alt = alt
self.num = num
@staticmethod
def create_from_json(json):
return Xkcd(json.get("title"), json.get("img"), json.get("alt"), json.get("num"))
def __str__(self):
return "title: {} || explanation: {} || date: {} || original-url: {}".format(self.title,
self.explanation,
self.date,
self.hdurl)
class MatrixModule(BotModule):
def __init__(self, name):
super().__init__(name)
self.uri_get_latest = 'https://xkcd.com/info.0.json'
async def matrix_message(self, bot, room, event):
self.logger.debug(f"room: {room.name} sender: {event.sender} queried the xkcd module with body: {event.body}")
args = event.body.split()
if len(args) == 1:
await self.send_xkcd(bot, room, self.uri_get_latest)
elif len(args) == 2:
if args[1] == "help":
await self.command_help(bot, room)
else:
xkcd_id = args[1]
if re.match("\\d+", xkcd_id) is not None:
await self.send_xkcd(bot, room, self.uri_get_by_id(xkcd_id))
else:
await bot.send_text(room, "Invalid comic id. ids must be positive integers.")
async def send_xkcd(self, bot, room, uri):
self.logger.debug(f"send request using uri {uri}")
response = requests.get(uri)
if response.status_code != 200:
self.logger.error("unable to request api. response: [status: %d text: %s]", response.status_code, response.text)
return await bot.send_text(room, "sorry. something went wrong accessing the api")
xkcd = Xkcd.create_from_json(response.json())
self.logger.debug(xkcd)
img_url = xkcd.img
try:
matrix_uri = None
matrix_uri, mimetype, w, h, size = bot.get_uri_cache(img_url)
except (TypeError, ValueError):
self.logger.debug(f"Not found in cache: {img_url}")
try:
matrix_uri, mimetype, w, h, size = await bot.upload_image(img_url)
except (UploadFailed, TypeError, ValueError):
await bot.send_text(room, f"Something went wrong uploading {img_url}.")
await bot.send_html(room, f"<b>{html.escape(xkcd.title)} ({html.escape(str(xkcd.num))})</b>", f"{xkcd.title} ({str(xkcd.num)})")
await bot.send_image(room, matrix_uri, img_url, None, mimetype, w, h, size)
await bot.send_text(room, f"{xkcd.alt}")
def uri_get_by_id(self, id):
return 'https://xkcd.com/' + str(int(id)) + '/info.0.json'
def help(self):
return 'Sends latest/any specified XCKD web comic to the room. See https://xkcd.com/ '
async def command_help(self, bot, room):
msg = """commands:
- no arguments: fetch latest xkcd comic
- (\\d+) fetch and display the xkcd comic with the given id
- help - show command help
"""
await bot.send_text(room, msg)