diff --git a/litecord/blueprints/channels.py b/litecord/blueprints/channels.py index 11c18a5..2c9824b 100644 --- a/litecord/blueprints/channels.py +++ b/litecord/blueprints/channels.py @@ -25,7 +25,7 @@ from quart import Blueprint, request, current_app as app, jsonify from logbook import Logger from litecord.auth import token_check -from litecord.enums import ChannelType, GUILD_CHANS, MessageType +from litecord.enums import ChannelType, GUILD_CHANS, MessageType, MessageFlags from litecord.errors import ChannelNotFound, Forbidden, BadRequest from litecord.schemas import ( validate, CHAN_UPDATE, CHAN_OVERWRITE, SEARCH_CHANNEL, GROUP_DM_UPDATE, @@ -615,12 +615,43 @@ async def _search_channel(channel_id): return jsonify(await search_result_from_list(rows)) +# NOTE that those functions stay here until some other +# route or code wants it. + + +async def _msg_update_flags(message_id: int, flags: int): + await app.db.execute(""" + UPDATE messages + SET flags = $1 + WHERE id = $2 + """, flags, message_id) + + +async def _msg_get_flags(message_id: int): + return await app.db.fetchval(""" + SELECT flags + FROM messages + WHERE id = $1 + """, message_id) + + +async def _msg_set_flags(message_id: int, new_flags: int): + flags = await _msg_get_flags(message_id) + flags |= new_flags + await _msg_update_flags(message_id, flags) + + +async def _msg_unset_flags(message_id: int, unset_flags: int): + flags = await _msg_get_flags(message_id) + flags &= ~unset_flags + await _msg_update_flags(message_id, flags) + @bp.route('//messages//suppress-embeds', methods=['POST']) async def suppress_embeds(channel_id: int, message_id: int): """Toggle the embeds in a message. - + Either the author of the message or a channel member with the Manage Messages permission can run this route. """ @@ -654,11 +685,27 @@ async def suppress_embeds(channel_id: int, message_id: int): url_embeds = sum( 1 for embed in message['embeds'] if embed['type'] == 'url') + # NOTE for any future self. discord doing flags an optional thing instead + # of just giving 0 is a pretty bad idea because now i have to deal with + # that behavior here, and likely in every other message update thing + if suppress and url_embeds: # delete all embeds then dispatch an update + await _msg_set_flags(message_id, MessageFlags.suppress_embeds) + + message['flags'] = \ + message.get('flags', 0) | MessageFlags.suppress_embeds + await msg_update_embeds(message, [], app.storage, app.dispatcher) elif not suppress and not url_embeds: # spawn process_url_embed to restore the embeds, if any + await _msg_unset_flags(message_id, MessageFlags.suppress_embeds) + + try: + message.pop('flags') + except KeyError: + pass + app.sched.spawn( process_url_embed( app.config, app.storage, app.dispatcher, app.session, diff --git a/litecord/embed/messages.py b/litecord/embed/messages.py index 1267538..4807c0f 100644 --- a/litecord/embed/messages.py +++ b/litecord/embed/messages.py @@ -82,6 +82,9 @@ async def msg_update_embeds(payload, new_embeds, storage, dispatcher): if 'guild_id' in payload: update_payload['guild_id'] = payload['guild_id'] + if 'flags' in payload: + update_payload['flags'] = payload['flags'] + await dispatcher.dispatch( 'channel', channel_id, 'MESSAGE_UPDATE', update_payload) diff --git a/litecord/enums.py b/litecord/enums.py index 98e036c..c9ecb9a 100644 --- a/litecord/enums.py +++ b/litecord/enums.py @@ -164,6 +164,15 @@ class UserFlags(Flags): premium_early = 512 +class MessageFlags(Flags): + """Message flags.""" + none = 0 + + crossposted = 1 << 0 + is_crosspost = 1 << 1 + suppress_embeds = 1 << 2 + + class StatusType(EasyEnum): """All statuses there can be in a presence.""" ONLINE = 'online' diff --git a/litecord/storage.py b/litecord/storage.py index 58c7bd6..08ea0c1 100644 --- a/litecord/storage.py +++ b/litecord/storage.py @@ -876,7 +876,7 @@ class Storage: row = await self.fetchrow_with_json(""" SELECT id::text, channel_id::text, author_id, content, created_at AS timestamp, edited_at AS edited_timestamp, - tts, mention_everyone, nonce, message_type, embeds + tts, mention_everyone, nonce, message_type, embeds, flags FROM messages WHERE id = $1 """, message_id) @@ -942,10 +942,17 @@ class Storage: # if message is not from a dm, guild_id is None and so, _member_basic # will just return None - res['member'] = await self._member_basic_with_roles(guild_id, user_id) - if res['member'] is None: - res.pop('member') + # user id can be none, though, and we need to watch out for that + if user_id is not None: + res['member'] = await self._member_basic_with_roles( + guild_id, user_id) + + if res.get('member') is None: + try: + res.pop('member') + except KeyError: + pass pin_id = await self.db.fetchval(""" SELECT message_id @@ -961,6 +968,9 @@ class Storage: if guild_id: res['guild_id'] = str(guild_id) + if res['flags'] == 0: + res.pop('flags') + return res async def get_invite(self, invite_code: str) -> Optional[Dict]: diff --git a/manage/cmd/migration/scripts/3_add_message_flags.sql b/manage/cmd/migration/scripts/3_add_message_flags.sql new file mode 100644 index 0000000..28eea36 --- /dev/null +++ b/manage/cmd/migration/scripts/3_add_message_flags.sql @@ -0,0 +1,2 @@ +ALTER TABLE messages + ADD COLUMN flags bigint DEFAULT 0; diff --git a/manage/cmd/users.py b/manage/cmd/users.py index 4d37757..d835c6a 100644 --- a/manage/cmd/users.py +++ b/manage/cmd/users.py @@ -30,6 +30,7 @@ async def find_user(username, discrim, ctx) -> int: WHERE username = $1 AND discriminator = $2 """, username, discrim) + async def set_user_staff(user_id, ctx): """Give a single user staff status.""" old_flags = await ctx.db.fetchval("""