litecord/litecord/blueprints/dm_channels.py

240 lines
6.4 KiB
Python

"""
Litecord
Copyright (C) 2018-2019 Luna Mendes
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, version 3 of the License.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
"""
from quart import Blueprint, current_app as app, jsonify
from logbook import Logger
from litecord.blueprints.auth import token_check
from litecord.blueprints.checks import channel_check
from litecord.enums import ChannelType, MessageType
from litecord.errors import BadRequest, Forbidden
from winter import get_snowflake
from litecord.system_messages import send_sys_message
from litecord.pubsub.channel import gdm_recipient_view
log = Logger(__name__)
bp = Blueprint("dm_channels", __name__)
async def _raw_gdm_add(channel_id, user_id):
await app.db.execute(
"""
INSERT INTO group_dm_members (id, member_id)
VALUES ($1, $2)
""",
channel_id,
user_id,
)
async def _raw_gdm_remove(channel_id, user_id):
await app.db.execute(
"""
DELETE FROM group_dm_members
WHERE id = $1 AND member_id = $2
""",
channel_id,
user_id,
)
async def gdm_create(user_id, peer_id) -> int:
"""Create a group dm, given two users.
Returns the new GDM id.
"""
channel_id = get_snowflake()
await app.db.execute(
"""
INSERT INTO channels (id, channel_type)
VALUES ($1, $2)
""",
channel_id,
ChannelType.GROUP_DM.value,
)
await app.db.execute(
"""
INSERT INTO group_dm_channels (id, owner_id, name, icon)
VALUES ($1, $2, NULL, NULL)
""",
channel_id,
user_id,
)
await _raw_gdm_add(channel_id, user_id)
await _raw_gdm_add(channel_id, peer_id)
await app.dispatcher.sub("channel", channel_id, user_id)
await app.dispatcher.sub("channel", channel_id, peer_id)
chan = await app.storage.get_channel(channel_id)
await app.dispatcher.dispatch("channel", channel_id, "CHANNEL_CREATE", chan)
return channel_id
async def gdm_add_recipient(channel_id: int, peer_id: int, *, user_id=None):
"""Add a recipient to a Group DM.
Dispatches:
- A system message with the join (depending of user_id)
- A CHANNEL_CREATE to the peer.
- A CHANNEL_UPDATE to all remaining recipients.
"""
await _raw_gdm_add(channel_id, peer_id)
chan = await app.storage.get_channel(channel_id)
# the reasoning behind gdm_recipient_view is in its docstring.
await app.dispatcher.dispatch(
"user", peer_id, "CHANNEL_CREATE", gdm_recipient_view(chan, peer_id)
)
await app.dispatcher.dispatch("channel", channel_id, "CHANNEL_UPDATE", chan)
await app.dispatcher.sub("channel", peer_id)
if user_id:
await send_sys_message(
app, channel_id, MessageType.RECIPIENT_ADD, user_id, peer_id
)
async def gdm_remove_recipient(channel_id: int, peer_id: int, *, user_id=None):
"""Remove a member from a GDM.
Dispatches:
- A system message with the leave or forced removal (depending if user_id)
exists or not.
- A CHANNEL_DELETE to the peer.
- A CHANNEL_UPDATE to all remaining recipients.
"""
await _raw_gdm_remove(channel_id, peer_id)
chan = await app.storage.get_channel(channel_id)
await app.dispatcher.dispatch(
"user", peer_id, "CHANNEL_DELETE", gdm_recipient_view(chan, user_id)
)
await app.dispatcher.unsub("channel", peer_id)
await app.dispatcher.dispatch(
"channel",
channel_id,
"CHANNEL_RECIPIENT_REMOVE",
{"channel_id": str(channel_id), "user": await app.storage.get_user(peer_id)},
)
author_id = peer_id if user_id is None else user_id
await send_sys_message(
app, channel_id, MessageType.RECIPIENT_REMOVE, author_id, peer_id
)
async def gdm_destroy(channel_id):
"""Destroy a Group DM."""
chan = await app.storage.get_channel(channel_id)
await app.db.execute(
"""
DELETE FROM group_dm_members
WHERE id = $1
""",
channel_id,
)
await app.db.execute(
"""
DELETE FROM group_dm_channels
WHERE id = $1
""",
channel_id,
)
await app.db.execute(
"""
DELETE FROM channels
WHERE id = $1
""",
channel_id,
)
await app.dispatcher.dispatch("channel", channel_id, "CHANNEL_DELETE", chan)
await app.dispatcher.remove("channel", channel_id)
async def gdm_is_member(channel_id: int, user_id: int) -> bool:
"""Return if the given user is a member of the Group DM."""
row = await app.db.fetchval(
"""
SELECT id
FROM group_dm_members
WHERE id = $1 AND member_id = $2
""",
channel_id,
user_id,
)
return row is not None
@bp.route("/<int:dm_chan>/recipients/<int:peer_id>", methods=["PUT"])
async def add_to_group_dm(dm_chan, peer_id):
"""Adds a member to a group dm OR creates a group dm."""
user_id = await token_check()
# other_id is the owner of the group dm (gdm) if the
# given channel is a gdm
# other_id is the peer of the dm if the given channel is a dm
ctype, other_id = await channel_check(
user_id, dm_chan, only=[ChannelType.DM, ChannelType.GROUP_DM]
)
# check relationship with the given user id
# and the user id making the request
friends = await app.user_storage.are_friends_with(user_id, peer_id)
if not friends:
raise BadRequest("Cant insert peer into dm")
if ctype == ChannelType.DM:
dm_chan = await gdm_create(user_id, other_id)
await gdm_add_recipient(dm_chan, peer_id, user_id=user_id)
return jsonify(await app.storage.get_channel(dm_chan))
@bp.route("/<int:dm_chan>/recipients/<int:peer_id>", methods=["DELETE"])
async def remove_from_group_dm(dm_chan, peer_id):
"""Remove users from group dm."""
user_id = await token_check()
_ctype, owner_id = await channel_check(user_id, dm_chan, only=ChannelType.GROUP_DM)
if owner_id != user_id:
raise Forbidden("You are now the owner of the group DM")
await gdm_remove_recipient(dm_chan, peer_id)
return "", 204