Compare commits
43 commits
3546092a56
...
dff30e1ede
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dff30e1ede | ||
|
|
672b9dbac0 | ||
|
|
786ee0b59c | ||
|
|
b4b7504a64 | ||
|
|
6a8bc89f3a | ||
|
|
64715f5aa5 | ||
|
|
f7d9baa8a3 | ||
|
|
0504d3083f | ||
|
|
ec0abb5e24 | ||
|
|
d897f369f7 | ||
|
|
9c7607b7ca | ||
|
|
0f4b25fcde | ||
|
|
4f96dfbee7 | ||
|
|
afd0894952 | ||
|
|
f0757619a7 | ||
|
|
90469a052a | ||
|
|
16c325a9cb | ||
|
|
b49c4767a0 | ||
|
|
a61c48e99e | ||
|
|
dfd17aa27c | ||
|
|
66fff6fd7d | ||
|
|
a548c2febc | ||
|
|
f533efee4f | ||
|
|
cb88c24e2e | ||
|
|
36638b1c64 | ||
|
|
7fd716cecc | ||
|
|
3d482dd5f5 | ||
|
|
fb1e88ab03 | ||
|
|
5fa5a9f48e | ||
|
|
9704ed4ddf | ||
|
|
12935b79cb | ||
|
|
37f7b98c67 | ||
|
|
d120d718f9 | ||
|
|
b1f8c08d25 | ||
|
|
4dc4b9cfca | ||
|
|
07bc5a2686 | ||
|
|
3ae4a08ad5 | ||
|
|
767c92000b | ||
|
|
13c20ca245 | ||
|
|
fd09b381a6 | ||
|
|
35eff0c416 | ||
|
|
003a10b273 | ||
|
|
dc454800cd |
|
|
@ -14,12 +14,13 @@
|
|||
0. `cd backend`
|
||||
1. Activate your dev environment with `source .venv/bin/activate`
|
||||
2. Install with `pip install .`
|
||||
3. Turn off production mode: `sudo su -c 'echo "production = 0" >> /etc/kibicara.conf'`
|
||||
3. Create a config file: `echo "production = 0" > kibicara.conf`
|
||||
|
||||
#### Cheatsheet
|
||||
|
||||
- Install Kibicara with `pip install .`
|
||||
- Execute Kibicara with `kibicara` (verbose: `kibicara -vvv`)
|
||||
- Execute Kibicara with `kibicara -f kibicara.conf`
|
||||
(verbose: `kibicara -vvv -f kibicara.conf`)
|
||||
- Interact with Swagger REST-API Documentation: `http://localhost:8000/api/docs`
|
||||
- Test and stylecheck with `tox`
|
||||
- Fix style issues with `black -S src tests`
|
||||
|
|
|
|||
2
COPYING
2
COPYING
|
|
@ -1,4 +1,4 @@
|
|||
Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
Copyright (C) 2020, 2023 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>
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -34,6 +34,8 @@ install_requires =
|
|||
pytoml
|
||||
requests
|
||||
scrypt
|
||||
Mastodon.py
|
||||
pydantic[email]
|
||||
|
||||
[options.packages.find]
|
||||
where = src
|
||||
|
|
@ -53,11 +55,12 @@ deps =
|
|||
black
|
||||
flake8
|
||||
mypy
|
||||
types-requests
|
||||
commands =
|
||||
black -S --check --diff src tests
|
||||
black --check --diff src tests
|
||||
flake8 src tests
|
||||
# not yet
|
||||
#mypy src tests
|
||||
#mypy --ignore-missing-imports src tests
|
||||
|
||||
[testenv]
|
||||
deps =
|
||||
|
|
|
|||
|
|
@ -1,84 +1,27 @@
|
|||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# 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
|
||||
|
||||
"""Configuration file and command line argument parser.
|
||||
|
||||
Gives a dictionary named `config` with configuration parsed either from
|
||||
`/etc/kibicara.conf` or from a file given by command line argument `-f`.
|
||||
If no configuration was found at all, the defaults are used.
|
||||
|
||||
Example:
|
||||
```
|
||||
from kibicara.config import config
|
||||
print(config)
|
||||
```
|
||||
"""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from sys import argv
|
||||
|
||||
from nacl.secret import SecretBox
|
||||
from nacl.utils import random
|
||||
from pytoml import load
|
||||
|
||||
config = {
|
||||
'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,
|
||||
'behind_proxy': False,
|
||||
'keyfile': None, # optional for ssl
|
||||
'certfile': None, # optional for ssl
|
||||
# dev params
|
||||
'root_url': 'http://localhost:8000', # url of backend
|
||||
'cors_allow_origin': 'http://localhost:4200',
|
||||
}
|
||||
"""Default configuration.
|
||||
|
||||
The default configuration gets overwritten by a configuration file if one exists.
|
||||
"""
|
||||
|
||||
args = None
|
||||
|
||||
if argv[0].endswith('kibicara'):
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-f',
|
||||
'--config',
|
||||
dest='configfile',
|
||||
default='/etc/kibicara.conf',
|
||||
help='path to config file',
|
||||
)
|
||||
parser.add_argument(
|
||||
'-v',
|
||||
'--verbose',
|
||||
action='count',
|
||||
help='Raise verbosity level',
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
if argv[0].endswith('kibicara_mda'):
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
'-f',
|
||||
'--config',
|
||||
dest='configfile',
|
||||
default='/etc/kibicara.conf',
|
||||
help='path to config file',
|
||||
)
|
||||
# the MDA passes the recipient address as command line argument
|
||||
parser.add_argument('recipient')
|
||||
args = parser.parse_args()
|
||||
|
||||
if args is not None:
|
||||
try:
|
||||
with open(args.configfile) as configfile:
|
||||
config.update(load(configfile))
|
||||
except FileNotFoundError:
|
||||
# run with default config
|
||||
pass
|
||||
config = {
|
||||
"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,
|
||||
"behind_proxy": False,
|
||||
"keyfile": None, # optional for ssl
|
||||
"certfile": None, # optional for ssl
|
||||
# dev params
|
||||
"root_url": "http://localhost:8000", # url of backend
|
||||
"cors_allow_origin": "http://localhost:4200",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ from socket import getfqdn
|
|||
logger = getLogger(__name__)
|
||||
|
||||
|
||||
def send_email(to, subject, sender='kibicara', body=''):
|
||||
def send_email(to, subject, sender="kibicara", body=""):
|
||||
"""E-Mail sender.
|
||||
|
||||
Sends an E-Mail to a specified recipient with a body
|
||||
|
|
@ -33,10 +33,10 @@ def send_email(to, subject, sender='kibicara', body=''):
|
|||
body (str): The body of the e-mail
|
||||
"""
|
||||
msg = MIMEMultipart()
|
||||
msg['From'] = 'Kibicara <{0}@{1}>'.format(sender, getfqdn())
|
||||
msg['To'] = to
|
||||
msg['Subject'] = '[Kibicara] {0}'.format(subject)
|
||||
msg["From"] = "Kibicara <{0}@{1}>".format(sender, getfqdn())
|
||||
msg["To"] = to
|
||||
msg["Subject"] = "[Kibicara] {0}".format(subject)
|
||||
msg.attach(MIMEText(body))
|
||||
|
||||
with SMTP('localhost') as smtp:
|
||||
with SMTP("localhost") as smtp:
|
||||
smtp.send_message(msg)
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# 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>
|
||||
#
|
||||
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
"""Entrypoint of Kibicara."""
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from asyncio import run as asyncio_run
|
||||
from logging import DEBUG, INFO, WARNING, basicConfig, getLogger
|
||||
|
||||
|
|
@ -14,8 +15,9 @@ from fastapi.middleware.cors import CORSMiddleware
|
|||
from fastapi.staticfiles import StaticFiles
|
||||
from hypercorn.asyncio import serve
|
||||
from hypercorn.config import Config
|
||||
from pytoml import load
|
||||
|
||||
from kibicara.config import args, config
|
||||
from kibicara.config import config
|
||||
from kibicara.model import Mapping
|
||||
from kibicara.platformapi import Spawner
|
||||
from kibicara.webapi import router
|
||||
|
|
@ -31,9 +33,29 @@ class Main:
|
|||
"""
|
||||
|
||||
def __init__(self):
|
||||
asyncio_run(self.__run())
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--config",
|
||||
dest="configfile",
|
||||
default="/etc/kibicara.conf",
|
||||
help="path to config file",
|
||||
)
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
action="count",
|
||||
help="Raise verbosity level",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
with open(args.configfile) as configfile:
|
||||
config.update(load(configfile))
|
||||
except FileNotFoundError:
|
||||
# run with default config
|
||||
pass
|
||||
|
||||
async def __run(self):
|
||||
LOGLEVELS = {
|
||||
None: WARNING,
|
||||
1: INFO,
|
||||
|
|
@ -41,10 +63,13 @@ class Main:
|
|||
}
|
||||
basicConfig(
|
||||
level=LOGLEVELS.get(args.verbose, DEBUG),
|
||||
format='%(asctime)s %(name)s %(message)s',
|
||||
format="%(asctime)s %(name)s %(message)s",
|
||||
)
|
||||
getLogger('aiosqlite').setLevel(WARNING)
|
||||
getLogger("aiosqlite").setLevel(WARNING)
|
||||
Mapping.create_all()
|
||||
asyncio_run(self.__run())
|
||||
|
||||
async def __run(self):
|
||||
await Spawner.init_all()
|
||||
await self.__start_webserver()
|
||||
|
||||
|
|
@ -53,31 +78,31 @@ class Main:
|
|||
async def get_response(self, path, scope):
|
||||
response = await super().get_response(path, scope)
|
||||
if response.status_code == 404:
|
||||
response = await super().get_response('.', scope)
|
||||
response = await super().get_response(".", scope)
|
||||
return response
|
||||
|
||||
app = FastAPI()
|
||||
server_config = Config()
|
||||
server_config.accesslog = '-'
|
||||
server_config.behind_proxy = config['behind_proxy']
|
||||
server_config.keyfile = config['keyfile']
|
||||
server_config.certfile = config['certfile']
|
||||
if config['production']:
|
||||
server_config.bind = ['0.0.0.0:8000', '[::]:8000']
|
||||
server_config.accesslog = "-"
|
||||
server_config.behind_proxy = config["behind_proxy"]
|
||||
server_config.keyfile = config["keyfile"]
|
||||
server_config.certfile = config["certfile"]
|
||||
if config["production"]:
|
||||
server_config.bind = ["0.0.0.0:8000", "[::]:8000"]
|
||||
api = FastAPI()
|
||||
api.include_router(router)
|
||||
app.mount('/api', api)
|
||||
if not config['production'] and config['cors_allow_origin']:
|
||||
app.mount("/api", api)
|
||||
if not config["production"] and config["cors_allow_origin"]:
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=config['cors_allow_origin'],
|
||||
allow_origins=config["cors_allow_origin"],
|
||||
allow_credentials=True,
|
||||
allow_methods=['*'],
|
||||
allow_headers=['*'],
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
if config['frontend_path'] is not None:
|
||||
if config["frontend_path"] is not None:
|
||||
app.mount(
|
||||
'/',
|
||||
app=SinglePageApplication(directory=config['frontend_path'], html=True),
|
||||
"/",
|
||||
app=SinglePageApplication(directory=config["frontend_path"], html=True),
|
||||
)
|
||||
await serve(app, server_config)
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ from kibicara.config import config
|
|||
|
||||
|
||||
class Mapping:
|
||||
database = Database(config['database_connection'])
|
||||
database = Database(config["database_connection"])
|
||||
metadata = MetaData()
|
||||
|
||||
@classmethod
|
||||
|
|
@ -34,7 +34,7 @@ class Admin(Model):
|
|||
passhash: Text()
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'admins'
|
||||
table_name = "admins"
|
||||
|
||||
|
||||
class Hood(Model):
|
||||
|
|
@ -44,7 +44,7 @@ class Hood(Model):
|
|||
email_enabled: Boolean() = True
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'hoods'
|
||||
table_name = "hoods"
|
||||
|
||||
|
||||
class AdminHoodRelation(Model):
|
||||
|
|
@ -53,7 +53,7 @@ class AdminHoodRelation(Model):
|
|||
hood: ForeignKey(Hood)
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'admin_hood_relations'
|
||||
table_name = "admin_hood_relations"
|
||||
|
||||
|
||||
class Trigger(Model):
|
||||
|
|
@ -62,7 +62,7 @@ class Trigger(Model):
|
|||
pattern: Text()
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'triggers'
|
||||
table_name = "triggers"
|
||||
|
||||
|
||||
class BadWord(Model):
|
||||
|
|
@ -71,4 +71,4 @@ class BadWord(Model):
|
|||
pattern: Text()
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'badwords'
|
||||
table_name = "badwords"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# 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>
|
||||
#
|
||||
|
|
@ -29,7 +29,7 @@ class Message:
|
|||
**kwargs (object, optional): Other platform-specific data.
|
||||
"""
|
||||
|
||||
def __init__(self, text, **kwargs):
|
||||
def __init__(self, text: str, **kwargs):
|
||||
self.text = text
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
|
|
@ -71,7 +71,7 @@ class Censor:
|
|||
hood (Hood): A Hood Model object
|
||||
"""
|
||||
|
||||
__instances = {}
|
||||
__instances: dict[int, list["Censor"]] = {}
|
||||
|
||||
def __init__(self, hood):
|
||||
self.hood = hood
|
||||
|
|
@ -82,19 +82,19 @@ class Censor:
|
|||
self.__hood_censors.append(self)
|
||||
self.status = BotStatus.INSTANTIATED
|
||||
|
||||
def start(self):
|
||||
def start(self) -> None:
|
||||
"""Start the bot."""
|
||||
if self.__task is None:
|
||||
self.__task = create_task(self.__run())
|
||||
|
||||
def stop(self):
|
||||
def stop(self) -> None:
|
||||
"""Stop the bot."""
|
||||
if self.__task is not None:
|
||||
self.__task.cancel()
|
||||
|
||||
async def __run(self):
|
||||
async def __run(self) -> None:
|
||||
await self.hood.load()
|
||||
self.__task.set_name('{0} {1}'.format(self.__class__.__name__, self.hood.name))
|
||||
self.__task.set_name("{0} {1}".format(self.__class__.__name__, self.hood.name))
|
||||
try:
|
||||
self.status = BotStatus.RUNNING
|
||||
await self.run()
|
||||
|
|
@ -104,7 +104,7 @@ class Censor:
|
|||
self.__task = None
|
||||
self.status = BotStatus.STOPPED
|
||||
|
||||
async def run(self):
|
||||
async def run(self) -> None:
|
||||
"""Entry point for a bot.
|
||||
|
||||
Note: Override this in the derived bot class.
|
||||
|
|
@ -112,14 +112,14 @@ class Censor:
|
|||
pass
|
||||
|
||||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
async def destroy_hood(cls, hood) -> None:
|
||||
"""Remove all of its database entries.
|
||||
|
||||
Note: Override this in the derived bot class.
|
||||
"""
|
||||
pass
|
||||
|
||||
async def publish(self, message):
|
||||
async def publish(self, message: Message) -> bool:
|
||||
"""Distribute a message to the bots in a hood.
|
||||
|
||||
Args:
|
||||
|
|
@ -132,28 +132,28 @@ class Censor:
|
|||
await censor._inbox.put(message)
|
||||
return True
|
||||
|
||||
async def receive(self):
|
||||
async def receive(self) -> Message:
|
||||
"""Receive a message.
|
||||
|
||||
Returns (Message): Received message
|
||||
"""
|
||||
return await self._inbox.get()
|
||||
|
||||
async def __is_appropriate(self, message):
|
||||
async def __is_appropriate(self, message: Message) -> bool:
|
||||
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)
|
||||
"Matched bad word - dropped message: {0}".format(message.text)
|
||||
)
|
||||
return False
|
||||
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)
|
||||
"Matched trigger - passed message: {0}".format(message.text)
|
||||
)
|
||||
return True
|
||||
logger.debug(
|
||||
'Did not match any trigger - dropped message: {0}'.format(message.text)
|
||||
"Did not match any trigger - dropped message: {0}".format(message.text)
|
||||
)
|
||||
return False
|
||||
|
||||
|
|
@ -177,7 +177,7 @@ class Spawner:
|
|||
BotClass (Censor subclass): A Bot Class object
|
||||
"""
|
||||
|
||||
__instances = []
|
||||
__instances: list["Spawner"] = []
|
||||
|
||||
def __init__(self, ORMClass, BotClass):
|
||||
self.ORMClass = ORMClass
|
||||
|
|
@ -186,13 +186,13 @@ class Spawner:
|
|||
self.__instances.append(self)
|
||||
|
||||
@classmethod
|
||||
async def init_all(cls):
|
||||
async def init_all(cls) -> None:
|
||||
"""Instantiate and start a bot for every row in the corresponding ORM model."""
|
||||
for spawner in cls.__instances:
|
||||
await spawner._init()
|
||||
|
||||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
async def destroy_hood(cls, hood) -> None:
|
||||
for spawner in cls.__instances:
|
||||
for pk in list(spawner.__bots):
|
||||
bot = spawner.__bots[pk]
|
||||
|
|
@ -201,11 +201,11 @@ class Spawner:
|
|||
bot.stop()
|
||||
await spawner.BotClass.destroy_hood(hood)
|
||||
|
||||
async def _init(self):
|
||||
async def _init(self) -> None:
|
||||
for item in await self.ORMClass.objects.all():
|
||||
self.start(item)
|
||||
|
||||
def start(self, item):
|
||||
def start(self, item) -> None:
|
||||
"""Instantiate and start a bot with the provided ORM object.
|
||||
|
||||
Example:
|
||||
|
|
@ -221,7 +221,7 @@ class Spawner:
|
|||
if bot.enabled:
|
||||
bot.start()
|
||||
|
||||
def stop(self, item):
|
||||
def stop(self, item) -> None:
|
||||
"""Stop and delete a bot.
|
||||
|
||||
Args:
|
||||
|
|
@ -231,7 +231,7 @@ class Spawner:
|
|||
if bot is not None:
|
||||
bot.stop()
|
||||
|
||||
def get(self, item):
|
||||
def get(self, item) -> Censor:
|
||||
"""Get a running bot.
|
||||
|
||||
Args:
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ class EmailBot(Censor):
|
|||
while True:
|
||||
message = await self.receive()
|
||||
logger.debug(
|
||||
'Received message from censor ({0}): {1}'.format(
|
||||
"Received message from censor ({0}): {1}".format(
|
||||
self.hood.name, message.text
|
||||
)
|
||||
)
|
||||
|
|
@ -45,19 +45,19 @@ class EmailBot(Censor):
|
|||
).all():
|
||||
token = to_token(email=subscriber.email, hood=self.hood.id)
|
||||
body = (
|
||||
'{0}\n\n--\n'
|
||||
+ 'If you want to stop receiving these mails,'
|
||||
+ 'follow this link: {1}/hoods/{2}/email-unsubscribe?token={3}'
|
||||
).format(message.text, config['frontend_url'], self.hood.id, token)
|
||||
"{0}\n\n--\n"
|
||||
+ "If you want to stop receiving these mails,"
|
||||
+ "follow this link: {1}/hoods/{2}/email-unsubscribe?token={3}"
|
||||
).format(message.text, config["frontend_url"], self.hood.id, token)
|
||||
try:
|
||||
logger.debug('Trying to send: \n{0}'.format(body))
|
||||
logger.debug("Trying to send: \n{0}".format(body))
|
||||
email.send_email(
|
||||
subscriber.email,
|
||||
'Kibicara {0}'.format(self.hood.name),
|
||||
"Kibicara {0}".format(self.hood.name),
|
||||
body=body,
|
||||
)
|
||||
except (ConnectionRefusedError, SMTPException):
|
||||
logger.exception('Sending email to subscriber failed.')
|
||||
logger.exception("Sending email to subscriber failed.")
|
||||
|
||||
|
||||
spawner = Spawner(Hood, EmailBot)
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
# Copyright (C) 2020 by Maike <maike@systemli.org>
|
||||
# Copyright (C) 2020 by Cathy Hu <cathy.hu@fau.de>
|
||||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020, 2023 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from argparse import ArgumentParser
|
||||
from asyncio import run as asyncio_run
|
||||
from email.parser import BytesParser
|
||||
from email.policy import default
|
||||
|
|
@ -15,9 +16,10 @@ from sys import stdin
|
|||
|
||||
from fastapi import status
|
||||
from ormantic import NoMatch
|
||||
from pytoml import load
|
||||
from requests import post
|
||||
|
||||
from kibicara.config import args, config
|
||||
from kibicara.config import config
|
||||
from kibicara.platforms.email.model import Email, EmailSubscribers
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
|
@ -25,62 +27,82 @@ logger = getLogger(__name__)
|
|||
|
||||
class Main:
|
||||
def __init__(self):
|
||||
asyncio_run(self.__run())
|
||||
parser = ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-f",
|
||||
"--config",
|
||||
dest="configfile",
|
||||
default="/etc/kibicara.conf",
|
||||
help="path to config file",
|
||||
)
|
||||
# the MDA passes the recipient address as command line argument
|
||||
parser.add_argument("recipient")
|
||||
args = parser.parse_args()
|
||||
|
||||
try:
|
||||
with open(args.configfile) as configfile:
|
||||
config.update(load(configfile))
|
||||
except FileNotFoundError:
|
||||
# run with default config
|
||||
pass
|
||||
|
||||
async def __run(self):
|
||||
# extract email from the recipient
|
||||
email_name = args.recipient.lower()
|
||||
|
||||
asyncio_run(self.__run(email_name))
|
||||
|
||||
async def __run(self, email_name):
|
||||
try:
|
||||
email = await Email.objects.get(name=email_name)
|
||||
except NoMatch:
|
||||
logger.error('No recipient with this name')
|
||||
logger.error("No recipient with this name")
|
||||
exit(1)
|
||||
|
||||
# read mail from STDIN and parse to EmailMessage object
|
||||
message = BytesParser(policy=default).parsebytes(stdin.buffer.read())
|
||||
|
||||
sender = ''
|
||||
if message.get('sender'):
|
||||
sender = message.get('sender')
|
||||
elif message.get('from'):
|
||||
sender = message.get('from')
|
||||
sender = ""
|
||||
if message.get("sender"):
|
||||
sender = message.get("sender")
|
||||
elif message.get("from"):
|
||||
sender = message.get("from")
|
||||
else:
|
||||
logger.error('No Sender of From header')
|
||||
logger.error("No Sender of From header")
|
||||
exit(1)
|
||||
|
||||
sender = parseaddr(sender)[1]
|
||||
if not sender:
|
||||
logger.error('Could not parse sender')
|
||||
logger.error("Could not parse sender")
|
||||
exit(1)
|
||||
|
||||
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')
|
||||
logger.error("Not a subscriber")
|
||||
exit(1)
|
||||
|
||||
# extract relevant data from mail
|
||||
text = sub(
|
||||
r'<[^>]*>',
|
||||
'',
|
||||
message.get_body(preferencelist=('plain', 'html')).get_content(),
|
||||
r"<[^>]*>",
|
||||
"",
|
||||
message.get_body(preferencelist=("plain", "html")).get_content(),
|
||||
)
|
||||
|
||||
response = post(
|
||||
'{0}/api/hoods/{1}/email/messages/'.format(
|
||||
config['root_url'], email.hood.pk
|
||||
"{0}/api/hoods/{1}/email/messages/".format(
|
||||
config["root_url"], email.hood.pk
|
||||
),
|
||||
json={'text': text, 'secret': email.secret},
|
||||
json={"text": text, "secret": email.secret},
|
||||
)
|
||||
if response.status_code == status.HTTP_201_CREATED:
|
||||
exit(0)
|
||||
elif response.status_code == status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS:
|
||||
logger.error('Message was\'t accepted: {0}'.format(text))
|
||||
logger.error("Message was't accepted: {0}".format(text))
|
||||
elif response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY:
|
||||
logger.error('Malformed request: {0}'.format(response.json()))
|
||||
logger.error("Malformed request: {0}".format(response.json()))
|
||||
elif response.status_code == status.HTTP_401_UNAUTHORIZED:
|
||||
logger.error('Wrong API secret. kibicara_mda seems to be misconfigured')
|
||||
logger.error("Wrong API secret. kibicara_mda seems to be misconfigured")
|
||||
else:
|
||||
logger.error(
|
||||
'REST-API failed with response status {0}'.format(response.status_code)
|
||||
"REST-API failed with response status {0}".format(response.status_code)
|
||||
)
|
||||
exit(1)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ class Email(Model):
|
|||
secret: Text()
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'email'
|
||||
table_name = "email"
|
||||
|
||||
|
||||
class EmailSubscribers(Model):
|
||||
|
|
@ -30,4 +30,4 @@ class EmailSubscribers(Model):
|
|||
email: Text(unique=True)
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'email_subscribers'
|
||||
table_name = "email_subscribers"
|
||||
|
|
|
|||
|
|
@ -29,10 +29,10 @@ logger = getLogger(__name__)
|
|||
class BodyEmail(BaseModel):
|
||||
name: str
|
||||
|
||||
@validator('name')
|
||||
@validator("name")
|
||||
def valid_prefix(cls, value):
|
||||
if not value.startswith('kibicara-'):
|
||||
raise ValueError('Recipient address didn\'t start with kibicara-')
|
||||
if not value.startswith("kibicara-"):
|
||||
raise ValueError("Recipient address didn't start with kibicara-")
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -79,9 +79,9 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get(
|
||||
'/public',
|
||||
"/public",
|
||||
# TODO response_model
|
||||
operation_id='get_emails_public',
|
||||
operation_id="get_emails_public",
|
||||
)
|
||||
async def email_read_all_public(hood=Depends(get_hood_unauthorized)):
|
||||
if hood.email_enabled:
|
||||
|
|
@ -91,19 +91,19 @@ async def email_read_all_public(hood=Depends(get_hood_unauthorized)):
|
|||
|
||||
|
||||
@router.get(
|
||||
'/',
|
||||
"/",
|
||||
# TODO response_model
|
||||
operation_id='get_emails',
|
||||
operation_id="get_emails",
|
||||
)
|
||||
async def email_read_all(hood=Depends(get_hood)):
|
||||
return await Email.objects.filter(hood=hood).select_related('hood').all()
|
||||
return await Email.objects.filter(hood=hood).select_related("hood").all()
|
||||
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model
|
||||
operation_id='create_email',
|
||||
operation_id="create_email",
|
||||
)
|
||||
async def email_create(values: BodyEmail, response: Response, hood=Depends(get_hood)):
|
||||
"""Create an Email bot. Call this when creating a hood.
|
||||
|
|
@ -115,27 +115,27 @@ async def email_create(values: BodyEmail, response: Response, hood=Depends(get_h
|
|||
email = await Email.objects.create(
|
||||
hood=hood, secret=urandom(32).hex(), **values.__dict__
|
||||
)
|
||||
response.headers['Location'] = str(hood.id)
|
||||
response.headers["Location"] = str(hood.id)
|
||||
return email
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/status',
|
||||
"/status",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id='status_email',
|
||||
operation_id="status_email",
|
||||
)
|
||||
async def email_status(hood=Depends(get_hood)):
|
||||
return {'status': spawner.get(hood).status.name}
|
||||
return {"status": spawner.get(hood).status.name}
|
||||
|
||||
|
||||
@router.post(
|
||||
'/start',
|
||||
"/start",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id='start_email',
|
||||
operation_id="start_email",
|
||||
)
|
||||
async def email_start(hood=Depends(get_hood)):
|
||||
await hood.update(email_enabled=True)
|
||||
|
|
@ -144,10 +144,10 @@ async def email_start(hood=Depends(get_hood)):
|
|||
|
||||
|
||||
@router.post(
|
||||
'/stop',
|
||||
"/stop",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id='stop_email',
|
||||
operation_id="stop_email",
|
||||
)
|
||||
async def email_stop(hood=Depends(get_hood)):
|
||||
await hood.update(email_enabled=False)
|
||||
|
|
@ -156,16 +156,16 @@ async def email_stop(hood=Depends(get_hood)):
|
|||
|
||||
|
||||
@router.get(
|
||||
'/{email_id}',
|
||||
"/{email_id}",
|
||||
# TODO response_model
|
||||
operation_id='get_email',
|
||||
operation_id="get_email",
|
||||
)
|
||||
async def email_read(email=Depends(get_email)):
|
||||
return email
|
||||
|
||||
|
||||
@router.delete(
|
||||
'/{email_id}', status_code=status.HTTP_204_NO_CONTENT, operation_id='delete_email'
|
||||
"/{email_id}", status_code=status.HTTP_204_NO_CONTENT, operation_id="delete_email"
|
||||
)
|
||||
async def email_delete(email=Depends(get_email)):
|
||||
"""Delete an Email bot.
|
||||
|
|
@ -179,9 +179,9 @@ async def email_delete(email=Depends(get_email)):
|
|||
|
||||
|
||||
@router.post(
|
||||
'/subscribe/',
|
||||
"/subscribe/",
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
operation_id='subscribe',
|
||||
operation_id="subscribe",
|
||||
response_model=BaseModel,
|
||||
)
|
||||
async def email_subscribe(
|
||||
|
|
@ -194,8 +194,8 @@ async def email_subscribe(
|
|||
:return: Returns status code 200 after sending confirmation email.
|
||||
"""
|
||||
token = to_token(hood=hood.id, email=subscriber.email)
|
||||
confirm_link = '{0}/hoods/{1}/email-confirm?token={2}'.format(
|
||||
config['frontend_url'],
|
||||
confirm_link = "{0}/hoods/{1}/email-confirm?token={2}".format(
|
||||
config["frontend_url"],
|
||||
hood.id,
|
||||
token,
|
||||
)
|
||||
|
|
@ -205,26 +205,26 @@ async def email_subscribe(
|
|||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
email.send_email(
|
||||
subscriber.email,
|
||||
'Subscribe to Kibicara {0}'.format(hood.name),
|
||||
body='To confirm your subscription, follow this link: {0}'.format(
|
||||
"Subscribe to Kibicara {0}".format(hood.name),
|
||||
body="To confirm your subscription, follow this link: {0}".format(
|
||||
confirm_link
|
||||
),
|
||||
)
|
||||
return {}
|
||||
except ConnectionRefusedError:
|
||||
logger.info(token)
|
||||
logger.error('Sending subscription confirmation email failed.', exc_info=True)
|
||||
logger.error("Sending subscription confirmation email failed.", exc_info=True)
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY)
|
||||
except SMTPException:
|
||||
logger.info(token)
|
||||
logger.error('Sending subscription confirmation email failed.', exc_info=True)
|
||||
logger.error("Sending subscription confirmation email failed.", exc_info=True)
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY)
|
||||
|
||||
|
||||
@router.post(
|
||||
'/subscribe/confirm/{token}',
|
||||
"/subscribe/confirm/{token}",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
operation_id='confirm_subscriber',
|
||||
operation_id="confirm_subscriber",
|
||||
response_model=BaseModel,
|
||||
)
|
||||
async def email_subscribe_confirm(token, hood=Depends(get_hood_unauthorized)):
|
||||
|
|
@ -236,19 +236,19 @@ async def email_subscribe_confirm(token, hood=Depends(get_hood_unauthorized)):
|
|||
"""
|
||||
payload = from_token(token)
|
||||
# If token.hood and url.hood are different, raise an error:
|
||||
if hood.id is not payload['hood']:
|
||||
if hood.id is not payload["hood"]:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||
try:
|
||||
await EmailSubscribers.objects.create(hood=hood.id, email=payload['email'])
|
||||
await EmailSubscribers.objects.create(hood=hood.id, email=payload["email"])
|
||||
return {}
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
||||
|
||||
@router.delete(
|
||||
'/unsubscribe/{token}',
|
||||
"/unsubscribe/{token}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='unsubscribe',
|
||||
operation_id="unsubscribe",
|
||||
)
|
||||
async def email_unsubscribe(token, hood=Depends(get_hood_unauthorized)):
|
||||
"""Remove a subscriber from the database when they click on an unsubscribe link.
|
||||
|
|
@ -257,13 +257,13 @@ async def email_unsubscribe(token, hood=Depends(get_hood_unauthorized)):
|
|||
:param hood: Hood the Email bot belongs to.
|
||||
"""
|
||||
try:
|
||||
logger.warning('token is: {0}'.format(token))
|
||||
logger.warning("token is: {0}".format(token))
|
||||
payload = from_token(token)
|
||||
# If token.hood and url.hood are different, raise an error:
|
||||
if hood.id is not payload['hood']:
|
||||
if hood.id is not payload["hood"]:
|
||||
raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST)
|
||||
subscriber = await EmailSubscribers.objects.filter(
|
||||
hood=payload['hood'], email=payload['email']
|
||||
hood=payload["hood"], email=payload["email"]
|
||||
).get()
|
||||
await subscriber.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
|
@ -274,28 +274,28 @@ async def email_unsubscribe(token, hood=Depends(get_hood_unauthorized)):
|
|||
|
||||
|
||||
@router.get(
|
||||
'/subscribers/',
|
||||
"/subscribers/",
|
||||
# TODO response_model
|
||||
operation_id='get_subscribers',
|
||||
operation_id="get_subscribers",
|
||||
)
|
||||
async def subscribers_read_all(hood=Depends(get_hood)):
|
||||
return await EmailSubscribers.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.get(
|
||||
'/subscribers/{subscriber_id}',
|
||||
"/subscribers/{subscriber_id}",
|
||||
# TODO response_model
|
||||
operation_id='get_subscriber',
|
||||
operation_id="get_subscriber",
|
||||
)
|
||||
async def subscribers_read(subscriber=Depends(get_subscriber)):
|
||||
return subscriber
|
||||
|
||||
|
||||
@router.post(
|
||||
'/messages/',
|
||||
"/messages/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model
|
||||
operation_id='send_message',
|
||||
operation_id="send_message",
|
||||
)
|
||||
async def email_message_create(
|
||||
message: BodyMessage, hood=Depends(get_hood_unauthorized)
|
||||
|
|
@ -310,14 +310,14 @@ async def email_message_create(
|
|||
if message.secret == receiver.secret:
|
||||
# pass message.text to bot.py
|
||||
if await spawner.get(hood).publish(Message(message.text)):
|
||||
logger.warning('Message was accepted: {0}'.format(message.text))
|
||||
logger.warning("Message was accepted: {0}".format(message.text))
|
||||
return {}
|
||||
else:
|
||||
logger.warning('Message wasn\'t accepted: {0}'.format(message.text))
|
||||
logger.warning("Message wasn't accepted: {0}".format(message.text))
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_451_UNAVAILABLE_FOR_LEGAL_REASONS
|
||||
)
|
||||
logger.warning(
|
||||
'Someone is trying to submit an email without the correct API secret'
|
||||
"Someone is trying to submit an email without the correct API secret"
|
||||
)
|
||||
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED)
|
||||
|
|
|
|||
101
backend/src/kibicara/platforms/mastodon/bot.py
Normal file
101
backend/src/kibicara/platforms/mastodon/bot.py
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
# 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 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__)
|
||||
|
||||
|
||||
class MastodonBot(Censor):
|
||||
def __init__(self, mastodon_account_model):
|
||||
super().__init__(mastodon_account_model.hood)
|
||||
self.model = mastodon_account_model
|
||||
self.enabled = self.model.enabled
|
||||
self.polling_interval_sec = 60
|
||||
|
||||
@classmethod
|
||||
async def destroy_hood(cls, hood):
|
||||
"""Removes all its database entries."""
|
||||
for mastodon in await MastodonAccount.objects.filter(hood=hood).all():
|
||||
await mastodon.delete()
|
||||
|
||||
async def run(self):
|
||||
try:
|
||||
await self.model.instance.load()
|
||||
self.account = Mastodon(
|
||||
client_id=self.model.instance.client_id,
|
||||
client_secret=self.model.instance.client_secret,
|
||||
api_base_url=self.model.instance.name,
|
||||
access_token=self.model.access_token,
|
||||
)
|
||||
account_details = await get_event_loop().run_in_executor(
|
||||
None, self.account.account_verify_credentials
|
||||
)
|
||||
if username := account_details.get("username"):
|
||||
await self.model.update(username=username)
|
||||
await gather(self.poll(), self.push())
|
||||
except Exception as e:
|
||||
logger.debug("Bot {0} threw an Error: {1}".format(self.model.hood.name, e))
|
||||
finally:
|
||||
logger.debug("Bot {0} stopped.".format(self.model.hood.name))
|
||||
|
||||
async def poll(self):
|
||||
"""Get new mentions and DMs from Mastodon"""
|
||||
while True:
|
||||
try:
|
||||
notifications = await get_event_loop().run_in_executor(
|
||||
None, self.account.notifications
|
||||
)
|
||||
except MastodonError as e:
|
||||
logger.warning("%s in hood %s" % (e, self.model.hood.name))
|
||||
continue
|
||||
for status in notifications:
|
||||
try:
|
||||
status_id = int(status["status"]["id"])
|
||||
except KeyError:
|
||||
self.account.notifications_dismiss(status["id"])
|
||||
continue # ignore notifications which don't have a status
|
||||
text = re.sub(r"<[^>]*>", "", status["status"]["content"])
|
||||
text = re.sub(
|
||||
"(?<=^|(?<=[^a-zA-Z0-9-_.]))@([A-Za-z]+[A-Za-z0-9-_]+)", "", text
|
||||
)
|
||||
logger.debug(
|
||||
"Mastodon in %s received toot #%s: %s"
|
||||
% (self.model.hood.name, status_id, text)
|
||||
)
|
||||
if status["status"]["visibility"] == "public":
|
||||
await self.publish(Message(text, toot_id=status_id))
|
||||
else:
|
||||
await self.publish(Message(text))
|
||||
await get_event_loop().run_in_executor(
|
||||
None, self.account.notifications_dismiss, status["id"]
|
||||
)
|
||||
await sleep(self.polling_interval_sec)
|
||||
|
||||
async def push(self):
|
||||
"""Push new Ticketfrei reports to Mastodon; if source is mastodon, boost it."""
|
||||
while True:
|
||||
message = await self.receive()
|
||||
if hasattr(message, "toot_id"):
|
||||
logger.debug("Boosting post %s: %s" % (message.toot_id, message.text))
|
||||
await get_event_loop().run_in_executor(
|
||||
None, self.account.status_reblog, message.toot_id
|
||||
)
|
||||
else:
|
||||
logger.debug("Posting message: %s" % (message.text,))
|
||||
await get_event_loop().run_in_executor(
|
||||
None, self.account.status_post, message.text
|
||||
)
|
||||
|
||||
|
||||
spawner = Spawner(MastodonAccount, MastodonBot)
|
||||
30
backend/src/kibicara/platforms/mastodon/model.py
Normal file
30
backend/src/kibicara/platforms/mastodon/model.py
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Copyright (C) 2020 by Thomas Lindner <tom@dl6tom.de>
|
||||
# Copyright (C) 2020 by Martin Rey <martin.rey@mailbox.org>
|
||||
#
|
||||
# SPDX-License-Identifier: 0BSD
|
||||
|
||||
from ormantic import ForeignKey, Integer, Text, Boolean, Model
|
||||
|
||||
from kibicara.model import Hood, Mapping
|
||||
|
||||
|
||||
class MastodonInstance(Model):
|
||||
id: Integer(primary_key=True) = None
|
||||
name: Text()
|
||||
client_id: Text()
|
||||
client_secret: Text()
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = "mastodoninstances"
|
||||
|
||||
|
||||
class MastodonAccount(Model):
|
||||
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 Mapping(Mapping):
|
||||
table_name = "mastodonaccounts"
|
||||
184
backend/src/kibicara/platforms/mastodon/webapi.py
Normal file
184
backend/src/kibicara/platforms/mastodon/webapi.py
Normal file
|
|
@ -0,0 +1,184 @@
|
|||
# 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 get_event_loop
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pydantic import BaseModel, validate_email, validator
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from kibicara.config import config
|
||||
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__)
|
||||
|
||||
|
||||
class BodyMastodonPublic(BaseModel):
|
||||
username: str
|
||||
instance: str
|
||||
|
||||
|
||||
class BodyMastodonAccount(BaseModel):
|
||||
email: str
|
||||
instance_url: str
|
||||
password: str
|
||||
|
||||
@validator("email")
|
||||
def validate_email(cls, value):
|
||||
return validate_email(value)
|
||||
|
||||
|
||||
async def get_mastodon(mastodon_id, hood=Depends(get_hood)):
|
||||
try:
|
||||
return await MastodonAccount.objects.get(id=mastodon_id, hood=hood)
|
||||
except NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
|
||||
|
||||
async def get_mastodon_instance(instance_url: str) -> MastodonInstance:
|
||||
"""Return a MastodonInstance ORM object with valid client_id and client_secret.
|
||||
|
||||
:param: instance_url: the API base URL of the mastodon server
|
||||
:return the MastodonInstance ORM object
|
||||
"""
|
||||
try:
|
||||
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.objects.create(
|
||||
name=instance_url, client_id=client_id, client_secret=client_secret
|
||||
)
|
||||
return await MastodonInstance.objects.get(name=instance_url)
|
||||
|
||||
|
||||
router = APIRouter()
|
||||
twitter_callback_router = APIRouter()
|
||||
|
||||
|
||||
@router.get(
|
||||
"/public",
|
||||
# TODO response_model,
|
||||
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 = []
|
||||
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=instance.name)
|
||||
)
|
||||
return mbots
|
||||
|
||||
|
||||
@router.get(
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id="get_mastodons",
|
||||
)
|
||||
async def mastodon_read_all(hood=Depends(get_hood)):
|
||||
return await MastodonAccount.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{mastodon_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
# TODO response_model
|
||||
operation_id="delete_mastodon",
|
||||
)
|
||||
async def mastodon_delete(mastodon=Depends(get_mastodon)):
|
||||
spawner.stop(mastodon)
|
||||
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:
|
||||
await mastodon.instance.delete()
|
||||
await mastodon.delete()
|
||||
return Response(status_code=status.HTTP_204_NO_CONTENT)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/{mastodon_id}/status",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id="status_mastodon",
|
||||
)
|
||||
async def mastodon_status(mastodon=Depends(get_mastodon)):
|
||||
return {"status": spawner.get(mastodon).status.name}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{mastodon_id}/start",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id="start_mastodon",
|
||||
)
|
||||
async def mastodon_start(mastodon=Depends(get_mastodon)):
|
||||
await mastodon.update(enabled=True)
|
||||
spawner.get(mastodon).start()
|
||||
return {}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/{mastodon_id}/stop",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model
|
||||
operation_id="stop_mastodon",
|
||||
)
|
||||
async def mastodon_stop(mastodon=Depends(get_mastodon)):
|
||||
await mastodon.update(enabled=False)
|
||||
spawner.get(mastodon).stop()
|
||||
return {}
|
||||
|
||||
|
||||
@router.post(
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model
|
||||
operation_id="create_mastodon",
|
||||
)
|
||||
async def mastodon_create(values: BodyMastodonAccount, hood=Depends(get_hood)):
|
||||
"""Add a Mastodon Account to a Ticketfrei account.
|
||||
|
||||
open questions:
|
||||
can the instance_url have different ways of writing?
|
||||
|
||||
:param: values: a BodyMastodonAccount object in json
|
||||
:param: hood: the hood ORM object
|
||||
"""
|
||||
try:
|
||||
instance = await get_mastodon_instance(values.instance_url)
|
||||
except MastodonNetworkError:
|
||||
raise HTTPException(422, "Invalid Mastodon Instance")
|
||||
account = Mastodon(
|
||||
instance.client_id, instance.client_secret, api_base_url=values.instance_url
|
||||
)
|
||||
try:
|
||||
access_token = await get_event_loop().run_in_executor(
|
||||
None, account.log_in, values.email, values.password
|
||||
)
|
||||
logger.debug(f"{access_token}")
|
||||
mastodon = await MastodonAccount.objects.create(
|
||||
hood=hood, instance=instance, access_token=access_token, enabled=True
|
||||
)
|
||||
spawner.start(mastodon)
|
||||
return mastodon
|
||||
except MastodonIllegalArgumentError:
|
||||
logger.warning("Login to Mastodon failed.", exc_info=True)
|
||||
raise HTTPException(status_code=status.HTTP_422_INVALID_INPUT)
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
|
@ -32,9 +32,9 @@ class TelegramBot(Censor):
|
|||
|
||||
def _create_dispatcher(self):
|
||||
dp = Dispatcher(self.bot)
|
||||
dp.register_message_handler(self._send_welcome, commands=['start'])
|
||||
dp.register_message_handler(self._remove_user, commands=['stop'])
|
||||
dp.register_message_handler(self._send_help, commands=['help'])
|
||||
dp.register_message_handler(self._send_welcome, commands=["start"])
|
||||
dp.register_message_handler(self._remove_user, commands=["stop"])
|
||||
dp.register_message_handler(self._send_help, commands=["help"])
|
||||
dp.register_message_handler(self._receive_message)
|
||||
return dp
|
||||
|
||||
|
|
@ -42,30 +42,30 @@ class TelegramBot(Censor):
|
|||
try:
|
||||
self.bot = Bot(token=self.telegram_model.api_token)
|
||||
self.dp = self._create_dispatcher()
|
||||
logger.debug('Bot {0} starting.'.format(self.telegram_model.hood.name))
|
||||
logger.debug("Bot {0} starting.".format(self.telegram_model.hood.name))
|
||||
user = await self.bot.get_me()
|
||||
if user.username:
|
||||
await self.telegram_model.update(username=user.username)
|
||||
await gather(self.dp.start_polling(), self._push())
|
||||
except CancelledError:
|
||||
logger.debug(
|
||||
'Bot {0} received Cancellation.'.format(self.telegram_model.hood.name)
|
||||
"Bot {0} received Cancellation.".format(self.telegram_model.hood.name)
|
||||
)
|
||||
self.dp = None
|
||||
raise
|
||||
except exceptions.ValidationError:
|
||||
logger.debug(
|
||||
'Bot {0} has invalid auth token.'.format(self.telegram_model.hood.name)
|
||||
"Bot {0} has invalid auth token.".format(self.telegram_model.hood.name)
|
||||
)
|
||||
await self.telegram_model.update(enabled=False)
|
||||
finally:
|
||||
logger.debug('Bot {0} stopped.'.format(self.telegram_model.hood.name))
|
||||
logger.debug("Bot {0} stopped.".format(self.telegram_model.hood.name))
|
||||
|
||||
async def _push(self):
|
||||
while True:
|
||||
message = await self.receive()
|
||||
logger.debug(
|
||||
'Received message from censor ({0}): {1}'.format(
|
||||
"Received message from censor ({0}): {1}".format(
|
||||
self.telegram_model.hood.name, message.text
|
||||
)
|
||||
)
|
||||
|
|
@ -79,34 +79,34 @@ class TelegramBot(Censor):
|
|||
await self.bot.send_message(user_id, message, disable_notification=False)
|
||||
except exceptions.BotBlocked:
|
||||
logger.error(
|
||||
'Target [ID:{0}] ({1}): blocked by user'.format(
|
||||
"Target [ID:{0}] ({1}): blocked by user".format(
|
||||
user_id, self.telegram_model.hood.name
|
||||
)
|
||||
)
|
||||
except exceptions.ChatNotFound:
|
||||
logger.error(
|
||||
'Target [ID:{0}] ({1}): invalid user ID'.format(
|
||||
"Target [ID:{0}] ({1}): invalid user ID".format(
|
||||
user_id, self.telegram_model.hood.name
|
||||
)
|
||||
)
|
||||
except exceptions.RetryAfter as e:
|
||||
logger.error(
|
||||
'Target [ID:{0}] ({1}): Flood limit is exceeded.'.format(
|
||||
"Target [ID:{0}] ({1}): Flood limit is exceeded.".format(
|
||||
user_id, self.telegram_model.hood.name
|
||||
)
|
||||
+ 'Sleep {0} seconds.'.format(e.timeout)
|
||||
+ "Sleep {0} seconds.".format(e.timeout)
|
||||
)
|
||||
await sleep(e.timeout)
|
||||
return await self._send_message(user_id, message)
|
||||
except exceptions.UserDeactivated:
|
||||
logger.error(
|
||||
'Target [ID:{0}] ({1}): user is deactivated'.format(
|
||||
"Target [ID:{0}] ({1}): user is deactivated".format(
|
||||
user_id, self.telegram_model.hood.name
|
||||
)
|
||||
)
|
||||
except exceptions.TelegramAPIError:
|
||||
logger.exception(
|
||||
'Target [ID:{0}] ({1}): failed'.format(
|
||||
"Target [ID:{0}] ({1}): failed".format(
|
||||
user_id, self.telegram_model.hood.name
|
||||
)
|
||||
)
|
||||
|
|
@ -114,14 +114,14 @@ class TelegramBot(Censor):
|
|||
async def _send_welcome(self, message: types.Message):
|
||||
try:
|
||||
if message.from_user.is_bot:
|
||||
await message.reply('Error: Bots can not join here.')
|
||||
await message.reply("Error: Bots can not join here.")
|
||||
return
|
||||
await TelegramUser.objects.create(
|
||||
user_id=message.from_user.id, bot=self.telegram_model
|
||||
)
|
||||
await message.reply(self.telegram_model.welcome_message)
|
||||
except IntegrityError:
|
||||
await message.reply('Error: You are already registered.')
|
||||
await message.reply("Error: You are already registered.")
|
||||
|
||||
async def _remove_user(self, message: types.Message):
|
||||
try:
|
||||
|
|
@ -129,19 +129,19 @@ class TelegramBot(Censor):
|
|||
user_id=message.from_user.id, bot=self.telegram_model
|
||||
)
|
||||
await telegram_user.delete()
|
||||
await message.reply('You were removed successfully from this bot.')
|
||||
await message.reply("You were removed successfully from this bot.")
|
||||
except NoMatch:
|
||||
await message.reply('Error: You are not subscribed to this bot.')
|
||||
await message.reply("Error: You are not subscribed to this bot.")
|
||||
|
||||
async def _send_help(self, message: types.Message):
|
||||
if message.from_user.is_bot:
|
||||
await message.reply('Error: Bots can\'t be helped.')
|
||||
await message.reply("Error: Bots can't be helped.")
|
||||
return
|
||||
await message.reply('Send messages here to broadcast them to your hood')
|
||||
await message.reply("Send messages here to broadcast them to your hood")
|
||||
|
||||
async def _receive_message(self, message: types.Message):
|
||||
if not message.text:
|
||||
await message.reply('Error: Only text messages are allowed.')
|
||||
await message.reply("Error: Only text messages are allowed.")
|
||||
return
|
||||
await self.publish(Message(message.text))
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ class Telegram(Model):
|
|||
enabled: Boolean() = True
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'telegrambots'
|
||||
table_name = "telegrambots"
|
||||
|
||||
|
||||
class TelegramUser(Model):
|
||||
|
|
@ -27,4 +27,4 @@ class TelegramUser(Model):
|
|||
bot: ForeignKey(Telegram)
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'telegramusers'
|
||||
table_name = "telegramusers"
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ logger = getLogger(__name__)
|
|||
|
||||
class BodyTelegram(BaseModel):
|
||||
api_token: str
|
||||
welcome_message: str = 'Welcome!'
|
||||
welcome_message: str = "Welcome!"
|
||||
|
||||
@validator('api_token')
|
||||
@validator("api_token")
|
||||
def valid_api_token(cls, value):
|
||||
try:
|
||||
check_token(value)
|
||||
|
|
@ -48,9 +48,9 @@ telegram_callback_router = APIRouter()
|
|||
|
||||
|
||||
@router.get(
|
||||
'/public',
|
||||
"/public",
|
||||
# TODO response_model,
|
||||
operation_id='get_telegrams_public',
|
||||
operation_id="get_telegrams_public",
|
||||
)
|
||||
async def telegram_read_all_public(hood=Depends(get_hood_unauthorized)):
|
||||
telegrambots = await Telegram.objects.filter(hood=hood).all()
|
||||
|
|
@ -62,27 +62,27 @@ async def telegram_read_all_public(hood=Depends(get_hood_unauthorized)):
|
|||
|
||||
|
||||
@router.get(
|
||||
'/',
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id='get_telegrams',
|
||||
operation_id="get_telegrams",
|
||||
)
|
||||
async def telegram_read_all(hood=Depends(get_hood)):
|
||||
return await Telegram.objects.filter(hood=hood).all()
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{telegram_id}',
|
||||
"/{telegram_id}",
|
||||
# TODO response_model,
|
||||
operation_id='get_telegram',
|
||||
operation_id="get_telegram",
|
||||
)
|
||||
async def telegram_read(telegram=Depends(get_telegram)):
|
||||
return telegram
|
||||
|
||||
|
||||
@router.delete(
|
||||
'/{telegram_id}',
|
||||
"/{telegram_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='delete_telegram',
|
||||
operation_id="delete_telegram",
|
||||
)
|
||||
async def telegram_delete(telegram=Depends(get_telegram)):
|
||||
spawner.stop(telegram)
|
||||
|
|
@ -93,10 +93,10 @@ async def telegram_delete(telegram=Depends(get_telegram)):
|
|||
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id='create_telegram',
|
||||
operation_id="create_telegram",
|
||||
)
|
||||
async def telegram_create(
|
||||
response: Response, values: BodyTelegram, hood=Depends(get_hood)
|
||||
|
|
@ -104,17 +104,17 @@ async def telegram_create(
|
|||
try:
|
||||
telegram = await Telegram.objects.create(hood=hood, **values.__dict__)
|
||||
spawner.start(telegram)
|
||||
response.headers['Location'] = str(telegram.id)
|
||||
response.headers["Location"] = str(telegram.id)
|
||||
return telegram
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
||||
|
||||
@router.put(
|
||||
'/{telegram_id}',
|
||||
"/{telegram_id}",
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
# TODO response_model,
|
||||
operation_id='update_telegram',
|
||||
operation_id="update_telegram",
|
||||
)
|
||||
async def telegram_update(values: BodyTelegram, telegram=Depends(get_telegram)):
|
||||
try:
|
||||
|
|
@ -127,20 +127,20 @@ async def telegram_update(values: BodyTelegram, telegram=Depends(get_telegram)):
|
|||
|
||||
|
||||
@router.get(
|
||||
'/{telegram_id}/status',
|
||||
"/{telegram_id}/status",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model,
|
||||
operation_id='status_telegram',
|
||||
operation_id="status_telegram",
|
||||
)
|
||||
async def telegram_status(telegram=Depends(get_telegram)):
|
||||
return {'status': spawner.get(telegram).status.name}
|
||||
return {"status": spawner.get(telegram).status.name}
|
||||
|
||||
|
||||
@router.post(
|
||||
'/{telegram_id}/start',
|
||||
"/{telegram_id}/start",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model,
|
||||
operation_id='start_telegram',
|
||||
operation_id="start_telegram",
|
||||
)
|
||||
async def telegram_start(telegram=Depends(get_telegram)):
|
||||
await telegram.update(enabled=True)
|
||||
|
|
@ -149,10 +149,10 @@ async def telegram_start(telegram=Depends(get_telegram)):
|
|||
|
||||
|
||||
@router.post(
|
||||
'/{telegram_id}/stop',
|
||||
"/{telegram_id}/stop",
|
||||
status_code=status.HTTP_200_OK,
|
||||
# TODO response_model,
|
||||
operation_id='stop_telegram',
|
||||
operation_id="stop_telegram",
|
||||
)
|
||||
async def telegram_stop(telegram=Depends(get_telegram)):
|
||||
await telegram.update(enabled=False)
|
||||
|
|
|
|||
|
|
@ -13,4 +13,4 @@ class Test(Model):
|
|||
hood: ForeignKey(Hood)
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'testapi'
|
||||
table_name = "testapi"
|
||||
|
|
|
|||
|
|
@ -30,39 +30,39 @@ async def get_test(test_id: int, hood=Depends(get_hood)):
|
|||
router = APIRouter()
|
||||
|
||||
|
||||
@router.get('/')
|
||||
@router.get("/")
|
||||
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)
|
||||
@router.post("/", status_code=status.HTTP_201_CREATED)
|
||||
async def test_create(response: Response, hood=Depends(get_hood)):
|
||||
try:
|
||||
test = await Test.objects.create(hood=hood)
|
||||
spawner.start(test)
|
||||
response.headers['Location'] = str(test.id)
|
||||
response.headers["Location"] = str(test.id)
|
||||
return test
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
||||
|
||||
@router.get('/{test_id}')
|
||||
@router.get("/{test_id}")
|
||||
async def test_read(test=Depends(get_test)):
|
||||
return test
|
||||
|
||||
|
||||
@router.delete('/{test_id}', status_code=status.HTTP_204_NO_CONTENT)
|
||||
@router.delete("/{test_id}", status_code=status.HTTP_204_NO_CONTENT)
|
||||
async def test_delete(test=Depends(get_test)):
|
||||
spawner.stop(test)
|
||||
await test.delete()
|
||||
|
||||
|
||||
@router.get('/{test_id}/messages/')
|
||||
@router.get("/{test_id}/messages/")
|
||||
async def test_message_read_all(test=Depends(get_test)):
|
||||
return spawner.get(test).messages
|
||||
|
||||
|
||||
@router.post('/{test_id}/messages/')
|
||||
@router.post("/{test_id}/messages/")
|
||||
async def test_message_create(message: BodyMessage, test=Depends(get_test)):
|
||||
await spawner.get(test).publish(Message(message.text))
|
||||
return {}
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
# 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 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.objects.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,23 +0,0 @@
|
|||
# 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 ormantic import Boolean, ForeignKey, Integer, Model, Text
|
||||
|
||||
from kibicara.model import Hood, Mapping
|
||||
|
||||
|
||||
class Twitter(Model):
|
||||
id: Integer(primary_key=True) = None
|
||||
hood: ForeignKey(Hood)
|
||||
dms_since_id: Integer(allow_null=True) = None
|
||||
mentions_since_id: Integer(allow_null=True) = None
|
||||
access_token: Text()
|
||||
access_token_secret: Text()
|
||||
username: Text(allow_null=True) = None
|
||||
verified: Boolean() = False
|
||||
enabled: Boolean() = False
|
||||
|
||||
class Mapping(Mapping):
|
||||
table_name = 'twitterbots'
|
||||
|
|
@ -1,183 +0,0 @@
|
|||
# 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 logging import getLogger
|
||||
from sqlite3 import IntegrityError
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Response, status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from peony.exceptions import NotAuthenticated
|
||||
from peony.oauth_dance import get_access_token, get_oauth_token
|
||||
from pydantic import BaseModel
|
||||
|
||||
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.objects.get(id=twitter_id, hood=hood)
|
||||
except NoMatch:
|
||||
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.objects.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.objects.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.objects.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.objects.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.objects.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.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 NoMatch:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
except (KeyError, ValueError, NotAuthenticated):
|
||||
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR)
|
||||
|
|
@ -15,28 +15,26 @@ from fastapi import APIRouter
|
|||
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.twitter.webapi import router as twitter_router
|
||||
from kibicara.platforms.twitter.webapi import twitter_callback_router
|
||||
from kibicara.platforms.mastodon.webapi import router as mastodon_router
|
||||
from kibicara.webapi.admin import router as admin_router
|
||||
from kibicara.webapi.hoods import router as hoods_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'])
|
||||
router.include_router(admin_router, prefix="/admin", tags=["admin"])
|
||||
hoods_router.include_router(
|
||||
triggers_router, prefix='/{hood_id}/triggers', tags=['triggers']
|
||||
triggers_router, prefix="/{hood_id}/triggers", tags=["triggers"]
|
||||
)
|
||||
hoods_router.include_router(
|
||||
badwords_router, prefix='/{hood_id}/badwords', tags=['badwords']
|
||||
badwords_router, prefix="/{hood_id}/badwords", tags=["badwords"]
|
||||
)
|
||||
hoods_router.include_router(test_router, prefix='/{hood_id}/test', tags=['test'])
|
||||
hoods_router.include_router(test_router, prefix="/{hood_id}/test", tags=["test"])
|
||||
hoods_router.include_router(
|
||||
telegram_router, prefix='/{hood_id}/telegram', tags=['telegram']
|
||||
telegram_router, prefix="/{hood_id}/telegram", tags=["telegram"]
|
||||
)
|
||||
hoods_router.include_router(
|
||||
twitter_router, prefix='/{hood_id}/twitter', tags=['twitter']
|
||||
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')
|
||||
hoods_router.include_router(email_router, prefix="/{hood_id}/email", tags=["email"])
|
||||
router.include_router(hoods_router, prefix="/hoods")
|
||||
|
|
|
|||
|
|
@ -37,10 +37,10 @@ class BodyEmail(BaseModel):
|
|||
class BodyPassword(BaseModel):
|
||||
password: str
|
||||
|
||||
@validator('password')
|
||||
@validator("password")
|
||||
def valid_password(cls, value):
|
||||
if len(value) < 8:
|
||||
raise ValueError('Password is too short')
|
||||
raise ValueError("Password is too short")
|
||||
return value
|
||||
|
||||
|
||||
|
|
@ -50,22 +50,22 @@ class BodyAdmin(BodyEmail, BodyPassword):
|
|||
|
||||
class BodyAccessToken(BaseModel):
|
||||
access_token: str
|
||||
token_type: str = 'bearer'
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl='/api/admin/login')
|
||||
secret_box = SecretBox(bytes.fromhex(config['secret']))
|
||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/admin/login")
|
||||
secret_box = SecretBox(bytes.fromhex(config["secret"]))
|
||||
|
||||
|
||||
def to_token(**kwargs):
|
||||
return secret_box.encrypt(dumps(kwargs), encoder=URLSafeBase64Encoder).decode(
|
||||
'ascii'
|
||||
"ascii"
|
||||
)
|
||||
|
||||
|
||||
def from_token(token):
|
||||
return loads(
|
||||
secret_box.decrypt(token.encode('ascii'), encoder=URLSafeBase64Encoder)
|
||||
secret_box.decrypt(token.encode("ascii"), encoder=URLSafeBase64Encoder)
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -85,8 +85,8 @@ async def get_admin(access_token=Depends(oauth2_scheme)):
|
|||
except (CryptoError, ValueError):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
detail='Invalid authentication credentials',
|
||||
headers={'WWW-Authenticate': 'Bearer'},
|
||||
detail="Invalid authentication credentials",
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return admin
|
||||
|
||||
|
|
@ -95,10 +95,10 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.post(
|
||||
'/register/',
|
||||
"/register/",
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
response_model=BaseModel,
|
||||
operation_id='register',
|
||||
operation_id="register",
|
||||
)
|
||||
async def admin_register(values: BodyAdmin):
|
||||
"""Sends an email with a confirmation link.
|
||||
|
|
@ -107,28 +107,28 @@ async def admin_register(values: BodyAdmin):
|
|||
- **password**: Password of new hood admin
|
||||
"""
|
||||
register_token = to_token(**values.__dict__)
|
||||
logger.debug('register_token={0}'.format(register_token))
|
||||
logger.debug("register_token={0}".format(register_token))
|
||||
try:
|
||||
admin = await Admin.objects.filter(email=values.email).all()
|
||||
if admin:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
body = '{0}/confirm?token={1}'.format(config['frontend_url'], register_token)
|
||||
body = "{0}/confirm?token={1}".format(config["frontend_url"], register_token)
|
||||
logger.debug(body)
|
||||
email.send_email(
|
||||
to=values.email,
|
||||
subject='Confirm Account',
|
||||
subject="Confirm Account",
|
||||
body=body,
|
||||
)
|
||||
except (ConnectionRefusedError, SMTPException):
|
||||
logger.exception('Email sending failed')
|
||||
logger.exception("Email sending failed")
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY)
|
||||
return {}
|
||||
|
||||
|
||||
@router.post(
|
||||
'/confirm/{register_token}',
|
||||
"/confirm/{register_token}",
|
||||
response_model=BodyAccessToken,
|
||||
operation_id='confirm',
|
||||
operation_id="confirm",
|
||||
)
|
||||
async def admin_confirm(register_token: str):
|
||||
"""Registration confirmation and account creation.
|
||||
|
|
@ -137,17 +137,17 @@ async def admin_confirm(register_token: str):
|
|||
"""
|
||||
try:
|
||||
values = from_token(register_token)
|
||||
passhash = argon2.hash(values['password'])
|
||||
await Admin.objects.create(email=values['email'], passhash=passhash)
|
||||
passhash = argon2.hash(values["password"])
|
||||
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)
|
||||
|
||||
|
||||
@router.post(
|
||||
'/login/',
|
||||
"/login/",
|
||||
response_model=BodyAccessToken,
|
||||
operation_id='login',
|
||||
operation_id="login",
|
||||
)
|
||||
async def admin_login(form_data: OAuth2PasswordRequestForm = Depends()):
|
||||
"""Get an access token.
|
||||
|
|
@ -160,17 +160,17 @@ async def admin_login(form_data: OAuth2PasswordRequestForm = Depends()):
|
|||
except ValueError:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_400_BAD_REQUEST,
|
||||
detail='Incorrect email or password',
|
||||
detail="Incorrect email or password",
|
||||
)
|
||||
token = to_token(email=form_data.username, password=form_data.password)
|
||||
return BodyAccessToken(access_token=token)
|
||||
|
||||
|
||||
@router.post(
|
||||
'/reset/',
|
||||
"/reset/",
|
||||
status_code=status.HTTP_202_ACCEPTED,
|
||||
response_model=BaseModel,
|
||||
operation_id='reset',
|
||||
operation_id="reset",
|
||||
)
|
||||
async def admin_reset_password(values: BodyEmail):
|
||||
"""Sends an email with a password reset link.
|
||||
|
|
@ -179,41 +179,41 @@ async def admin_reset_password(values: BodyEmail):
|
|||
- **password**: Password of new hood admin
|
||||
"""
|
||||
register_token = to_token(datetime=datetime.now().isoformat(), **values.__dict__)
|
||||
logger.debug('register_token={0}'.format(register_token))
|
||||
logger.debug("register_token={0}".format(register_token))
|
||||
try:
|
||||
admin = await Admin.objects.filter(email=values.email).all()
|
||||
if not admin:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND)
|
||||
body = '{0}/password-reset?token={1}'.format(
|
||||
config['frontend_url'], register_token
|
||||
body = "{0}/password-reset?token={1}".format(
|
||||
config["frontend_url"], register_token
|
||||
)
|
||||
logger.debug(body)
|
||||
email.send_email(
|
||||
to=values.email,
|
||||
subject='Reset your password',
|
||||
subject="Reset your password",
|
||||
body=body,
|
||||
)
|
||||
except (ConnectionRefusedError, SMTPException):
|
||||
logger.exception('Email sending failed')
|
||||
logger.exception("Email sending failed")
|
||||
raise HTTPException(status_code=status.HTTP_502_BAD_GATEWAY)
|
||||
return {}
|
||||
|
||||
|
||||
@router.post(
|
||||
'/reset/{reset_token}',
|
||||
"/reset/{reset_token}",
|
||||
response_model=BodyAccessToken,
|
||||
operation_id='confirm_reset',
|
||||
operation_id="confirm_reset",
|
||||
)
|
||||
async def admin_confirm_reset(reset_token: str, values: BodyPassword):
|
||||
try:
|
||||
token_values = from_token(reset_token)
|
||||
if (
|
||||
datetime.fromisoformat(token_values['datetime']) + timedelta(hours=3)
|
||||
datetime.fromisoformat(token_values["datetime"]) + timedelta(hours=3)
|
||||
< datetime.now()
|
||||
):
|
||||
raise HTTPException(status_code=status.HTTP_410_GONE)
|
||||
passhash = argon2.hash(values.password)
|
||||
admins = await Admin.objects.filter(email=token_values['email']).all()
|
||||
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)
|
||||
|
|
@ -225,21 +225,21 @@ async def admin_confirm_reset(reset_token: str, values: BodyPassword):
|
|||
|
||||
|
||||
@router.get(
|
||||
'/hoods/',
|
||||
"/hoods/",
|
||||
# TODO response_model,
|
||||
operation_id='get_hoods_admin',
|
||||
operation_id="get_hoods_admin",
|
||||
)
|
||||
async def admin_hood_read_all(admin=Depends(get_admin)):
|
||||
"""Get a list of all hoods of a given admin."""
|
||||
return (
|
||||
await AdminHoodRelation.objects.select_related('hood').filter(admin=admin).all()
|
||||
await AdminHoodRelation.objects.select_related("hood").filter(admin=admin).all()
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/',
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id='get_admin',
|
||||
operation_id="get_admin",
|
||||
)
|
||||
async def admin_read(admin=Depends(get_admin)):
|
||||
"""Get a list of all hoods of a given admin."""
|
||||
|
|
@ -250,17 +250,17 @@ async def admin_read(admin=Depends(get_admin)):
|
|||
|
||||
|
||||
@router.delete(
|
||||
'/',
|
||||
"/",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='delete_admin',
|
||||
operation_id="delete_admin",
|
||||
)
|
||||
async def admin_delete(admin=Depends(get_admin)):
|
||||
hood_relations = (
|
||||
await AdminHoodRelation.objects.select_related('hood').filter(admin=admin).all()
|
||||
await AdminHoodRelation.objects.select_related("hood").filter(admin=admin).all()
|
||||
)
|
||||
for hood in hood_relations:
|
||||
admins = (
|
||||
await AdminHoodRelation.objects.select_related('admin')
|
||||
await AdminHoodRelation.objects.select_related("admin")
|
||||
.filter(hood=hood.id)
|
||||
.all()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,9 +20,9 @@ from kibicara.webapi.utils import delete_hood
|
|||
|
||||
class BodyHood(BaseModel):
|
||||
name: str
|
||||
landingpage: str = '''
|
||||
landingpage: str = """
|
||||
Default Landing Page
|
||||
'''
|
||||
"""
|
||||
|
||||
|
||||
async def get_hood_unauthorized(hood_id: int):
|
||||
|
|
@ -39,7 +39,7 @@ async def get_hood(hood=Depends(get_hood_unauthorized), admin=Depends(get_admin)
|
|||
except NoMatch:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_401_UNAUTHORIZED,
|
||||
headers={'WWW-Authenticate': 'Bearer'},
|
||||
headers={"WWW-Authenticate": "Bearer"},
|
||||
)
|
||||
return hood
|
||||
|
||||
|
|
@ -48,10 +48,10 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get(
|
||||
'/',
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id='get_hoods',
|
||||
tags=['hoods'],
|
||||
operation_id="get_hoods",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_read_all():
|
||||
"""Get all existing hoods."""
|
||||
|
|
@ -59,11 +59,11 @@ async def hood_read_all():
|
|||
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id='create_hood',
|
||||
tags=['hoods'],
|
||||
operation_id="create_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_create(values: BodyHood, response: Response, admin=Depends(get_admin)):
|
||||
"""Creates a hood.
|
||||
|
|
@ -77,19 +77,19 @@ async def hood_create(values: BodyHood, response: Response, admin=Depends(get_ad
|
|||
spawner.start(hood)
|
||||
|
||||
# Initialize Triggers to match all
|
||||
await Trigger.objects.create(hood=hood, pattern='.')
|
||||
await Trigger.objects.create(hood=hood, pattern=".")
|
||||
|
||||
response.headers['Location'] = str(hood.id)
|
||||
response.headers["Location"] = str(hood.id)
|
||||
return hood
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
||||
|
||||
@router.get(
|
||||
'/{hood_id}',
|
||||
"/{hood_id}",
|
||||
# TODO response_model,
|
||||
operation_id='get_hood',
|
||||
tags=['hoods'],
|
||||
operation_id="get_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_read(hood=Depends(get_hood_unauthorized)):
|
||||
"""Get hood with id **hood_id**."""
|
||||
|
|
@ -97,10 +97,10 @@ async def hood_read(hood=Depends(get_hood_unauthorized)):
|
|||
|
||||
|
||||
@router.put(
|
||||
'/{hood_id}',
|
||||
"/{hood_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='update_hood',
|
||||
tags=['hoods'],
|
||||
operation_id="update_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_update(values: BodyHood, hood=Depends(get_hood)):
|
||||
"""Updates hood with id **hood_id**.
|
||||
|
|
@ -113,10 +113,10 @@ async def hood_update(values: BodyHood, hood=Depends(get_hood)):
|
|||
|
||||
|
||||
@router.delete(
|
||||
'/{hood_id}',
|
||||
"/{hood_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='delete_hood',
|
||||
tags=['hoods'],
|
||||
operation_id="delete_hood",
|
||||
tags=["hoods"],
|
||||
)
|
||||
async def hood_delete(hood=Depends(get_hood)):
|
||||
"""Deletes hood with id **hood_id**."""
|
||||
|
|
|
|||
|
|
@ -38,9 +38,9 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get(
|
||||
'/',
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id='get_badwords',
|
||||
operation_id="get_badwords",
|
||||
)
|
||||
async def badword_read_all(hood=Depends(get_hood)):
|
||||
"""Get all badwords of hood with id **hood_id**."""
|
||||
|
|
@ -48,10 +48,10 @@ async def badword_read_all(hood=Depends(get_hood)):
|
|||
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id='create_badword',
|
||||
operation_id="create_badword",
|
||||
)
|
||||
async def badword_create(
|
||||
values: BodyBadWord, response: Response, hood=Depends(get_hood)
|
||||
|
|
@ -63,7 +63,7 @@ async def badword_create(
|
|||
try:
|
||||
regex_compile(values.pattern)
|
||||
badword = await BadWord.objects.create(hood=hood, **values.__dict__)
|
||||
response.headers['Location'] = str(badword.id)
|
||||
response.headers["Location"] = str(badword.id)
|
||||
return badword
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
|
@ -72,9 +72,9 @@ async def badword_create(
|
|||
|
||||
|
||||
@router.get(
|
||||
'/{badword_id}',
|
||||
"/{badword_id}",
|
||||
# TODO response_model,
|
||||
operation_id='get_badword',
|
||||
operation_id="get_badword",
|
||||
)
|
||||
async def badword_read(badword=Depends(get_badword)):
|
||||
"""Reads badword with id **badword_id** for hood with id **hood_id**."""
|
||||
|
|
@ -82,9 +82,9 @@ async def badword_read(badword=Depends(get_badword)):
|
|||
|
||||
|
||||
@router.put(
|
||||
'/{badword_id}',
|
||||
"/{badword_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='update_badword',
|
||||
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**.
|
||||
|
|
@ -96,9 +96,9 @@ async def badword_update(values: BodyBadWord, badword=Depends(get_badword)):
|
|||
|
||||
|
||||
@router.delete(
|
||||
'/{badword_id}',
|
||||
"/{badword_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='delete_badword',
|
||||
operation_id="delete_badword",
|
||||
)
|
||||
async def badword_delete(badword=Depends(get_badword)):
|
||||
"""Deletes badword with id **badword_id** for hood with id **hood_id**."""
|
||||
|
|
|
|||
|
|
@ -39,9 +39,9 @@ router = APIRouter()
|
|||
|
||||
|
||||
@router.get(
|
||||
'/',
|
||||
"/",
|
||||
# TODO response_model,
|
||||
operation_id='get_triggers',
|
||||
operation_id="get_triggers",
|
||||
)
|
||||
async def trigger_read_all(hood=Depends(get_hood)):
|
||||
"""Get all triggers of hood with id **hood_id**."""
|
||||
|
|
@ -49,10 +49,10 @@ async def trigger_read_all(hood=Depends(get_hood)):
|
|||
|
||||
|
||||
@router.post(
|
||||
'/',
|
||||
"/",
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
# TODO response_model,
|
||||
operation_id='create_trigger',
|
||||
operation_id="create_trigger",
|
||||
)
|
||||
async def trigger_create(
|
||||
values: BodyTrigger, response: Response, hood=Depends(get_hood)
|
||||
|
|
@ -64,7 +64,7 @@ async def trigger_create(
|
|||
try:
|
||||
regex_compile(values.pattern)
|
||||
trigger = await Trigger.objects.create(hood=hood, **values.__dict__)
|
||||
response.headers['Location'] = str(trigger.id)
|
||||
response.headers["Location"] = str(trigger.id)
|
||||
return trigger
|
||||
except IntegrityError:
|
||||
raise HTTPException(status_code=status.HTTP_409_CONFLICT)
|
||||
|
|
@ -73,9 +73,9 @@ async def trigger_create(
|
|||
|
||||
|
||||
@router.get(
|
||||
'/{trigger_id}',
|
||||
"/{trigger_id}",
|
||||
# TODO response_model,
|
||||
operation_id='get_trigger',
|
||||
operation_id="get_trigger",
|
||||
)
|
||||
async def trigger_read(trigger=Depends(get_trigger)):
|
||||
"""Reads trigger with id **trigger_id** for hood with id **hood_id**."""
|
||||
|
|
@ -83,9 +83,9 @@ async def trigger_read(trigger=Depends(get_trigger)):
|
|||
|
||||
|
||||
@router.put(
|
||||
'/{trigger_id}',
|
||||
"/{trigger_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='update_trigger',
|
||||
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**.
|
||||
|
|
@ -97,9 +97,9 @@ async def trigger_update(values: BodyTrigger, trigger=Depends(get_trigger)):
|
|||
|
||||
|
||||
@router.delete(
|
||||
'/{trigger_id}',
|
||||
"/{trigger_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
operation_id='delete_trigger',
|
||||
operation_id="delete_trigger",
|
||||
)
|
||||
async def trigger_delete(trigger=Depends(get_trigger)):
|
||||
"""Deletes trigger with id **trigger_id** for hood with id **hood_id**."""
|
||||
|
|
|
|||
|
|
@ -15,16 +15,16 @@ from kibicara.model import Mapping
|
|||
from kibicara.webapi import router
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def client():
|
||||
Mapping.drop_all()
|
||||
Mapping.create_all()
|
||||
app = FastAPI()
|
||||
app.include_router(router, prefix='/api')
|
||||
app.include_router(router, prefix="/api")
|
||||
return TestClient(app)
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def monkeymodule():
|
||||
from _pytest.monkeypatch import MonkeyPatch
|
||||
|
||||
|
|
@ -33,96 +33,96 @@ def monkeymodule():
|
|||
mpatch.undo()
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def receive_email(monkeymodule):
|
||||
mailbox = []
|
||||
|
||||
def mock_send_email(to, subject, sender='kibicara', body=''):
|
||||
def mock_send_email(to, subject, sender="kibicara", body=""):
|
||||
mailbox.append(dict(to=to, subject=subject, sender=sender, body=body))
|
||||
|
||||
def mock_receive_email():
|
||||
return mailbox.pop()
|
||||
|
||||
monkeymodule.setattr(email, 'send_email', mock_send_email)
|
||||
monkeymodule.setattr(email, "send_email", mock_send_email)
|
||||
return mock_receive_email
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def register_token(client, receive_email):
|
||||
response = client.post(
|
||||
'/api/admin/register/', json={'email': 'user', 'password': 'password'}
|
||||
"/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]
|
||||
return urlparse(receive_email()["body"]).query.split("=", 1)[1]
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def register_confirmed(client, register_token):
|
||||
response = client.post('/api/admin/confirm/{0}'.format(register_token))
|
||||
response = client.post("/api/admin/confirm/{0}".format(register_token))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def access_token(client, register_confirmed):
|
||||
response = client.post(
|
||||
'/api/admin/login/', data={'username': 'user', 'password': 'password'}
|
||||
"/api/admin/login/", data={"username": "user", "password": "password"}
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
return response.json()['access_token']
|
||||
return response.json()["access_token"]
|
||||
|
||||
|
||||
@fixture(scope='module')
|
||||
@fixture(scope="module")
|
||||
def auth_header(access_token):
|
||||
return {'Authorization': 'Bearer {0}'.format(access_token)}
|
||||
return {"Authorization": "Bearer {0}".format(access_token)}
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@fixture(scope="function")
|
||||
def hood_id(client, auth_header):
|
||||
response = client.post('/api/hoods/', json={'name': 'hood'}, headers=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'])
|
||||
hood_id = int(response.headers["Location"])
|
||||
yield hood_id
|
||||
client.delete('/api/hoods/{0}'.format(hood_id), headers=auth_header)
|
||||
client.delete("/api/hoods/{0}".format(hood_id), headers=auth_header)
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@fixture(scope="function")
|
||||
def trigger_id(client, hood_id, auth_header):
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/triggers/'.format(hood_id),
|
||||
json={'pattern': 'test'},
|
||||
"/api/hoods/{0}/triggers/".format(hood_id),
|
||||
json={"pattern": "test"},
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
trigger_id = int(response.headers['Location'])
|
||||
trigger_id = int(response.headers["Location"])
|
||||
yield trigger_id
|
||||
client.delete(
|
||||
'/api/hoods/{0}/triggers/{1}'.format(hood_id, trigger_id), headers=auth_header
|
||||
"/api/hoods/{0}/triggers/{1}".format(hood_id, trigger_id), headers=auth_header
|
||||
)
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@fixture(scope="function")
|
||||
def badword_id(client, hood_id, auth_header):
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/badwords/'.format(hood_id),
|
||||
json={'pattern': ''},
|
||||
"/api/hoods/{0}/badwords/".format(hood_id),
|
||||
json={"pattern": ""},
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
badword_id = int(response.headers['Location'])
|
||||
badword_id = int(response.headers["Location"])
|
||||
yield badword_id
|
||||
client.delete(
|
||||
'/api/hoods/{0}/badwords/{1}'.format(hood_id, badword_id), headers=auth_header
|
||||
"/api/hoods/{0}/badwords/{1}".format(hood_id, badword_id), headers=auth_header
|
||||
)
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@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
|
||||
"/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'])
|
||||
test_id = int(response.headers["Location"])
|
||||
yield test_id
|
||||
client.delete(
|
||||
'/api/hoods/{0}/test/{1}'.format(hood_id, test_id), headers=auth_header
|
||||
"/api/hoods/{0}/test/{1}".format(hood_id, test_id), headers=auth_header
|
||||
)
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ from fastapi import status
|
|||
|
||||
|
||||
def test_hoods_unauthorized(client):
|
||||
response = client.get('/api/admin/hoods/')
|
||||
response = client.get("/api/admin/hoods/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_hoods_success(client, auth_header):
|
||||
response = client.get('/api/admin/hoods/', headers=auth_header)
|
||||
response = client.get("/api/admin/hoods/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
|
|
|||
|
|
@ -8,75 +8,75 @@ from fastapi import status
|
|||
|
||||
|
||||
def test_hood_read_all(client):
|
||||
response = client.get('/api/hoods/')
|
||||
response = client.get("/api/hoods/")
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
def test_hood_create_unauthorized(client, hood_id):
|
||||
response = client.post('/api/hoods/')
|
||||
response = client.post("/api/hoods/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_hood_read(client, hood_id):
|
||||
response = client.get('/api/hoods/{0}'.format(hood_id))
|
||||
response = client.get("/api/hoods/{0}".format(hood_id))
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
|
||||
|
||||
def test_hood_update_unauthorized(client, hood_id):
|
||||
response = client.put('/api/hoods/{0}'.format(hood_id))
|
||||
response = client.put("/api/hoods/{0}".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_hood_delete_unauthorized(client, hood_id):
|
||||
response = client.delete('/api/hoods/{0}'.format(hood_id))
|
||||
response = client.delete("/api/hoods/{0}".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_trigger_read_all_unauthorized(client, hood_id):
|
||||
response = client.get('/api/hoods/{0}/triggers/'.format(hood_id))
|
||||
response = client.get("/api/hoods/{0}/triggers/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_trigger_create_unauthorized(client, hood_id):
|
||||
response = client.post('/api/hoods/{0}/triggers/'.format(hood_id))
|
||||
response = client.post("/api/hoods/{0}/triggers/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_trigger_read_unauthorized(client, hood_id, trigger_id):
|
||||
response = client.get('/api/hoods/{0}/triggers/{1}'.format(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
|
||||
|
||||
|
||||
def test_trigger_update_unauthorized(client, hood_id, trigger_id):
|
||||
response = client.put('/api/hoods/{0}/triggers/{1}'.format(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
|
||||
|
||||
|
||||
def test_trigger_delete_unauthorized(client, hood_id, trigger_id):
|
||||
response = client.delete('/api/hoods/{0}/triggers/{1}'.format(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
|
||||
|
||||
|
||||
def test_badword_read_all_unauthorized(client, hood_id):
|
||||
response = client.get('/api/hoods/{0}/badwords/'.format(hood_id))
|
||||
response = client.get("/api/hoods/{0}/badwords/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_badword_create_unauthorized(client, hood_id):
|
||||
response = client.post('/api/hoods/{0}/badwords/'.format(hood_id))
|
||||
response = client.post("/api/hoods/{0}/badwords/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_badword_read_unauthorized(client, hood_id, badword_id):
|
||||
response = client.get('/api/hoods/{0}/badwords/{1}'.format(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
|
||||
|
||||
|
||||
def test_badword_update_unauthorized(client, hood_id, badword_id):
|
||||
response = client.put('/api/hoods/{0}/badwords/{1}'.format(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
|
||||
|
||||
|
||||
def test_badword_delete_unauthorized(client, hood_id, badword_id):
|
||||
response = client.delete('/api/hoods/{0}/badwords/{1}'.format(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
|
||||
|
|
|
|||
|
|
@ -9,16 +9,16 @@ from fastapi import status
|
|||
from pytest import fixture
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@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'},
|
||||
"/api/hoods/{0}/email/".format(hood_id),
|
||||
json={"name": "kibicara-test"},
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
email_id = int(response.headers['Location'])
|
||||
email_id = int(response.headers["Location"])
|
||||
yield response.json()
|
||||
client.delete(
|
||||
'/api/hoods/{0}/email/{1}'.format(hood_id, email_id), headers=auth_header
|
||||
"/api/hoods/{0}/email/{1}".format(hood_id, email_id), headers=auth_header
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,49 +15,49 @@ from kibicara.webapi.admin import to_token
|
|||
|
||||
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'},
|
||||
"/api/hoods/{0}/email/subscribe/".format(hood_id),
|
||||
json={"email": "test@localhost"},
|
||||
)
|
||||
assert response.status_code == status.HTTP_202_ACCEPTED
|
||||
mail = receive_email()
|
||||
body = mail['body']
|
||||
body = mail["body"]
|
||||
confirm_url = findall(
|
||||
r'http[s]?://'
|
||||
+ r'(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+',
|
||||
r"http[s]?://"
|
||||
+ r"(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+",
|
||||
body,
|
||||
)[0]
|
||||
start = len('token=')
|
||||
start = len("token=")
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/email/subscribe/confirm/{1}'.format(
|
||||
"/api/hoods/{0}/email/subscribe/confirm/{1}".format(
|
||||
hood_id, urlparse(confirm_url).query[start:]
|
||||
)
|
||||
)
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/email/subscribe/confirm/{1}'.format(
|
||||
"/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)
|
||||
token = to_token(email=mail["to"], hood=hood_id)
|
||||
response = client.delete(
|
||||
'/api/hoods/{0}/email/unsubscribe/{1}'.format(hood_id, token)
|
||||
"/api/hoods/{0}/email/unsubscribe/{1}".format(hood_id, token)
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
||||
|
||||
def test_email_message(client, hood_id, trigger_id, email_row):
|
||||
body = {
|
||||
'text': 'test',
|
||||
'author': 'test@localhost',
|
||||
'secret': email_row['secret'],
|
||||
"text": "test",
|
||||
"author": "test@localhost",
|
||||
"secret": email_row["secret"],
|
||||
}
|
||||
response = 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
|
||||
|
||||
|
||||
def test_email_send_mda(trigger_id, email_row):
|
||||
skip('Only works if kibicara is listening on port 8000, and only sometimes')
|
||||
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
|
||||
|
|
@ -85,6 +85,6 @@ test
|
|||
--AqNPlAX243a8sip3B7kXv8UKD8wuti--
|
||||
"""
|
||||
proc = subprocess.run(
|
||||
['kibicara_mda', 'hood'], stdout=subprocess.PIPE, input=mail, encoding='ascii'
|
||||
["kibicara_mda", "hood"], stdout=subprocess.PIPE, input=mail, encoding="ascii"
|
||||
)
|
||||
assert proc.returncode == 0
|
||||
|
|
|
|||
|
|
@ -8,18 +8,18 @@ from fastapi import status
|
|||
|
||||
|
||||
def test_email_create_unauthorized(client, hood_id):
|
||||
response = client.post('/api/hoods/{0}/email/'.format(hood_id))
|
||||
response = client.post("/api/hoods/{0}/email/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_email_delete_unauthorized(client, hood_id, email_row):
|
||||
response = client.delete(
|
||||
'/api/hoods/{0}/email/{1}'.format(hood_id, email_row['id'])
|
||||
"/api/hoods/{0}/email/{1}".format(hood_id, email_row["id"])
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
||||
|
||||
def test_email_message_unauthorized(client, hood_id, email_row):
|
||||
body = {'text': 'test', 'author': 'author', 'secret': 'wrong'}
|
||||
response = client.post('/api/hoods/{0}/email/messages/'.format(hood_id), json=body)
|
||||
body = {"text": "test", "author": "author", "secret": "wrong"}
|
||||
response = client.post("/api/hoods/{0}/email/messages/".format(hood_id), json=body)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ from nacl.exceptions import CryptoError
|
|||
|
||||
|
||||
def test_email_subscribe_empty(client, hood_id):
|
||||
response = client.post('/api/hoods/{0}/email/subscribe/'.format(hood_id))
|
||||
response = client.post("/api/hoods/{0}/email/subscribe/".format(hood_id))
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
def test_email_subscribe_confirm_wrong_token(client, hood_id):
|
||||
try:
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/email/subscribe/confirm/'.format(hood_id)
|
||||
+ 'asdfasdfasdfasdfasdfasdfasdfasdf'
|
||||
"/api/hoods/{0}/email/subscribe/confirm/".format(hood_id)
|
||||
+ "asdfasdfasdfasdfasdfasdfasdfasdf"
|
||||
)
|
||||
assert response.status_code is not status.HTTP_201_CREATED
|
||||
except CryptoError:
|
||||
|
|
@ -25,25 +25,25 @@ def test_email_subscribe_confirm_wrong_token(client, hood_id):
|
|||
|
||||
def test_email_subscribe_confirm_wrong_hood(client):
|
||||
response = client.delete(
|
||||
'/api/hoods/99999/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf'
|
||||
"/api/hoods/99999/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf"
|
||||
)
|
||||
assert response.json()['detail'] == 'Not Found'
|
||||
assert response.json()["detail"] == "Not Found"
|
||||
|
||||
|
||||
def test_email_message_wrong(client, hood_id, email_row):
|
||||
body = {
|
||||
'text': '',
|
||||
'author': 'test@localhost',
|
||||
'secret': email_row['secret'],
|
||||
"text": "",
|
||||
"author": "test@localhost",
|
||||
"secret": email_row["secret"],
|
||||
}
|
||||
response = 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
|
||||
|
||||
|
||||
def test_email_unsubscribe_wrong_token(client, hood_id):
|
||||
try:
|
||||
client.delete(
|
||||
'/api/hoods/{0}/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf'.format(
|
||||
"/api/hoods/{0}/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf".format(
|
||||
hood_id
|
||||
)
|
||||
)
|
||||
|
|
@ -53,6 +53,6 @@ def test_email_unsubscribe_wrong_token(client, hood_id):
|
|||
|
||||
def test_email_unsubscribe_wrong_hood(client):
|
||||
response = client.delete(
|
||||
'/api/hoods/99999/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf'
|
||||
"/api/hoods/99999/email/unsubscribe/asdfasdfasdfasdfasdfasdfasdfasdf"
|
||||
)
|
||||
assert response.json()['detail'] == 'Not Found'
|
||||
assert response.json()["detail"] == "Not Found"
|
||||
|
|
|
|||
34
backend/tests/tests_mastodon/conftest.py
Normal file
34
backend/tests/tests_mastodon/conftest.py
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
# 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 pytest import fixture
|
||||
|
||||
from kibicara.model import Hood
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount, MastodonInstance
|
||||
|
||||
|
||||
@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",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@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",
|
||||
)
|
||||
)
|
||||
106
backend/tests/tests_mastodon/test_api_mastodon_create_bot.py
Normal file
106
backend/tests/tests_mastodon/test_api_mastodon_create_bot.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
# 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 status
|
||||
from pytest import fixture, mark
|
||||
from mastodon.Mastodon import Mastodon
|
||||
|
||||
from kibicara.platforms import mastodon
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount, MastodonInstance
|
||||
|
||||
|
||||
@fixture(scope="function")
|
||||
def disable_spawner(monkeypatch):
|
||||
class DoNothing:
|
||||
def start(self, bot):
|
||||
assert bot is not None
|
||||
|
||||
monkeypatch.setattr(mastodon.webapi, "spawner", DoNothing())
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"body",
|
||||
[
|
||||
{
|
||||
"instance_url": "botsin.space",
|
||||
"email": "test@example.org",
|
||||
"password": "string",
|
||||
}
|
||||
],
|
||||
)
|
||||
def test_mastodon_create_bot(
|
||||
event_loop,
|
||||
client,
|
||||
disable_spawner,
|
||||
hood_id,
|
||||
auth_header,
|
||||
monkeypatch,
|
||||
body,
|
||||
):
|
||||
def log_in_mock(self, username, password):
|
||||
return "acc3ss_t0ken"
|
||||
|
||||
monkeypatch.setattr(Mastodon, "log_in", log_in_mock)
|
||||
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/mastodon/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
)
|
||||
print(response.json())
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
bot_id = response.json()["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
|
||||
|
||||
|
||||
@mark.parametrize(
|
||||
"body",
|
||||
[
|
||||
{"instance_url": "botsin.space", "email": "notanemail", "password": "asdf1234"},
|
||||
{"instance_url": "wrong", "email": "asdf@example.org", "password": "asdf1234"},
|
||||
],
|
||||
)
|
||||
def test_mastodon_invalid_input(
|
||||
event_loop,
|
||||
client,
|
||||
disable_spawner,
|
||||
hood_id,
|
||||
auth_header,
|
||||
monkeypatch,
|
||||
body,
|
||||
):
|
||||
response = client.post(
|
||||
"/api/hoods/{0}/mastodon/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
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 = client.post("/api/hoods/wrong/mastodon/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
def test_mastodon_create_unauthorized(client, hood_id):
|
||||
response = client.post("/api/hoods/{hood_id}/mastodon/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
48
backend/tests/tests_mastodon/test_api_mastodon_delete_bot.py
Normal file
48
backend/tests/tests_mastodon/test_api_mastodon_delete_bot.py
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
# 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 status
|
||||
from ormantic.exceptions import NoMatch
|
||||
from pytest import raises
|
||||
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount
|
||||
|
||||
|
||||
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 raises(NoMatch):
|
||||
event_loop.run_until_complete(
|
||||
MastodonAccount.objects.get(id=mastodon_account.id)
|
||||
)
|
||||
|
||||
|
||||
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 = client.delete("/api/hoods/wrong/mastodon/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/mastodon/7331".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = client.delete(
|
||||
"/api/hoods/{0}/mastodon/wrong".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
|
||||
|
||||
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
|
||||
)
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
50
backend/tests/tests_mastodon/test_api_mastodon_get_bots.py
Normal file
50
backend/tests/tests_mastodon/test_api_mastodon_get_bots.py
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
# 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 status
|
||||
|
||||
from kibicara.platforms.mastodon.model import MastodonAccount
|
||||
|
||||
|
||||
def test_mastodon_get_bots(
|
||||
client, auth_header, event_loop, hood_id, mastodon_account, mastodon_instance
|
||||
):
|
||||
mastodon2 = event_loop.run_until_complete(
|
||||
MastodonAccount.objects.create(
|
||||
hood=mastodon_account.hood,
|
||||
instance=mastodon_instance,
|
||||
access_token="4cc3ss",
|
||||
enabled=True,
|
||||
username="us4r",
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
"/api/hoods/{0}/mastodon".format(mastodon_account.hood.id), headers=auth_header
|
||||
)
|
||||
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
|
||||
assert response.json()[1]["id"] == mastodon2.id
|
||||
assert response.json()[1]["access_token"] == mastodon2.access_token
|
||||
|
||||
|
||||
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 = client.get("/api/hoods/wrong/mastodon", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
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
|
||||
assert response.json()[0]["instance"] == mastodon_instance.name
|
||||
|
|
@ -9,13 +9,13 @@ from kibicara.model import Hood
|
|||
from kibicara.platforms.telegram.model import Telegram
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@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'],
|
||||
api_token=bot["api_token"],
|
||||
welcome_message=bot["welcome_message"],
|
||||
)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -10,16 +10,16 @@ from kibicara.platforms import telegram
|
|||
from kibicara.platforms.telegram.model import Telegram
|
||||
|
||||
|
||||
@fixture(scope='function')
|
||||
@fixture(scope="function")
|
||||
def disable_spawner(monkeypatch):
|
||||
class DoNothing:
|
||||
def start(self, bot):
|
||||
assert bot is not None
|
||||
|
||||
monkeypatch.setattr(telegram.webapi, 'spawner', DoNothing())
|
||||
monkeypatch.setattr(telegram.webapi, "spawner", DoNothing())
|
||||
|
||||
|
||||
@mark.parametrize('body', [{'api_token': 'string', 'welcome_message': 'string'}])
|
||||
@mark.parametrize("body", [{"api_token": "string", "welcome_message": "string"}])
|
||||
def test_telegram_create_bot(
|
||||
event_loop,
|
||||
client,
|
||||
|
|
@ -32,27 +32,27 @@ def test_telegram_create_bot(
|
|||
def check_token_mock(token):
|
||||
return True
|
||||
|
||||
monkeypatch.setattr(telegram.webapi, 'check_token', check_token_mock)
|
||||
monkeypatch.setattr(telegram.webapi, "check_token", check_token_mock)
|
||||
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/telegram/'.format(hood_id),
|
||||
"/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']
|
||||
bot_id = response.json()["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()["api_token"] == body["api_token"] == telegram_obj.api_token
|
||||
assert (
|
||||
response.json()['welcome_message']
|
||||
== body['welcome_message']
|
||||
response.json()["welcome_message"]
|
||||
== body["welcome_message"]
|
||||
== telegram_obj.welcome_message
|
||||
)
|
||||
assert response.json()['hood']['id'] == telegram_obj.hood.id
|
||||
assert response.json()["hood"]["id"] == telegram_obj.hood.id
|
||||
assert telegram_obj.enabled
|
||||
|
||||
|
||||
@mark.parametrize('body', [{'api_token': 'string', 'welcome_message': 'string'}])
|
||||
@mark.parametrize("body", [{"api_token": "string", "welcome_message": "string"}])
|
||||
def test_telegram_invalid_api_token(
|
||||
event_loop,
|
||||
client,
|
||||
|
|
@ -63,7 +63,7 @@ def test_telegram_invalid_api_token(
|
|||
body,
|
||||
):
|
||||
response = client.post(
|
||||
'/api/hoods/{0}/telegram/'.format(hood_id),
|
||||
"/api/hoods/{0}/telegram/".format(hood_id),
|
||||
json=body,
|
||||
headers=auth_header,
|
||||
)
|
||||
|
|
@ -71,12 +71,12 @@ def test_telegram_invalid_api_token(
|
|||
|
||||
|
||||
def test_telegram_create_telegram_invalid_id(client, auth_header):
|
||||
response = client.post('/api/hoods/1337/telegram/', headers=auth_header)
|
||||
response = client.post("/api/hoods/1337/telegram/", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = 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
|
||||
|
||||
|
||||
def test_telegram_create_unauthorized(client, hood_id):
|
||||
response = client.post('/api/hoods/{hood_id}/telegram/')
|
||||
response = client.post("/api/hoods/{hood_id}/telegram/")
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ from pytest import mark, raises
|
|||
from kibicara.platforms.telegram.model import Telegram, TelegramUser
|
||||
|
||||
|
||||
@mark.parametrize('bot', [{'api_token': 'apitoken123', 'welcome_message': 'msg'}])
|
||||
@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)
|
||||
|
|
@ -19,7 +19,7 @@ def test_telegram_delete_bot(client, event_loop, bot, telegram, auth_header):
|
|||
TelegramUser.objects.create(user_id=5678, bot=telegram.id)
|
||||
)
|
||||
response = client.delete(
|
||||
'/api/hoods/{0}/telegram/{1}'.format(telegram.hood.id, telegram.id),
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id),
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||
|
|
@ -30,23 +30,23 @@ def test_telegram_delete_bot(client, event_loop, bot, telegram, 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)
|
||||
response = client.delete("/api/hoods/1337/telegram/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = 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 = client.delete(
|
||||
'/api/hoods/{0}/telegram/7331'.format(hood_id), headers=auth_header
|
||||
"/api/hoods/{0}/telegram/7331".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = client.delete(
|
||||
'/api/hoods/{0}/telegram/wrong'.format(hood_id), headers=auth_header
|
||||
"/api/hoods/{0}/telegram/wrong".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@mark.parametrize('bot', [{'api_token': 'apitoken123', 'welcome_message': 'msg'}])
|
||||
@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)
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id)
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
|||
|
|
@ -7,36 +7,36 @@ from fastapi import status
|
|||
from pytest import mark
|
||||
|
||||
|
||||
@mark.parametrize('bot', [{'api_token': 'apitoken123', 'welcome_message': 'msg'}])
|
||||
@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),
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id),
|
||||
headers=auth_header,
|
||||
)
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()['id'] == telegram.id
|
||||
assert response.json()['api_token'] == telegram.api_token
|
||||
assert response.json()['welcome_message'] == telegram.welcome_message
|
||||
assert response.json()["id"] == telegram.id
|
||||
assert response.json()["api_token"] == telegram.api_token
|
||||
assert response.json()["welcome_message"] == telegram.welcome_message
|
||||
|
||||
|
||||
def test_telegram_get_bot_invalid_id(client, auth_header, hood_id):
|
||||
response = client.get('/api/hoods/1337/telegram/123', headers=auth_header)
|
||||
response = client.get("/api/hoods/1337/telegram/123", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = 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 = client.get(
|
||||
'/api/hoods/{0}/telegram/7331'.format(hood_id), headers=auth_header
|
||||
"/api/hoods/{0}/telegram/7331".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = client.get(
|
||||
'/api/hoods/{0}/telegram/wrong'.format(hood_id), headers=auth_header
|
||||
"/api/hoods/{0}/telegram/wrong".format(hood_id), headers=auth_header
|
||||
)
|
||||
assert response.status_code == status.HTTP_422_UNPROCESSABLE_ENTITY
|
||||
|
||||
|
||||
@mark.parametrize('bot', [{'api_token': 'apitoken456', 'welcome_message': 'msg'}])
|
||||
@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)
|
||||
"/api/hoods/{0}/telegram/{1}".format(telegram.hood.id, telegram.id)
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
|||
|
|
@ -14,34 +14,34 @@ def test_telegram_get_bots(client, auth_header, event_loop, hood_id):
|
|||
telegram0 = event_loop.run_until_complete(
|
||||
Telegram.objects.create(
|
||||
hood=hood,
|
||||
api_token='api_token123',
|
||||
welcome_message='welcome_message123',
|
||||
api_token="api_token123",
|
||||
welcome_message="welcome_message123",
|
||||
)
|
||||
)
|
||||
telegram1 = event_loop.run_until_complete(
|
||||
Telegram.objects.create(
|
||||
hood=hood,
|
||||
api_token='api_token456',
|
||||
welcome_message='welcome_message123',
|
||||
api_token="api_token456",
|
||||
welcome_message="welcome_message123",
|
||||
)
|
||||
)
|
||||
response = client.get(
|
||||
'/api/hoods/{0}/telegram'.format(telegram0.hood.id), headers=auth_header
|
||||
"/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
|
||||
assert response.json()[0]['api_token'] == telegram0.api_token
|
||||
assert response.json()[1]['id'] == telegram1.id
|
||||
assert response.json()[1]['api_token'] == telegram1.api_token
|
||||
assert response.json()[0]["id"] == telegram0.id
|
||||
assert response.json()[0]["api_token"] == telegram0.api_token
|
||||
assert response.json()[1]["id"] == telegram1.id
|
||||
assert response.json()[1]["api_token"] == telegram1.api_token
|
||||
|
||||
|
||||
def test_telegram_get_bots_invalid_id(client, auth_header, hood_id):
|
||||
response = client.get('/api/hoods/1337/telegram', headers=auth_header)
|
||||
response = client.get("/api/hoods/1337/telegram", headers=auth_header)
|
||||
assert response.status_code == status.HTTP_404_NOT_FOUND
|
||||
response = 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
|
||||
|
||||
|
||||
def test_telegram_get_bots_unauthorized(client, hood_id):
|
||||
response = client.get('/api/hoods/{0}/telegram'.format(hood_id))
|
||||
response = client.get("/api/hoods/{0}/telegram".format(hood_id))
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
|
|
|
|||
|
|
@ -1,15 +1,41 @@
|
|||
# KibicaraFrontend
|
||||
# Kibicara Frontend
|
||||
|
||||
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 9.1.4.
|
||||
## Maintenance
|
||||
### Compatibility List
|
||||
|
||||
## Development server
|
||||
** The current compatible nodejs version is nodejs18 **
|
||||
|
||||
To check which nodejs version is required for an angular version, see [this stackoverflow post](https://stackoverflow.com/questions/60248452/is-there-a-compatibility-list-for-angular-angular-cli-and-node-js).
|
||||
|
||||
### Updating Angular to the newest version
|
||||
|
||||
To update Angular to a newer version, please refer to the [official Angular update page](https://update.angular.io/).
|
||||
|
||||
tldr of the update process:
|
||||
0. Check which Angular version this project is currently using by looking at the version of @angular/core in the [package.json](./package.json) file.
|
||||
1. Decide to which version you want to bump (e.g. 9.2 to 15.2). This depends which node version is running on the servers and which one is compatible with the angular version (see stackoverflow post above).
|
||||
2. Add all existing dependencies listed on the update page e.g. `npm run-script ng add @angular/localize`
|
||||
3. Bump the versions: You need to bump to every major version, so from 9.2 to 15.2 you will need to repeat these steps for 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> 15
|
||||
1. Version bump to the next qangular/core and @angular/cli version (e.g. here we do it from version 9 to version 10): `npx @angular/cli@10 update @angular/core@10 @angular/cli@10`
|
||||
2. Version bump material: `npx @angular/cli@10 update @angular/material@10`
|
||||
3. Update ngx-markdown to the next version: `npm install ngx-markdown@10 --save-dev`
|
||||
4. Test if the frontend still works and fix all errors: `npm run-script ng s -o`
|
||||
4. Check the official angular update page for any breaking changes and google how to fix them: `ng generate @angular/material:mdc-migration`
|
||||
|
||||
## Development
|
||||
|
||||
### Development server
|
||||
|
||||
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
|
||||
|
||||
## Code scaffolding
|
||||
### Code scaffolding
|
||||
|
||||
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
|
||||
|
||||
### Updating the openapi frontend part
|
||||
|
||||
The frontend uses openapi-generator to generate the calls to the backend. To regenerate this after an API change, please run: `npm run openapi-generator`.
|
||||
|
||||
## Build
|
||||
|
||||
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"name": "kibicara-frontend",
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"openapi-generator": "openapi-generator generate -g typescript-angular --additional-properties=providedInRoot=true -o src/app/core/api -i http://localhost:8000/api/openapi.json",
|
||||
"openapi-generator": "wget http://localhost:8000/api/openapi.json && openapi-generator generate -g typescript-angular --additional-properties=providedInRoot=true -o src/app/core/api -i openapi.json && rm openapi.json",
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ api/api.ts
|
|||
api/badwords.service.ts
|
||||
api/email.service.ts
|
||||
api/hoods.service.ts
|
||||
api/mastodon.service.ts
|
||||
api/telegram.service.ts
|
||||
api/test.service.ts
|
||||
api/triggers.service.ts
|
||||
|
|
@ -16,9 +17,10 @@ git_push.sh
|
|||
index.ts
|
||||
model/bodyAccessToken.ts
|
||||
model/bodyAdmin.ts
|
||||
model/bodyAdminLoginAdminLoginPost.ts
|
||||
model/bodyBadWord.ts
|
||||
model/bodyHood.ts
|
||||
model/bodyLogin.ts
|
||||
model/bodyMastodonAccount.ts
|
||||
model/bodyPassword.ts
|
||||
model/bodySubscriber.ts
|
||||
model/bodyTelegram.ts
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ import { AdminService } from './api/admin.service';
|
|||
import { BadwordsService } from './api/badwords.service';
|
||||
import { EmailService } from './api/email.service';
|
||||
import { HoodsService } from './api/hoods.service';
|
||||
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: [],
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class AdminService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
@ -256,7 +256,7 @@ export class AdminService {
|
|||
|
||||
/**
|
||||
* Admin Read
|
||||
* Get a list of all hoods of a given admin.
|
||||
* Get a list of all hoods of a given admin.
|
||||
* @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.
|
||||
*/
|
||||
|
|
@ -305,7 +305,7 @@ export class AdminService {
|
|||
|
||||
/**
|
||||
* Admin Hood Read All
|
||||
* Get a list of all hoods of a given admin.
|
||||
* Get a list of all hoods of a given admin.
|
||||
* @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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,12 +6,12 @@ export * from './email.service';
|
|||
import { EmailService } from './email.service';
|
||||
export * from './hoods.service';
|
||||
import { HoodsService } from './hoods.service';
|
||||
export * from './mastodon.service';
|
||||
import { MastodonService } from './mastodon.service';
|
||||
export * from './telegram.service';
|
||||
import { TelegramService } from './telegram.service';
|
||||
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, TelegramService, TestService, TriggersService, TwitterService];
|
||||
export const APIS = [AdminService, BadwordsService, EmailService, HoodsService, MastodonService, TelegramService, TestService, TriggersService];
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class BadwordsService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
@ -154,7 +154,7 @@ export class BadwordsService {
|
|||
|
||||
/**
|
||||
* Badword Delete
|
||||
* Deletes badword with id **badword_id** for hood with id **hood_id**.
|
||||
* Deletes badword with id **badword_id** for hood with id **hood_id**.
|
||||
* @param badwordId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
|
|
@ -211,7 +211,7 @@ export class BadwordsService {
|
|||
|
||||
/**
|
||||
* Badword Read
|
||||
* Reads badword with id **badword_id** for hood with id **hood_id**.
|
||||
* Reads badword with id **badword_id** for hood with id **hood_id**.
|
||||
* @param badwordId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
|
|
@ -268,7 +268,7 @@ export class BadwordsService {
|
|||
|
||||
/**
|
||||
* Badword Read All
|
||||
* Get all badwords of hood with id **hood_id**.
|
||||
* Get all badwords of hood with id **hood_id**.
|
||||
* @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.
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class EmailService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
@ -207,7 +207,7 @@ export class EmailService {
|
|||
|
||||
/**
|
||||
* Email Delete
|
||||
* Delete an Email bot. Stops and deletes the Email bot. :param hood: Hood the Email bot belongs to.
|
||||
* Delete an Email bot. Stops and deletes the Email bot. :param hood: Hood the Email bot belongs to.
|
||||
* @param emailId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class HoodsService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
@ -150,7 +150,7 @@ export class HoodsService {
|
|||
|
||||
/**
|
||||
* Hood Delete
|
||||
* Deletes hood with id **hood_id**.
|
||||
* Deletes hood with id **hood_id**.
|
||||
* @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.
|
||||
|
|
@ -203,7 +203,7 @@ export class HoodsService {
|
|||
|
||||
/**
|
||||
* Hood Read
|
||||
* Get hood with id **hood_id**.
|
||||
* Get hood with id **hood_id**.
|
||||
* @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.
|
||||
|
|
@ -249,7 +249,7 @@ export class HoodsService {
|
|||
|
||||
/**
|
||||
* Hood Read All
|
||||
* Get all existing hoods.
|
||||
* Get all existing hoods.
|
||||
* @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.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import { HttpClient, HttpHeaders, HttpParams,
|
|||
import { CustomHttpParameterCodec } from '../encoder';
|
||||
import { Observable } from 'rxjs';
|
||||
|
||||
import { BodyMastodonAccount } from '../model/models';
|
||||
import { HTTPValidationError } from '../model/models';
|
||||
|
||||
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||
|
|
@ -27,9 +28,9 @@ import { Configuration } from '../configurat
|
|||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class TwitterService {
|
||||
export class MastodonService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
@ -85,39 +86,22 @@ export class TwitterService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Twitter Read Callback
|
||||
* @param oauthToken
|
||||
* @param oauthVerifier
|
||||
* Mastodon Create
|
||||
* Add a Mastodon Account to a Ticketfrei account. open questions: can the instance_url have different ways of writing? :param: values: a BodyMastodonAccount object in json :param: hood: the hood ORM object
|
||||
* @param hoodId
|
||||
* @param bodyMastodonAccount
|
||||
* @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.');
|
||||
}
|
||||
public createMastodon(hoodId: number, bodyMastodonAccount: BodyMastodonAccount, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public createMastodon(hoodId: number, bodyMastodonAccount: BodyMastodonAccount, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public createMastodon(hoodId: number, bodyMastodonAccount: BodyMastodonAccount, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public createMastodon(hoodId: number, bodyMastodonAccount: BodyMastodonAccount, 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 callbackTwitter.');
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling createMastodon.');
|
||||
}
|
||||
|
||||
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');
|
||||
if (bodyMastodonAccount === null || bodyMastodonAccount === undefined) {
|
||||
throw new Error('Required parameter bodyMastodonAccount was null or undefined when calling createMastodon.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
|
@ -142,14 +126,23 @@ export class TwitterService {
|
|||
}
|
||||
|
||||
|
||||
// to determine the Content-Type header
|
||||
const consumes: string[] = [
|
||||
'application/json'
|
||||
];
|
||||
const httpContentTypeSelected: string | undefined = this.configuration.selectHeaderContentType(consumes);
|
||||
if (httpContentTypeSelected !== undefined) {
|
||||
headers = headers.set('Content-Type', httpContentTypeSelected);
|
||||
}
|
||||
|
||||
let responseType: 'text' | 'json' = 'json';
|
||||
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/twitter/callback`,
|
||||
return this.httpClient.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/mastodon/`,
|
||||
bodyMastodonAccount,
|
||||
{
|
||||
params: queryParameters,
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
|
|
@ -160,18 +153,21 @@ export class TwitterService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Twitter Create
|
||||
* `https://api.twitter.com/oauth/authorize?oauth_token=`
|
||||
* Mastodon Delete
|
||||
* @param mastodonId
|
||||
* @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> {
|
||||
public deleteMastodon(mastodonId: any, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public deleteMastodon(mastodonId: any, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public deleteMastodon(mastodonId: any, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public deleteMastodon(mastodonId: any, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (mastodonId === null || mastodonId === undefined) {
|
||||
throw new Error('Required parameter mastodonId was null or undefined when calling deleteMastodon.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling createTwitter.');
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling deleteMastodon.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
|
@ -201,7 +197,216 @@ export class TwitterService {
|
|||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/`,
|
||||
return this.httpClient.delete<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/mastodon/${encodeURIComponent(String(mastodonId))}`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastodon Read
|
||||
* @param mastodonId
|
||||
* @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 getMastodon(mastodonId: any, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public getMastodon(mastodonId: any, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public getMastodon(mastodonId: any, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public getMastodon(mastodonId: any, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (mastodonId === null || mastodonId === undefined) {
|
||||
throw new Error('Required parameter mastodonId was null or undefined when calling getMastodon.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling getMastodon.');
|
||||
}
|
||||
|
||||
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))}/mastodon/${encodeURIComponent(String(mastodonId))}`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastodon 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 getMastodons(hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public getMastodons(hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public getMastodons(hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public getMastodons(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 getMastodons.');
|
||||
}
|
||||
|
||||
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))}/mastodon/`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastodon 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 getMastodonsPublic(hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public getMastodonsPublic(hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public getMastodonsPublic(hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public getMastodonsPublic(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 getMastodonsPublic.');
|
||||
}
|
||||
|
||||
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))}/mastodon/public`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
headers: headers,
|
||||
observe: observe,
|
||||
reportProgress: reportProgress
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Mastodon Start
|
||||
* @param mastodonId
|
||||
* @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 startMastodon(mastodonId: any, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public startMastodon(mastodonId: any, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public startMastodon(mastodonId: any, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public startMastodon(mastodonId: any, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (mastodonId === null || mastodonId === undefined) {
|
||||
throw new Error('Required parameter mastodonId was null or undefined when calling startMastodon.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling startMastodon.');
|
||||
}
|
||||
|
||||
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))}/mastodon/${encodeURIComponent(String(mastodonId))}/start`,
|
||||
null,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
|
|
@ -214,21 +419,21 @@ export class TwitterService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Twitter Delete
|
||||
* @param twitterId
|
||||
* Mastodon Status
|
||||
* @param mastodonId
|
||||
* @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.');
|
||||
public statusMastodon(mastodonId: any, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public statusMastodon(mastodonId: any, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public statusMastodon(mastodonId: any, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public statusMastodon(mastodonId: any, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (mastodonId === null || mastodonId === undefined) {
|
||||
throw new Error('Required parameter mastodonId was null or undefined when calling statusMastodon.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling deleteTwitter.');
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling statusMastodon.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
|
@ -258,7 +463,7 @@ export class TwitterService {
|
|||
responseType = 'text';
|
||||
}
|
||||
|
||||
return this.httpClient.delete<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/twitter/${encodeURIComponent(String(twitterId))}`,
|
||||
return this.httpClient.get<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/mastodon/${encodeURIComponent(String(mastodonId))}/status`,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
withCredentials: this.configuration.withCredentials,
|
||||
|
|
@ -270,21 +475,21 @@ export class TwitterService {
|
|||
}
|
||||
|
||||
/**
|
||||
* Twitter Read
|
||||
* @param twitterId
|
||||
* Mastodon Stop
|
||||
* @param mastodonId
|
||||
* @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.');
|
||||
public stopMastodon(mastodonId: any, hoodId: number, observe?: 'body', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<any>;
|
||||
public stopMastodon(mastodonId: any, hoodId: number, observe?: 'response', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpResponse<any>>;
|
||||
public stopMastodon(mastodonId: any, hoodId: number, observe?: 'events', reportProgress?: boolean, options?: {httpHeaderAccept?: 'application/json'}): Observable<HttpEvent<any>>;
|
||||
public stopMastodon(mastodonId: any, hoodId: number, observe: any = 'body', reportProgress: boolean = false, options?: {httpHeaderAccept?: 'application/json'}): Observable<any> {
|
||||
if (mastodonId === null || mastodonId === undefined) {
|
||||
throw new Error('Required parameter mastodonId was null or undefined when calling stopMastodon.');
|
||||
}
|
||||
if (hoodId === null || hoodId === undefined) {
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling getTwitter.');
|
||||
throw new Error('Required parameter hoodId was null or undefined when calling stopMastodon.');
|
||||
}
|
||||
|
||||
let headers = this.defaultHeaders;
|
||||
|
|
@ -314,273 +519,7 @@ export class TwitterService {
|
|||
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`,
|
||||
return this.httpClient.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/mastodon/${encodeURIComponent(String(mastodonId))}/stop`,
|
||||
null,
|
||||
{
|
||||
responseType: <any>responseType,
|
||||
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class TelegramService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class TestService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
|||
})
|
||||
export class TriggersService {
|
||||
|
||||
protected basePath = 'http://localhost';
|
||||
protected basePath = 'http://localhost/api';
|
||||
public defaultHeaders = new HttpHeaders();
|
||||
public configuration = new Configuration();
|
||||
public encoder: HttpParameterCodec;
|
||||
|
|
@ -154,7 +154,7 @@ export class TriggersService {
|
|||
|
||||
/**
|
||||
* Trigger Delete
|
||||
* Deletes trigger with id **trigger_id** for hood with id **hood_id**.
|
||||
* Deletes trigger with id **trigger_id** for hood with id **hood_id**.
|
||||
* @param triggerId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
|
|
@ -211,7 +211,7 @@ export class TriggersService {
|
|||
|
||||
/**
|
||||
* Trigger Read
|
||||
* Reads trigger with id **trigger_id** for hood with id **hood_id**.
|
||||
* Reads trigger with id **trigger_id** for hood with id **hood_id**.
|
||||
* @param triggerId
|
||||
* @param hoodId
|
||||
* @param observe set whether or not to return the data Observable as the body, response or events. defaults to returning the body.
|
||||
|
|
@ -268,7 +268,7 @@ export class TriggersService {
|
|||
|
||||
/**
|
||||
* Trigger Read All
|
||||
* Get all triggers of hood with id **hood_id**.
|
||||
* Get all triggers of hood with id **hood_id**.
|
||||
* @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.
|
||||
|
|
|
|||
22
frontend/src/app/core/api/model/bodyLogin.ts
Normal file
22
frontend/src/app/core/api/model/bodyLogin.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
export interface BodyLogin {
|
||||
grant_type?: string;
|
||||
username: string;
|
||||
password: string;
|
||||
scope?: string;
|
||||
client_id?: string;
|
||||
client_secret?: string;
|
||||
}
|
||||
|
||||
19
frontend/src/app/core/api/model/bodyMastodonAccount.ts
Normal file
19
frontend/src/app/core/api/model/bodyMastodonAccount.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
/**
|
||||
* 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.
|
||||
*/
|
||||
|
||||
|
||||
export interface BodyMastodonAccount {
|
||||
email: string;
|
||||
instance_url: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
|
||||
/**
|
||||
* This model holds the email address of a fresh subscriber.
|
||||
* This model holds the email address of a fresh subscriber.
|
||||
*/
|
||||
export interface BodySubscriber {
|
||||
email: string;
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
|
||||
/**
|
||||
* This model shows which values are supplied by the MDA listener script.
|
||||
* This model shows which values are supplied by the MDA listener script.
|
||||
*/
|
||||
export interface KibicaraPlatformsEmailWebapiBodyMessage {
|
||||
text: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
export * from './bodyAccessToken';
|
||||
export * from './bodyAdmin';
|
||||
export * from './bodyAdminLoginAdminLoginPost';
|
||||
export * from './bodyBadWord';
|
||||
export * from './bodyHood';
|
||||
export * from './bodyLogin';
|
||||
export * from './bodyMastodonAccount';
|
||||
export * from './bodyPassword';
|
||||
export * from './bodySubscriber';
|
||||
export * from './bodyTelegram';
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@
|
|||
|
||||
|
||||
export interface ValidationError {
|
||||
loc: Array<string>;
|
||||
loc: Array<string | number>;
|
||||
msg: string;
|
||||
type: string;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,6 +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],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
<div *ngIf="mastodons$ | loading | async as mastodons">
|
||||
<ng-template [ngIf]="mastodons.value">
|
||||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<div mat-card-avatar class="mastodon"></div>
|
||||
<mat-card-title class="platform-title">
|
||||
mastodon
|
||||
<button mat-icon-button aria-label="How to use">
|
||||
<mat-icon
|
||||
matTooltip="How to send and receive hood broadcast messages with mastodon"
|
||||
class="info-button"
|
||||
(click)="onInfoClick()"
|
||||
>info</mat-icon
|
||||
>
|
||||
</button>
|
||||
</mat-card-title>
|
||||
</mat-card-header>
|
||||
<mat-card-content *ngIf="mastodons.value.length !== 0; else nomastodon">
|
||||
<mat-selection-list [multiple]="false" class="list">
|
||||
<a
|
||||
*ngFor="let mastodon of mastodons.value"
|
||||
href="https://{{mastodon.instance}}/@{{ mastodon.username }}"
|
||||
routerLinkActive="router-link-active"
|
||||
>
|
||||
<mat-list-option>
|
||||
@{{ mastodon.username }}
|
||||
<mat-divider></mat-divider>
|
||||
</mat-list-option>
|
||||
</a>
|
||||
</mat-selection-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
<ng-template #nomastodon>
|
||||
<mat-card-content>
|
||||
Unfortunately your hood admin has not configured mastodon as platform
|
||||
yet.
|
||||
</mat-card-content>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template [ngIf]="mastodons.error"
|
||||
><mat-icon class="warning">warning</mat-icon></ng-template
|
||||
>
|
||||
<ng-template [ngIf]="mastodons.loading">
|
||||
<mat-spinner [diameter]="45" class="spinner"></mat-spinner>
|
||||
</ng-template>
|
||||
</div>
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MastodonBotCardComponent } from './mastodon-bot-card.component';
|
||||
|
||||
describe('MastodonBotCardComponent', () => {
|
||||
let component: MastodonBotCardComponent;
|
||||
let fixture: ComponentFixture<MastodonBotCardComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ MastodonBotCardComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MastodonBotCardComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { MastodonService } from 'src/app/core/api';
|
||||
import { MastodonBotInfoDialogComponent } from './mastodon-bot-info-dialog/mastodon-bot-info-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mastodon-bot-card',
|
||||
templateUrl: './mastodon-bot-card.component.html',
|
||||
styleUrls: ['./mastodon-bot-card.component.scss'],
|
||||
})
|
||||
export class MastodonBotCardComponent implements OnInit {
|
||||
@Input() hoodId;
|
||||
mastodons$;
|
||||
|
||||
constructor(
|
||||
private mastodonService: MastodonService,
|
||||
private dialog: MatDialog
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.mastodons$ = this.mastodonService.getMastodonsPublic(this.hoodId);
|
||||
}
|
||||
|
||||
onInfoClick() {
|
||||
this.dialog.open(MastodonBotInfoDialogComponent);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<mat-dialog-content>
|
||||
<div class="container">
|
||||
<h2>How to communicate with the hood via Telegram?</h2>
|
||||
<mat-accordion>
|
||||
<mat-expansion-panel>
|
||||
<mat-expansion-panel-header>
|
||||
<mat-panel-title
|
||||
>How to subscribe to the hood via Telegram?</mat-panel-title
|
||||
>
|
||||
</mat-expansion-panel-header>
|
||||
<ol>
|
||||
<li>
|
||||
Click on the telegram bot name that is shown in the telegram card.
|
||||
</li>
|
||||
<li>
|
||||
Start messaging the telegram bot that the link leads to by writing a
|
||||
message containing only the word <strong>/start</strong>. Done!
|
||||
</li>
|
||||
</ol>
|
||||
</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>
|
||||
Write a direct message to the bot. This message will be broadcasted to
|
||||
the other platforms.
|
||||
</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>
|
||||
If you subscribed to the bot, you will automatically receive the
|
||||
messages of your hood from the bot.
|
||||
</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>
|
||||
Write a message with content <strong>/stop</strong> to the bot. You
|
||||
should receive a message from the bot which confirms that the
|
||||
unsubscription was successful.
|
||||
</p>
|
||||
</mat-expansion-panel>
|
||||
</mat-accordion>
|
||||
</div>
|
||||
</mat-dialog-content>
|
||||
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { Component, OnInit } from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mastodon-bot-info-dialog',
|
||||
templateUrl: './mastodon-bot-info-dialog.component.html',
|
||||
styleUrls: ['./mastodon-bot-info-dialog.component.scss']
|
||||
})
|
||||
export class MastodonBotInfoDialogComponent implements OnInit {
|
||||
constructor() {}
|
||||
|
||||
ngOnInit(): void {}
|
||||
}
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
<h2 mat-dialog-title>Create new inbox</h2>
|
||||
|
||||
<mat-dialog-content>
|
||||
<form class="input" [formGroup]="form">
|
||||
<mat-form-field>
|
||||
<mat-label>Mastodon Instance URL</mat-label>
|
||||
<input matInput formControlName="instance_url" />
|
||||
<mat-error
|
||||
*ngIf="
|
||||
form.controls.instance_url.errors &&
|
||||
form.controls.instance_url.errors.required
|
||||
"
|
||||
>Mastodon Instance URL is required</mat-error
|
||||
>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Mastodon E-Mail</mat-label>
|
||||
<input matInput formControlName="email" />
|
||||
<mat-error
|
||||
*ngIf="
|
||||
form.controls.email.errors &&
|
||||
form.controls.email.errors.required
|
||||
"
|
||||
>Mastodon E-Mail is required</mat-error
|
||||
>
|
||||
</mat-form-field>
|
||||
<mat-form-field>
|
||||
<mat-label>Mastodon Password</mat-label>
|
||||
<input matInput formControlName="password" />
|
||||
<mat-error
|
||||
*ngIf="
|
||||
form.controls.password.errors &&
|
||||
form.controls.password.errors.required
|
||||
"
|
||||
>Mastodon Password is required</mat-error
|
||||
>
|
||||
</mat-form-field>
|
||||
</form>
|
||||
</mat-dialog-content>
|
||||
|
||||
<mat-dialog-actions align="end">
|
||||
<button mat-button (click)="onCancel()">Cancel</button>
|
||||
<button mat-button (click)="onSubmit()" cdkFocusInitial>
|
||||
Add Mastodon bot
|
||||
</button>
|
||||
</mat-dialog-actions>
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
.input {
|
||||
display: grid;
|
||||
grid-template-rows: 1fr 1fr 1fr;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
form {
|
||||
margin-top: 10px;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
height: 120px;
|
||||
}
|
||||
|
||||
.example-image {
|
||||
margin-left: 10%;
|
||||
margin-right: 10%;
|
||||
width: 80%;
|
||||
@media screen and (max-width: 600px) {
|
||||
width: 100%;
|
||||
margin-left: 0%;
|
||||
margin-right: 0%;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MastodonDialogComponent } from './mastodon-dialog.component';
|
||||
|
||||
describe('MastodonDialogComponent', () => {
|
||||
let component: MastodonDialogComponent;
|
||||
let fixture: ComponentFixture<MastodonDialogComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ MastodonDialogComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MastodonDialogComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { Component, OnInit, Inject } from '@angular/core';
|
||||
import { Validators, UntypedFormBuilder, UntypedFormGroup } from '@angular/forms';
|
||||
import { MatDialogRef, MAT_DIALOG_DATA } from '@angular/material/dialog';
|
||||
import { MastodonService } from 'src/app/core/api';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
import { first } from 'rxjs/operators';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mastodon-dialog',
|
||||
templateUrl: './mastodon-dialog.component.html',
|
||||
styleUrls: ['./mastodon-dialog.component.scss'],
|
||||
})
|
||||
export class MastodonDialogComponent implements OnInit {
|
||||
form: UntypedFormGroup;
|
||||
|
||||
constructor(
|
||||
public dialogRef: MatDialogRef<MastodonDialogComponent>,
|
||||
private formBuilder: UntypedFormBuilder,
|
||||
private mastodonService: MastodonService,
|
||||
private snackBar: MatSnackBar,
|
||||
@Inject(MAT_DIALOG_DATA) public data
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.form = this.formBuilder.group({
|
||||
email: ['', Validators.required],
|
||||
password: ['', Validators.required],
|
||||
instance_url: ['', Validators.required],
|
||||
});
|
||||
|
||||
if (this.data.mastodonId) {
|
||||
this.mastodonService
|
||||
.getMastodon(this.data.mastodonId, this.data.hoodId)
|
||||
.subscribe((data) => {
|
||||
this.form.controls.email.setValue(data.email);
|
||||
this.form.controls.password.setValue(data.password);
|
||||
this.form.controls.instance_url.setValue(data.instance_url);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
onCancel() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
success() {
|
||||
this.dialogRef.close();
|
||||
}
|
||||
|
||||
error() {
|
||||
this.snackBar.open('Invalid API Key. Try again!', 'Close', {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
|
||||
onSubmit() {
|
||||
if (this.form.invalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = {
|
||||
email: this.form.controls.email.value,
|
||||
instance_url: this.form.controls.instance_url.value,
|
||||
password: this.form.controls.password.value
|
||||
}
|
||||
|
||||
this.mastodonService
|
||||
.createMastodon(this.data.hoodId, response)
|
||||
.pipe(first())
|
||||
.subscribe(
|
||||
() => {
|
||||
this.success();
|
||||
},
|
||||
() => {
|
||||
this.error();
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<mat-card appearance="outlined">
|
||||
<mat-card-header>
|
||||
<div mat-card-avatar class="mastodon"></div>
|
||||
<mat-card-title class="platform-title">
|
||||
Mastodon
|
||||
<button mat-icon-button aria-label="How to use">
|
||||
<mat-icon
|
||||
matTooltip="How to add an mastodon 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="mastodons$ | loading | async as mastodons">
|
||||
<ng-template [ngIf]="mastodons.value">
|
||||
<mat-list-item *ngIf="mastodons.value.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 mastodon of mastodons.value">
|
||||
<div class="entry">
|
||||
@{{ mastodon.username }}
|
||||
<mat-slide-toggle
|
||||
[checked]="mastodon.enabled === 1"
|
||||
(change)="onChange(mastodon)"
|
||||
></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)="onEdit(mastodon.id)">
|
||||
<mat-icon>edit</mat-icon>
|
||||
<span>Edit</span>
|
||||
</button>
|
||||
<button mat-menu-item (click)="onDelete(mastodon.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]="mastodons.error"
|
||||
><mat-icon class="warning">warning</mat-icon></ng-template
|
||||
>
|
||||
<ng-template [ngIf]="mastodons.loading">
|
||||
<mat-spinner [diameter]="45" class="spinner"></mat-spinner>
|
||||
</ng-template>
|
||||
</mat-list>
|
||||
</mat-card-content>
|
||||
</mat-card>
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
.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;
|
||||
}
|
||||
|
||||
.mastodon {
|
||||
background-image: url("../../../../assets/mastodon.png");
|
||||
background-size: cover;
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||
|
||||
import { MastodonSettingsComponent } from './mastodon-settings.component';
|
||||
|
||||
describe('MastodonSettingsComponent', () => {
|
||||
let component: MastodonSettingsComponent;
|
||||
let fixture: ComponentFixture<MastodonSettingsComponent>;
|
||||
|
||||
beforeEach(async () => {
|
||||
await TestBed.configureTestingModule({
|
||||
declarations: [ MastodonSettingsComponent ]
|
||||
})
|
||||
.compileComponents();
|
||||
|
||||
fixture = TestBed.createComponent(MastodonSettingsComponent);
|
||||
component = fixture.componentInstance;
|
||||
fixture.detectChanges();
|
||||
});
|
||||
|
||||
it('should create', () => {
|
||||
expect(component).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
import { Component, OnInit, Input } from '@angular/core';
|
||||
import { MastodonService } from 'src/app/core/api';
|
||||
import { Observable } from 'rxjs';
|
||||
import { MastodonBotInfoDialogComponent } from '../mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component';
|
||||
import { MatDialog } from '@angular/material/dialog';
|
||||
import { MastodonDialogComponent } from './mastodon-dialog/mastodon-dialog.component';
|
||||
import { YesNoDialogComponent } from 'src/app/shared/yes-no-dialog/yes-no-dialog.component';
|
||||
import { MatSnackBar } from '@angular/material/snack-bar';
|
||||
|
||||
@Component({
|
||||
selector: 'app-mastodon-settings',
|
||||
templateUrl: './mastodon-settings.component.html',
|
||||
styleUrls: ['./mastodon-settings.component.scss'],
|
||||
})
|
||||
export class MastodonSettingsComponent implements OnInit {
|
||||
@Input() hoodId;
|
||||
mastodons$: Observable<Array<any>>;
|
||||
|
||||
constructor(
|
||||
private mastodonService: MastodonService,
|
||||
public dialog: MatDialog,
|
||||
private snackBar: MatSnackBar
|
||||
) {}
|
||||
|
||||
ngOnInit(): void {
|
||||
this.reload();
|
||||
}
|
||||
|
||||
private reload() {
|
||||
this.mastodons$ = this.mastodonService.getMastodons(this.hoodId);
|
||||
}
|
||||
|
||||
onInfoClick() {
|
||||
this.dialog.open(MastodonBotInfoDialogComponent);
|
||||
}
|
||||
|
||||
onDelete(mastodonId) {
|
||||
const dialogRef = this.dialog.open(YesNoDialogComponent, {
|
||||
data: {
|
||||
title: 'Warning',
|
||||
content:
|
||||
'This will also delete the list of subscribers of the mastodon bot.',
|
||||
},
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe((response) => {
|
||||
if (response) {
|
||||
this.mastodonService
|
||||
.deleteMastodon(mastodonId, this.hoodId)
|
||||
.subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
onCreate() {
|
||||
const dialogRef = this.dialog.open(MastodonDialogComponent, {
|
||||
data: { hoodId: this.hoodId },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
|
||||
onEdit(mastodonId) {
|
||||
const dialogRef = this.dialog.open(MastodonDialogComponent, {
|
||||
data: { hoodId: this.hoodId, mastodonId },
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(() => {
|
||||
this.reload();
|
||||
});
|
||||
}
|
||||
|
||||
onChange(mastodon) {
|
||||
if (mastodon.enabled === 0) {
|
||||
this.mastodonService.startMastodon(mastodon.id, this.hoodId).subscribe(
|
||||
() => {},
|
||||
(error) => {
|
||||
this.snackBar.open('Could not start. Check your settings.', 'Close', {
|
||||
duration: 2000,
|
||||
});
|
||||
}
|
||||
);
|
||||
} else if (mastodon.enabled === 1) {
|
||||
this.mastodonService.stopMastodon(mastodon.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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
<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>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -2,51 +2,47 @@ 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';
|
||||
import { MastodonSettingsComponent } from './mastodon/mastodon-settings/mastodon-settings.component';
|
||||
import { MastodonDialogComponent } from './mastodon/mastodon-settings/mastodon-dialog/mastodon-dialog.component';
|
||||
import { MastodonBotInfoDialogComponent } from './mastodon/mastodon-bot-card/mastodon-bot-info-dialog/mastodon-bot-info-dialog.component';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
TelegramSettingsComponent,
|
||||
TwitterSettingsComponent,
|
||||
EmailSettingsComponent,
|
||||
EmailDialogComponent,
|
||||
EmailInfoDialogComponent,
|
||||
TelegramInfoDialogComponent,
|
||||
TelegramDialogComponent,
|
||||
TwitterInfoDialogComponent,
|
||||
TwitterCallbackComponent,
|
||||
TwitterCorpsesPipe,
|
||||
PlatformsInfoPageComponent,
|
||||
EmailBotCardComponent,
|
||||
TelegramBotCardComponent,
|
||||
TwitterBotCardComponent,
|
||||
EmailBotInfoDialogComponent,
|
||||
TelegramBotInfoDialogComponent,
|
||||
TwitterBotInfoDialogComponent,
|
||||
EmailConfirmationComponent,
|
||||
EmailUnsubscribeComponent,
|
||||
MastodonBotCardComponent,
|
||||
MastodonSettingsComponent,
|
||||
MastodonDialogComponent,
|
||||
MastodonBotInfoDialogComponent
|
||||
],
|
||||
imports: [CommonModule, SharedModule],
|
||||
exports: [
|
||||
TelegramSettingsComponent,
|
||||
TwitterSettingsComponent,
|
||||
MastodonSettingsComponent,
|
||||
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 {}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue