Merge branch 'impl/bulk-ack' into 'master'

Implement Bulk ACK endpoint

Closes #52

See merge request litecord/litecord!73
This commit is contained in:
luna 2021-07-11 02:47:30 +00:00
commit 16665e5692
7 changed files with 126 additions and 62 deletions

View File

@ -33,6 +33,7 @@ from .nodeinfo import bp as nodeinfo
from .static import bp as static from .static import bp as static
from .attachments import bp as attachments from .attachments import bp as attachments
from .dm_channels import bp as dm_channels from .dm_channels import bp as dm_channels
from .read_states import bp as read_states
__all__ = [ __all__ = [
"gateway", "gateway",
@ -51,4 +52,5 @@ __all__ = [
"static", "static",
"attachments", "attachments",
"dm_channels", "dm_channels",
"read_states",
] ]

View File

@ -42,10 +42,8 @@ from litecord.system_messages import send_sys_message
from litecord.blueprints.dm_channels import gdm_remove_recipient, gdm_destroy from litecord.blueprints.dm_channels import gdm_remove_recipient, gdm_destroy
from litecord.utils import search_result_from_list from litecord.utils import search_result_from_list
from litecord.embed.messages import process_url_embed, msg_update_embeds from litecord.embed.messages import process_url_embed, msg_update_embeds
from litecord.common.channels import channel_ack
from litecord.pubsub.user import dispatch_user from litecord.pubsub.user import dispatch_user
from litecord.permissions import get_permissions, Permissions from litecord.permissions import get_permissions, Permissions
from litecord.errors import GuildNotFound
log = Logger(__name__) log = Logger(__name__)
bp = Blueprint("channels", __name__) bp = Blueprint("channels", __name__)
@ -653,50 +651,6 @@ async def trigger_typing(channel_id):
return "", 204 return "", 204
@bp.route("/<int:channel_id>/messages/<int:message_id>/ack", methods=["POST"])
async def ack_channel(channel_id, message_id):
"""Acknowledge a channel."""
user_id = await token_check()
ctype, guild_id = await channel_check(user_id, channel_id)
if ctype == ChannelType.DM:
guild_id = None
await channel_ack(user_id, guild_id, channel_id, message_id)
return jsonify(
{
# token seems to be used for
# data collection activities,
# so we never use it.
"token": None
}
)
@bp.route("/<int:channel_id>/messages/ack", methods=["DELETE"])
async def delete_read_state(channel_id):
"""Delete the read state of a channel."""
user_id = await token_check()
try:
await channel_check(user_id, channel_id)
except GuildNotFound:
# ignore when guild isn't found because we're deleting the
# read state regardless.
pass
await app.db.execute(
"""
DELETE FROM user_read_state
WHERE user_id = $1 AND channel_id = $2
""",
user_id,
channel_id,
)
return "", 204
@bp.route("/<int:channel_id>/messages/search", methods=["GET"]) @bp.route("/<int:channel_id>/messages/search", methods=["GET"])
async def _search_channel(channel_id): async def _search_channel(channel_id):
"""Search in DMs or group DMs""" """Search in DMs or group DMs"""

View File

@ -39,7 +39,6 @@ from ..schemas import (
VANITY_URL_PATCH, VANITY_URL_PATCH,
) )
from .checks import guild_check, guild_owner_check, guild_perm_check from .checks import guild_check, guild_owner_check, guild_perm_check
from ..common.channels import channel_ack
from litecord.utils import to_update, search_result_from_list from litecord.utils import to_update, search_result_from_list
from litecord.errors import BadRequest from litecord.errors import BadRequest
from litecord.permissions import get_permissions from litecord.permissions import get_permissions
@ -438,20 +437,6 @@ async def search_messages(guild_id):
return jsonify(await search_result_from_list(rows)) return jsonify(await search_result_from_list(rows))
@bp.route("/<int:guild_id>/ack", methods=["POST"])
async def ack_guild(guild_id):
"""ACKnowledge all messages in the guild."""
user_id = await token_check()
await guild_check(user_id, guild_id)
chan_ids = await app.storage.get_channel_ids(guild_id)
for chan_id in chan_ids:
await channel_ack(user_id, guild_id, chan_id)
return "", 204
@bp.route("/<int:guild_id>/vanity-url", methods=["GET"]) @bp.route("/<int:guild_id>/vanity-url", methods=["GET"])
async def get_vanity_url(guild_id: int): async def get_vanity_url(guild_id: int):
"""Get the vanity url of a guild.""" """Get the vanity url of a guild."""

View File

@ -0,0 +1,105 @@
"""
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, request
from litecord.auth import token_check
from litecord.common.channels import channel_ack
from litecord.errors import GuildNotFound
from litecord.blueprints.checks import channel_check, guild_check
from litecord.schemas import validate, BULK_ACK
from litecord.enums import GUILD_CHANS
bp = Blueprint("read_states", __name__)
@bp.route("/channels/<int:channel_id>/messages/<int:message_id>/ack", methods=["POST"])
async def ack_channel(channel_id, message_id):
"""Acknowledge a channel."""
user_id = await token_check()
ctype, guild_id = await channel_check(user_id, channel_id)
if ctype not in GUILD_CHANS:
guild_id = None
await channel_ack(user_id, channel_id, guild_id, message_id)
return jsonify(
{
# token seems to be used for
# data collection activities,
# so we never use it.
"token": None
}
)
@bp.route("/read-states/ack-bulk", methods=["POST"])
async def bulk_ack():
"""Acknowledge multiple channels in a row"""
user_id = await token_check()
j = validate(await request.get_json(), BULK_ACK)
for ack_request in j:
channel_id, message_id = ack_request["channel_id"], ack_request["message_id"]
ctype, guild_id = await channel_check(user_id, channel_id)
if ctype not in GUILD_CHANS:
guild_id = None
await channel_ack(user_id, channel_id, guild_id, message_id)
# TODO: validate if this is the correct response
return "", 204
@bp.route("/channels/<int:channel_id>/messages/ack", methods=["DELETE"])
async def delete_read_state(channel_id):
"""Delete the read state of a channel."""
user_id = await token_check()
try:
await channel_check(user_id, channel_id)
except GuildNotFound:
# ignore when guild isn't found because we're deleting the
# read state regardless.
pass
await app.db.execute(
"""
DELETE FROM user_read_state
WHERE user_id = $1 AND channel_id = $2
""",
user_id,
channel_id,
)
return "", 204
@bp.route("/guilds/<int:guild_id>/ack", methods=["POST"])
async def ack_guild(guild_id):
"""ACKnowledge all messages in the guild."""
user_id = await token_check()
await guild_check(user_id, guild_id)
chan_ids = await app.storage.get_channel_ids(guild_id)
for chan_id in chan_ids:
await channel_ack(user_id, chan_id, guild_id)
return "", 204

View File

@ -27,7 +27,10 @@ from litecord.pubsub.user import dispatch_user
async def channel_ack( async def channel_ack(
user_id: int, guild_id: int, channel_id: int, message_id: Optional[int] = None user_id: int,
channel_id: int,
guild_id: Optional[int] = None,
message_id: Optional[int] = None,
): ):
"""ACK a channel.""" """ACK a channel."""

View File

@ -619,3 +619,16 @@ BULK_DELETE = {
"schema": {"coerce": int}, "schema": {"coerce": int},
} }
} }
BULK_ACK = {
"read_states": {
"type": "list",
"required": True,
"minlength": 0,
"maxlength": 100,
"schema": {
"channel_id": {"coerce": int},
"message_id": {"coerce": int},
},
}
}

2
run.py
View File

@ -50,6 +50,7 @@ from litecord.blueprints import (
static, static,
attachments, attachments,
dm_channels, dm_channels,
read_states,
) )
# those blueprints are separated from the "main" ones # those blueprints are separated from the "main" ones
@ -167,6 +168,7 @@ def set_blueprints(app_):
guilds_admin: "/admin/guilds", guilds_admin: "/admin/guilds",
users_admin: "/admin/users", users_admin: "/admin/users",
instance_invites: "/admin/instance/invites", instance_invites: "/admin/instance/invites",
read_states: "",
} }
for bp, suffix in bps.items(): for bp, suffix in bps.items():