Repository navigation
Who may send as Petto is checked first; every DM says where it is from and can be reported to the team; priority error log and self-healing tickets; Pinterest no longer repeats an empty card - #399
Merged
Conversation
…and deny when a permission cannot be read `send` now needs Manage Messages in the server (what its default permission means) plus View, Send and Manage Messages in the target channel, found on a real member of the server. The check runs first, for `!send`, `petto send`, `@Petto send`, `!message send`, its short alias and `/message send`, and the refusal is private (ephemeral for the slash command) and names the permission. A member with no readable permissions, a raw interaction member or a channel that cannot answer is refused. The prefix handler and the umbrella gate use the same strict yes-or-no instead of throwing or `?? false` shortcuts. New scripts/check-send-permissions.js (real PermissionsBitField, computed channel overwrites) runs every route with members that have nothing, only Send Messages, Manage Messages in one channel only, Manage Messages in the server and Administrator, with levels, restrictions and a moderation role that must only add limits, server aliases, every command word and every umbrella subcommand as a plain member. check-member-access.js now fakes channel permissions honestly. Signed-off-by: Liamt8dd <juliancamilparratrujillo@gmail.com>
…o report it to the Petto team
Each direct message Petto starts now begins with a small grey line (From <server>, or From Petto when no server is behind it) and ends with one row of three icon buttons in Petto's emojis: about the message, delete it and report it.
- utils/dmOrigin.js: sendDm(target, payload, { guild, kind }) is the one place that adds the line and the row, for plain text, embeds and Components V2 (the line is the first component, the row the last), keeping the message when a limit is reached (5 rows, 2000 characters, 40 components, 4000 characters of text); server names are escaped and clipped to 60 characters. User#send and GuildMember#send carry the same step as a net.
- Every user-directed send in the code (join, sanctions, automod, escalation, anti-scam, honeypot, forms, appeals, tickets, giveaways, verification, levels, suggestions, report updates, setup, backups, confessions, custom commands, embed buttons, owner notices) goes through sendDm; the setup messages keep their line when redrawn after a language change.
- interactions/dmOrigin.js: info answers with what is public about the server and why the message was sent; delete removes that message; report opens a form (notice, "does NOT go to the server's moderators", reason, details of 20 to 1000 characters, delete-after switch) in en, es and pt-BR. The buttons only work in the person's own chat with Petto, on messages of Petto, and refuse inside a server.
- utils/dmReports.js and db/dmReports.js: the report is stored (table dm_reports, one per message, 3 an hour per person) and sent as a card to the Petto team in PETTO_DM_REPORTS_CHANNEL_ID with a Mark handled button; without a channel it waits in the table and a warning is logged. It never goes to the server's moderators.
- scripts/check-dm-origin.js (last of the check script) covers the shapes, limits, escaping, handlers, form, rate limit, delivery, fallback, languages and that no direct message goes around the helper.
Signed-off-by: Liamt8dd <juliancamilparratrujillo@gmail.com>
…and a failure after a send never reposts A Pinterest board can list a pin that no longer exists: no picture, no title, no date, and a new short address on every reading. Its id was new on every look, so every 15 minutes it was posted as an empty card. - Pinterest: a pin with no picture asks the public pin information once (numeric ids only, 6 s, answer kept, paced); with still no picture the engine does not post it, marks it as seen, and logs it once an hour for each feed (ids only). - Engine: what a look found is marked as seen before the first send, and kept in memory when the database cannot write it; a failure after a send can no longer cause a repost. A live message that cannot be saved is not counted as a failure to post. Many new items at once are cut to the newest three and counted in one log line. - Items with no date (Reddit, Pinterest, RSS, Tumblr, Mastodon, Letterboxd) are as old as the first time they were seen, so they are never fresh for ever. - The test of a feed no longer picks a pin without a picture. - Checks: the engine over a fake store with the shape of a real board, failing writes, the cap of three, the pin information, and ids with special characters through the real SQL. Signed-off-by: Liamt8dd <juliancamilparratrujillo@gmail.com>
The existing error channel is now the general log: warnings, rate limits, Redis and the errors Petto expects (an interaction that expired, a channel or message that was already deleted, a missing permission of a server, a network or Discord hiccup) arrive there as warnings with a line that says what they mean, and never ping anybody. A new priority log (PETTO_PRIORITY_ERRORS_CHANNEL_ID) gets every other error, as a card with what it probably means, where it happened, a count for the last ten minutes, the top stack frames with secrets removed and a "Copy this to investigate" block. Repeats edit the card. It pings PETTO_PRIORITY_PING_USER_IDS (default the first developer, else the owner) and optionally PETTO_PRIORITY_PING_ROLE_ID: one ping per problem every 30 minutes and 5 an hour, and no text of an error can mention anybody. The triage (utils/errorTriage.js) is a table of Discord codes, network and database errors and message patterns, with fingerprints that group repeats. Every ticket button, menu, form and rating goes through one safety net (utils/safeTicketAction.js): it answers Discord first, a second press is harmless, a channel or message that is already gone counts as done and finishes the bookkeeping, an expired interaction is a warning and the work still finishes, a timeout or rate limit is retried once, and a real failure tells the member kindly, leaves the ticket safe and reaches the priority log. Deleting a ticket saves its transcript first and is not done if that fails. The channel sweep also forgets the message of a panel whose channel is gone and reports what it healed. Expired interactions on any command or button are warnings now. Signed-off-by: Liamt8dd <juliancamilparratrujillo@gmail.com>
…ons branch Signed-off-by: Liamt8dd <juliancamilparratrujillo@gmail.com>
Signed-off-by: Liamt8dd <juliancamilparratrujillo@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
No version change: the notes are under
[Unreleased]. No new slash command (nodeploy-commands.jsneeded). This PR changes the database (a new tabledm_reports, created byschema.sqlon start): back up the database before deploying. Set these environment variables (see "To configure" below).1.
sendand the permission checksA normal member was seen making Petto talk with
send. The routes could not be made to post for a plain member in the tests, so this hardens every place where a missing or unreadable permission could be read as "allowed", makes the refusals private and adds a test that tries every route as a plain member.send(src/commands/config/send.js): the check is the first thingexecutedoes. The member must be a real guild member with Manage Messages in the server (stricthas(...) === true; anything unreadable is a no), then View, Send and Manage Messages in the target channel (permissionsFornull is a no). Every refusal is private (the defer is ephemeral too) and names the permission.checkDefaultPermission): a member whose permissions cannot be read used to make it throw silently; now it denies with a card. Umbrellas (checkPermission,autocomplete): stricthas(needed) === true.scripts/check-send-permissions.js(realPermissionsBitField, channel overwrites, the real prefix and slash pipelines):!send,petto send,@Petto send,!message send,!msen,/message send... for eight kinds of member, 7173 command words, 783 umbrella subcommands and 1654 slash subcommands; it fails on the old code.check-member-access.jshas an honestpermissionsForfake now./messageis open at Discord (a whole command is gated, never one subcommand), so/message sendis listed to every member and refuses them privately; a prefix refusal is a public reply; standalone slash commands (/honeypot add,/partnerconfig welcome,/logs add,/verify setup,/webhook create,/channel lock|unlock|topic) rely on Discord'sdefault_member_permissionsand run for everybody only if a server opens them in Integrations; custom commands (including thesaytemplate) run before!restrictcommand,!disablecommandand permission levels.2. Every DM from Petto says who it is from, and can be reported to the Petto team
src/utils/dmOrigin.js,sendDm) for every DM sent to a user (sanctions, join DM, form results, tickets, appeals, giveaways, verification, levels, backups, confessions, setup and team notices;User#sendandGuildMember#sendare also wrapped as a net). A test fails if a raw DM send appears outside the helper.-# From <server>line and ends with three small icon buttons: info (public facts about the server and why the DM was sent, and how to stop it where there is a real setting), delete (deletes that DM) and report. It works for plain text, embeds and Components V2 messages, and never drops the message to make room.dm_reports. 3 reports an hour per person, one per message. Only the DM's recipient can use the buttons and they refuse inside a server.3. Priority error log and self-healing tickets
PETTO_PRIORITY_ERRORS_CHANNEL_ID; it pingsPETTO_PRIORITY_PING_USER_IDS(default: the first developer id) and optionallyPETTO_PRIORITY_PING_ROLE_ID, at most once per fingerprint per 30 minutes and 5 pings an hour; nothing else can be pinged. Repeats of the same error edit one card (a count) instead of posting again.src/utils/errorTriage.js, a tested table with an English explanation per code): expected errors (10062 Unknown interaction, 10003 Unknown Channel, 10008, 10007, 40060, 50001/50013 from a server's setup, 50007, 429, 5xx, network and Redis timeouts, database connection loss) go to the general log as warnings; everything else (bugs, database errors, unknown codes, "Petto sent something wrong") is priority. The priority card has what it probably means, server/channel/user, command, count, the top stack frames, secrets redacted, and a "Copy this to investigate" block to paste when asking for help.src/utils/safeTicketAction.js, used by every ticket button, menu, form and rating handler): acknowledge first, a channel or message that is already gone counts as done and the bookkeeping is finished, a late interaction is a warning and the work still completes, one retry on transient failures, a friendly message on a final failure plus a priority error, and a ticket is never left half deleted (transcript first, then close, then delete). The ticket job also heals open tickets whose channel is gone and panels whose message is gone.4. Pinterest posted an empty card again and again
The public RSS of a board can contain a ghost pin (empty title, empty picture, invalid date) whose id changes on every read, so it was new every 15 minutes. Dateless items are no longer "fresh for ever" (reddit, rss, pinterest, mastodon, tumblr, letterboxd), what was seen is marked before sending (a failure after a send cannot repost), a pin without a picture asks Pinterest's public pin info for numeric ids and is not posted as an empty card if there is still none (one warning per feed per hour, no content), and at most 3 new items per feed per pass are posted.
To configure
PETTO_DM_REPORTS_CHANNEL_ID: the team's channel for DM reports (without it they are only stored indm_reportsand a warning is logged).PETTO_PRIORITY_ERRORS_CHANNEL_ID,PETTO_PRIORITY_PING_USER_IDS, optionalPETTO_PRIORITY_PING_ROLE_ID(the old error channel variable is unchanged and is now the general log).pe_infoandpe_deleteexist as application emojis (the ids come from the voicemaster buttons).Checks
New:
check-send-permissions.js,check-dm-origin.js,check-error-triage.js; extended:check-feeds.js(the real engine twice over real-shaped Pinterest data),check-member-access.js,check-error-log.js,check-send.jsand others.npm run checkandnpm run types:checkpass. Nothing was run against real Discord, Pinterest or a production database: the look of the grey line and buttons in DMs, the modal checkbox, V2 priority cards, real button clicks in DMs and the live pin info endpoint are unverified.