Compare commits
5 commits
developmen
...
deprecate-
Author | SHA1 | Date | |
---|---|---|---|
missytake | dff30e1ede | ||
missytake | 672b9dbac0 | ||
missytake | 786ee0b59c | ||
missytake | b4b7504a64 | ||
missytake | 6a8bc89f3a |
120
CONTRIBUTING.md
120
CONTRIBUTING.md
|
@ -29,13 +29,13 @@
|
|||
|
||||
1. Install node.js (e.g. via
|
||||
[nvm](https://github.com/nvm-sh/nvm#installation-and-update))
|
||||
2. `cd frontend`
|
||||
2. `cd kibicara-frontend`
|
||||
3. Install the dependencies with `npm i`
|
||||
4. Install Angular with `npm i @angular/cli`
|
||||
4. Install Angular with `npm i -g @angular/cli`
|
||||
5. Turn off production mode if you have not already (see above in backend).
|
||||
6. Start the backend in a different terminal
|
||||
7. To serve and open the application, run `node_modules/@angular/cli/bin/ng.js s -o`.
|
||||
The application will open under [http://127.0.0.1:4200](http://127.0.0.1:4200).
|
||||
7. To serve and open the application, run `ng s -o`. The application will open
|
||||
under [http://127.0.0.1:4200](http://127.0.0.1:4200).
|
||||
|
||||
### Creating an account
|
||||
|
||||
|
@ -128,29 +128,17 @@ development team.
|
|||
|
||||
## How to implement a new Platform/Social Network
|
||||
|
||||
For transferring messages, Kibicara supports a range of platforms/social
|
||||
networks, e.g. Mastodon, E-Mail, and Telegram - but more can be added easily.
|
||||
This guide explains what you need to do to add another platform, e.g. Matrix or
|
||||
XMPP.
|
||||
### tl;dr
|
||||
|
||||
### Overview:
|
||||
|
||||
1. Implement the backend modules in `platforms/<your-platform>/`:
|
||||
1. Implement the following modules in `platforms/<your-platform>/`:
|
||||
- `bot.py`
|
||||
- `model.py`
|
||||
- `webapi.py`
|
||||
2. Import your bot in `kibicara/webapi/__init__.py`.
|
||||
3. Generate the FastAPI boilerplate code
|
||||
4. Generate the angular boilerplate code
|
||||
5. Copy-paste frontend components from other bots into the angular boilerplate
|
||||
and adjust them to your needs
|
||||
3. Generate the FastAPI stuff
|
||||
4. Generate the angular components for the kibicara-frontend from the FastAPI stuff
|
||||
|
||||
At the bottom you can find a checklist what your pull request needs to be
|
||||
merged into kibicara.
|
||||
|
||||
### Step by step
|
||||
|
||||
#### Implement the backend modules
|
||||
### Explanation
|
||||
|
||||
In `kibicara/platforms/<your-platform>/bot.py`, you write the functions through
|
||||
which the platform asks the social network for new messages, and publishes
|
||||
|
@ -166,90 +154,14 @@ You will probably need to store the following things:
|
|||
* platform-specific settings
|
||||
* anything else your platform needs
|
||||
|
||||
In `kibicara/platforms/<your-platform>/webapi.py`, you can define REST API
|
||||
routes. You will need them to:
|
||||
In `kibicara/platforms/<your-platform>/webapi.py`, you can define HTTP routes.
|
||||
You will need them to:
|
||||
|
||||
* let admins authenticate to the social network in the kibicara web interface
|
||||
* update platform-specific settings
|
||||
|
||||
#### Import your bot into the kibicara REST API
|
||||
|
||||
To run the platform, you need to import the bot in
|
||||
`kibicara/webapi/__init__.py`. You can see how the other platforms did it.
|
||||
|
||||
#### Generate the FastAPI boilerplate code
|
||||
|
||||
Whenever you changed the REST API in the backend, you need to re-generate the
|
||||
FastAPI boilerplate code:
|
||||
|
||||
1. Start backend with `kibicara > /dev/null 2>&1 &`
|
||||
2. Go to the frontend directory: `cd frontend`
|
||||
3. Use this command to download the openapi.json from the backend and
|
||||
generate the boilerplate: `npm run openapi-generator`
|
||||
4. (Now you can stop the backend again, e.g. with `pkill kibicara`)
|
||||
5. Append `/api` to all relevant URLs:
|
||||
`find src/app/core/api/ -type f -exec sed -i "s#{this.configuration.basePath}#{this.configuration.basePath}/api#g" {} +`
|
||||
6. Check if everything is okay (e.g. all api calls need the `/api` prefix)
|
||||
|
||||
#### Generate the Angular boilerplate code
|
||||
|
||||
##### Generate the platform-specific "public facing" page
|
||||
|
||||
Generate boilerplate "card" for myplatform:
|
||||
|
||||
```
|
||||
ng generate component platforms/myplatform/myplatform-bot-card
|
||||
```
|
||||
|
||||
Generate boilerplate for the popup that shows the users the guide on how to use
|
||||
the bot:
|
||||
|
||||
```
|
||||
ng generate component platforms/myplatform/myplatform-bot-card/myplatform-bot-info-dialog
|
||||
```
|
||||
|
||||
##### Generate the platform-specific "settings" page
|
||||
|
||||
Generate bot card for settings page:
|
||||
|
||||
```
|
||||
ng generate component platforms/myplatform/myplatform-settings
|
||||
```
|
||||
|
||||
Generate popup that will show when you click on "Add" to add the myplatform
|
||||
credentials (e.g. login data, api tokens etc):
|
||||
|
||||
```
|
||||
ng generate component platforms/myplatform/myplatform-settings/mastodon-dialog
|
||||
```
|
||||
|
||||
If something does not work, try to check `platforms.module.ts` and check if the
|
||||
module was imported there. Every module needs to be imported there
|
||||
|
||||
#### Adjust the Angular code for your specific platform
|
||||
|
||||
Every frontend part for a bot has a similar structure. Basically copy the
|
||||
content of the other files e.g. the telegram bot into the generated boilerplate
|
||||
above and search and replace all occurrences with `myplatform`. You can see
|
||||
the UI with `ng s -o`, it will auto-update on code change.
|
||||
|
||||
A component in angular has 3-4 files, only these ones ending with
|
||||
`*.component.ts` (typescript) and `*.component.html`(html) are important for
|
||||
us. Basically the typescript controls what is shown in the html. Please correct
|
||||
every error that stops the angular page build or shows up on the page while you
|
||||
go, otherwise this can become a mess.
|
||||
|
||||
With that in mind, first write the logic to call the /create endpoint:
|
||||
- `src/app/platforms/myplatform/myplatform-settings/myplatform-dialog/myplatform-dialog.component.ts`:
|
||||
implement the form to take the user inputs and the onSubmit() function
|
||||
- `src/app/platforms/myplatform/myplatform-settings/myplatform-dialog/myplatform-dialog.component.html`:
|
||||
implement the html skeleton that takes the form from the user
|
||||
|
||||
Then, fix up the public user facing page:
|
||||
- `src/app/platforms/myplatform/myplatform-bot-card/myplatform-bot-info-dialog/myplatform-bot-info-dialog.component.html`
|
||||
|
||||
Finally, check the other typescript and html pages and adjust e.g. the tutorial
|
||||
text for the users.
|
||||
`kibicara/webapi/__init__.py`.
|
||||
|
||||
### Acceptance criteria for bots (Checklist)
|
||||
|
||||
|
@ -288,10 +200,4 @@ A bot should have at least this functionality:
|
|||
- e.g. Telegram via direct message from the bot
|
||||
- e.g. E-Mail via e-mail to the user's address
|
||||
|
||||
- Web Interface (hood admins and users)
|
||||
- A card which allows hood admins to add, configure, start, stop, and
|
||||
delete a platform to their hood
|
||||
- A pop-up which explains to hood admins how to configure the platform
|
||||
- A card which allows users to subscribe on a platform or links to the
|
||||
platform's account
|
||||
- A pop-up which explains to users how to use the platform
|
||||
- Web Interface (hood admins)
|
||||
|
|
|
@ -87,21 +87,6 @@ query_mailaddr SELECT 1 FROM email WHERE ? IN (name || '@kibicara.example.com');
|
|||
```
|
||||
- Don't forget to restart OpenSMTPd when you change your database: `rcctl stop && rcctl start`
|
||||
|
||||
#### Configure Twitter
|
||||
|
||||
Twitter needs you to create a Twitter App, which hood admins can permit to read and write messages.
|
||||
|
||||
- Create Twitter account and app: https://developer.twitter.com
|
||||
- Get your customer key and customer secret and append this to `/etc/kibicara.conf`:
|
||||
```
|
||||
[twitter]
|
||||
consumer_key = '<your_consumer_key>'
|
||||
consumer_secret = '<your_consumer_secret>'
|
||||
```
|
||||
- You need to configure a Callback Url in your Twitter App:
|
||||
- Go to: `https://developer.twitter.com/en/apps`
|
||||
- Add `https://kibicara.example.com/dashboard/twitter-callback` as Callback Url of your Twitter App. This is needed to successfully create a twitter oauth handshake.
|
||||
|
||||
#### Configure Telegram
|
||||
Nothing to do, because telegram has a nice API.
|
||||
|
||||
|
|
|
@ -26,15 +26,16 @@ install_requires =
|
|||
fastapi
|
||||
httpx
|
||||
hypercorn
|
||||
Mastodon.py
|
||||
ormantic @ https://github.com/dl6tom/ormantic/tarball/master#egg=ormantic-0.0.32
|
||||
passlib
|
||||
peony-twitter[all]
|
||||
pydantic[email]
|
||||
pynacl
|
||||
python-multipart
|
||||
pytoml
|
||||
requests
|
||||
tortoise-orm
|
||||
scrypt
|
||||
Mastodon.py
|
||||
pydantic[email]
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
@ -43,7 +44,6 @@ where = src
|
|||
console_scripts =
|
||||
kibicara = kibicara.kibicara:Main
|
||||
kibicara_mda = kibicara.platforms.email.mda:Main
|
||||
migrate_from_ticketfrei2 = kibicara.migratefromticketfrei:Main
|
||||
|
||||
[tox:tox]
|
||||
envlist = lint, py310
|
||||
|
@ -54,19 +54,19 @@ skip_install = True
|
|||
deps =
|
||||
black
|
||||
flake8
|
||||
mypy
|
||||
types-requests
|
||||
commands =
|
||||
black --check --diff src tests
|
||||
flake8 src tests
|
||||
# not yet
|
||||
#mypy --ignore-missing-imports src tests
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
mypy
|
||||
pytest
|
||||
pytest-asyncio
|
||||
types-requests
|
||||
commands =
|
||||
# not yet
|
||||
#mypy --ignore-missing-imports src tests
|
||||
pytest tests
|
||||
|
||||
[flake8]
|
||||
|
|
|
@ -4,13 +4,17 @@
|
|||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from nacl.secret import SecretBox
|
||||
from nacl.utils import random
|
||||
|
||||
"""Default configuration.
|
||||
|
||||
The default configuration gets overwritten by a configuration file if one exists.
|
||||
"""
|
||||
config = {
|
||||
"database_connection": "sqlite://:memory:",
|
||||
"database_connection": "sqlite:////tmp/kibicara.sqlite",
|
||||
"frontend_url": "http://localhost:4200", # url of frontend, change in prod
|
||||
"secret": random(SecretBox.KEY_SIZE).hex(), # generate with: openssl rand -hex 32
|
||||
# production params
|
||||
"frontend_path": None, # required, path to frontend html/css/js files
|
||||
"production": True,
|
||||
|
|
|
@ -16,9 +16,9 @@ from fastapi.staticfiles import StaticFiles
|
|||
from hypercorn.asyncio import serve
|
||||
from hypercorn.config import Config
|
||||
from pytoml import load
|
||||
from tortoise import Tortoise
|
||||
|
||||
from kibicara.config import config
|
||||
from kibicara.model import Mapping
|
||||
from kibicara.platformapi import Spawner
|
||||
from kibicara.webapi import router
|
||||
|
||||
|
@ -66,26 +66,12 @@ class Main:
|
|||
format="%(asctime)s %(name)s %(message)s",
|
||||
)
|
||||
getLogger("aiosqlite").setLevel(WARNING)
|
||||
Mapping.create_all()
|
||||
asyncio_run(self.__run())
|
||||
|
||||
async def __run(self):
|
||||
await Tortoise.init(
|
||||
db_url=config["database_connection"],
|
||||
modules={
|
||||
"models": [
|
||||
"kibicara.model",
|
||||
"kibicara.platforms.email.model",
|
||||
"kibicara.platforms.mastodon.model",
|
||||
"kibicara.platforms.telegram.model",
|
||||
"kibicara.platforms.test.model",
|
||||
"kibicara.platforms.twitter.model",
|
||||
]
|
||||
},
|
||||
)
|
||||
await Tortoise.generate_schemas()
|
||||
await Spawner.init_all()
|
||||
await self.__start_webserver()
|
||||
await Tortoise.close_connections()
|
||||
|
||||
async def __start_webserver(self):
|
||||
class SinglePageApplication(StaticFiles):
|
||||
|
|
|
@ -1,225 +0,0 @@
|
|||
import sqlite3
|
||||
import argparse
|
||||
from time import time, sleep
|
||||
from os import urandom
|
||||
from tortoise import Tortoise
|
||||
from tortoise.exceptions import DoesNotExist
|
||||
import asyncio
|
||||
from mastodon import Mastodon
|
||||
|
||||
from kibicara.model import Admin, Hood, IncludePattern, ExcludePattern
|
||||
from kibicara.platforms.mastodon.model import MastodonInstance, MastodonAccount
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramSubscriber
|
||||
from kibicara.platforms.email.model import Email, EmailSubscriber
|
||||
from kibicara.platforms.twitter.model import Twitter
|
||||
|
||||
|
||||
class OldDataBase:
|
||||
def __init__(self, old_database_path):
|
||||
self.conn = sqlite3.connect(old_database_path)
|
||||
self.cur = self.conn.cursor()
|
||||
|
||||
def execute(self, *args, **kwargs):
|
||||
return self.cur.execute(*args, **kwargs)
|
||||
|
||||
def commit(self):
|
||||
start_time = time()
|
||||
while 1:
|
||||
try:
|
||||
self.conn.commit()
|
||||
break
|
||||
except sqlite3.OperationalError:
|
||||
# another thread may be writing, give it a chance to finish
|
||||
sleep(0.1)
|
||||
if time() - start_time > 5:
|
||||
# if it takes this long, something is wrong
|
||||
raise
|
||||
|
||||
def close(self):
|
||||
self.conn.close()
|
||||
|
||||
|
||||
class Main:
|
||||
def __init__(self):
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"old_database_path", help="path to the ticketfrei2 sqlite3 database"
|
||||
)
|
||||
parser.add_argument(
|
||||
"new_database_path",
|
||||
help="path to the ticketfrei3 sqlite3 database",
|
||||
default="ticketfrei3.sqlite",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
# open old database
|
||||
self.old_db = OldDataBase(args.old_database_path)
|
||||
|
||||
# open new database
|
||||
asyncio.run(self.new_database(args.new_database_path))
|
||||
|
||||
async def new_database(self, new_database_path):
|
||||
await Tortoise.init(
|
||||
db_url=f"sqlite://{new_database_path}",
|
||||
modules={
|
||||
"models": [
|
||||
"kibicara.model",
|
||||
"kibicara.platforms.email.model",
|
||||
"kibicara.platforms.mastodon.model",
|
||||
"kibicara.platforms.telegram.model",
|
||||
"kibicara.platforms.test.model",
|
||||
"kibicara.platforms.twitter.model",
|
||||
]
|
||||
},
|
||||
)
|
||||
await Tortoise.generate_schemas()
|
||||
|
||||
# read table per table and write it to new database.
|
||||
# mastodon instances
|
||||
old_mastodon_instances = self.old_db.execute(
|
||||
"SELECT * FROM mastodon_instances"
|
||||
).fetchall()
|
||||
for instance in old_mastodon_instances:
|
||||
url = instance[1]
|
||||
client_id = instance[2]
|
||||
client_secret = instance[3]
|
||||
await MastodonInstance.create(
|
||||
name=url, client_id=client_id, client_secret=client_secret
|
||||
)
|
||||
print(f"Created Mastodon Instance: {url}")
|
||||
|
||||
old_users = self.old_db.execute("SELECT * FROM user;").fetchall()
|
||||
for user in old_users:
|
||||
user_id = user[0]
|
||||
user_passhash = user[1]
|
||||
user_enabled = user[2]
|
||||
|
||||
if user_enabled == 0:
|
||||
print(f"skipping user {user_id}, inactive")
|
||||
|
||||
email = self.old_db.execute(
|
||||
"SELECT email FROM email WHERE user_id=?", (user_id,)
|
||||
).fetchone()[0]
|
||||
try:
|
||||
await Admin.get(email=email)
|
||||
continue # skip if the user already exists.
|
||||
except DoesNotExist:
|
||||
admin = await Admin.create(email=email, passhash=user_passhash)
|
||||
|
||||
city = self.old_db.execute(
|
||||
"SELECT * FROM cities WHERE user_id=?", (user_id,)
|
||||
).fetchone()
|
||||
city_name = city[2]
|
||||
city_markdown = city[3]
|
||||
hood = await Hood.create(name=city_name, landingpage=city_markdown)
|
||||
await hood.admins.add(admin)
|
||||
|
||||
print()
|
||||
print(
|
||||
"Migrated user %s with email %s for the city %s "
|
||||
"with the following accounts:" % (user_id, email, city_name)
|
||||
)
|
||||
|
||||
patterns = self.old_db.execute(
|
||||
"SELECT patterns FROM triggerpatterns WHERE user_id=?", (user_id,)
|
||||
).fetchone()[0]
|
||||
for pattern in patterns.splitlines():
|
||||
await IncludePattern.create(hood=hood, pattern=pattern)
|
||||
|
||||
badwords = self.old_db.execute(
|
||||
"SELECT words FROM badwords WHERE user_id=?", (user_id,)
|
||||
).fetchone()[0]
|
||||
for badword in badwords.splitlines():
|
||||
await ExcludePattern.create(hood=hood, pattern=badword)
|
||||
|
||||
mastodon_account = self.old_db.execute(
|
||||
"SELECT * FROM mastodon_accounts WHERE user_id=?", (user_id,)
|
||||
).fetchone()
|
||||
if mastodon_account:
|
||||
instance_url = self.old_db.execute(
|
||||
"SELECT instance FROM mastodon_instances WHERE id=?",
|
||||
(mastodon_account[3],),
|
||||
).fetchone()[0]
|
||||
new_instance = await MastodonInstance.get(name=instance_url)
|
||||
access_token = mastodon_account[2]
|
||||
mastodon_enabled = mastodon_account[4]
|
||||
await MastodonAccount.create(
|
||||
hood=hood,
|
||||
instance=new_instance,
|
||||
access_token=access_token,
|
||||
enabled=mastodon_enabled,
|
||||
)
|
||||
await dismiss_notifications(hood)
|
||||
print(f"Mastodon: {instance_url}, {access_token}")
|
||||
|
||||
telegram_account = self.old_db.execute(
|
||||
"SELECT apikey, active FROM telegram_accounts WHERE user_id=?",
|
||||
(user_id,),
|
||||
).fetchone()
|
||||
if telegram_account[0] != "":
|
||||
telegram_apikey = telegram_account[0]
|
||||
telegram_enabled = telegram_account[1]
|
||||
telegram = await Telegram.create(
|
||||
hood=hood,
|
||||
api_token=telegram_apikey,
|
||||
enabled=telegram_enabled,
|
||||
welcome_message="",
|
||||
)
|
||||
telegram_subscribers = self.old_db.execute(
|
||||
"SELECT subscriber_id FROM telegram_subscribers WHERE user_id=?",
|
||||
(user_id,),
|
||||
).fetchall()
|
||||
for subscriber in telegram_subscribers:
|
||||
subscriber_id = subscriber[0]
|
||||
await TelegramSubscriber.create(bot=telegram, user_id=subscriber_id)
|
||||
print(f"Telegram: {len(telegram_subscribers)} subscribers")
|
||||
|
||||
mail_subscribers = self.old_db.execute(
|
||||
"SELECT email FROM mailinglist WHERE user_id=?", (user_id,)
|
||||
).fetchall()
|
||||
if mail_subscribers is not []:
|
||||
email_name = f"kibicara-{city_name}"
|
||||
email = await Email.create(
|
||||
hood=hood, name=email_name, secret=urandom(32).hex()
|
||||
)
|
||||
for subscriber in mail_subscribers:
|
||||
subscriber_email = subscriber[0]
|
||||
await EmailSubscriber.create(email=subscriber_email, hood=hood)
|
||||
print(f"E-Mail: {len(mail_subscribers)} subscribers")
|
||||
|
||||
twitter = self.old_db.execute(
|
||||
"SELECT * FROM twitter_accounts WHERE user_id=?", (user_id,)
|
||||
).fetchone()
|
||||
if twitter:
|
||||
client_id = twitter[2]
|
||||
client_secret = twitter[3]
|
||||
seen_tweet = self.old_db.execute(
|
||||
"SELECT tweet_id FROM seen_tweets WHERE user_id=?", (user_id,)
|
||||
).fetchone()[0]
|
||||
await Twitter.create(
|
||||
hood=hood,
|
||||
dms_since_id=1, # didn't work in ticketfrei2 anyway
|
||||
mentions_since_id=seen_tweet,
|
||||
access_token=client_id,
|
||||
access_token_secret=client_secret,
|
||||
username=" ",
|
||||
verified=False,
|
||||
enabled=True,
|
||||
)
|
||||
print(f"Twitter: last ID: {seen_tweet}")
|
||||
await Tortoise.close_connections()
|
||||
|
||||
|
||||
async def dismiss_notifications(hood):
|
||||
model = await MastodonAccount.get(hood=hood)
|
||||
await model.fetch_related("instance")
|
||||
account = Mastodon(
|
||||
client_id=model.instance.client_id,
|
||||
client_secret=model.instance.client_secret,
|
||||
api_base_url=model.instance.name,
|
||||
access_token=model.access_token,
|
||||
)
|
||||
notifications = account.notifications()
|
||||
for status in notifications:
|
||||
account.notifications_dismiss(status["id"])
|
||||
print(f"Dismissed {len(notifications)} notifications on Mastodon.")
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
|
@ -6,52 +6,69 @@
|
|||
|
||||
"""ORM Models for core."""
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
from databases import Database
|
||||
from ormantic import Boolean, ForeignKey, Integer, Model, Text
|
||||
from sqlalchemy import MetaData, create_engine
|
||||
|
||||
from kibicara.config import config
|
||||
|
||||
|
||||
class Mapping:
|
||||
database = Database(config["database_connection"])
|
||||
metadata = MetaData()
|
||||
|
||||
@classmethod
|
||||
def create_all(cls):
|
||||
engine = create_engine(str(cls.database.url))
|
||||
cls.metadata.create_all(engine)
|
||||
|
||||
@classmethod
|
||||
def drop_all(cls):
|
||||
engine = create_engine(str(cls.database.url))
|
||||
cls.metadata.drop_all(engine)
|
||||
|
||||
|
||||
class Admin(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
email = fields.CharField(64, unique=True)
|
||||
passhash = fields.TextField()
|
||||
hoods: fields.ManyToManyRelation["Hood"] = fields.ManyToManyField(
|
||||
"models.Hood", related_name="admins", through="admin_hood_relations"
|
||||
)
|
||||
id: Integer(primary_key=True) = None
|
||||
email: Text(unique=True)
|
||||
passhash: Text()
|
||||
|
||||
class Meta:
|
||||
table = "admins"
|
||||
class Mapping(Mapping):
|
||||
table_name = "admins"
|
||||
|
||||
|
||||
class Hood(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
name = fields.CharField(64, unique=True)
|
||||
landingpage = fields.TextField()
|
||||
email_enabled = fields.BooleanField(default=True)
|
||||
admins: fields.ManyToManyRelation[Admin]
|
||||
include_patterns: fields.ReverseRelation["IncludePattern"]
|
||||
exclude_patterns: fields.ReverseRelation["ExcludePattern"]
|
||||
id: Integer(primary_key=True) = None
|
||||
name: Text(unique=True)
|
||||
landingpage: Text()
|
||||
email_enabled: Boolean() = True
|
||||
|
||||
class Meta:
|
||||
table = "hoods"
|
||||
class Mapping(Mapping):
|
||||
table_name = "hoods"
|
||||
|
||||
|
||||
class IncludePattern(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="include_patterns"
|
||||
)
|
||||
pattern = fields.TextField()
|
||||
class AdminHoodRelation(Model):
|
||||
id: Integer(primary_key=True) = None
|
||||
admin: ForeignKey(Admin)
|
||||
hood: ForeignKey(Hood)
|
||||
|
||||
class Meta:
|
||||
table = "include_patterns"
|
||||
class Mapping(Mapping):
|
||||
table_name = "admin_hood_relations"
|
||||
|
||||
|
||||
class ExcludePattern(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="exclude_patterns"
|
||||
)
|
||||
pattern = fields.TextField()
|
||||
class Trigger(Model):
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
pattern: Text()
|
||||
|
||||
class Meta:
|
||||
table = "exclude_patterns"
|
||||
class Mapping(Mapping):
|
||||
table_name = "triggers"
|
||||
|
||||
|
||||
class BadWord(Model):
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
pattern: Text()
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = "badwords"
|
||||
|
|
|
@ -6,15 +6,12 @@
|
|||
|
||||
"""API classes for implementing bots for platforms."""
|
||||
|
||||
from asyncio import Queue, Task, create_task
|
||||
from asyncio import Queue, create_task
|
||||
from enum import Enum, auto
|
||||
from logging import getLogger
|
||||
from re import IGNORECASE, search
|
||||
from typing import Generic, Optional, Type, TypeVar
|
||||
|
||||
from tortoise.models import Model
|
||||
|
||||
from kibicara.model import ExcludePattern, Hood, IncludePattern
|
||||
from kibicara.model import BadWord, Trigger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -32,7 +29,7 @@ class Message:
|
|||
**kwargs (object, optional): Other platform-specific data.
|
||||
"""
|
||||
|
||||
def __init__(self, text: str, **kwargs) -> None:
|
||||
def __init__(self, text: str, **kwargs):
|
||||
self.text = text
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
|
@ -76,11 +73,11 @@ class Censor:
|
|||
|
||||
__instances: dict[int, list["Censor"]] = {}
|
||||
|
||||
def __init__(self, hood: Hood) -> None:
|
||||
def __init__(self, hood):
|
||||
self.hood = hood
|
||||
self.enabled = True
|
||||
self._inbox: Queue[Message] = Queue()
|
||||
self.__task: Optional[Task[None]] = None
|
||||
self._inbox = Queue()
|
||||
self.__task = None
|
||||
self.__hood_censors = self.__instances.setdefault(hood.id, [])
|
||||
self.__hood_censors.append(self)
|
||||
self.status = BotStatus.INSTANTIATED
|
||||
|
@ -96,8 +93,7 @@ class Censor:
|
|||
self.__task.cancel()
|
||||
|
||||
async def __run(self) -> None:
|
||||
assert self.__task is not None
|
||||
await self.hood.refresh_from_db()
|
||||
await self.hood.load()
|
||||
self.__task.set_name("{0} {1}".format(self.__class__.__name__, self.hood.name))
|
||||
try:
|
||||
self.status = BotStatus.RUNNING
|
||||
|
@ -116,7 +112,7 @@ class Censor:
|
|||
pass
|
||||
|
||||
@classmethod
|
||||
async def destroy_hood(cls, hood: Hood) -> None:
|
||||
async def destroy_hood(cls, hood) -> None:
|
||||
"""Remove all of its database entries.
|
||||
|
||||
Note: Override this in the derived bot class.
|
||||
|
@ -144,29 +140,25 @@ class Censor:
|
|||
return await self._inbox.get()
|
||||
|
||||
async def __is_appropriate(self, message: Message) -> bool:
|
||||
for exclude in await ExcludePattern.filter(hood=self.hood):
|
||||
if search(exclude.pattern, message.text, IGNORECASE):
|
||||
logger.info(
|
||||
for badword in await BadWord.objects.filter(hood=self.hood).all():
|
||||
if search(badword.pattern, message.text, IGNORECASE):
|
||||
logger.debug(
|
||||
"Matched bad word - dropped message: {0}".format(message.text)
|
||||
)
|
||||
return False
|
||||
for include in await IncludePattern.filter(hood=self.hood):
|
||||
if search(include.pattern, message.text, IGNORECASE):
|
||||
logger.info(
|
||||
for trigger in await Trigger.objects.filter(hood=self.hood).all():
|
||||
if search(trigger.pattern, message.text, IGNORECASE):
|
||||
logger.debug(
|
||||
"Matched trigger - passed message: {0}".format(message.text)
|
||||
)
|
||||
return True
|
||||
logger.info(
|
||||
logger.debug(
|
||||
"Did not match any trigger - dropped message: {0}".format(message.text)
|
||||
)
|
||||
return False
|
||||
|
||||
|
||||
ORMClass = TypeVar("ORMClass", bound=Model)
|
||||
BotClass = TypeVar("BotClass", bound=Censor)
|
||||
|
||||
|
||||
class Spawner(Generic[ORMClass, BotClass]):
|
||||
class Spawner:
|
||||
"""Spawns a bot with a specific bot model.
|
||||
|
||||
Examples:
|
||||
|
@ -187,10 +179,10 @@ class Spawner(Generic[ORMClass, BotClass]):
|
|||
|
||||
__instances: list["Spawner"] = []
|
||||
|
||||
def __init__(self, orm_class: Type[ORMClass], bot_class: Type[BotClass]) -> None:
|
||||
self.ORMClass = orm_class
|
||||
self.BotClass = bot_class
|
||||
self.__bots: dict[int, BotClass] = {}
|
||||
def __init__(self, ORMClass, BotClass):
|
||||
self.ORMClass = ORMClass
|
||||
self.BotClass = BotClass
|
||||
self.__bots = {}
|
||||
self.__instances.append(self)
|
||||
|
||||
@classmethod
|
||||
|
@ -200,7 +192,7 @@ class Spawner(Generic[ORMClass, BotClass]):
|
|||
await spawner._init()
|
||||
|
||||
@classmethod
|
||||
async def destroy_hood(cls, hood: Hood) -> None:
|
||||
async def destroy_hood(cls, hood) -> None:
|
||||
for spawner in cls.__instances:
|
||||
for pk in list(spawner.__bots):
|
||||
bot = spawner.__bots[pk]
|
||||
|
@ -210,15 +202,15 @@ class Spawner(Generic[ORMClass, BotClass]):
|
|||
await spawner.BotClass.destroy_hood(hood)
|
||||
|
||||
async def _init(self) -> None:
|
||||
async for item in self.ORMClass.all():
|
||||
for item in await self.ORMClass.objects.all():
|
||||
self.start(item)
|
||||
|
||||
def start(self, item: ORMClass) -> None:
|
||||
def start(self, item) -> None:
|
||||
"""Instantiate and start a bot with the provided ORM object.
|
||||
|
||||
Example:
|
||||
```
|
||||
xyz = await XYZ.create(hood=hood, **values.__dict__)
|
||||
xyz = await XYZ.objects.create(hood=hood, **values.__dict__)
|
||||
spawner.start(xyz)
|
||||
```
|
||||
|
||||
|
@ -229,7 +221,7 @@ class Spawner(Generic[ORMClass, BotClass]):
|
|||
if bot.enabled:
|
||||
bot.start()
|
||||
|
||||
def stop(self, item: ORMClass) -> None:
|
||||
def stop(self, item) -> None:
|
||||
"""Stop and delete a bot.
|
||||
|
||||
Args:
|
||||
|
@ -239,10 +231,10 @@ class Spawner(Generic[ORMClass, BotClass]):
|
|||
if bot is not None:
|
||||
bot.stop()
|
||||
|
||||
def get(self, item: ORMClass) -> BotClass:
|
||||
def get(self, item) -> Censor:
|
||||
"""Get a running bot.
|
||||
|
||||
Args:
|
||||
item (ORM Model object): ORM object corresponding to bot.
|
||||
"""
|
||||
return self.__bots[item.pk]
|
||||
return self.__bots.get(item.pk)
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (C) 2020 by Maike <maike@systemli.org>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
@ -12,7 +12,7 @@ from kibicara import email
|
|||
from kibicara.config import config
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platformapi import Censor, Spawner
|
||||
from kibicara.platforms.email.model import Email, EmailSubscriber
|
||||
from kibicara.platforms.email.model import Email, EmailSubscribers
|
||||
from kibicara.webapi.admin import to_token
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
@ -26,9 +26,9 @@ class EmailBot(Censor):
|
|||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
"""Removes all its database entries."""
|
||||
for inbox in await Email.filter(hood=hood).all():
|
||||
for inbox in await Email.objects.filter(hood=hood).all():
|
||||
await inbox.delete()
|
||||
for subscriber in await EmailSubscriber.filter(hood=hood).all():
|
||||
for subscriber in await EmailSubscribers.objects.filter(hood=hood).all():
|
||||
await subscriber.delete()
|
||||
|
||||
async def run(self):
|
||||
|
@ -40,7 +40,9 @@ class EmailBot(Censor):
|
|||
self.hood.name, message.text
|
||||
)
|
||||
)
|
||||
for subscriber in await EmailSubscriber.filter(hood=self.hood).all():
|
||||
for subscriber in await EmailSubscribers.objects.filter(
|
||||
hood=self.hood
|
||||
).all():
|
||||
token = to_token(email=subscriber.email, hood=self.hood.id)
|
||||
body = (
|
||||
"{0}\n\n--\n"
|
||||
|
|
|
@ -20,7 +20,7 @@ from pytoml import load
|
|||
from requests import post
|
||||
|
||||
from kibicara.config import config
|
||||
from kibicara.platforms.email.model import Email, EmailSubscriber
|
||||
from kibicara.platforms.email.model import Email, EmailSubscribers
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -53,7 +53,7 @@ class Main:
|
|||
|
||||
async def __run(self, email_name):
|
||||
try:
|
||||
email = await Email.get(name=email_name)
|
||||
email = await Email.objects.get(name=email_name)
|
||||
except NoMatch:
|
||||
logger.error("No recipient with this name")
|
||||
exit(1)
|
||||
|
@ -75,7 +75,7 @@ class Main:
|
|||
logger.error("Could not parse sender")
|
||||
exit(1)
|
||||
|
||||
maybe_subscriber = await EmailSubscriber.filter(email=sender).all()
|
||||
maybe_subscriber = await EmailSubscribers.objects.filter(email=sender).all()
|
||||
if len(maybe_subscriber) != 1 or maybe_subscriber[0].hood.id != email.hood.id:
|
||||
logger.error("Not a subscriber")
|
||||
exit(1)
|
||||
|
|
|
@ -1,38 +1,33 @@
|
|||
# Copyright (C) 2020 by Maike <maike@systemli.org>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
from ormantic import ForeignKey, Integer, Model, Text
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.model import Hood, Mapping
|
||||
|
||||
|
||||
class Email(Model):
|
||||
"""This table is used to track the names. It also stores the token secret."""
|
||||
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="platforms_email", unique=True
|
||||
)
|
||||
name = fields.CharField(32, unique=True)
|
||||
secret = fields.TextField()
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
name: Text(unique=True)
|
||||
secret: Text()
|
||||
|
||||
class Meta:
|
||||
table = "platforms_email"
|
||||
class Mapping(Mapping):
|
||||
table_name = "email"
|
||||
|
||||
|
||||
class EmailSubscriber(Model):
|
||||
class EmailSubscribers(Model):
|
||||
"""This table stores all subscribers, who want to receive messages via email."""
|
||||
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="platforms_email_subscribers"
|
||||
)
|
||||
email = fields.CharField(64, unique=True)
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
email: Text(unique=True)
|
||||
|
||||
class Meta:
|
||||
table = "platforms_email_subscribers"
|
||||
class Mapping(Mapping):
|
||||
table_name = "email_subscribers"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Copyright (C) 2020 by Maike <maike@systemli.org>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
@ -8,18 +8,18 @@
|
|||
from logging import getLogger
|
||||
from os import urandom
|
||||
from smtplib import SMTPException
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from nacl import exceptions
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel, validator
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
|
||||
from kibicara import email
|
||||
from kibicara.config import config
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platformapi import Message
|
||||
from kibicara.platforms.email.bot import spawner
|
||||
from kibicara.platforms.email.model import Email, EmailSubscriber
|
||||
from kibicara.platforms.email.model import Email, EmailSubscribers
|
||||
from kibicara.webapi.admin import from_token, to_token
|
||||
from kibicara.webapi.hoods import get_hood, get_hood_unauthorized
|
||||
|
||||
|
@ -53,7 +53,7 @@ class BodySubscriber(BaseModel):
|
|||
email: str
|
||||
|
||||
|
||||
async def get_email(email_id: int, hood: Hood = Depends(get_hood)):
|
||||
async def get_email(email_id: int, hood=Depends(get_hood)):
|
||||
"""Get Email row by hood.
|
||||
|
||||
You can specify an email_id to nail it down, but it works without as well.
|
||||
|
@ -62,16 +62,16 @@ async def get_email(email_id: int, hood: Hood = Depends(get_hood)):
|
|||
:return: Email row of the found email bot.
|
||||
"""
|
||||
try:
|
||||
return await Email.get(id=email_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
return await Email.objects.get(id=email_id, hood=hood)
|
||||
except NoMatch:
|
||||
return HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
async def get_subscriber(subscriber_id: int, hood: Hood = Depends(get_hood)):
|
||||
async def get_subscriber(subscriber_id: int, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await EmailSubscriber.get(id=subscriber_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
return await EmailSubscribers.objects.get(id=subscriber_id, hood=hood)
|
||||
except NoMatch:
|
||||
return HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
# registers the routes, gets imported in /kibicara/webapi/__init__.py
|
||||
|
@ -83,9 +83,9 @@ router = APIRouter()
|
|||
# TODO response_model
|
||||
operation_id="get_emails_public",
|
||||
)
|
||||
async def email_read_all_public(hood: Hood = Depends(get_hood_unauthorized)):
|
||||
async def email_read_all_public(hood=Depends(get_hood_unauthorized)):
|
||||
if hood.email_enabled:
|
||||
emails = await Email.filter(hood=hood)
|
||||
emails = await Email.objects.filter(hood=hood).all()
|
||||
return [BodyEmailPublic(name=email.name) for email in emails]
|
||||
return []
|
||||
|
||||
|
@ -95,8 +95,8 @@ async def email_read_all_public(hood: Hood = Depends(get_hood_unauthorized)):
|
|||
# TODO response_model
|
||||
operation_id="get_emails",
|
||||
)
|
||||
async def email_read_all(hood: Hood = Depends(get_hood)):
|
||||
return await Email.filter(hood=hood)
|
||||
async def email_read_all(hood=Depends(get_hood)):
|
||||
return await Email.objects.filter(hood=hood).select_related("hood").all()
|
||||
|
||||
|
||||
@router.post(
|
||||
|
@ -112,7 +112,7 @@ async def email_create(values: BodyEmail, response: Response, hood=Depends(get_h
|
|||
:return: Email row of the new email bot.
|
||||
"""
|
||||
try:
|
||||
email = await Email.create(
|
||||
email = await Email.objects.create(
|
||||
hood=hood, secret=urandom(32).hex(), **values.__dict__
|
||||
)
|
||||
response.headers["Location"] = str(hood.id)
|
||||
|
@ -200,7 +200,7 @@ async def email_subscribe(
|
|||
token,
|
||||
)
|
||||
try:
|
||||
subs = await EmailSubscriber.filter(email=subscriber.email).all()
|
||||
subs = await EmailSubscribers.objects.filter(email=subscriber.email).all()
|
||||
if subs:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
email.send_email(
|
||||
|
@ -239,7 +239,7 @@ async def email_subscribe_confirm(token, hood=Depends(get_hood_unauthorized)):
|
|||
if hood.id is not payload["hood"]:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
await EmailSubscriber.create(hood=hood, email=payload["email"])
|
||||
await EmailSubscribers.objects.create(hood=hood.id, email=payload["email"])
|
||||
return {}
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
@ -262,12 +262,12 @@ async def email_unsubscribe(token, hood=Depends(get_hood_unauthorized)):
|
|||
# If token.hood and url.hood are different, raise an error:
|
||||
if hood.id is not payload["hood"]:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||
subscriber = await EmailSubscriber.filter(
|
||||
subscriber = await EmailSubscribers.objects.filter(
|
||||
hood=payload["hood"], email=payload["email"]
|
||||
).get()
|
||||
await subscriber.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
except DoesNotExist:
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
except exceptions.CryptoError:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||
|
@ -279,7 +279,7 @@ async def email_unsubscribe(token, hood=Depends(get_hood_unauthorized)):
|
|||
operation_id="get_subscribers",
|
||||
)
|
||||
async def subscribers_read_all(hood=Depends(get_hood)):
|
||||
return await EmailSubscriber.filter(hood=hood).all()
|
||||
return await EmailSubscribers.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.get(
|
||||
|
@ -306,7 +306,7 @@ async def email_message_create(
|
|||
:param hood: Hood the Email bot belongs to.
|
||||
:return: returns status code 201 if the message is accepted by the censor.
|
||||
"""
|
||||
for receiver in await Email.filter(hood=hood).all():
|
||||
for receiver in await Email.objects.filter(hood=hood).all():
|
||||
if message.secret == receiver.secret:
|
||||
# pass message.text to bot.py
|
||||
if await spawner.get(hood).publish(Message(message.text)):
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from asyncio import gather, get_event_loop, sleep
|
||||
from logging import getLogger
|
||||
import re
|
||||
|
||||
from mastodon import Mastodon, MastodonError
|
||||
|
||||
from asyncio import get_event_loop, sleep
|
||||
from kibicara.platformapi import Censor, Spawner, Message
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount
|
||||
from logging import getLogger
|
||||
|
||||
from mastodon import Mastodon, MastodonError
|
||||
from asyncio import gather
|
||||
import re
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -26,12 +26,12 @@ class MastodonBot(Censor):
|
|||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
"""Removes all its database entries."""
|
||||
for mastodon in await MastodonAccount.filter(hood=hood).all():
|
||||
for mastodon in await MastodonAccount.objects.filter(hood=hood).all():
|
||||
await mastodon.delete()
|
||||
|
||||
async def run(self):
|
||||
try:
|
||||
await self.model.fetch_related("instance")
|
||||
await self.model.instance.load()
|
||||
self.account = Mastodon(
|
||||
client_id=self.model.instance.client_id,
|
||||
client_secret=self.model.instance.client_secret,
|
||||
|
|
|
@ -1,36 +1,30 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
from ormantic import ForeignKey, Integer, Text, Boolean, Model
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.model import Hood, Mapping
|
||||
|
||||
|
||||
class MastodonInstance(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
name = fields.TextField()
|
||||
client_id = fields.TextField()
|
||||
client_secret = fields.TextField()
|
||||
accounts: fields.ReverseRelation["MastodonAccount"]
|
||||
id: Integer(primary_key=True) = None
|
||||
name: Text()
|
||||
client_id: Text()
|
||||
client_secret: Text()
|
||||
|
||||
class Meta:
|
||||
table = "platforms_mastodon_instances"
|
||||
class Mapping(Mapping):
|
||||
table_name = "mastodoninstances"
|
||||
|
||||
|
||||
class MastodonAccount(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="platforms_mastodon"
|
||||
)
|
||||
instance: fields.ForeignKeyRelation[MastodonInstance] = fields.ForeignKeyField(
|
||||
"models.MastodonInstance", related_name="accounts"
|
||||
)
|
||||
access_token = fields.TextField()
|
||||
username = fields.TextField(null=True)
|
||||
enabled = fields.BooleanField()
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
instance: ForeignKey(MastodonInstance)
|
||||
access_token: Text()
|
||||
username: Text(allow_null=True) = None
|
||||
enabled: Boolean() = False
|
||||
|
||||
class Meta:
|
||||
table = "platforms_mastodon_accounts"
|
||||
class Mapping(Mapping):
|
||||
table_name = "mastodonaccounts"
|
||||
|
|
|
@ -1,24 +1,24 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from asyncio import get_event_loop
|
||||
from logging import getLogger
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from mastodon import Mastodon, MastodonNetworkError
|
||||
from mastodon.errors import MastodonIllegalArgumentError
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel, validate_email, validator
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from kibicara.config import config
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platforms.mastodon.bot import spawner
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount, MastodonInstance
|
||||
from kibicara.webapi.hoods import get_hood, get_hood_unauthorized
|
||||
|
||||
from mastodon import Mastodon, MastodonNetworkError
|
||||
from mastodon.errors import MastodonIllegalArgumentError
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
|
@ -37,12 +37,10 @@ class BodyMastodonAccount(BaseModel):
|
|||
return validate_email(value)
|
||||
|
||||
|
||||
async def get_mastodon(
|
||||
mastodon_id: int, hood: Hood = Depends(get_hood)
|
||||
) -> MastodonAccount:
|
||||
async def get_mastodon(mastodon_id, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await MastodonAccount.get(id=mastodon_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
return await MastodonAccount.objects.get(id=mastodon_id, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
|
@ -53,16 +51,16 @@ async def get_mastodon_instance(instance_url: str) -> MastodonInstance:
|
|||
:return the MastodonInstance ORM object
|
||||
"""
|
||||
try:
|
||||
return await MastodonInstance.get(name=instance_url)
|
||||
except DoesNotExist:
|
||||
return await MastodonInstance.objects.get(name=instance_url)
|
||||
except NoMatch:
|
||||
app_name = config.get("frontend_url")
|
||||
client_id, client_secret = Mastodon.create_app(
|
||||
app_name, api_base_url=instance_url
|
||||
)
|
||||
await MastodonInstance.create(
|
||||
await MastodonInstance.objects.create(
|
||||
name=instance_url, client_id=client_id, client_secret=client_secret
|
||||
)
|
||||
return await MastodonInstance.get(name=instance_url)
|
||||
return await MastodonInstance.objects.get(name=instance_url)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
@ -75,11 +73,13 @@ twitter_callback_router = APIRouter()
|
|||
operation_id="get_mastodons_public",
|
||||
)
|
||||
async def mastodon_read_all_public(hood=Depends(get_hood_unauthorized)):
|
||||
mastodonbots = await MastodonAccount.objects.filter(hood=hood).all()
|
||||
mbots = []
|
||||
async for mbot in MastodonAccount.filter(hood=hood).prefetch_related("instance"):
|
||||
for mbot in mastodonbots:
|
||||
if mbot.enabled == 1 and mbot.username:
|
||||
instance = await MastodonInstance.objects.get(id=mbot.instance)
|
||||
mbots.append(
|
||||
BodyMastodonPublic(username=mbot.username, instance=mbot.instance.name)
|
||||
BodyMastodonPublic(username=mbot.username, instance=instance.name)
|
||||
)
|
||||
return mbots
|
||||
|
||||
|
@ -90,7 +90,7 @@ async def mastodon_read_all_public(hood=Depends(get_hood_unauthorized)):
|
|||
operation_id="get_mastodons",
|
||||
)
|
||||
async def mastodon_read_all(hood=Depends(get_hood)):
|
||||
return await MastodonAccount.filter(hood=hood).all()
|
||||
return await MastodonAccount.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.delete(
|
||||
|
@ -101,8 +101,8 @@ async def mastodon_read_all(hood=Depends(get_hood)):
|
|||
)
|
||||
async def mastodon_delete(mastodon=Depends(get_mastodon)):
|
||||
spawner.stop(mastodon)
|
||||
await mastodon.fetch_related("instance")
|
||||
object_with_instance = await MastodonAccount.filter(
|
||||
await mastodon.instance.load()
|
||||
object_with_instance = await MastodonAccount.objects.filter(
|
||||
instance=mastodon.instance
|
||||
).all()
|
||||
if len(object_with_instance) == 1 and object_with_instance[0] == mastodon:
|
||||
|
@ -172,7 +172,7 @@ async def mastodon_create(values: BodyMastodonAccount, hood=Depends(get_hood)):
|
|||
None, account.log_in, values.email, values.password
|
||||
)
|
||||
logger.debug(f"{access_token}")
|
||||
mastodon = await MastodonAccount.create(
|
||||
mastodon = await MastodonAccount.objects.create(
|
||||
hood=hood, instance=instance, access_token=access_token, enabled=True
|
||||
)
|
||||
spawner.start(mastodon)
|
||||
|
@ -181,5 +181,4 @@ async def mastodon_create(values: BodyMastodonAccount, hood=Depends(get_hood)):
|
|||
logger.warning("Login to Mastodon failed.", exc_info=True)
|
||||
raise HTTPException(status_code=status.HTTP_422_INVALID_INPUT)
|
||||
except IntegrityError:
|
||||
logger.warning("Login to Mastodon failed.", exc_info=True)
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from asyncio import CancelledError, gather, sleep
|
||||
from logging import getLogger
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from aiogram import Bot, Dispatcher, exceptions, types
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
from ormantic.exceptions import NoMatch
|
||||
|
||||
from kibicara.platformapi import Censor, Message, Spawner
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramSubscriber
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramUser
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
@ -25,8 +25,8 @@ class TelegramBot(Censor):
|
|||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
"""Removes all its database entries."""
|
||||
for telegram in await Telegram.filter(hood=hood).all():
|
||||
for user in await TelegramSubscriber.filter(bot=telegram).all():
|
||||
for telegram in await Telegram.objects.filter(hood=hood).all():
|
||||
for user in await TelegramUser.objects.filter(bot=telegram).all():
|
||||
await user.delete()
|
||||
await telegram.delete()
|
||||
|
||||
|
@ -69,7 +69,9 @@ class TelegramBot(Censor):
|
|||
self.telegram_model.hood.name, message.text
|
||||
)
|
||||
)
|
||||
for user in await TelegramSubscriber.filter(bot=self.telegram_model).all():
|
||||
for user in await TelegramUser.objects.filter(
|
||||
bot=self.telegram_model
|
||||
).all():
|
||||
await self._send_message(user.user_id, message.text)
|
||||
|
||||
async def _send_message(self, user_id, message):
|
||||
|
@ -114,7 +116,7 @@ class TelegramBot(Censor):
|
|||
if message.from_user.is_bot:
|
||||
await message.reply("Error: Bots can not join here.")
|
||||
return
|
||||
await TelegramSubscriber.create(
|
||||
await TelegramUser.objects.create(
|
||||
user_id=message.from_user.id, bot=self.telegram_model
|
||||
)
|
||||
await message.reply(self.telegram_model.welcome_message)
|
||||
|
@ -123,12 +125,12 @@ class TelegramBot(Censor):
|
|||
|
||||
async def _remove_user(self, message: types.Message):
|
||||
try:
|
||||
telegram_user = await TelegramSubscriber.get(
|
||||
telegram_user = await TelegramUser.objects.get(
|
||||
user_id=message.from_user.id, bot=self.telegram_model
|
||||
)
|
||||
await telegram_user.delete()
|
||||
await message.reply("You were removed successfully from this bot.")
|
||||
except DoesNotExist:
|
||||
except NoMatch:
|
||||
await message.reply("Error: You are not subscribed to this bot.")
|
||||
|
||||
async def _send_help(self, message: types.Message):
|
||||
|
|
|
@ -1,36 +1,30 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
from ormantic import Boolean, ForeignKey, Integer, Model, Text
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.model import Hood, Mapping
|
||||
|
||||
|
||||
class Telegram(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="platforms_telegram"
|
||||
)
|
||||
api_token = fields.CharField(64, unique=True)
|
||||
welcome_message = fields.TextField()
|
||||
username = fields.TextField(null=True)
|
||||
enabled = fields.BooleanField(default=True)
|
||||
subscribers: fields.ReverseRelation["TelegramSubscriber"]
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
api_token: Text(unique=True)
|
||||
welcome_message: Text()
|
||||
username: Text(allow_null=True) = None
|
||||
enabled: Boolean() = True
|
||||
|
||||
class Meta:
|
||||
table = "platforms_telegram"
|
||||
class Mapping(Mapping):
|
||||
table_name = "telegrambots"
|
||||
|
||||
|
||||
class TelegramSubscriber(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
bot: fields.ForeignKeyRelation[Telegram] = fields.ForeignKeyField(
|
||||
"models.Telegram", related_name="subscribers"
|
||||
)
|
||||
user_id = fields.IntField()
|
||||
class TelegramUser(Model):
|
||||
id: Integer(primary_key=True) = None
|
||||
user_id: Integer(unique=True)
|
||||
# TODO unique
|
||||
bot: ForeignKey(Telegram)
|
||||
|
||||
class Meta:
|
||||
table = "platforms_telegram_subscribers"
|
||||
class Mapping(Mapping):
|
||||
table_name = "telegramusers"
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from logging import getLogger
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from aiogram import exceptions
|
||||
from aiogram.bot.api import check_token
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel, validator
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
|
||||
from kibicara.platforms.telegram.bot import spawner
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramSubscriber
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramUser
|
||||
from kibicara.webapi.hoods import get_hood, get_hood_unauthorized
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
@ -38,8 +38,8 @@ class BodyTelegramPublic(BaseModel):
|
|||
|
||||
async def get_telegram(telegram_id: int, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await Telegram.get(id=telegram_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
return await Telegram.objects.get(id=telegram_id, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
|
@ -53,7 +53,7 @@ telegram_callback_router = APIRouter()
|
|||
operation_id="get_telegrams_public",
|
||||
)
|
||||
async def telegram_read_all_public(hood=Depends(get_hood_unauthorized)):
|
||||
telegrambots = await Telegram.filter(hood=hood).all()
|
||||
telegrambots = await Telegram.objects.filter(hood=hood).all()
|
||||
return [
|
||||
BodyTelegramPublic(username=telegrambot.username)
|
||||
for telegrambot in telegrambots
|
||||
|
@ -67,7 +67,7 @@ async def telegram_read_all_public(hood=Depends(get_hood_unauthorized)):
|
|||
operation_id="get_telegrams",
|
||||
)
|
||||
async def telegram_read_all(hood=Depends(get_hood)):
|
||||
return await Telegram.filter(hood=hood).all()
|
||||
return await Telegram.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.get(
|
||||
|
@ -86,7 +86,7 @@ async def telegram_read(telegram=Depends(get_telegram)):
|
|||
)
|
||||
async def telegram_delete(telegram=Depends(get_telegram)):
|
||||
spawner.stop(telegram)
|
||||
for user in await TelegramSubscriber.filter(bot=telegram).all():
|
||||
for user in await TelegramUser.objects.filter(bot=telegram).all():
|
||||
await user.delete()
|
||||
await telegram.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
@ -102,7 +102,7 @@ async def telegram_create(
|
|||
response: Response, values: BodyTelegram, hood=Depends(get_hood)
|
||||
):
|
||||
try:
|
||||
telegram = await Telegram.create(hood=hood, **values.__dict__)
|
||||
telegram = await Telegram.objects.create(hood=hood, **values.__dict__)
|
||||
spawner.start(telegram)
|
||||
response.headers["Location"] = str(telegram.id)
|
||||
return telegram
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from kibicara.platformapi import Censor, Message, Spawner
|
||||
from kibicara.platformapi import Censor, Spawner
|
||||
from kibicara.platforms.test.model import Test
|
||||
|
||||
|
||||
class TestBot(Censor):
|
||||
def __init__(self, test: Test):
|
||||
def __init__(self, test):
|
||||
super().__init__(test.hood)
|
||||
self.messages: list[Message] = []
|
||||
self.messages = []
|
||||
|
||||
async def run(self):
|
||||
while True:
|
||||
|
|
|
@ -1,19 +1,16 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
from ormantic import ForeignKey, Integer, Model
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.model import Hood, Mapping
|
||||
|
||||
|
||||
class Test(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="platforms_test"
|
||||
)
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
|
||||
class Meta:
|
||||
table = "platforms_test"
|
||||
class Mapping(Mapping):
|
||||
table_name = "testapi"
|
||||
|
|
|
@ -1,14 +1,15 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from pydantic import BaseModel
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platformapi import Message
|
||||
from kibicara.platforms.test.bot import spawner
|
||||
from kibicara.platforms.test.model import Test
|
||||
|
@ -19,10 +20,10 @@ class BodyMessage(BaseModel):
|
|||
text: str
|
||||
|
||||
|
||||
async def get_test(test_id: int, hood: Hood = Depends(get_hood)) -> Test:
|
||||
async def get_test(test_id: int, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await Test.get(id=test_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
return await Test.objects.get(id=test_id, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
|
@ -30,14 +31,14 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get("/")
|
||||
async def test_read_all(hood: Hood = Depends(get_hood)):
|
||||
return await Test.filter(hood=hood)
|
||||
async def test_read_all(hood=Depends(get_hood)):
|
||||
return await Test.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.post("/", status_code=status.HTTP_201_CREATED)
|
||||
async def test_create(response: Response, hood: Hood = Depends(get_hood)):
|
||||
async def test_create(response: Response, hood=Depends(get_hood)):
|
||||
try:
|
||||
test = await Test.create(hood=hood)
|
||||
test = await Test.objects.create(hood=hood)
|
||||
spawner.start(test)
|
||||
response.headers["Location"] = str(test.id)
|
||||
return test
|
||||
|
@ -46,22 +47,22 @@ async def test_create(response: Response, hood: Hood = Depends(get_hood)):
|
|||
|
||||
|
||||
@router.get("/{test_id}")
|
||||
async def test_read(test: Test = Depends(get_test)):
|
||||
async def test_read(test=Depends(get_test)):
|
||||
return test
|
||||
|
||||
|
||||
@router.delete("/{test_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def test_delete(test: Test = Depends(get_test)):
|
||||
async def test_delete(test=Depends(get_test)):
|
||||
spawner.stop(test)
|
||||
await test.delete()
|
||||
|
||||
|
||||
@router.get("/{test_id}/messages/")
|
||||
async def test_message_read_all(test: Test = Depends(get_test)):
|
||||
async def test_message_read_all(test=Depends(get_test)):
|
||||
return spawner.get(test).messages
|
||||
|
||||
|
||||
@router.post("/{test_id}/messages/")
|
||||
async def test_message_create(message: BodyMessage, test: Test = Depends(get_test)):
|
||||
async def test_message_create(message: BodyMessage, test=Depends(get_test)):
|
||||
await spawner.get(test).publish(Message(message.text))
|
||||
return {}
|
||||
|
|
|
@ -1,165 +0,0 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from asyncio import CancelledError, gather, sleep
|
||||
from logging import getLogger
|
||||
|
||||
from peony import PeonyClient, exceptions
|
||||
|
||||
from kibicara.config import config
|
||||
from kibicara.platformapi import Censor, Message, Spawner
|
||||
from kibicara.platforms.twitter.model import Twitter
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class TwitterBot(Censor):
|
||||
def __init__(self, twitter_model):
|
||||
super().__init__(twitter_model.hood)
|
||||
self.twitter_model = twitter_model
|
||||
self.enabled = self.twitter_model.enabled
|
||||
self.polling_interval_sec = 60
|
||||
self.mentions_since_id = self.twitter_model.mentions_since_id
|
||||
self.dms_since_id = self.twitter_model.dms_since_id
|
||||
|
||||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
"""Removes all its database entries."""
|
||||
for twitter in await Twitter.filter(hood=hood).all():
|
||||
await twitter.delete()
|
||||
|
||||
async def run(self):
|
||||
try:
|
||||
if not self.twitter_model.verified:
|
||||
raise ValueError("Oauth Handshake not completed")
|
||||
self.client = PeonyClient(
|
||||
consumer_key=config["twitter"]["consumer_key"],
|
||||
consumer_secret=config["twitter"]["consumer_secret"],
|
||||
access_token=self.twitter_model.access_token,
|
||||
access_token_secret=self.twitter_model.access_token_secret,
|
||||
)
|
||||
if self.twitter_model.mentions_since_id is None:
|
||||
logger.debug("since_id is None in model, fetch newest mention id")
|
||||
await self._poll_mentions()
|
||||
if self.twitter_model.dms_since_id is None:
|
||||
logger.debug("since_id is None in model, fetch newest dm id")
|
||||
await self._poll_direct_messages()
|
||||
user = await self.client.user
|
||||
if user.screen_name:
|
||||
await self.twitter_model.update(username=user.screen_name)
|
||||
logger.debug(
|
||||
"Starting Twitter bot: {0}".format(self.twitter_model.__dict__)
|
||||
)
|
||||
await gather(self.poll(), self.push())
|
||||
except CancelledError:
|
||||
logger.debug(
|
||||
"Bot {0} received Cancellation.".format(self.twitter_model.hood.name)
|
||||
)
|
||||
except exceptions.Unauthorized:
|
||||
logger.debug(
|
||||
"Bot {0} has invalid auth token.".format(self.twitter_model.hood.name)
|
||||
)
|
||||
await self.twitter_model.update(enabled=False)
|
||||
self.enabled = self.twitter_model.enabled
|
||||
except (KeyError, ValueError, exceptions.NotAuthenticated):
|
||||
logger.warning("Missing consumer_keys for Twitter in your configuration.")
|
||||
await self.twitter_model.update(enabled=False)
|
||||
self.enabled = self.twitter_model.enabled
|
||||
finally:
|
||||
logger.debug("Bot {0} stopped.".format(self.twitter_model.hood.name))
|
||||
|
||||
async def poll(self):
|
||||
while True:
|
||||
dms = await self._poll_direct_messages()
|
||||
logger.debug(
|
||||
"Polled dms ({0}): {1}".format(self.twitter_model.hood.name, str(dms))
|
||||
)
|
||||
mentions = await self._poll_mentions()
|
||||
logger.debug(
|
||||
"Polled mentions ({0}): {1}".format(
|
||||
self.twitter_model.hood.name, str(mentions)
|
||||
)
|
||||
)
|
||||
await self.twitter_model.update(
|
||||
dms_since_id=self.dms_since_id, mentions_since_id=self.mentions_since_id
|
||||
)
|
||||
for message in dms:
|
||||
await self.publish(Message(message))
|
||||
for message_id, message in mentions:
|
||||
await self.publish(Message(message, twitter_mention_id=message_id))
|
||||
await sleep(self.polling_interval_sec)
|
||||
|
||||
async def _poll_direct_messages(self):
|
||||
dms = await self.client.api.direct_messages.events.list.get()
|
||||
dms = dms.events
|
||||
# TODO check for next_cursor (see twitter api)
|
||||
dms_filtered = []
|
||||
if dms:
|
||||
for dm in dms:
|
||||
if int(dm.id) == self.dms_since_id:
|
||||
break
|
||||
dms_filtered.append(dm)
|
||||
self.dms_since_id = int(dms[0].id)
|
||||
messages = []
|
||||
for dm in dms_filtered:
|
||||
filtered_text = await self._filter_text(
|
||||
dm.message_create.message_data.entities,
|
||||
dm.message_create.message_data.text,
|
||||
)
|
||||
if not filtered_text:
|
||||
continue
|
||||
messages.append(filtered_text)
|
||||
return messages
|
||||
|
||||
async def _poll_mentions(self):
|
||||
mentions = await self.client.api.statuses.mentions_timeline.get(
|
||||
since_id=self.mentions_since_id
|
||||
)
|
||||
if mentions:
|
||||
self.mentions_since_id = mentions[0].id
|
||||
messages = []
|
||||
for mention in mentions:
|
||||
filtered_text = await self._filter_text(mention.entities, mention.text)
|
||||
if not filtered_text:
|
||||
continue
|
||||
messages.append((mention.id, filtered_text))
|
||||
return messages
|
||||
|
||||
async def _filter_text(self, entities, text):
|
||||
remove_indices = set()
|
||||
for user in entities.user_mentions:
|
||||
remove_indices.update(range(user.indices[0], user.indices[1] + 1))
|
||||
for url in entities.urls:
|
||||
remove_indices.update(range(url.indices[0], url.indices[1] + 1))
|
||||
for symbol in entities.symbols:
|
||||
remove_indices.update(range(symbol.indices[0], symbol.indices[1] + 1))
|
||||
filtered_text = ""
|
||||
for index, character in enumerate(text):
|
||||
if index not in remove_indices:
|
||||
filtered_text += character
|
||||
return filtered_text.strip()
|
||||
|
||||
async def push(self):
|
||||
while True:
|
||||
message = await self.receive()
|
||||
logger.debug(
|
||||
"Received message from censor ({0}): {1}".format(
|
||||
self.twitter_model.hood.name, message.text
|
||||
)
|
||||
)
|
||||
if hasattr(message, "twitter_mention_id"):
|
||||
await self._retweet(message.twitter_mention_id)
|
||||
else:
|
||||
await self._post_tweet(message.text)
|
||||
|
||||
async def _post_tweet(self, message):
|
||||
return await self.client.api.statuses.update.post(status=message)
|
||||
|
||||
async def _retweet(self, message_id):
|
||||
return await self.client.api.statuses.retweet.post(id=message_id)
|
||||
|
||||
|
||||
spawner = Spawner(Twitter, TwitterBot)
|
|
@ -1,27 +0,0 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from tortoise import fields
|
||||
from tortoise.models import Model
|
||||
|
||||
from kibicara.model import Hood
|
||||
|
||||
|
||||
class Twitter(Model):
|
||||
id = fields.IntField(pk=True)
|
||||
hood: fields.ForeignKeyRelation[Hood] = fields.ForeignKeyField(
|
||||
"models.Hood", related_name="platforms_twitter"
|
||||
)
|
||||
dms_since_id = fields.IntField()
|
||||
mentions_since_id = fields.IntField()
|
||||
access_token = fields.TextField()
|
||||
access_token_secret = fields.TextField()
|
||||
username = fields.TextField()
|
||||
verified = fields.BooleanField(default=False)
|
||||
enabled = fields.BooleanField(default=False)
|
||||
|
||||
class Meta:
|
||||
table = "platforms_twitter"
|
|
@ -1,183 +0,0 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from logging import getLogger
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from peony.exceptions import NotAuthenticated
|
||||
from peony.oauth_dance import get_access_token, get_oauth_token
|
||||
from pydantic import BaseModel
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
|
||||
from kibicara.config import config
|
||||
from kibicara.platforms.twitter.bot import spawner
|
||||
from kibicara.platforms.twitter.model import Twitter
|
||||
from kibicara.webapi.hoods import get_hood, get_hood_unauthorized
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
class BodyTwitterPublic(BaseModel):
|
||||
username: str
|
||||
|
||||
|
||||
async def get_twitter(twitter_id: int, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await Twitter.get(id=twitter_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
twitter_callback_router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/public",
|
||||
# TODO response_model,
|
||||
operation_id="get_twitters_public",
|
||||
)
|
||||
async def twitter_read_all_public(hood=Depends(get_hood_unauthorized)):
|
||||
twitterbots = await Twitter.filter(hood=hood).all()
|
||||
return [
|
||||
BodyTwitterPublic(username=twitterbot.username)
|
||||
for twitterbot in twitterbots
|
||||
if twitterbot.verified == 1 and twitterbot.enabled == 1 and twitterbot.username
|
||||
]
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id="get_twitters",
|
||||
)
|
||||
async def twitter_read_all(hood=Depends(get_hood)):
|
||||
return await Twitter.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{twitter_id}",
|
||||
# TODO response_model
|
||||
operation_id="get_twitter",
|
||||
)
|
||||
async def twitter_read(twitter=Depends(get_twitter)):
|
||||
return twitter
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{twitter_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
# TODO response_model
|
||||
operation_id="delete_twitter",
|
||||
)
|
||||
async def twitter_delete(twitter=Depends(get_twitter)):
|
||||
spawner.stop(twitter)
|
||||
await twitter.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{twitter_id}/status",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id="status_twitter",
|
||||
)
|
||||
async def twitter_status(twitter=Depends(get_twitter)):
|
||||
return {"status": spawner.get(twitter).status.name}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{twitter_id}/start",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id="start_twitter",
|
||||
)
|
||||
async def twitter_start(twitter=Depends(get_twitter)):
|
||||
await twitter.update(enabled=True)
|
||||
spawner.get(twitter).start()
|
||||
return {}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{twitter_id}/stop",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id="stop_twitter",
|
||||
)
|
||||
async def twitter_stop(twitter=Depends(get_twitter)):
|
||||
await twitter.update(enabled=False)
|
||||
spawner.get(twitter).stop()
|
||||
return {}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model
|
||||
operation_id="create_twitter",
|
||||
)
|
||||
async def twitter_create(response: Response, hood=Depends(get_hood)):
|
||||
"""
|
||||
`https://api.twitter.com/oauth/authorize?oauth_token=`
|
||||
"""
|
||||
try:
|
||||
# Purge Twitter corpses
|
||||
for corpse in await Twitter.filter(hood=hood, verified=False).all():
|
||||
await corpse.delete()
|
||||
# Create Twitter
|
||||
request_token = await get_oauth_token(
|
||||
config["twitter"]["consumer_key"],
|
||||
config["twitter"]["consumer_secret"],
|
||||
callback_uri="{0}/dashboard/twitter-callback?hood={1}".format(
|
||||
config["frontend_url"], hood.id
|
||||
),
|
||||
)
|
||||
if request_token["oauth_callback_confirmed"] != "true":
|
||||
raise HTTPException(status_code=status.HTTP_503_SERVICE_UNAVAILABLE)
|
||||
twitter = await Twitter.create(
|
||||
hood=hood,
|
||||
access_token=request_token["oauth_token"],
|
||||
access_token_secret=request_token["oauth_token_secret"],
|
||||
)
|
||||
response.headers["Location"] = str(twitter.id)
|
||||
return twitter
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except (KeyError, ValueError, NotAuthenticated):
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
||||
|
||||
@twitter_callback_router.get(
|
||||
"/callback",
|
||||
# TODO response_model
|
||||
operation_id="callback_twitter",
|
||||
)
|
||||
async def twitter_read_callback(
|
||||
oauth_token: str, oauth_verifier: str, hood=Depends(get_hood)
|
||||
):
|
||||
try:
|
||||
twitter = await Twitter.filter(access_token=oauth_token).get()
|
||||
access_token = await get_access_token(
|
||||
config["twitter"]["consumer_key"],
|
||||
config["twitter"]["consumer_secret"],
|
||||
twitter.access_token,
|
||||
twitter.access_token_secret,
|
||||
oauth_verifier,
|
||||
)
|
||||
await Twitter.filter(id=twitter).update(
|
||||
access_token=access_token["oauth_token"],
|
||||
access_token_secret=access_token["oauth_token_secret"],
|
||||
verified=True,
|
||||
enabled=True,
|
||||
)
|
||||
spawner.start(twitter)
|
||||
return {}
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
except (KeyError, ValueError, NotAuthenticated):
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
|
@ -16,35 +16,25 @@ from kibicara.platforms.email.webapi import router as email_router
|
|||
from kibicara.platforms.telegram.webapi import router as telegram_router
|
||||
from kibicara.platforms.test.webapi import router as test_router
|
||||
from kibicara.platforms.mastodon.webapi import router as mastodon_router
|
||||
from kibicara.platforms.twitter.webapi import router as twitter_router
|
||||
from kibicara.platforms.twitter.webapi import twitter_callback_router
|
||||
from kibicara.webapi.admin import router as admin_router
|
||||
from kibicara.webapi.hoods import router as hoods_router
|
||||
from kibicara.webapi.hoods.exclude_patterns import router as exclude_patterns_router
|
||||
from kibicara.webapi.hoods.include_patterns import router as include_patterns_router
|
||||
from kibicara.webapi.hoods.badwords import router as badwords_router
|
||||
from kibicara.webapi.hoods.triggers import router as triggers_router
|
||||
|
||||
router = APIRouter()
|
||||
router.include_router(admin_router, prefix="/admin", tags=["admin"])
|
||||
hoods_router.include_router(
|
||||
include_patterns_router,
|
||||
prefix="/{hood_id}/triggers",
|
||||
tags=["include_patterns"],
|
||||
triggers_router, prefix="/{hood_id}/triggers", tags=["triggers"]
|
||||
)
|
||||
hoods_router.include_router(
|
||||
exclude_patterns_router,
|
||||
prefix="/{hood_id}/badwords",
|
||||
tags=["exclude_patterns"],
|
||||
badwords_router, prefix="/{hood_id}/badwords", tags=["badwords"]
|
||||
)
|
||||
hoods_router.include_router(test_router, prefix="/{hood_id}/test", tags=["test"])
|
||||
hoods_router.include_router(
|
||||
telegram_router, prefix="/{hood_id}/telegram", tags=["telegram"]
|
||||
)
|
||||
hoods_router.include_router(
|
||||
twitter_router, prefix="/{hood_id}/twitter", tags=["twitter"]
|
||||
)
|
||||
hoods_router.include_router(
|
||||
mastodon_router, prefix="/{hood_id}/mastodon", tags=["mastodon"]
|
||||
)
|
||||
router.include_router(twitter_callback_router, prefix="/twitter", tags=["twitter"])
|
||||
hoods_router.include_router(email_router, prefix="/{hood_id}/email", tags=["email"])
|
||||
router.include_router(hoods_router, prefix="/hoods")
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Christian Hagenest <c.hagenest@pm.me>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
|
@ -11,20 +11,20 @@ from datetime import datetime, timedelta
|
|||
from logging import getLogger
|
||||
from pickle import dumps, loads
|
||||
from smtplib import SMTPException
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, status
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from fastapi.security import OAuth2PasswordBearer, OAuth2PasswordRequestForm
|
||||
from nacl.encoding import URLSafeBase64Encoder
|
||||
from nacl.exceptions import CryptoError
|
||||
from nacl.secret import SecretBox
|
||||
from nacl.utils import random
|
||||
from ormantic.exceptions import NoMatch
|
||||
from passlib.hash import argon2
|
||||
from pydantic import BaseModel, validator
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
|
||||
from kibicara import email
|
||||
from kibicara.config import config
|
||||
from kibicara.model import Admin, Hood
|
||||
from kibicara.model import Admin, AdminHoodRelation, Hood
|
||||
from kibicara.webapi.utils import delete_hood
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
@ -54,42 +54,41 @@ class BodyAccessToken(BaseModel):
|
|||
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/admin/login")
|
||||
secret_box = SecretBox(
|
||||
bytes.fromhex(str(config.get("secret", random(SecretBox.KEY_SIZE).hex())))
|
||||
)
|
||||
secret_box = SecretBox(bytes.fromhex(config["secret"]))
|
||||
|
||||
|
||||
def to_token(**kwargs) -> str:
|
||||
def to_token(**kwargs):
|
||||
return secret_box.encrypt(dumps(kwargs), encoder=URLSafeBase64Encoder).decode(
|
||||
"ascii"
|
||||
)
|
||||
|
||||
|
||||
def from_token(token: str) -> dict:
|
||||
def from_token(token):
|
||||
return loads(
|
||||
secret_box.decrypt(token.encode("ascii"), encoder=URLSafeBase64Encoder)
|
||||
)
|
||||
|
||||
|
||||
async def get_auth(email: str, password: str) -> Admin:
|
||||
async def get_auth(email, password):
|
||||
try:
|
||||
admin = await Admin.get(email=email)
|
||||
admin = await Admin.objects.get(email=email)
|
||||
if argon2.verify(password, admin.passhash):
|
||||
return admin
|
||||
raise ValueError
|
||||
except DoesNotExist:
|
||||
except NoMatch:
|
||||
raise ValueError
|
||||
|
||||
|
||||
async def get_admin(access_token: str = Depends(oauth2_scheme)) -> Admin:
|
||||
async def get_admin(access_token=Depends(oauth2_scheme)):
|
||||
try:
|
||||
return await get_auth(**from_token(access_token))
|
||||
admin = await get_auth(**from_token(access_token))
|
||||
except (CryptoError, ValueError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail="Invalid authentication credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return admin
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
@ -110,9 +109,9 @@ async def admin_register(values: BodyAdmin):
|
|||
register_token = to_token(**values.__dict__)
|
||||
logger.debug("register_token={0}".format(register_token))
|
||||
try:
|
||||
if await Admin.exists(email=values.email):
|
||||
admin = await Admin.objects.filter(email=values.email).all()
|
||||
if admin:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
# link goes to frontend. this is not the confirm API endpoint below!
|
||||
body = "{0}/confirm?token={1}".format(config["frontend_url"], register_token)
|
||||
logger.debug(body)
|
||||
email.send_email(
|
||||
|
@ -139,8 +138,7 @@ async def admin_confirm(register_token: str):
|
|||
try:
|
||||
values = from_token(register_token)
|
||||
passhash = argon2.hash(values["password"])
|
||||
await Admin.create(email=values["email"], passhash=passhash)
|
||||
# XXX login and registration tokens are exchangeable. does this hurt?
|
||||
await Admin.objects.create(email=values["email"], passhash=passhash)
|
||||
return BodyAccessToken(access_token=register_token)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
@ -180,14 +178,14 @@ async def admin_reset_password(values: BodyEmail):
|
|||
- **email**: E-Mail Address of new hood admin
|
||||
- **password**: Password of new hood admin
|
||||
"""
|
||||
reset_token = to_token(datetime=datetime.now().isoformat(), **values.__dict__)
|
||||
logger.debug("reset_token={0}".format(reset_token))
|
||||
register_token = to_token(datetime=datetime.now().isoformat(), **values.__dict__)
|
||||
logger.debug("register_token={0}".format(register_token))
|
||||
try:
|
||||
if not await Admin.exists(email=values.email):
|
||||
admin = await Admin.objects.filter(email=values.email).all()
|
||||
if not admin:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
# link goes to frontend. this is not the reset API endpoint below!
|
||||
body = "{0}/password-reset?token={1}".format(
|
||||
config["frontend_url"], reset_token
|
||||
config["frontend_url"], register_token
|
||||
)
|
||||
logger.debug(body)
|
||||
email.send_email(
|
||||
|
@ -215,10 +213,11 @@ async def admin_confirm_reset(reset_token: str, values: BodyPassword):
|
|||
):
|
||||
raise HTTPException(status_code=status.HTTP_410_GONE)
|
||||
passhash = argon2.hash(values.password)
|
||||
await Admin.filter(email=token_values["email"]).update(passhash=passhash)
|
||||
admins = await Admin.objects.filter(email=token_values["email"]).all()
|
||||
if len(admins) != 1:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
await admins[0].update(passhash=passhash)
|
||||
return BodyAccessToken(access_token=reset_token)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except CryptoError:
|
||||
|
@ -230,9 +229,11 @@ async def admin_confirm_reset(reset_token: str, values: BodyPassword):
|
|||
# TODO response_model,
|
||||
operation_id="get_hoods_admin",
|
||||
)
|
||||
async def admin_hood_read_all(admin: Admin = Depends(get_admin)):
|
||||
async def admin_hood_read_all(admin=Depends(get_admin)):
|
||||
"""Get a list of all hoods of a given admin."""
|
||||
return await Hood.filter(admins=admin)
|
||||
return (
|
||||
await AdminHoodRelation.objects.select_related("hood").filter(admin=admin).all()
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
|
@ -240,8 +241,12 @@ async def admin_hood_read_all(admin: Admin = Depends(get_admin)):
|
|||
# TODO response_model,
|
||||
operation_id="get_admin",
|
||||
)
|
||||
async def admin_read(admin: Admin = Depends(get_admin)):
|
||||
return BodyEmail(email=admin.email)
|
||||
async def admin_read(admin=Depends(get_admin)):
|
||||
"""Get a list of all hoods of a given admin."""
|
||||
admin = await Admin.objects.filter(email=admin.email).all()
|
||||
if len(admin) != 1:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
return BodyEmail(email=admin[0].email)
|
||||
|
||||
|
||||
@router.delete(
|
||||
|
@ -249,10 +254,18 @@ async def admin_read(admin: Admin = Depends(get_admin)):
|
|||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="delete_admin",
|
||||
)
|
||||
async def admin_delete(admin: Admin = Depends(get_admin)):
|
||||
async for hood in Hood.filter(admins__contains=admin):
|
||||
await hood.admins.remove(admin)
|
||||
await hood.fetch_related("admins")
|
||||
if len(hood.admins) == 0:
|
||||
await delete_hood(hood)
|
||||
async def admin_delete(admin=Depends(get_admin)):
|
||||
hood_relations = (
|
||||
await AdminHoodRelation.objects.select_related("hood").filter(admin=admin).all()
|
||||
)
|
||||
for hood in hood_relations:
|
||||
admins = (
|
||||
await AdminHoodRelation.objects.select_related("admin")
|
||||
.filter(hood=hood.id)
|
||||
.all()
|
||||
)
|
||||
if len(admins) == 1 and admins[0].id == admin.id:
|
||||
actual_hood = await Hood.objects.filter(id=hood.id).all()
|
||||
await delete_hood(actual_hood[0])
|
||||
await admin.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
|
@ -6,11 +6,13 @@
|
|||
|
||||
"""REST API Endpoints for managing hoods."""
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from pydantic import BaseModel
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from kibicara.model import Admin, Hood, IncludePattern
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel
|
||||
|
||||
from kibicara.model import AdminHoodRelation, Hood, Trigger
|
||||
from kibicara.platforms.email.bot import spawner
|
||||
from kibicara.webapi.admin import get_admin
|
||||
from kibicara.webapi.utils import delete_hood
|
||||
|
@ -18,26 +20,28 @@ from kibicara.webapi.utils import delete_hood
|
|||
|
||||
class BodyHood(BaseModel):
|
||||
name: str
|
||||
landingpage: str = "Default Landing Page"
|
||||
landingpage: str = """
|
||||
Default Landing Page
|
||||
"""
|
||||
|
||||
|
||||
async def get_hood_unauthorized(hood_id: int) -> Hood:
|
||||
async def get_hood_unauthorized(hood_id: int):
|
||||
try:
|
||||
return await Hood.get(id=hood_id)
|
||||
except DoesNotExist:
|
||||
hood = await Hood.objects.get(id=hood_id)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
return hood
|
||||
|
||||
|
||||
async def get_hood(
|
||||
hood: Hood = Depends(get_hood_unauthorized), admin: Admin = Depends(get_admin)
|
||||
) -> Hood:
|
||||
await hood.fetch_related("admins")
|
||||
if admin in hood.admins:
|
||||
return hood
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
async def get_hood(hood=Depends(get_hood_unauthorized), admin=Depends(get_admin)):
|
||||
try:
|
||||
await AdminHoodRelation.objects.get(admin=admin, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return hood
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
@ -51,7 +55,7 @@ router = APIRouter()
|
|||
)
|
||||
async def hood_read_all():
|
||||
"""Get all existing hoods."""
|
||||
return await Hood.all()
|
||||
return await Hood.objects.all()
|
||||
|
||||
|
||||
@router.post(
|
||||
|
@ -61,21 +65,19 @@ async def hood_read_all():
|
|||
operation_id="create_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_create(
|
||||
values: BodyHood, response: Response, admin: Admin = Depends(get_admin)
|
||||
):
|
||||
async def hood_create(values: BodyHood, response: Response, admin=Depends(get_admin)):
|
||||
"""Creates a hood.
|
||||
|
||||
- **name**: Name of the hood
|
||||
- **landingpage**: Markdown formatted description of the hood
|
||||
"""
|
||||
try:
|
||||
hood = await Hood.create(**values.__dict__)
|
||||
await admin.hoods.add(hood)
|
||||
hood = await Hood.objects.create(**values.__dict__)
|
||||
await AdminHoodRelation.objects.create(admin=admin.id, hood=hood.id)
|
||||
spawner.start(hood)
|
||||
|
||||
# Initialize Triggers to match all
|
||||
await IncludePattern.create(hood=hood, pattern=".")
|
||||
await Trigger.objects.create(hood=hood, pattern=".")
|
||||
|
||||
response.headers["Location"] = str(hood.id)
|
||||
return hood
|
||||
|
@ -89,24 +91,25 @@ async def hood_create(
|
|||
operation_id="get_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_read(hood: Hood = Depends(get_hood_unauthorized)):
|
||||
async def hood_read(hood=Depends(get_hood_unauthorized)):
|
||||
"""Get hood with id **hood_id**."""
|
||||
return hood
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{hood_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="update_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_update(values: BodyHood, hood: Hood = Depends(get_hood)):
|
||||
async def hood_update(values: BodyHood, hood=Depends(get_hood)):
|
||||
"""Updates hood with id **hood_id**.
|
||||
|
||||
- **name**: New name of the hood
|
||||
- **landingpage**: New Markdown formatted description of the hood
|
||||
"""
|
||||
await Hood.filter(id=hood).update(**values.__dict__)
|
||||
return hood
|
||||
await hood.update(**values.__dict__)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
|
@ -118,3 +121,4 @@ async def hood_update(values: BodyHood, hood: Hood = Depends(get_hood)):
|
|||
async def hood_delete(hood=Depends(get_hood)):
|
||||
"""Deletes hood with id **hood_id**."""
|
||||
await delete_hood(hood)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
|
106
backend/src/kibicara/webapi/hoods/badwords.py
Normal file
106
backend/src/kibicara/webapi/hoods/badwords.py
Normal file
|
@ -0,0 +1,106 @@
|
|||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
"""REST API endpoints for managing badwords.
|
||||
|
||||
Provides API endpoints for adding, removing and reading regular expressions that block a
|
||||
received message to be dropped by a censor. This provides a message filter customizable
|
||||
by the hood admins.
|
||||
"""
|
||||
|
||||
from re import compile as regex_compile
|
||||
from re import error as RegexError
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel
|
||||
|
||||
from kibicara.model import BadWord
|
||||
from kibicara.webapi.hoods import get_hood
|
||||
|
||||
|
||||
class BodyBadWord(BaseModel):
|
||||
pattern: str
|
||||
|
||||
|
||||
async def get_badword(badword_id: int, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await BadWord.objects.get(id=badword_id, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id="get_badwords",
|
||||
)
|
||||
async def badword_read_all(hood=Depends(get_hood)):
|
||||
"""Get all badwords of hood with id **hood_id**."""
|
||||
return await BadWord.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id="create_badword",
|
||||
)
|
||||
async def badword_create(
|
||||
values: BodyBadWord, response: Response, hood=Depends(get_hood)
|
||||
):
|
||||
"""Creates a new badword for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a badword.
|
||||
"""
|
||||
try:
|
||||
regex_compile(values.pattern)
|
||||
badword = await BadWord.objects.create(hood=hood, **values.__dict__)
|
||||
response.headers["Location"] = str(badword.id)
|
||||
return badword
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except RegexError:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{badword_id}",
|
||||
# TODO response_model,
|
||||
operation_id="get_badword",
|
||||
)
|
||||
async def badword_read(badword=Depends(get_badword)):
|
||||
"""Reads badword with id **badword_id** for hood with id **hood_id**."""
|
||||
return badword
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{badword_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="update_badword",
|
||||
)
|
||||
async def badword_update(values: BodyBadWord, badword=Depends(get_badword)):
|
||||
"""Updates badword with id **badword_id** for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a badword
|
||||
"""
|
||||
await badword.update(**values.__dict__)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{badword_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="delete_badword",
|
||||
)
|
||||
async def badword_delete(badword=Depends(get_badword)):
|
||||
"""Deletes badword with id **badword_id** for hood with id **hood_id**."""
|
||||
await badword.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
@ -1,116 +0,0 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
"""REST API endpoints for managing exclude_patterns.
|
||||
|
||||
Provides API endpoints for adding, removing and reading regular expressions that block a
|
||||
received message to be dropped by a censor. This provides a message filter customizable
|
||||
by the hood admins.
|
||||
"""
|
||||
|
||||
from re import compile as regex_compile, error as RegexError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from pydantic import BaseModel
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
|
||||
from kibicara.model import ExcludePattern, Hood
|
||||
from kibicara.webapi.hoods import get_hood
|
||||
|
||||
|
||||
class BodyExcludePattern(BaseModel):
|
||||
pattern: str
|
||||
|
||||
|
||||
async def get_exclude_pattern(
|
||||
exclude_pattern_id: int, hood: Hood = Depends(get_hood)
|
||||
) -> ExcludePattern:
|
||||
try:
|
||||
return await ExcludePattern.get(id=exclude_pattern_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id="get_exclude_patterns",
|
||||
)
|
||||
async def exclude_pattern_read_all(hood: Hood = Depends(get_hood)):
|
||||
"""Get all exclude_patterns of hood with id **hood_id**."""
|
||||
return await ExcludePattern.filter(hood=hood)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id="create_exclude_pattern",
|
||||
)
|
||||
async def exclude_pattern_create(
|
||||
values: BodyExcludePattern, response: Response, hood: Hood = Depends(get_hood)
|
||||
):
|
||||
"""Creates a new exclude_pattern for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a exclude_pattern.
|
||||
"""
|
||||
try:
|
||||
regex_compile(values.pattern)
|
||||
exclude_pattern = await ExcludePattern.create(hood=hood, **values.__dict__)
|
||||
response.headers["Location"] = str(exclude_pattern.id)
|
||||
return exclude_pattern
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except RegexError:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{exclude_pattern_id}",
|
||||
# TODO response_model,
|
||||
operation_id="get_exclude_pattern",
|
||||
)
|
||||
async def exclude_pattern_read(
|
||||
exclude_pattern: ExcludePattern = Depends(get_exclude_pattern),
|
||||
):
|
||||
"""
|
||||
Reads exclude_pattern with id **exclude_pattern_id** for hood with id **hood_id**.
|
||||
"""
|
||||
return exclude_pattern
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{exclude_pattern_id}",
|
||||
operation_id="update_exclude_pattern",
|
||||
)
|
||||
async def exclude_pattern_update(
|
||||
values: BodyExcludePattern,
|
||||
exclude_pattern: ExcludePattern = Depends(get_exclude_pattern),
|
||||
):
|
||||
"""
|
||||
Updates exclude_pattern with id **exclude_pattern_id** for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a exclude_pattern
|
||||
"""
|
||||
await ExcludePattern.filter(id=exclude_pattern).update(**values.__dict__)
|
||||
return exclude_pattern
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{exclude_pattern_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="delete_exclude_pattern",
|
||||
)
|
||||
async def exclude_pattern_delete(
|
||||
exclude_pattern: ExcludePattern = Depends(get_exclude_pattern),
|
||||
):
|
||||
"""
|
||||
Deletes exclude_pattern with id **exclude_pattern_id** for hood with id **hood_id**.
|
||||
"""
|
||||
await exclude_pattern.delete()
|
|
@ -1,115 +0,0 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
"""REST API endpoints for managing include_patterns.
|
||||
|
||||
Provides API endpoints for adding, removing and reading regular expressions that allow a
|
||||
message to be passed through by a censor. A published message must match one of these
|
||||
regular expressions otherwise it gets dropped by the censor. This provides a message
|
||||
filter customizable by the hood admins.
|
||||
"""
|
||||
|
||||
from re import compile as regex_compile, error as RegexError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from pydantic import BaseModel
|
||||
from tortoise.exceptions import DoesNotExist, IntegrityError
|
||||
|
||||
from kibicara.model import IncludePattern, Hood
|
||||
from kibicara.webapi.hoods import get_hood
|
||||
|
||||
|
||||
class BodyIncludePattern(BaseModel):
|
||||
pattern: str
|
||||
|
||||
|
||||
async def get_include_pattern(include_pattern_id: int, hood: Hood = Depends(get_hood)):
|
||||
try:
|
||||
return await IncludePattern.get(id=include_pattern_id, hood=hood)
|
||||
except DoesNotExist:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id="get_include_patterns",
|
||||
)
|
||||
async def include_pattern_read_all(hood: Hood = Depends(get_hood)):
|
||||
"""Get all include_patterns of hood with id **hood_id**."""
|
||||
return await IncludePattern.filter(hood=hood)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id="create_include_pattern",
|
||||
)
|
||||
async def include_pattern_create(
|
||||
values: BodyIncludePattern, response: Response, hood: Hood = Depends(get_hood)
|
||||
):
|
||||
"""Creates a new include_pattern for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a include_pattern.
|
||||
"""
|
||||
try:
|
||||
regex_compile(values.pattern)
|
||||
include_pattern = await IncludePattern.create(hood=hood, **values.__dict__)
|
||||
response.headers["Location"] = str(include_pattern.id)
|
||||
return include_pattern
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except RegexError:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{include_pattern_id}",
|
||||
# TODO response_model,
|
||||
operation_id="get_include_pattern",
|
||||
)
|
||||
async def include_pattern_read(
|
||||
include_pattern: IncludePattern = Depends(get_include_pattern),
|
||||
):
|
||||
"""
|
||||
Reads include_pattern with id **include_pattern_id** for hood with id **hood_id**.
|
||||
"""
|
||||
return include_pattern
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{include_pattern_id}",
|
||||
operation_id="update_include_pattern",
|
||||
)
|
||||
async def include_pattern_update(
|
||||
values: BodyIncludePattern,
|
||||
include_pattern: IncludePattern = Depends(get_include_pattern),
|
||||
):
|
||||
"""
|
||||
Updates include_pattern with id **include_pattern_id** for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a include_pattern
|
||||
"""
|
||||
await IncludePattern.filter(id=include_pattern).update(**values.__dict__)
|
||||
return include_pattern
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{include_pattern_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="delete_include_pattern",
|
||||
)
|
||||
async def include_pattern_delete(
|
||||
include_pattern: IncludePattern = Depends(get_include_pattern),
|
||||
):
|
||||
"""
|
||||
Deletes include_pattern with id **include_pattern_id** for hood with id **hood_id**.
|
||||
"""
|
||||
await include_pattern.delete()
|
107
backend/src/kibicara/webapi/hoods/triggers.py
Normal file
107
backend/src/kibicara/webapi/hoods/triggers.py
Normal file
|
@ -0,0 +1,107 @@
|
|||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
"""REST API endpoints for managing triggers.
|
||||
|
||||
Provides API endpoints for adding, removing and reading regular expressions that allow a
|
||||
message to be passed through by a censor. A published message must match one of these
|
||||
regular expressions otherwise it gets dropped by the censor. This provides a message
|
||||
filter customizable by the hood admins.
|
||||
"""
|
||||
|
||||
from re import compile as regex_compile
|
||||
from re import error as RegexError
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel
|
||||
|
||||
from kibicara.model import Trigger
|
||||
from kibicara.webapi.hoods import get_hood
|
||||
|
||||
|
||||
class BodyTrigger(BaseModel):
|
||||
pattern: str
|
||||
|
||||
|
||||
async def get_trigger(trigger_id: int, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await Trigger.objects.get(id=trigger_id, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id="get_triggers",
|
||||
)
|
||||
async def trigger_read_all(hood=Depends(get_hood)):
|
||||
"""Get all triggers of hood with id **hood_id**."""
|
||||
return await Trigger.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id="create_trigger",
|
||||
)
|
||||
async def trigger_create(
|
||||
values: BodyTrigger, response: Response, hood=Depends(get_hood)
|
||||
):
|
||||
"""Creates a new trigger for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a trigger.
|
||||
"""
|
||||
try:
|
||||
regex_compile(values.pattern)
|
||||
trigger = await Trigger.objects.create(hood=hood, **values.__dict__)
|
||||
response.headers["Location"] = str(trigger.id)
|
||||
return trigger
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
except RegexError:
|
||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{trigger_id}",
|
||||
# TODO response_model,
|
||||
operation_id="get_trigger",
|
||||
)
|
||||
async def trigger_read(trigger=Depends(get_trigger)):
|
||||
"""Reads trigger with id **trigger_id** for hood with id **hood_id**."""
|
||||
return trigger
|
||||
|
||||
|
||||
@router.put(
|
||||
"/{trigger_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="update_trigger",
|
||||
)
|
||||
async def trigger_update(values: BodyTrigger, trigger=Depends(get_trigger)):
|
||||
"""Updates trigger with id **trigger_id** for hood with id **hood_id**.
|
||||
|
||||
- **pattern**: Regular expression which is used to match a trigger
|
||||
"""
|
||||
await trigger.update(**values.__dict__)
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{trigger_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id="delete_trigger",
|
||||
)
|
||||
async def trigger_delete(trigger=Depends(get_trigger)):
|
||||
"""Deletes trigger with id **trigger_id** for hood with id **hood_id**."""
|
||||
await trigger.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
|
@ -1,14 +1,17 @@
|
|||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from kibicara.model import ExcludePattern, Hood, IncludePattern
|
||||
from kibicara.model import AdminHoodRelation, BadWord, Trigger
|
||||
from kibicara.platformapi import Spawner
|
||||
|
||||
|
||||
async def delete_hood(hood: Hood) -> None:
|
||||
async def delete_hood(hood):
|
||||
await Spawner.destroy_hood(hood)
|
||||
await IncludePattern.filter(hood=hood).delete()
|
||||
await ExcludePattern.filter(hood=hood).delete()
|
||||
for trigger in await Trigger.objects.filter(hood=hood).all():
|
||||
await trigger.delete()
|
||||
for badword in await BadWord.objects.filter(hood=hood).all():
|
||||
await badword.delete()
|
||||
for relation in await AdminHoodRelation.objects.filter(hood=hood).all():
|
||||
await relation.delete()
|
||||
await hood.delete()
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Christian Hagenest <c.hagenest@pm.me>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
|
@ -7,43 +7,24 @@
|
|||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import FastAPI, status
|
||||
from httpx import AsyncClient
|
||||
import pytest
|
||||
from tortoise import Tortoise
|
||||
from fastapi.testclient import TestClient
|
||||
from pytest import fixture
|
||||
|
||||
from kibicara import email
|
||||
from kibicara.model import Mapping
|
||||
from kibicara.webapi import router
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def anyio_backend():
|
||||
return "asyncio"
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.mark.anyio
|
||||
async def client():
|
||||
await Tortoise.init(
|
||||
db_url="sqlite://:memory:",
|
||||
modules={
|
||||
"models": [
|
||||
"kibicara.model",
|
||||
"kibicara.platforms.email.model",
|
||||
"kibicara.platforms.mastodon.model",
|
||||
"kibicara.platforms.telegram.model",
|
||||
"kibicara.platforms.test.model",
|
||||
"kibicara.platforms.twitter.model",
|
||||
]
|
||||
},
|
||||
)
|
||||
await Tortoise.generate_schemas()
|
||||
@fixture(scope="module")
|
||||
def client():
|
||||
Mapping.drop_all()
|
||||
Mapping.create_all()
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix="/api")
|
||||
yield AsyncClient(app=app, base_url="http://test")
|
||||
await Tortoise.close_connections()
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@fixture(scope="module")
|
||||
def monkeymodule():
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
@ -52,7 +33,7 @@ def monkeymodule():
|
|||
mpatch.undo()
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@fixture(scope="module")
|
||||
def receive_email(monkeymodule):
|
||||
mailbox = []
|
||||
|
||||
|
@ -66,54 +47,47 @@ def receive_email(monkeymodule):
|
|||
return mock_receive_email
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.mark.anyio
|
||||
async def register_token(client, receive_email):
|
||||
response = await client.post(
|
||||
@fixture(scope="module")
|
||||
def register_token(client, receive_email):
|
||||
response = client.post(
|
||||
"/api/admin/register/", json={"email": "user", "password": "password"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
return urlparse(receive_email()["body"]).query.split("=", 1)[1]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.mark.anyio
|
||||
async def register_confirmed(client, register_token):
|
||||
response = await client.post("/api/admin/confirm/{0}".format(register_token))
|
||||
@fixture(scope="module")
|
||||
def register_confirmed(client, register_token):
|
||||
response = client.post("/api/admin/confirm/{0}".format(register_token))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@pytest.mark.anyio
|
||||
async def access_token(client, register_confirmed):
|
||||
response = await client.post(
|
||||
@fixture(scope="module")
|
||||
def access_token(client, register_confirmed):
|
||||
response = client.post(
|
||||
"/api/admin/login/", data={"username": "user", "password": "password"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
return response.json()["access_token"]
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@fixture(scope="module")
|
||||
def auth_header(access_token):
|
||||
return {"Authorization": "Bearer {0}".format(access_token)}
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def hood_id(client, auth_header):
|
||||
response = await client.post(
|
||||
"/api/hoods/", json={"name": "hood"}, headers=auth_header
|
||||
)
|
||||
@fixture(scope="function")
|
||||
def hood_id(client, auth_header):
|
||||
response = client.post("/api/hoods/", json={"name": "hood"}, headers=auth_header)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
hood_id = int(response.headers["Location"])
|
||||
yield hood_id
|
||||
await client.delete("/api/hoods/{0}".format(hood_id), headers=auth_header)
|
||||
client.delete("/api/hoods/{0}".format(hood_id), headers=auth_header)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def trigger_id(client, hood_id, auth_header):
|
||||
response = await client.post(
|
||||
@fixture(scope="function")
|
||||
def trigger_id(client, hood_id, auth_header):
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/triggers/".format(hood_id),
|
||||
json={"pattern": "test"},
|
||||
headers=auth_header,
|
||||
|
@ -121,15 +95,14 @@ async def trigger_id(client, hood_id, auth_header):
|
|||
assert response.status_code == status.HTTP_201_CREATED
|
||||
trigger_id = int(response.headers["Location"])
|
||||
yield trigger_id
|
||||
await client.delete(
|
||||
client.delete(
|
||||
"/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id), headers=auth_header
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def badword_id(client, hood_id, auth_header):
|
||||
response = await client.post(
|
||||
@fixture(scope="function")
|
||||
def badword_id(client, hood_id, auth_header):
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/badwords/".format(hood_id),
|
||||
json={"pattern": ""},
|
||||
headers=auth_header,
|
||||
|
@ -137,20 +110,19 @@ async def badword_id(client, hood_id, auth_header):
|
|||
assert response.status_code == status.HTTP_201_CREATED
|
||||
badword_id = int(response.headers["Location"])
|
||||
yield badword_id
|
||||
await client.delete(
|
||||
client.delete(
|
||||
"/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id), headers=auth_header
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def test_id(client, hood_id, auth_header):
|
||||
response = await client.post(
|
||||
@fixture(scope="function")
|
||||
def test_id(client, hood_id, auth_header):
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/test/".format(hood_id), json={}, headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
test_id = int(response.headers["Location"])
|
||||
yield test_id
|
||||
await client.delete(
|
||||
client.delete(
|
||||
"/api/hoods/{0}/test/{1}".format(hood_id, test_id), headers=auth_header
|
||||
)
|
||||
|
|
|
@ -3,16 +3,13 @@
|
|||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hoods_unauthorized(client):
|
||||
response = await client.get("/api/admin/hoods/")
|
||||
def test_hoods_unauthorized(client):
|
||||
response = client.get("/api/admin/hoods/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hoods_success(client, auth_header):
|
||||
response = await client.get("/api/admin/hoods/", headers=auth_header)
|
||||
def test_hoods_success(client, auth_header):
|
||||
response = client.get("/api/admin/hoods/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
|
|
@ -3,107 +3,80 @@
|
|||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
import pytest
|
||||
|
||||
from fastapi import status
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hood_read_all(client):
|
||||
response = await client.get("/api/hoods/")
|
||||
def test_hood_read_all(client):
|
||||
response = client.get("/api/hoods/")
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hood_create_unauthorized(client, hood_id):
|
||||
response = await client.post("/api/hoods/")
|
||||
def test_hood_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hood_read(client, hood_id):
|
||||
response = await client.get("/api/hoods/{0}".format(hood_id))
|
||||
def test_hood_read(client, hood_id):
|
||||
response = client.get("/api/hoods/{0}".format(hood_id))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hood_update_unauthorized(client, hood_id):
|
||||
response = await client.put("/api/hoods/{0}".format(hood_id))
|
||||
def test_hood_update_unauthorized(client, hood_id):
|
||||
response = client.put("/api/hoods/{0}".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_hood_delete_unauthorized(client, hood_id):
|
||||
response = await client.delete("/api/hoods/{0}".format(hood_id))
|
||||
def test_hood_delete_unauthorized(client, hood_id):
|
||||
response = client.delete("/api/hoods/{0}".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_trigger_read_all_unauthorized(client, hood_id):
|
||||
response = await client.get("/api/hoods/{0}/triggers/".format(hood_id))
|
||||
def test_trigger_read_all_unauthorized(client, hood_id):
|
||||
response = client.get("/api/hoods/{0}/triggers/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_trigger_create_unauthorized(client, hood_id):
|
||||
response = await client.post("/api/hoods/{0}/triggers/".format(hood_id))
|
||||
def test_trigger_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/{0}/triggers/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_trigger_read_unauthorized(client, hood_id, trigger_id):
|
||||
response = await client.get(
|
||||
"/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id)
|
||||
)
|
||||
def test_trigger_read_unauthorized(client, hood_id, trigger_id):
|
||||
response = client.get("/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_trigger_update_unauthorized(client, hood_id, trigger_id):
|
||||
response = await client.put(
|
||||
"/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id)
|
||||
)
|
||||
def test_trigger_update_unauthorized(client, hood_id, trigger_id):
|
||||
response = client.put("/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_trigger_delete_unauthorized(client, hood_id, trigger_id):
|
||||
response = await client.delete(
|
||||
"/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id)
|
||||
)
|
||||
def test_trigger_delete_unauthorized(client, hood_id, trigger_id):
|
||||
response = client.delete("/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_badword_read_all_unauthorized(client, hood_id):
|
||||
response = await client.get("/api/hoods/{0}/badwords/".format(hood_id))
|
||||
def test_badword_read_all_unauthorized(client, hood_id):
|
||||
response = client.get("/api/hoods/{0}/badwords/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_badword_create_unauthorized(client, hood_id):
|
||||
response = await client.post("/api/hoods/{0}/badwords/".format(hood_id))
|
||||
def test_badword_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/{0}/badwords/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_badword_read_unauthorized(client, hood_id, badword_id):
|
||||
response = await client.get(
|
||||
"/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id)
|
||||
)
|
||||
def test_badword_read_unauthorized(client, hood_id, badword_id):
|
||||
response = client.get("/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_badword_update_unauthorized(client, hood_id, badword_id):
|
||||
response = await client.put(
|
||||
"/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id)
|
||||
)
|
||||
def test_badword_update_unauthorized(client, hood_id, badword_id):
|
||||
response = client.put("/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_badword_delete_unauthorized(client, hood_id, badword_id):
|
||||
response = await client.delete(
|
||||
"/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id)
|
||||
)
|
||||
def test_badword_delete_unauthorized(client, hood_id, badword_id):
|
||||
response = client.delete("/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -6,13 +6,12 @@
|
|||
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from pytest import fixture
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def email_row(client, hood_id, auth_header):
|
||||
response = await client.post(
|
||||
@fixture(scope="function")
|
||||
def email_row(client, hood_id, auth_header):
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/email/".format(hood_id),
|
||||
json={"name": "kibicara-test"},
|
||||
headers=auth_header,
|
||||
|
@ -20,6 +19,6 @@ async def email_row(client, hood_id, auth_header):
|
|||
assert response.status_code == status.HTTP_201_CREATED
|
||||
email_id = int(response.headers["Location"])
|
||||
yield response.json()
|
||||
await client.delete(
|
||||
client.delete(
|
||||
"/api/hoods/{0}/email/{1}".format(hood_id, email_id), headers=auth_header
|
||||
)
|
||||
|
|
|
@ -8,14 +8,13 @@ from re import findall
|
|||
from urllib.parse import urlparse
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from pytest import skip
|
||||
|
||||
from kibicara.webapi.admin import to_token
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_subscribe_unsubscribe(client, hood_id, receive_email):
|
||||
response = await client.post(
|
||||
def test_email_subscribe_unsubscribe(client, hood_id, receive_email):
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/email/subscribe/".format(hood_id),
|
||||
json={"email": "test@localhost"},
|
||||
)
|
||||
|
@ -28,41 +27,37 @@ async def test_email_subscribe_unsubscribe(client, hood_id, receive_email):
|
|||
body,
|
||||
)[0]
|
||||
start = len("token=")
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/email/subscribe/confirm/{1}".format(
|
||||
hood_id, urlparse(confirm_url).query[start:]
|
||||
)
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/email/subscribe/confirm/{1}".format(
|
||||
hood_id, urlparse(confirm_url).query[start:]
|
||||
)
|
||||
)
|
||||
assert response.status_code == status.HTTP_409_CONFLICT
|
||||
token = to_token(email=mail["to"], hood=hood_id)
|
||||
response = await client.delete(
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/email/unsubscribe/{1}".format(hood_id, token)
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_message(client, hood_id, trigger_id, email_row):
|
||||
def test_email_message(client, hood_id, trigger_id, email_row):
|
||||
body = {
|
||||
"text": "test",
|
||||
"author": "test@localhost",
|
||||
"secret": email_row["secret"],
|
||||
}
|
||||
response = await client.post(
|
||||
"/api/hoods/{0}/email/messages/".format(hood_id), json=body
|
||||
)
|
||||
response = client.post("/api/hoods/{0}/email/messages/".format(hood_id), json=body)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_send_mda(trigger_id, email_row):
|
||||
pytest.skip("Only works if kibicara is listening on port 8000, and only sometimes")
|
||||
def test_email_send_mda(trigger_id, email_row):
|
||||
skip("Only works if kibicara is listening on port 8000, and only sometimes")
|
||||
mail = """From test@example.com Tue Jun 16 15:33:19 2020
|
||||
Return-path: <test@example.com>
|
||||
Envelope-to: hood@localhost
|
||||
|
|
|
@ -5,27 +5,21 @@
|
|||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_create_unauthorized(client, hood_id):
|
||||
response = await client.post("/api/hoods/{0}/email/".format(hood_id))
|
||||
def test_email_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/{0}/email/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_delete_unauthorized(client, hood_id, email_row):
|
||||
response = await client.delete(
|
||||
def test_email_delete_unauthorized(client, hood_id, email_row):
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/email/{1}".format(hood_id, email_row["id"])
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_message_unauthorized(client, hood_id, email_row):
|
||||
def test_email_message_unauthorized(client, hood_id, email_row):
|
||||
body = {"text": "test", "author": "author", "secret": "wrong"}
|
||||
response = await client.post(
|
||||
"/api/hoods/{0}/email/messages/".format(hood_id), json=body
|
||||
)
|
||||
response = client.post("/api/hoods/{0}/email/messages/".format(hood_id), json=body)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -5,19 +5,16 @@
|
|||
|
||||
from fastapi import status
|
||||
from nacl.exceptions import CryptoError
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_subscribe_empty(client, hood_id):
|
||||
response = await client.post("/api/hoods/{0}/email/subscribe/".format(hood_id))
|
||||
def test_email_subscribe_empty(client, hood_id):
|
||||
response = client.post("/api/hoods/{0}/email/subscribe/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_subscribe_confirm_wrong_token(client, hood_id):
|
||||
def test_email_subscribe_confirm_wrong_token(client, hood_id):
|
||||
try:
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/email/subscribe/confirm/".format(hood_id)
|
||||
+ "asdfasdfasdfasdfasdfasdfasdfasdf"
|
||||
)
|
||||
|
@ -26,31 +23,26 @@ async def test_email_subscribe_confirm_wrong_token(client, hood_id):
|
|||
pass
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_subscribe_confirm_wrong_hood(client):
|
||||
response = await client.delete(
|
||||
def test_email_subscribe_confirm_wrong_hood(client):
|
||||
response = client.delete(
|
||||
"/api/hoods/99999/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf"
|
||||
)
|
||||
assert response.json()["detail"] == "Not Found"
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_message_wrong(client, hood_id, email_row):
|
||||
def test_email_message_wrong(client, hood_id, email_row):
|
||||
body = {
|
||||
"text": "",
|
||||
"author": "test@localhost",
|
||||
"secret": email_row["secret"],
|
||||
}
|
||||
response = await client.post(
|
||||
"/api/hoods/{0}/email/messages/".format(hood_id), json=body
|
||||
)
|
||||
response = client.post("/api/hoods/{0}/email/messages/".format(hood_id), json=body)
|
||||
assert response.status_code == status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_unsubscribe_wrong_token(client, hood_id):
|
||||
def test_email_unsubscribe_wrong_token(client, hood_id):
|
||||
try:
|
||||
await client.delete(
|
||||
client.delete(
|
||||
"/api/hoods/{0}/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf".format(
|
||||
hood_id
|
||||
)
|
||||
|
@ -59,9 +51,8 @@ async def test_email_unsubscribe_wrong_token(client, hood_id):
|
|||
pass
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_email_unsubscribe_wrong_hood(client):
|
||||
response = await client.delete(
|
||||
def test_email_unsubscribe_wrong_hood(client):
|
||||
response = client.delete(
|
||||
"/api/hoods/99999/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf"
|
||||
)
|
||||
assert response.json()["detail"] == "Not Found"
|
||||
|
|
|
@ -1,33 +1,34 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
import pytest
|
||||
from pytest import fixture
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount, MastodonInstance
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def mastodon_instance():
|
||||
return await MastodonInstance.create(
|
||||
name="inst4nce",
|
||||
client_id="cl13nt_id",
|
||||
client_secret="cl13nt_s3cr3t",
|
||||
@fixture(scope="function")
|
||||
def mastodon_instance(event_loop):
|
||||
return event_loop.run_until_complete(
|
||||
MastodonInstance.objects.create(
|
||||
name="inst4nce",
|
||||
client_id="cl13nt_id",
|
||||
client_secret="cl13nt_s3cr3t",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def mastodon_account(hood_id, mastodon_instance):
|
||||
hood = await Hood.get(id=hood_id)
|
||||
return await MastodonAccount.create(
|
||||
hood=hood,
|
||||
instance=mastodon_instance,
|
||||
access_token="t0k3n",
|
||||
enabled=True,
|
||||
username="us3r",
|
||||
@fixture(scope="function")
|
||||
def mastodon_account(event_loop, hood_id, mastodon_instance):
|
||||
hood = event_loop.run_until_complete(Hood.objects.get(id=hood_id))
|
||||
return event_loop.run_until_complete(
|
||||
MastodonAccount.objects.create(
|
||||
hood=hood,
|
||||
instance=mastodon_instance,
|
||||
access_token="t0k3n",
|
||||
enabled=True,
|
||||
username="us3r",
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,18 +1,17 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from pytest import fixture, mark
|
||||
from mastodon.Mastodon import Mastodon
|
||||
|
||||
from kibicara.platforms import mastodon
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount, MastodonInstance
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@fixture(scope="function")
|
||||
def disable_spawner(monkeypatch):
|
||||
class DoNothing:
|
||||
def start(self, bot):
|
||||
|
@ -21,7 +20,7 @@ def disable_spawner(monkeypatch):
|
|||
monkeypatch.setattr(mastodon.webapi, "spawner", DoNothing())
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@mark.parametrize(
|
||||
"body",
|
||||
[
|
||||
{
|
||||
|
@ -31,8 +30,8 @@ def disable_spawner(monkeypatch):
|
|||
}
|
||||
],
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_create_bot(
|
||||
def test_mastodon_create_bot(
|
||||
event_loop,
|
||||
client,
|
||||
disable_spawner,
|
||||
hood_id,
|
||||
|
@ -45,7 +44,7 @@ async def test_mastodon_create_bot(
|
|||
|
||||
monkeypatch.setattr(Mastodon, "log_in", log_in_mock)
|
||||
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/mastodon/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
|
@ -53,20 +52,33 @@ async def test_mastodon_create_bot(
|
|||
print(response.json())
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
bot_id = response.json()["id"]
|
||||
mastodon_obj = await MastodonAccount.get(id=bot_id)
|
||||
mastodon_obj = event_loop.run_until_complete(MastodonAccount.objects.get(id=bot_id))
|
||||
assert response.json()["access_token"] == mastodon_obj.access_token
|
||||
mastodon_instance = event_loop.run_until_complete(
|
||||
MastodonInstance.objects.get(id=mastodon_obj.instance.id)
|
||||
)
|
||||
assert (
|
||||
response.json()["instance"]["name"]
|
||||
== body["instance_url"]
|
||||
== mastodon_instance.name
|
||||
)
|
||||
assert response.json()["hood"]["id"] == mastodon_obj.hood.id
|
||||
assert response.json()["instance"]["client_id"] == mastodon_instance.client_id
|
||||
assert (
|
||||
response.json()["instance"]["client_secret"] == mastodon_instance.client_secret
|
||||
)
|
||||
assert mastodon_obj.enabled
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
@mark.parametrize(
|
||||
"body",
|
||||
[
|
||||
{"instance_url": "botsin.space", "email": "notanemail", "password": "asdf1234"},
|
||||
{"instance_url": "wrong", "email": "asdf@example.org", "password": "asdf1234"},
|
||||
],
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_invalid_input(
|
||||
def test_mastodon_invalid_input(
|
||||
event_loop,
|
||||
client,
|
||||
disable_spawner,
|
||||
hood_id,
|
||||
|
@ -74,7 +86,7 @@ async def test_mastodon_invalid_input(
|
|||
monkeypatch,
|
||||
body,
|
||||
):
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/mastodon/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
|
@ -82,15 +94,13 @@ async def test_mastodon_invalid_input(
|
|||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_create_mastodon_invalid_id(client, auth_header):
|
||||
response = await client.post("/api/hoods/1337/mastodon/", headers=auth_header)
|
||||
def test_mastodon_create_mastodon_invalid_id(client, auth_header):
|
||||
response = client.post("/api/hoods/1337/mastodon/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.post("/api/hoods/wrong/mastodon/", headers=auth_header)
|
||||
response = client.post("/api/hoods/wrong/mastodon/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_create_unauthorized(client, hood_id):
|
||||
response = await client.post("/api/hoods/{hood_id}/mastodon/")
|
||||
def test_mastodon_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/{hood_id}/mastodon/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -1,48 +1,46 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from tortoise.exceptions import DoesNotExist
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pytest import raises
|
||||
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_delete_bot(client, mastodon_account, auth_header):
|
||||
response = await client.delete(
|
||||
def test_mastodon_delete_bot(client, event_loop, mastodon_account, auth_header):
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/mastodon/{1}".format(
|
||||
mastodon_account.hood.id, mastodon_account.id
|
||||
),
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
with pytest.raises(DoesNotExist):
|
||||
await MastodonAccount.get(id=mastodon_account.id)
|
||||
with raises(NoMatch):
|
||||
event_loop.run_until_complete(
|
||||
MastodonAccount.objects.get(id=mastodon_account.id)
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_delete_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = await client.delete("/api/hoods/1337/mastodon/123", headers=auth_header)
|
||||
def test_mastodon_delete_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = client.delete("/api/hoods/1337/mastodon/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.delete("/api/hoods/wrong/mastodon/123", headers=auth_header)
|
||||
response = client.delete("/api/hoods/wrong/mastodon/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
response = await client.delete(
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/mastodon/7331".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.delete(
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/mastodon/wrong".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_delete_bot_unauthorized(client, mastodon_account):
|
||||
response = await client.delete(
|
||||
def test_mastodon_delete_bot_unauthorized(client, mastodon_account):
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/mastodon/{1}".format(
|
||||
mastodon_account.hood.id, mastodon_account.id
|
||||
)
|
||||
|
|
|
@ -1,30 +1,28 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_get_bots(
|
||||
client, auth_header, hood_id, mastodon_account, mastodon_instance
|
||||
def test_mastodon_get_bots(
|
||||
client, auth_header, event_loop, hood_id, mastodon_account, mastodon_instance
|
||||
):
|
||||
mastodon2 = await MastodonAccount.create(
|
||||
hood=mastodon_account.hood,
|
||||
instance=mastodon_instance,
|
||||
access_token="4cc3ss",
|
||||
enabled=True,
|
||||
username="us4r",
|
||||
mastodon2 = event_loop.run_until_complete(
|
||||
MastodonAccount.objects.create(
|
||||
hood=mastodon_account.hood,
|
||||
instance=mastodon_instance,
|
||||
access_token="4cc3ss",
|
||||
enabled=True,
|
||||
username="us4r",
|
||||
)
|
||||
)
|
||||
response = await client.get(
|
||||
"/api/hoods/{0}/mastodon/".format(mastodon_account.hood.id), headers=auth_header
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/mastodon".format(mastodon_account.hood.id), headers=auth_header
|
||||
)
|
||||
print(response.headers)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()[0]["id"] == mastodon_account.id
|
||||
assert response.json()[0]["access_token"] == mastodon_account.access_token
|
||||
|
@ -32,23 +30,20 @@ async def test_mastodon_get_bots(
|
|||
assert response.json()[1]["access_token"] == mastodon2.access_token
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_get_bots_invalid_id(client, auth_header, hood_id):
|
||||
response = await client.get("/api/hoods/1337/mastodon/", headers=auth_header)
|
||||
def test_mastodon_get_bots_invalid_id(client, auth_header, hood_id):
|
||||
response = client.get("/api/hoods/1337/mastodon", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.get("/api/hoods/wrong/mastodon/", headers=auth_header)
|
||||
response = client.get("/api/hoods/wrong/mastodon", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_get_bots_unauthorized(client, hood_id):
|
||||
response = await client.get("/api/hoods/{0}/mastodon/".format(hood_id))
|
||||
def test_mastodon_get_bots_unauthorized(client, hood_id):
|
||||
response = client.get("/api/hoods/{0}/mastodon".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_mastodon_public(client, mastodon_account, mastodon_instance):
|
||||
response = await client.get(
|
||||
def test_mastodon_public(client, mastodon_account, mastodon_instance, event_loop):
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/mastodon/public".format(mastodon_account.hood.id)
|
||||
)
|
||||
assert response.json()[0]["username"] == mastodon_account.username
|
||||
|
|
|
@ -1,21 +1,21 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
import pytest
|
||||
from pytest import fixture
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platforms.telegram.model import Telegram
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@pytest.mark.anyio
|
||||
async def telegram(hood_id, bot):
|
||||
hood = await Hood.get(id=hood_id)
|
||||
return await Telegram.create(
|
||||
hood=hood,
|
||||
api_token=bot["api_token"],
|
||||
welcome_message=bot["welcome_message"],
|
||||
@fixture(scope="function")
|
||||
def telegram(event_loop, hood_id, bot):
|
||||
hood = event_loop.run_until_complete(Hood.objects.get(id=hood_id))
|
||||
return event_loop.run_until_complete(
|
||||
Telegram.objects.create(
|
||||
hood=hood,
|
||||
api_token=bot["api_token"],
|
||||
welcome_message=bot["welcome_message"],
|
||||
)
|
||||
)
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from pytest import fixture, mark
|
||||
|
||||
from kibicara.platforms import telegram
|
||||
from kibicara.platforms.telegram.model import Telegram
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
@fixture(scope="function")
|
||||
def disable_spawner(monkeypatch):
|
||||
class DoNothing:
|
||||
def start(self, bot):
|
||||
|
@ -20,9 +19,9 @@ def disable_spawner(monkeypatch):
|
|||
monkeypatch.setattr(telegram.webapi, "spawner", DoNothing())
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", [{"api_token": "string", "welcome_message": "string"}])
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_create_bot(
|
||||
@mark.parametrize("body", [{"api_token": "string", "welcome_message": "string"}])
|
||||
def test_telegram_create_bot(
|
||||
event_loop,
|
||||
client,
|
||||
disable_spawner,
|
||||
hood_id,
|
||||
|
@ -35,26 +34,27 @@ async def test_telegram_create_bot(
|
|||
|
||||
monkeypatch.setattr(telegram.webapi, "check_token", check_token_mock)
|
||||
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/telegram/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
bot_id = response.json()["id"]
|
||||
telegram_obj = await Telegram.get(id=bot_id)
|
||||
telegram_obj = event_loop.run_until_complete(Telegram.objects.get(id=bot_id))
|
||||
assert response.json()["api_token"] == body["api_token"] == telegram_obj.api_token
|
||||
assert (
|
||||
response.json()["welcome_message"]
|
||||
== body["welcome_message"]
|
||||
== telegram_obj.welcome_message
|
||||
)
|
||||
assert response.json()["hood"]["id"] == telegram_obj.hood.id
|
||||
assert telegram_obj.enabled
|
||||
|
||||
|
||||
@pytest.mark.parametrize("body", [{"api_token": "string", "welcome_message": "string"}])
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_invalid_api_token(
|
||||
@mark.parametrize("body", [{"api_token": "string", "welcome_message": "string"}])
|
||||
def test_telegram_invalid_api_token(
|
||||
event_loop,
|
||||
client,
|
||||
disable_spawner,
|
||||
hood_id,
|
||||
|
@ -62,7 +62,7 @@ async def test_telegram_invalid_api_token(
|
|||
monkeypatch,
|
||||
body,
|
||||
):
|
||||
response = await client.post(
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/telegram/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
|
@ -70,15 +70,13 @@ async def test_telegram_invalid_api_token(
|
|||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_create_telegram_invalid_id(client, auth_header):
|
||||
response = await client.post("/api/hoods/1337/telegram/", headers=auth_header)
|
||||
def test_telegram_create_telegram_invalid_id(client, auth_header):
|
||||
response = client.post("/api/hoods/1337/telegram/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.post("/api/hoods/wrong/telegram/", headers=auth_header)
|
||||
response = client.post("/api/hoods/wrong/telegram/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_create_unauthorized(client, hood_id):
|
||||
response = await client.post("/api/hoods/{hood_id}/telegram/")
|
||||
def test_telegram_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/{hood_id}/telegram/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -1,56 +1,52 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from tortoise.exceptions import DoesNotExist
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pytest import mark, raises
|
||||
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramSubscriber
|
||||
from kibicara.platforms.telegram.model import Telegram, TelegramUser
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bot", [{"api_token": "apitoken123", "welcome_message": "msg"}]
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_delete_bot(client, bot, telegram, auth_header):
|
||||
await TelegramSubscriber.create(user_id=1234, bot=telegram)
|
||||
await TelegramSubscriber.create(user_id=5678, bot=telegram)
|
||||
response = await client.delete(
|
||||
@mark.parametrize("bot", [{"api_token": "apitoken123", "welcome_message": "msg"}])
|
||||
def test_telegram_delete_bot(client, event_loop, bot, telegram, auth_header):
|
||||
event_loop.run_until_complete(
|
||||
TelegramUser.objects.create(user_id=1234, bot=telegram.id)
|
||||
)
|
||||
event_loop.run_until_complete(
|
||||
TelegramUser.objects.create(user_id=5678, bot=telegram.id)
|
||||
)
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id),
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
with pytest.raises(DoesNotExist):
|
||||
await Telegram.get(id=telegram.id)
|
||||
with pytest.raises(DoesNotExist):
|
||||
await TelegramSubscriber.get(id=telegram.id)
|
||||
with raises(NoMatch):
|
||||
event_loop.run_until_complete(Telegram.objects.get(id=telegram.id))
|
||||
with raises(NoMatch):
|
||||
event_loop.run_until_complete(TelegramUser.objects.get(id=telegram.id))
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_delete_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = await client.delete("/api/hoods/1337/telegram/123", headers=auth_header)
|
||||
def test_telegram_delete_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = client.delete("/api/hoods/1337/telegram/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.delete("/api/hoods/wrong/telegram/123", headers=auth_header)
|
||||
response = client.delete("/api/hoods/wrong/telegram/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
response = await client.delete(
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/telegram/7331".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.delete(
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/telegram/wrong".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bot", [{"api_token": "apitoken123", "welcome_message": "msg"}]
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_delete_bot_unauthorized(client, bot, telegram):
|
||||
response = await client.delete(
|
||||
@mark.parametrize("bot", [{"api_token": "apitoken123", "welcome_message": "msg"}])
|
||||
def test_telegram_delete_bot_unauthorized(client, bot, telegram):
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id)
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -4,15 +4,12 @@
|
|||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
from pytest import mark
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bot", [{"api_token": "apitoken123", "welcome_message": "msg"}]
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_get_bot(client, auth_header, bot, telegram):
|
||||
response = await client.get(
|
||||
@mark.parametrize("bot", [{"api_token": "apitoken123", "welcome_message": "msg"}])
|
||||
def test_telegram_get_bot(client, auth_header, event_loop, bot, telegram):
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id),
|
||||
headers=auth_header,
|
||||
)
|
||||
|
@ -22,28 +19,24 @@ async def test_telegram_get_bot(client, auth_header, bot, telegram):
|
|||
assert response.json()["welcome_message"] == telegram.welcome_message
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_get_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = await client.get("/api/hoods/1337/telegram/123", headers=auth_header)
|
||||
def test_telegram_get_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = client.get("/api/hoods/1337/telegram/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.get("/api/hoods/wrong/telegram/123", headers=auth_header)
|
||||
response = client.get("/api/hoods/wrong/telegram/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
response = await client.get(
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/telegram/7331".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.get(
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/telegram/wrong".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"bot", [{"api_token": "apitoken456", "welcome_message": "msg"}]
|
||||
)
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_get_bot_unauthorized(client, bot, telegram):
|
||||
response = await client.get(
|
||||
@mark.parametrize("bot", [{"api_token": "apitoken456", "welcome_message": "msg"}])
|
||||
def test_telegram_get_bot_unauthorized(client, bot, telegram):
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id)
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -1,31 +1,32 @@
|
|||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
# Copyright (C) 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from fastapi import status
|
||||
import pytest
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platforms.telegram.model import Telegram
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_get_bots(client, auth_header, hood_id):
|
||||
hood = await Hood.get(id=hood_id)
|
||||
telegram0 = await Telegram.create(
|
||||
hood=hood,
|
||||
api_token="api_token123",
|
||||
welcome_message="welcome_message123",
|
||||
def test_telegram_get_bots(client, auth_header, event_loop, hood_id):
|
||||
hood = event_loop.run_until_complete(Hood.objects.get(id=hood_id))
|
||||
telegram0 = event_loop.run_until_complete(
|
||||
Telegram.objects.create(
|
||||
hood=hood,
|
||||
api_token="api_token123",
|
||||
welcome_message="welcome_message123",
|
||||
)
|
||||
)
|
||||
telegram1 = await Telegram.create(
|
||||
hood=hood,
|
||||
api_token="api_token456",
|
||||
welcome_message="welcome_message123",
|
||||
telegram1 = event_loop.run_until_complete(
|
||||
Telegram.objects.create(
|
||||
hood=hood,
|
||||
api_token="api_token456",
|
||||
welcome_message="welcome_message123",
|
||||
)
|
||||
)
|
||||
response = await client.get(
|
||||
"/api/hoods/{0}/telegram/".format(telegram0.hood.id), headers=auth_header
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/telegram".format(telegram0.hood.id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()[0]["id"] == telegram0.id
|
||||
|
@ -34,15 +35,13 @@ async def test_telegram_get_bots(client, auth_header, hood_id):
|
|||
assert response.json()[1]["api_token"] == telegram1.api_token
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_get_bots_invalid_id(client, auth_header, hood_id):
|
||||
response = await client.get("/api/hoods/1337/telegram/", headers=auth_header)
|
||||
def test_telegram_get_bots_invalid_id(client, auth_header, hood_id):
|
||||
response = client.get("/api/hoods/1337/telegram", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = await client.get("/api/hoods/wrong/telegram/", headers=auth_header)
|
||||
response = client.get("/api/hoods/wrong/telegram", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_telegram_get_bots_unauthorized(client, hood_id):
|
||||
response = await client.get("/api/hoods/{0}/telegram/".format(hood_id))
|
||||
def test_telegram_get_bots_unauthorized(client, hood_id):
|
||||
response = client.get("/api/hoods/{0}/telegram".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
@ -10,7 +10,6 @@ import { MastodonService } from './api/mastodon.service';
|
|||
import { TelegramService } from './api/telegram.service';
|
||||
import { TestService } from './api/test.service';
|
||||
import { TriggersService } from './api/triggers.service';
|
||||
import { TwitterService } from './api/twitter.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
|
|
|
@ -14,6 +14,4 @@ export * from './test.service';
|
|||
import { TestService } from './test.service';
|
||||
export * from './triggers.service';
|
||||
import { TriggersService } from './triggers.service';
|
||||
export * from './twitter.service';
|
||||
import { TwitterService } from './twitter.service';
|
||||
export const APIS = [AdminService, BadwordsService, EmailService, HoodsService, MastodonService, TelegramService, TestService, TriggersService, TwitterService];
|
||||
export const APIS = [AdminService, BadwordsService, EmailService, HoodsService, MastodonService, TelegramService, TestService, TriggersService];
|
||||
|
|
|
@ -1,595 +0,0 @@
|
|||
/**
|
||||
* FastAPI
|
||||
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||
*
|
||||
* The version of the OpenAPI document: 0.1.0
|
||||
*
|
||||
*
|
||||
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||
* https://openapi-generator.tech
|
||||
* Do not edit the class manually.
|
||||
*/
|
||||
/* tslint:disable:no-unused-variable member-ordering */
|
||||
|
||||
import { Inject, Injectable, Optional } from '@angular/core';
|
||||
import { HttpClient, HttpHeaders, HttpParams,
|
||||
HttpResponse, HttpEvent, HttpParameterCodec } from '@angular/common/http';
|
||||
import { CustomHttpParameterCodec } from '../encoder';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { HTTPValidationError } from '../model/models';
|
||||
|
||||
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||
import { Configuration } from '../configuration';
|
||||
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TwitterService {
|
||||
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
||||
constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
|
||||
if (configuration) {
|
||||
this.configuration = configuration;
|
||||
}
|
||||
if (typeof this.configuration.basePath !== 'string') {
|
||||
if (typeof basePath !== 'string') {
|
||||
basePath = this.basePath;
|
||||
}
|
||||
this.configuration.basePath = basePath;
|
||||
}
|
||||
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
|
||||
}
|
||||
|
||||
|
||||
private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
|
||||
if (typeof value === "object" && value instanceof Date === false) {
|
||||
httpParams = this.addToHttpParamsRecursive(httpParams, value);
|
||||
} else {
|
||||
httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
|
||||
}
|
||||
return httpParams;
|
||||
}
|
||||
|
||||
private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
|
||||
if (value == null) {
|
||||
return httpParams;
|
||||
}
|
||||
|
||||
if (typeof value === "object") {
|
||||
if (Array.isArray(value)) {
|
||||
(value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
|
||||
} else if (value instanceof Date) {
|
||||
if (key != null) {
|
||||
httpParams = httpParams.append(key,
|
||||
(value as Date).toISOString().substr(0, 10));
|
||||
} else {
|
||||
throw Error("key may not be null if value is Date");
|
||||
}
|
||||
} else {
|
||||
Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive(
|
||||
httpParams, value[k], key != null ? `${key}.${k}` : k));
|
||||
}
|
||||
} else if (key != null) {
|
||||
httpParams = httpParams.append(key, value);
|
||||
} else {
|
||||
throw Error("key may not be null if value is not object or array");
|
||||
}
|
||||
return httpParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Read Callback
|
||||
* @param oauthToken
|
||||
* @param oauthVerifier
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public callbackTwitter(oauthToken: string, oauthVerifier: string, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public callbackTwitter(oauthToken: string, oauthVerifier: string, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public callbackTwitter(oauthToken: string, oauthVerifier: string, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public callbackTwitter(oauthToken: string, oauthVerifier: string, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (oauthToken === null || oauthToken === undefined) {
|
||||
throw new Error('Required parameter oauthToken was null or undefined when calling callbackTwitter.');
|
||||
}
|
||||
if (oauthVerifier === null || oauthVerifier === undefined) {
|
||||
throw new Error('Required parameter oauthVerifier was null or undefined when calling callbackTwitter.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling callbackTwitter.');
|
||||
}
|
||||
|
||||
let queryParameters = new HttpParams({encoder: this.encoder});
|
||||
if (oauthToken !== undefined && oauthToken !== null) {
|
||||
queryParameters = this.addToHttpParams(queryParameters,
|
||||
<any>oauthToken, 'oauth_token');
|
||||
}
|
||||
if (oauthVerifier !== undefined && oauthVerifier !== null) {
|
||||
queryParameters = this.addToHttpParams(queryParameters,
|
||||
<any>oauthVerifier, 'oauth_verifier');
|
||||
}
|
||||
if (hoodId !== undefined && hoodId !== null) {
|
||||
queryParameters = this.addToHttpParams(queryParameters,
|
||||
<any>hoodId, 'hood_id');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/twitter/callback`,
|
||||
{
|
||||
params: queryParameters,
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Create
|
||||
* `https://api.twitter.com/oauth/authorize?oauth_token=`
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public createTwitter(hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public createTwitter(hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public createTwitter(hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public createTwitter(hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling createTwitter.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/`,
|
||||
null,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Delete
|
||||
* @param twitterId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public deleteTwitter(twitterId: number, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public deleteTwitter(twitterId: number, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public deleteTwitter(twitterId: number, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public deleteTwitter(twitterId: number, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (twitterId === null || twitterId === undefined) {
|
||||
throw new Error('Required parameter twitterId was null or undefined when calling deleteTwitter.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling deleteTwitter.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.delete<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/${encodeURIComponent(String(twitterId))}`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Read
|
||||
* @param twitterId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public getTwitter(twitterId: number, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public getTwitter(twitterId: number, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public getTwitter(twitterId: number, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public getTwitter(twitterId: number, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (twitterId === null || twitterId === undefined) {
|
||||
throw new Error('Required parameter twitterId was null or undefined when calling getTwitter.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling getTwitter.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/${encodeURIComponent(String(twitterId))}`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Read All
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public getTwitters(hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public getTwitters(hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public getTwitters(hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public getTwitters(hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling getTwitters.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Read All Public
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public getTwittersPublic(hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public getTwittersPublic(hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public getTwittersPublic(hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public getTwittersPublic(hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling getTwittersPublic.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/public`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Start
|
||||
* @param twitterId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public startTwitter(twitterId: number, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public startTwitter(twitterId: number, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public startTwitter(twitterId: number, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public startTwitter(twitterId: number, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (twitterId === null || twitterId === undefined) {
|
||||
throw new Error('Required parameter twitterId was null or undefined when calling startTwitter.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling startTwitter.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/${encodeURIComponent(String(twitterId))}/start`,
|
||||
null,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Status
|
||||
* @param twitterId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public statusTwitter(twitterId: number, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public statusTwitter(twitterId: number, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public statusTwitter(twitterId: number, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public statusTwitter(twitterId: number, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (twitterId === null || twitterId === undefined) {
|
||||
throw new Error('Required parameter twitterId was null or undefined when calling statusTwitter.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling statusTwitter.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/${encodeURIComponent(String(twitterId))}/status`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Twitter Stop
|
||||
* @param twitterId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
* @param reportProgress flag to report request and response progress.
|
||||
*/
|
||||
public stopTwitter(twitterId: number, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public stopTwitter(twitterId: number, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public stopTwitter(twitterId: number, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public stopTwitter(twitterId: number, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (twitterId === null || twitterId === undefined) {
|
||||
throw new Error('Required parameter twitterId was null or undefined when calling stopTwitter.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling stopTwitter.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
||||
let credential: string | undefined;
|
||||
// authentication (OAuth2PasswordBearer) required
|
||||
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||
if (credential) {
|
||||
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||
}
|
||||
|
||||
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||
if (httpHeaderAcceptSelected === undefined) {
|
||||
// to determine the Accept header
|
||||
const httpHeaderAccepts: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||
}
|
||||
if (httpHeaderAcceptSelected !== undefined) {
|
||||
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||
}
|
||||
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/${encodeURIComponent(String(twitterId))}/stop`,
|
||||
null,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -49,8 +49,8 @@
|
|||
<p>
|
||||
<strong>Share the hood page: </strong> Just send the link to your hood
|
||||
page to your community. We also recommend to link it in your platform
|
||||
account description (e.g. Twitter description) to give the users
|
||||
context and more information.
|
||||
account description (e.g. Mastodon account description) to give the
|
||||
users context and more information.
|
||||
</p>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
|
||||
<div class="platforms-container">
|
||||
<app-email-settings [hoodId]="hoodId"></app-email-settings>
|
||||
<app-twitter-settings [hoodId]="hoodId"></app-twitter-settings>
|
||||
<app-telegram-settings [hoodId]="hoodId"></app-telegram-settings>
|
||||
<app-mastodon-settings [hoodId]="hoodId"></app-mastodon-settings>
|
||||
</div>
|
||||
|
|
|
@ -4,7 +4,6 @@ import { DashboardComponent } from './dashboard.component';
|
|||
import { HoodsComponent } from './hoods/hoods.component';
|
||||
import { AuthGuard } from '../core/auth/auth.guard';
|
||||
import { BoardComponent } from './board/board.component';
|
||||
import { TwitterCallbackComponent } from '../platforms/twitter/twitter-callback/twitter-callback.component';
|
||||
import { AccountSettingsComponent } from './account-settings/account-settings.component';
|
||||
|
||||
const routes: Routes = [
|
||||
|
@ -15,8 +14,6 @@ const routes: Routes = [
|
|||
{ path: '', component: HoodsComponent },
|
||||
{ path: 'hoods/:id', component: BoardComponent },
|
||||
{ path: 'settings', component: AccountSettingsComponent },
|
||||
// Platform-specific Routes
|
||||
{ path: 'twitter-callback', component: TwitterCallbackComponent },
|
||||
],
|
||||
canActivate: [AuthGuard],
|
||||
},
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
<h2>Platforms - choose one, get all hood messages</h2>
|
||||
<div class="container">
|
||||
<app-twitter-bot-card [hoodId]="hoodId"></app-twitter-bot-card>
|
||||
<app-telegram-bot-card [hoodId]="hoodId"></app-telegram-bot-card>
|
||||
<app-email-bot-card [hoodId]="hoodId"></app-email-bot-card>
|
||||
<app-mastodon-bot-card [hoodId]="hoodId"></app-mastodon-bot-card>
|
||||
|
|
|
@ -2,22 +2,16 @@ import { NgModule } from '@angular/core';
|
|||
import { CommonModule } from '@angular/common';
|
||||
import { TelegramSettingsComponent } from './telegram/telegram-settings/telegram-settings.component';
|
||||
import { SharedModule } from '../shared/shared.module';
|
||||
import { TwitterSettingsComponent } from './twitter/twitter-settings/twitter-settings.component';
|
||||
import { EmailSettingsComponent } from './email/email-settings/email-settings.component';
|
||||
import { EmailDialogComponent } from './email/email-settings/email-dialog/email-dialog.component';
|
||||
import { EmailInfoDialogComponent } from './email/email-settings/email-info-dialog/email-info-dialog.component';
|
||||
import { TelegramInfoDialogComponent } from './telegram/telegram-settings/telegram-info-dialog/telegram-info-dialog.component';
|
||||
import { TelegramDialogComponent } from './telegram/telegram-settings/telegram-dialog/telegram-dialog.component';
|
||||
import { TwitterInfoDialogComponent } from './twitter/twitter-settings/twitter-info-dialog/twitter-info-dialog.component';
|
||||
import { TwitterCallbackComponent } from './twitter/twitter-callback/twitter-callback.component';
|
||||
import { TwitterCorpsesPipe } from './twitter/twitter-corpses-pipe/twitter-corpses.pipe';
|
||||
import { PlatformsInfoPageComponent } from './platforms-info-page/platforms-info-page.component';
|
||||
import { EmailBotCardComponent } from './email/email-bot-card/email-bot-card.component';
|
||||
import { TelegramBotCardComponent } from './telegram/telegram-bot-card/telegram-bot-card.component';
|
||||
import { TwitterBotCardComponent } from './twitter/twitter-bot-card/twitter-bot-card.component';
|
||||
import { EmailBotInfoDialogComponent } from './email/email-bot-card/email-bot-info-dialog/email-bot-info-dialog.component';
|
||||
import { TelegramBotInfoDialogComponent } from './telegram/telegram-bot-card/telegram-bot-info-dialog/telegram-bot-info-dialog.component';
|
||||
import { TwitterBotInfoDialogComponent } from './twitter/twitter-bot-card/twitter-bot-info-dialog/twitter-bot-info-dialog.component';
|
||||
import { EmailConfirmationComponent } from './email/email-confirmation/email-confirmation.component';
|
||||
import { EmailUnsubscribeComponent } from './email/email-unsubscribe/email-unsubscribe.component';
|
||||
import { MastodonBotCardComponent } from './mastodon/mastodon-bot-card/mastodon-bot-card.component';
|
||||
|
@ -28,22 +22,16 @@ import { MastodonBotInfoDialogComponent } from './mastodon/mastodon-bot-card/mas
|
|||
@NgModule({
|
||||
declarations: [
|
||||
TelegramSettingsComponent,
|
||||
TwitterSettingsComponent,
|
||||
EmailSettingsComponent,
|
||||
EmailDialogComponent,
|
||||
EmailInfoDialogComponent,
|
||||
TelegramInfoDialogComponent,
|
||||
TelegramDialogComponent,
|
||||
TwitterInfoDialogComponent,
|
||||
TwitterCallbackComponent,
|
||||
TwitterCorpsesPipe,
|
||||
PlatformsInfoPageComponent,
|
||||
EmailBotCardComponent,
|
||||
TelegramBotCardComponent,
|
||||
TwitterBotCardComponent,
|
||||
EmailBotInfoDialogComponent,
|
||||
TelegramBotInfoDialogComponent,
|
||||
TwitterBotInfoDialogComponent,
|
||||
EmailConfirmationComponent,
|
||||
EmailUnsubscribeComponent,
|
||||
MastodonBotCardComponent,
|
||||
|
@ -55,7 +43,6 @@ import { MastodonBotInfoDialogComponent } from './mastodon/mastodon-bot-card/mas
|
|||
exports: [
|
||||
TelegramSettingsComponent,
|
||||
MastodonSettingsComponent,
|
||||
TwitterSettingsComponent,
|
||||
EmailSettingsComponent,
|
||||
PlatformsInfoPageComponent,
|
||||
],
|
||||
|
|
|
@ -1,46 +0,0 @@
|
|||
<div *ngIf="twitters$ | loading | async as twitters">
|
||||
<ng-template [ngIf]="twitters.value">
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<div mat-card-avatar class="twitter"></div>
|
||||
<mat-card-title class="platform-title">
|
||||
Twitter
|
||||
<button mat-icon-button aria-label="How to use">
|
||||
<mat-icon
|
||||
matTooltip="How to send and receive hood broadcast messages with twitter"
|
||||
class="info-button"
|
||||
(click)="onInfoClick()"
|
||||
>info</mat-icon
|
||||
>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content *ngIf="twitters.value.length !== 0; else noTwitter">
|
||||
<mat-selection-list [multiple]="false" class="list">
|
||||
<a
|
||||
*ngFor="let twitter of twitters.value"
|
||||
href="https://twitter.com/{{ twitter.username }}"
|
||||
routerLinkActive="router-link-active"
|
||||
>
|
||||
<mat-list-option>
|
||||
@{{ twitter.username }}
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-option>
|
||||
</a>
|
||||
</mat-selection-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<ng-template #noTwitter>
|
||||
<mat-card-content>
|
||||
Unfortunately your hood admin has not configured Twitter as platform
|
||||
yet.
|
||||
</mat-card-content>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="twitters.error"
|
||||
><mat-icon class="warning">warning</mat-icon></ng-template
|
||||
>
|
||||
<ng-template [ngIf]="twitters.loading">
|
||||
<mat-spinner [diameter]="45" class="spinner"></mat-spinner>
|
||||
</ng-template>
|
||||
</div>
|
|
@ -1,11 +0,0 @@
|
|||
.twitter {
|
||||
background-image: url("../../../../assets/twitter.png");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 40px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { TwitterBotCardComponent } from './twitter-bot-card.component';
|
||||
|
||||
describe('TwitterBotCardComponent', () => {
|
||||
let component: TwitterBotCardComponent;
|
||||
let fixture: ComponentFixture<TwitterBotCardComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TwitterBotCardComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TwitterBotCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,27 +0,0 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { TwitterService } from 'src/app/core/api';
|
||||
import { TwitterBotInfoDialogComponent } from './twitter-bot-info-dialog/twitter-bot-info-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-twitter-bot-card',
|
||||
templateUrl: './twitter-bot-card.component.html',
|
||||
styleUrls: ['./twitter-bot-card.component.scss'],
|
||||
})
|
||||
export class TwitterBotCardComponent implements OnInit {
|
||||
@Input() hoodId;
|
||||
twitters$;
|
||||
|
||||
constructor(
|
||||
private twitterService: TwitterService,
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.twitters$ = this.twitterService.getTwittersPublic(this.hoodId);
|
||||
}
|
||||
|
||||
onInfoClick() {
|
||||
this.dialog.open(TwitterBotInfoDialogComponent);
|
||||
}
|
||||
}
|
|
@ -1,51 +0,0 @@
|
|||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<h2>How to communicate with the hood via Twitter?</h2>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title
|
||||
>How to subscribe to the hood via Twitter?</mat-panel-title
|
||||
>
|
||||
</mat-expansion-panel-header>
|
||||
<p>
|
||||
Click on the twitter bot name that is shown in the twitter card. Just
|
||||
follow the twitter account that the link leads to.
|
||||
</p>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title
|
||||
>How to send a broadcast message to the hood?</mat-panel-title
|
||||
>
|
||||
</mat-expansion-panel-header>
|
||||
<p>You have two options:</p>
|
||||
<ul>
|
||||
<li>
|
||||
Mention the bot in your tweet. The message of your tweet will be
|
||||
broadcasted.
|
||||
</li>
|
||||
<li>
|
||||
Write a direct message to the bot. The message will be broadcasted.
|
||||
</li>
|
||||
</ul>
|
||||
<p>Both options have the equal result.</p>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>How to receive messages?</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<p>
|
||||
Follow one of the twitter accounts in the twitter card. It will
|
||||
broadcast all messages it receives by tweeting the content.
|
||||
</p>
|
||||
</mat-expansion-panel>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title>How to stop receiving messages?</mat-panel-title>
|
||||
</mat-expansion-panel-header>
|
||||
<p>Just unfollow the twitter accounts that you previously followed.</p>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
</mat-dialog-content>
|
|
@ -1,4 +0,0 @@
|
|||
.container {
|
||||
margin-top: 10px;
|
||||
margin-bottom: 10px;
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-twitter-bot-info-dialog',
|
||||
templateUrl: './twitter-bot-info-dialog.component.html',
|
||||
styleUrls: ['./twitter-bot-info-dialog.component.scss'],
|
||||
})
|
||||
export class TwitterBotInfoDialogComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
|
@ -1,3 +0,0 @@
|
|||
<div>
|
||||
<mat-spinner class="spinner"></mat-spinner>
|
||||
</div>
|
|
@ -1,4 +0,0 @@
|
|||
.spinner {
|
||||
display: block;
|
||||
margin: auto;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { TwitterCallbackComponent } from './twitter-callback.component';
|
||||
|
||||
describe('TwitterCallbackComponent', () => {
|
||||
let component: TwitterCallbackComponent;
|
||||
let fixture: ComponentFixture<TwitterCallbackComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TwitterCallbackComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TwitterCallbackComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,47 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
import { ActivatedRoute, Router } from '@angular/router';
|
||||
import { TwitterService } from 'src/app/core/api';
|
||||
|
||||
@Component({
|
||||
selector: 'app-twitter-callback',
|
||||
templateUrl: './twitter-callback.component.html',
|
||||
styleUrls: ['./twitter-callback.component.scss'],
|
||||
})
|
||||
export class TwitterCallbackComponent implements OnInit {
|
||||
constructor(
|
||||
private route: ActivatedRoute,
|
||||
private router: Router,
|
||||
private twitterService: TwitterService
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
if (
|
||||
this.route.snapshot.queryParams.hood &&
|
||||
this.route.snapshot.queryParams.oauth_token &&
|
||||
this.route.snapshot.queryParams.oauth_verifier
|
||||
) {
|
||||
this.twitterService
|
||||
.callbackTwitter(
|
||||
this.route.snapshot.queryParams.oauth_token,
|
||||
this.route.snapshot.queryParams.oauth_verifier,
|
||||
this.route.snapshot.queryParams.hood
|
||||
)
|
||||
.subscribe(() => {
|
||||
this.router.navigate([
|
||||
'/dashboard/hoods',
|
||||
this.route.snapshot.queryParams.hood,
|
||||
]);
|
||||
});
|
||||
} else if (
|
||||
this.route.snapshot.queryParams.hood &&
|
||||
this.route.snapshot.queryParams.denied
|
||||
) {
|
||||
this.router.navigate([
|
||||
'/dashboard/hoods',
|
||||
this.route.snapshot.queryParams.hood,
|
||||
]);
|
||||
} else {
|
||||
this.router.navigate(['/404']);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,10 +0,0 @@
|
|||
import { Pipe, PipeTransform } from '@angular/core';
|
||||
|
||||
@Pipe({
|
||||
name: 'twitterCorpses',
|
||||
})
|
||||
export class TwitterCorpsesPipe implements PipeTransform {
|
||||
transform(twitters) {
|
||||
return twitters.filter((x) => x.verified === 1);
|
||||
}
|
||||
}
|
|
@ -1,41 +0,0 @@
|
|||
<mat-dialog-content>
|
||||
<h2>How to add an twitter bot to your hood</h2>
|
||||
<h3>What is a twitter bot?</h3>
|
||||
<p>
|
||||
Twitter bots are twitter accounts operated by software. In our case we will
|
||||
show you how to add a bot for users to subscribe to, so they can send
|
||||
messages into your hood system by mentioning the bot or direct messaging it,
|
||||
which get distributed to the other platforms. Also they can receive messages
|
||||
from other platforms by reading the tweets of the bot.
|
||||
</p>
|
||||
<p>
|
||||
<strong>Example: </strong> You as a hood admin added the bot
|
||||
@kibicara-my-hood to your hood. John wants to send a message to all hood
|
||||
subscribers. He then uses his twitter account to follow to the bot
|
||||
@kibicara-my-hood and sends his message to the bot by sending a tweet. His
|
||||
message will be distributed to the other platforms and Sandra who is
|
||||
subscribed to your email hood bot gets John's message via email.
|
||||
</p>
|
||||
<h3>How to add a twitter bot to your hood?</h3>
|
||||
<ol>
|
||||
<li>
|
||||
<a href="https://twitter.com" target="_blank">Create a twitter account</a>
|
||||
for the twitter bot and log in. We recommend <strong>NOT</strong> to use
|
||||
your own personal twitter account but to create a new seperate hood
|
||||
account since you will give full access to the account to our software.
|
||||
</li>
|
||||
<li>
|
||||
Click on the <strong>Add a new platform connection!</strong>-Button. This
|
||||
will redirect you to the twitter page to authorize access to the twitter
|
||||
account. Click the
|
||||
<strong>Authorize app</strong>
|
||||
Button. You will be redirected back here. Done!
|
||||
</li>
|
||||
</ol>
|
||||
<img class="example-image" src="assets/auth-app-twitter.png" />
|
||||
<h3>Can I stop relaying twitter messages?</h3>
|
||||
<p>
|
||||
Yes, if you want to stop relaying twitter messages from a specific twitter
|
||||
bot, just use the switch to the bot to stop relaying.
|
||||
</p>
|
||||
</mat-dialog-content>
|
|
@ -1,9 +0,0 @@
|
|||
.example-image {
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
@media screen and (max-width: 600px) {
|
||||
width: 100%;
|
||||
margin-left: 0%;
|
||||
margin-right: 0%;
|
||||
}
|
||||
}
|
|
@ -1,12 +0,0 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-twitter-info-dialog',
|
||||
templateUrl: './twitter-info-dialog.component.html',
|
||||
styleUrls: ['./twitter-info-dialog.component.scss'],
|
||||
})
|
||||
export class TwitterInfoDialogComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<div mat-card-avatar class="twitter"></div>
|
||||
<mat-card-title class="platform-title">
|
||||
Twitter
|
||||
<button mat-icon-button aria-label="How to use">
|
||||
<mat-icon
|
||||
matTooltip="How to add an twitter bot to your hood"
|
||||
class="info-button"
|
||||
(click)="onInfoClick()"
|
||||
>info</mat-icon
|
||||
>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content>
|
||||
<mat-list *ngIf="twitters$ | loading | async as twitters">
|
||||
<ng-template [ngIf]="twitters.value">
|
||||
<mat-list-item *ngIf="(twitters.value | twitterCorpses).length === 0">
|
||||
<button class="add-button" mat-button (click)="onCreate()">
|
||||
<div class="in-add-button">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span> Add a platform connection!</span>
|
||||
</div>
|
||||
</button>
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-item>
|
||||
<mat-list-item *ngFor="let twitter of twitters.value | twitterCorpses">
|
||||
<div class="entry">
|
||||
@{{ twitter.username }}
|
||||
<mat-slide-toggle
|
||||
[checked]="twitter.enabled === 1"
|
||||
(change)="onChange(twitter)"
|
||||
></mat-slide-toggle>
|
||||
<button
|
||||
mat-icon-button
|
||||
[matMenuTriggerFor]="menu"
|
||||
aria-label="Example icon-button with a menu"
|
||||
>
|
||||
<mat-icon>more_vert</mat-icon>
|
||||
</button>
|
||||
</div>
|
||||
<mat-divider></mat-divider>
|
||||
<mat-menu #menu="matMenu">
|
||||
<button mat-menu-item (click)="onDelete(twitter.id)">
|
||||
<mat-icon>delete</mat-icon>
|
||||
<span>Delete</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onCreate()">
|
||||
<mat-icon>add</mat-icon>
|
||||
<span>Add another</span>
|
||||
</button>
|
||||
</mat-menu>
|
||||
</mat-list-item>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="twitters.error"
|
||||
><mat-icon class="warning">warning</mat-icon></ng-template
|
||||
>
|
||||
<ng-template [ngIf]="twitters.loading">
|
||||
<mat-spinner [diameter]="45" class="spinner"></mat-spinner>
|
||||
</ng-template>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
|
@ -1,22 +0,0 @@
|
|||
.entry {
|
||||
display: grid;
|
||||
grid-template-columns: 4fr 40px 20px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.platform-title {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 40px;
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.platform-heading {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.twitter {
|
||||
background-image: url("../../../../assets/twitter.png");
|
||||
background-size: cover;
|
||||
}
|
|
@ -1,24 +0,0 @@
|
|||
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
|
||||
|
||||
import { TwitterSettingsComponent } from './twitter-settings.component';
|
||||
|
||||
describe('TwitterSettingsComponent', () => {
|
||||
let component: TwitterSettingsComponent;
|
||||
let fixture: ComponentFixture<TwitterSettingsComponent>;
|
||||
|
||||
beforeEach(waitForAsync(() => {
|
||||
TestBed.configureTestingModule({
|
||||
declarations: [TwitterSettingsComponent],
|
||||
}).compileComponents();
|
||||
}));
|
||||
|
||||
beforeEach(() => {
|
||||
fixture = TestBed.createComponent(TwitterSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { Observable } from 'rxjs';
|
||||
import { TwitterService } from 'src/app/core/api';
|
||||
import { TwitterInfoDialogComponent } from './twitter-info-dialog/twitter-info-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
@Component({
|
||||
selector: 'app-twitter-settings',
|
||||
templateUrl: './twitter-settings.component.html',
|
||||
styleUrls: ['./twitter-settings.component.scss'],
|
||||
})
|
||||
export class TwitterSettingsComponent implements OnInit {
|
||||
@Input() hoodId;
|
||||
twitters$: Observable<Array<any>>;
|
||||
|
||||
constructor(
|
||||
private twitterService: TwitterService,
|
||||
public dialog: MatDialog,
|
||||
private snackBar: MatSnackBar
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload();
|
||||
}
|
||||
|
||||
private reload() {
|
||||
this.twitters$ = this.twitterService.getTwitters(this.hoodId);
|
||||
}
|
||||
|
||||
onInfoClick() {
|
||||
this.dialog.open(TwitterInfoDialogComponent);
|
||||
}
|
||||
|
||||
onDelete(twitterId) {
|
||||
this.twitterService.deleteTwitter(twitterId, this.hoodId).subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
|
||||
onCreate() {
|
||||
this.twitterService.createTwitter(this.hoodId).subscribe((twitter) => {
|
||||
if (twitter && twitter.access_token) {
|
||||
const redirectUrl =
|
||||
'https://api.twitter.com/oauth/authorize?oauth_token=' +
|
||||
twitter.access_token;
|
||||
window.location.href = redirectUrl;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onChange(twitter) {
|
||||
if (twitter.enabled === 0) {
|
||||
this.twitterService.startTwitter(twitter.id, this.hoodId).subscribe(
|
||||
() => {},
|
||||
(error) => {
|
||||
this.snackBar.open('Could not start. Check your settings.', 'Close', {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
);
|
||||
} else if (twitter.enabled === 1) {
|
||||
this.twitterService.stopTwitter(twitter.id, this.hoodId).subscribe(
|
||||
() => {},
|
||||
(error) => {
|
||||
this.snackBar.open('Could not stop. Check your settings.', 'Close', {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
// TODO yeah i know this is bad, implement disabling/enabling
|
||||
setTimeout(() => {
|
||||
this.reload();
|
||||
}, 100);
|
||||
}
|
||||
}
|
|
@ -112,11 +112,6 @@
|
|||
use. If you are a normal user:
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
Twitter: We save the id of the last received message (mentions and
|
||||
direct messages). This can be a message of yours if you sent the
|
||||
latest message.
|
||||
</li>
|
||||
<li>Telegram: We save your telegram username.</li>
|
||||
<li>E-Mail: We save your e-mail address.</li>
|
||||
</ul>
|
||||
|
@ -130,12 +125,6 @@
|
|||
<p>For hood admins:</p>
|
||||
<ul>
|
||||
<li>We save your e-mail address and your password.</li>
|
||||
<li>
|
||||
Twitter: We save oauth tokens provided by twitter that give us access
|
||||
to the twitter account and the username of the twitter account. We
|
||||
disadvice the use of your private twitter account for acting as
|
||||
Kibicara platform bot. Please create a new twitter account.
|
||||
</li>
|
||||
<li>
|
||||
Telegram: We save the telegram API token to the provided telegram bot
|
||||
and a welcome message. Also we retrieve the name of the telegram bot.
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
<div class="big-paragraph-font">
|
||||
<h2 class="big-h2-font">Inclusive and easy</h2>
|
||||
<p>
|
||||
Messages sent with Kibicara reach people via Telegram, Twitter and even
|
||||
Messages sent with Kibicara reach people via Telegram, Mastodon and even
|
||||
E-Mail - perfect for announcing the next neighborhood meetings, creating
|
||||
news tickers or even providing disaster warnings.
|
||||
</p>
|
||||
|
@ -47,7 +47,7 @@
|
|||
community-administrated instances of Kibicara, being able to customize
|
||||
different platform accounts and filters. You subscribe to a Kibicara hood
|
||||
through your service of choice. This can either be E-Mail, Telegram or
|
||||
Twitter.
|
||||
Mastodon.
|
||||
</p>
|
||||
<a mat-raised-button [routerLink]="['/hoods']">Discover hoods!</a>
|
||||
</div>
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
# client-side git-hook - checks commit message style
|
||||
|
||||
pattern='\[(core|frontend|twitter|telegram|email|xmpp|mastodon|tests|doc|misc)\] [[:upper:]].*[^.]'
|
||||
pattern='\[(core|frontend|telegram|email|xmpp|mastodon|tests|doc|misc)\] [[:upper:]].*[^.]'
|
||||
head -n 1 "$1" | egrep -x "$pattern" > /dev/null
|
||||
if [ $? -ne 0 ]; then
|
||||
echo "commit message doesn't match \"$pattern\"" >&2
|
||||
|
|
Loading…
Reference in a new issue