Add Mastodon bot to Ticketfrei 3 #2
|
@ -34,6 +34,8 @@ install_requires =
|
||||||
pytoml
|
pytoml
|
||||||
requests
|
requests
|
||||||
scrypt
|
scrypt
|
||||||
|
Mastodon.py
|
||||||
|
pydantic[email]
|
||||||
|
|
||||||
[options.packages.find]
|
[options.packages.find]
|
||||||
where = src
|
where = src
|
||||||
|
|
0
backend/src/kibicara/platforms/mastodon/__init__.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"]
|
||||||
|
|||||||
|
)
|
||||||
missytake
commented
This is for rate limiting, I assume? We could maybe make this a bit more dynamic. https://mastodonpy.readthedocs.io/en/stable/01_general.html#rate-limiting mentions that the default is 300 requests in 5 minutes, that would allow polling once per second instead of once per minute. But we don't know in advance how often we're gonna post, and the rate limit can be non-default. Using the This is for rate limiting, I assume? We could maybe make this a bit more dynamic.
https://mastodonpy.readthedocs.io/en/stable/01_general.html#rate-limiting mentions that the default is 300 requests in 5 minutes, that would allow polling once per second instead of once per minute. But we don't know in advance how often we're gonna post, and the rate limit can be non-default.
Using the `pace` rate limit option is probably not possible with async? What we could use instead is the `throw` option to throw a `MastodonRateLimitError` if we run against the rate limit, request https://mastodonpy.readthedocs.io/en/stable/01_general.html#rate-limiting, and decide upon that for how long we do an `await sleep()`.
missytake
commented
Or even better, use https://mastodonpy.readthedocs.io/en/stable/01_general.html#mastodon.Mastodon.ratelimit_remaining and https://mastodonpy.readthedocs.io/en/stable/01_general.html#mastodon.Mastodon.ratelimit_reset to guess how long we should sleep between requests, and if we hit a Or even better, use https://mastodonpy.readthedocs.io/en/stable/01_general.html#mastodon.Mastodon.ratelimit_remaining and https://mastodonpy.readthedocs.io/en/stable/01_general.html#mastodon.Mastodon.ratelimit_reset to guess how long we should sleep between requests, and if we hit a `MastodonRateLimitError`, we wait until https://mastodonpy.readthedocs.io/en/stable/01_general.html#mastodon.Mastodon.ratelimit_reset.
|
|||||||
|
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
|
@ -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
|
@ -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)
|
|
@ -15,6 +15,7 @@ from fastapi import APIRouter
|
||||||
from kibicara.platforms.email.webapi import router as email_router
|
from kibicara.platforms.email.webapi import router as email_router
|
||||||
from kibicara.platforms.telegram.webapi import router as telegram_router
|
from kibicara.platforms.telegram.webapi import router as telegram_router
|
||||||
from kibicara.platforms.test.webapi import router as test_router
|
from kibicara.platforms.test.webapi import router as test_router
|
||||||
|
from kibicara.platforms.mastodon.webapi import router as mastodon_router
|
||||||
from kibicara.platforms.twitter.webapi import router as twitter_router
|
from kibicara.platforms.twitter.webapi import router as twitter_router
|
||||||
from kibicara.platforms.twitter.webapi import twitter_callback_router
|
from kibicara.platforms.twitter.webapi import twitter_callback_router
|
||||||
from kibicara.webapi.admin import router as admin_router
|
from kibicara.webapi.admin import router as admin_router
|
||||||
|
@ -37,6 +38,9 @@ hoods_router.include_router(
|
||||||
hoods_router.include_router(
|
hoods_router.include_router(
|
||||||
twitter_router, prefix="/{hood_id}/twitter", tags=["twitter"]
|
twitter_router, prefix="/{hood_id}/twitter", tags=["twitter"]
|
||||||
)
|
)
|
||||||
|
hoods_router.include_router(
|
||||||
|
mastodon_router, prefix="/{hood_id}/mastodon", tags=["mastodon"]
|
||||||
|
)
|
||||||
router.include_router(twitter_callback_router, prefix="/twitter", tags=["twitter"])
|
router.include_router(twitter_callback_router, prefix="/twitter", tags=["twitter"])
|
||||||
hoods_router.include_router(email_router, prefix="/{hood_id}/email", tags=["email"])
|
hoods_router.include_router(email_router, prefix="/{hood_id}/email", tags=["email"])
|
||||||
router.include_router(hoods_router, prefix="/hoods")
|
router.include_router(hoods_router, prefix="/hoods")
|
||||||
|
|
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
|
@ -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
|
@ -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
|
@ -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
|
|
@ -6,6 +6,7 @@ api/api.ts
|
||||||
api/badwords.service.ts
|
api/badwords.service.ts
|
||||||
api/email.service.ts
|
api/email.service.ts
|
||||||
api/hoods.service.ts
|
api/hoods.service.ts
|
||||||
|
api/mastodon.service.ts
|
||||||
api/telegram.service.ts
|
api/telegram.service.ts
|
||||||
api/test.service.ts
|
api/test.service.ts
|
||||||
api/triggers.service.ts
|
api/triggers.service.ts
|
||||||
|
@ -16,9 +17,10 @@ git_push.sh
|
||||||
index.ts
|
index.ts
|
||||||
model/bodyAccessToken.ts
|
model/bodyAccessToken.ts
|
||||||
model/bodyAdmin.ts
|
model/bodyAdmin.ts
|
||||||
model/bodyAdminLoginAdminLoginPost.ts
|
|
||||||
missytake
commented
Why was this removed? Why was this removed?
|
|||||||
model/bodyBadWord.ts
|
model/bodyBadWord.ts
|
||||||
model/bodyHood.ts
|
model/bodyHood.ts
|
||||||
|
model/bodyLogin.ts
|
||||||
|
model/bodyMastodonAccount.ts
|
||||||
model/bodyPassword.ts
|
model/bodyPassword.ts
|
||||||
model/bodySubscriber.ts
|
model/bodySubscriber.ts
|
||||||
model/bodyTelegram.ts
|
model/bodyTelegram.ts
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { AdminService } from './api/admin.service';
|
||||||
import { BadwordsService } from './api/badwords.service';
|
import { BadwordsService } from './api/badwords.service';
|
||||||
import { EmailService } from './api/email.service';
|
import { EmailService } from './api/email.service';
|
||||||
import { HoodsService } from './api/hoods.service';
|
import { HoodsService } from './api/hoods.service';
|
||||||
|
import { MastodonService } from './api/mastodon.service';
|
||||||
import { TelegramService } from './api/telegram.service';
|
import { TelegramService } from './api/telegram.service';
|
||||||
import { TestService } from './api/test.service';
|
import { TestService } from './api/test.service';
|
||||||
import { TriggersService } from './api/triggers.service';
|
import { TriggersService } from './api/triggers.service';
|
||||||
|
|
|
@ -33,7 +33,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class AdminService {
|
export class AdminService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
|
@ -6,6 +6,8 @@ export * from './email.service';
|
||||||
import { EmailService } from './email.service';
|
import { EmailService } from './email.service';
|
||||||
export * from './hoods.service';
|
export * from './hoods.service';
|
||||||
import { HoodsService } from './hoods.service';
|
import { HoodsService } from './hoods.service';
|
||||||
|
export * from './mastodon.service';
|
||||||
|
import { MastodonService } from './mastodon.service';
|
||||||
export * from './telegram.service';
|
export * from './telegram.service';
|
||||||
import { TelegramService } from './telegram.service';
|
import { TelegramService } from './telegram.service';
|
||||||
export * from './test.service';
|
export * from './test.service';
|
||||||
|
@ -14,4 +16,4 @@ export * from './triggers.service';
|
||||||
import { TriggersService } from './triggers.service';
|
import { TriggersService } from './triggers.service';
|
||||||
export * from './twitter.service';
|
export * from './twitter.service';
|
||||||
import { TwitterService } 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, TwitterService];
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class BadwordsService {
|
export class BadwordsService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
|
@ -32,7 +32,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class EmailService {
|
export class EmailService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class HoodsService {
|
export class HoodsService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
534
frontend/src/app/core/api/api/mastodon.service.ts
Normal file
|
@ -0,0 +1,534 @@
|
||||||
|
/**
|
||||||
|
* FastAPI
|
||||||
|
* No description provided (generated by Openapi Generator https://github.com/openapitools/openapi-generator)
|
||||||
|
*
|
||||||
|
* The version of the OpenAPI document: 0.1.0
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
* https://openapi-generator.tech
|
||||||
|
* Do not edit the class manually.
|
||||||
|
*/
|
||||||
|
/* tslint:disable:no-unused-variable member-ordering */
|
||||||
|
|
||||||
|
import { Inject, Injectable, Optional } from '@angular/core';
|
||||||
|
import { HttpClient, HttpHeaders, HttpParams,
|
||||||
|
HttpResponse, HttpEvent, HttpParameterCodec } from '@angular/common/http';
|
||||||
|
import { CustomHttpParameterCodec } from '../encoder';
|
||||||
|
import { Observable } from 'rxjs';
|
||||||
|
|
||||||
|
import { BodyMastodonAccount } from '../model/models';
|
||||||
|
import { HTTPValidationError } from '../model/models';
|
||||||
|
|
||||||
|
import { BASE_PATH, COLLECTION_FORMATS } from '../variables';
|
||||||
|
import { Configuration } from '../configuration';
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class MastodonService {
|
||||||
|
|
||||||
|
protected basePath = 'http://localhost/api';
|
||||||
|
public defaultHeaders = new HttpHeaders();
|
||||||
|
public configuration = new Configuration();
|
||||||
|
public encoder: HttpParameterCodec;
|
||||||
|
|
||||||
|
constructor(protected httpClient: HttpClient, @Optional()@Inject(BASE_PATH) basePath: string, @Optional() configuration: Configuration) {
|
||||||
|
if (configuration) {
|
||||||
|
this.configuration = configuration;
|
||||||
|
}
|
||||||
|
if (typeof this.configuration.basePath !== 'string') {
|
||||||
|
if (typeof basePath !== 'string') {
|
||||||
|
basePath = this.basePath;
|
||||||
|
}
|
||||||
|
this.configuration.basePath = basePath;
|
||||||
|
}
|
||||||
|
this.encoder = this.configuration.encoder || new CustomHttpParameterCodec();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private addToHttpParams(httpParams: HttpParams, value: any, key?: string): HttpParams {
|
||||||
|
if (typeof value === "object" && value instanceof Date === false) {
|
||||||
|
httpParams = this.addToHttpParamsRecursive(httpParams, value);
|
||||||
|
} else {
|
||||||
|
httpParams = this.addToHttpParamsRecursive(httpParams, value, key);
|
||||||
|
}
|
||||||
|
return httpParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
private addToHttpParamsRecursive(httpParams: HttpParams, value?: any, key?: string): HttpParams {
|
||||||
|
if (value == null) {
|
||||||
|
return httpParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === "object") {
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
(value as any[]).forEach( elem => httpParams = this.addToHttpParamsRecursive(httpParams, elem, key));
|
||||||
|
} else if (value instanceof Date) {
|
||||||
|
if (key != null) {
|
||||||
|
httpParams = httpParams.append(key,
|
||||||
|
(value as Date).toISOString().substr(0, 10));
|
||||||
|
} else {
|
||||||
|
throw Error("key may not be null if value is Date");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Object.keys(value).forEach( k => httpParams = this.addToHttpParamsRecursive(
|
||||||
|
httpParams, value[k], key != null ? `${key}.${k}` : k));
|
||||||
|
}
|
||||||
|
} else if (key != null) {
|
||||||
|
httpParams = httpParams.append(key, value);
|
||||||
|
} else {
|
||||||
|
throw Error("key may not be null if value is not object or array");
|
||||||
|
}
|
||||||
|
return httpParams;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 createMastodon.');
|
||||||
|
}
|
||||||
|
if (bodyMastodonAccount === null || bodyMastodonAccount === undefined) {
|
||||||
|
throw new Error('Required parameter bodyMastodonAccount was null or undefined when calling createMastodon.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// 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.post<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/mastodon/`,
|
||||||
|
bodyMastodonAccount,
|
||||||
|
{
|
||||||
|
responseType: <any>responseType,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: headers,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 deleteMastodon.');
|
||||||
|
}
|
||||||
|
|
||||||
|
let headers = this.defaultHeaders;
|
||||||
|
|
||||||
|
let credential: string | undefined;
|
||||||
|
// authentication (OAuth2PasswordBearer) required
|
||||||
|
credential = this.configuration.lookupCredential('OAuth2PasswordBearer');
|
||||||
|
if (credential) {
|
||||||
|
headers = headers.set('Authorization', 'Bearer ' + credential);
|
||||||
|
}
|
||||||
|
|
||||||
|
let httpHeaderAcceptSelected: string | undefined = options && options.httpHeaderAccept;
|
||||||
|
if (httpHeaderAcceptSelected === undefined) {
|
||||||
|
// to determine the Accept header
|
||||||
|
const httpHeaderAccepts: string[] = [
|
||||||
|
'application/json'
|
||||||
|
];
|
||||||
|
httpHeaderAcceptSelected = this.configuration.selectHeaderAccept(httpHeaderAccepts);
|
||||||
|
}
|
||||||
|
if (httpHeaderAcceptSelected !== undefined) {
|
||||||
|
headers = headers.set('Accept', httpHeaderAcceptSelected);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
let responseType: 'text' | 'json' = 'json';
|
||||||
|
if(httpHeaderAcceptSelected && httpHeaderAcceptSelected.startsWith('text')) {
|
||||||
|
responseType = 'text';
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.httpClient.delete<any>(`${this.configuration.basePath}/api/hoods/${encodeURIComponent(String(hoodId))}/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,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: headers,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 statusMastodon.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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))}/status`,
|
||||||
|
{
|
||||||
|
responseType: <any>responseType,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: headers,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 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 stopMastodon.');
|
||||||
|
}
|
||||||
|
|
||||||
|
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))}/stop`,
|
||||||
|
null,
|
||||||
|
{
|
||||||
|
responseType: <any>responseType,
|
||||||
|
withCredentials: this.configuration.withCredentials,
|
||||||
|
headers: headers,
|
||||||
|
observe: observe,
|
||||||
|
reportProgress: reportProgress
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class TelegramService {
|
export class TelegramService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class TestService {
|
export class TestService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
|
@ -30,7 +30,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class TriggersService {
|
export class TriggersService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
|
@ -29,7 +29,7 @@ import { Configuration } from '../configurat
|
||||||
})
|
})
|
||||||
export class TwitterService {
|
export class TwitterService {
|
||||||
|
|
||||||
protected basePath = 'http://localhost';
|
protected basePath = 'http://localhost/api';
|
||||||
public defaultHeaders = new HttpHeaders();
|
public defaultHeaders = new HttpHeaders();
|
||||||
public configuration = new Configuration();
|
public configuration = new Configuration();
|
||||||
public encoder: HttpParameterCodec;
|
public encoder: HttpParameterCodec;
|
||||||
|
|
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
|
@ -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;
|
||||||
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
export * from './bodyAccessToken';
|
export * from './bodyAccessToken';
|
||||||
export * from './bodyAdmin';
|
export * from './bodyAdmin';
|
||||||
export * from './bodyAdminLoginAdminLoginPost';
|
|
||||||
export * from './bodyBadWord';
|
export * from './bodyBadWord';
|
||||||
export * from './bodyHood';
|
export * from './bodyHood';
|
||||||
|
export * from './bodyLogin';
|
||||||
|
export * from './bodyMastodonAccount';
|
||||||
export * from './bodyPassword';
|
export * from './bodyPassword';
|
||||||
export * from './bodySubscriber';
|
export * from './bodySubscriber';
|
||||||
export * from './bodyTelegram';
|
export * from './bodyTelegram';
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
|
|
||||||
|
|
||||||
export interface ValidationError {
|
export interface ValidationError {
|
||||||
loc: Array<string>;
|
loc: Array<string | number>;
|
||||||
msg: string;
|
msg: string;
|
||||||
type: string;
|
type: string;
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,4 +39,5 @@
|
||||||
<app-email-settings [hoodId]="hoodId"></app-email-settings>
|
<app-email-settings [hoodId]="hoodId"></app-email-settings>
|
||||||
<app-twitter-settings [hoodId]="hoodId"></app-twitter-settings>
|
<app-twitter-settings [hoodId]="hoodId"></app-twitter-settings>
|
||||||
<app-telegram-settings [hoodId]="hoodId"></app-telegram-settings>
|
<app-telegram-settings [hoodId]="hoodId"></app-telegram-settings>
|
||||||
|
<app-mastodon-settings [hoodId]="hoodId"></app-mastodon-settings>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,4 +3,5 @@
|
||||||
<app-twitter-bot-card [hoodId]="hoodId"></app-twitter-bot-card>
|
<app-twitter-bot-card [hoodId]="hoodId"></app-twitter-bot-card>
|
||||||
<app-telegram-bot-card [hoodId]="hoodId"></app-telegram-bot-card>
|
<app-telegram-bot-card [hoodId]="hoodId"></app-telegram-bot-card>
|
||||||
<app-email-bot-card [hoodId]="hoodId"></app-email-bot-card>
|
<app-email-bot-card [hoodId]="hoodId"></app-email-bot-card>
|
||||||
|
<app-mastodon-bot-card [hoodId]="hoodId"></app-mastodon-bot-card>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -20,6 +20,10 @@ import { TelegramBotInfoDialogComponent } from './telegram/telegram-bot-card/tel
|
||||||
import { TwitterBotInfoDialogComponent } from './twitter/twitter-bot-card/twitter-bot-info-dialog/twitter-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 { EmailConfirmationComponent } from './email/email-confirmation/email-confirmation.component';
|
||||||
import { EmailUnsubscribeComponent } from './email/email-unsubscribe/email-unsubscribe.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({
|
@NgModule({
|
||||||
declarations: [
|
declarations: [
|
||||||
|
@ -42,10 +46,15 @@ import { EmailUnsubscribeComponent } from './email/email-unsubscribe/email-unsub
|
||||||
TwitterBotInfoDialogComponent,
|
TwitterBotInfoDialogComponent,
|
||||||
EmailConfirmationComponent,
|
EmailConfirmationComponent,
|
||||||
EmailUnsubscribeComponent,
|
EmailUnsubscribeComponent,
|
||||||
|
MastodonBotCardComponent,
|
||||||
|
MastodonSettingsComponent,
|
||||||
|
MastodonDialogComponent,
|
||||||
|
MastodonBotInfoDialogComponent
|
||||||
],
|
],
|
||||||
imports: [CommonModule, SharedModule],
|
imports: [CommonModule, SharedModule],
|
||||||
exports: [
|
exports: [
|
||||||
TelegramSettingsComponent,
|
TelegramSettingsComponent,
|
||||||
|
MastodonSettingsComponent,
|
||||||
TwitterSettingsComponent,
|
TwitterSettingsComponent,
|
||||||
EmailSettingsComponent,
|
EmailSettingsComponent,
|
||||||
PlatformsInfoPageComponent,
|
PlatformsInfoPageComponent,
|
||||||
|
|
BIN
frontend/src/assets/mastodon.png
Normal file
After Width: | Height: | Size: 15 KiB |
2
setup.sh
|
@ -6,4 +6,4 @@ ln -sf ../../git-hooks/commit-msg .git/hooks/commit-msg
|
||||||
|
|
||||||
# create virtualenv
|
# create virtualenv
|
||||||
virtualenv -p $(which python3.10) backend/.venv
|
virtualenv -p $(which python3.10) backend/.venv
|
||||||
backend/.venv/bin/pip install tox black
|
backend/.venv/bin/pip install tox black pytest pytest-aiohttp
|
||||||
|
|
I like dismissing old notifications on the platform instead of keeping track of
last_seen
ourselves, but we didn't do this in ticketfrei2, so we need to dismiss all the past notifications during the migration.