First, a bit of background.
When I first started working on the server, a role existed called "Not
There was nothing notable about it, but having seen a very similarly named
role with a specific purpose at a previous event, I took it to mean a sort
of "penalty box" role which could be used to specifically *disallow* normal
It wasn't set up that way as I found it and nobody could really say what
it was intended for, but in various discussions about incident response, I
assumed we might need something like that for limited but judicious use
But a spat over who should have authority and equivalent of "arrest power" was still raging on in the management stratosphere. Some seemed semi-horrified by the idea of muting someone, saying that attachment of a "penalty" or "mute" role would be too obvious and demeaning, or something. I didn't understand that -- if someone is misbehaving badly enough for Safety or IRT to be called to intervene, and restraint is deemed necessary, why not muzzle the offender as needed and never mind if evidence of that is visible or not? They earned it, at least temporarily. The point is not to protect the offender somehow, the point is to handle the incident and hopefully de-escalate the whole situation and release the offender back to normal status with no further problem.
Either way, as part of making things ready for a large influx of people I either co-opted "not Arisian" or made a new "penalty box" role, I forget which, and added channel permissions such that having the role would simply prohibit the sending of messages in most of the spaces. Those affected would still be able to *view* ongoing traffic, they just wouldn't be able to reply [or continue spewing bad stuff]. It was harmless to have such a means on tap, even if never used. I also described a more "invisible" way to handle a single channel or category thereof, by adding a negative posting permission for a single user -- doable, but far klunkier especially if having to search down a long user list.
Anyway, in a normal Discord setup, some entities would ultimately need to wield the "manage roles" capability to effect usage of this. That is a fairly broad power, and misuse can really screw things up. Having better insight into what could be done with bot commands, I began thinking about how this power could be channeled, such that its *only* capabilty would be a streamlined "mute function", and nobody would have to romp around in the Discord administrative panel or even have "manage roles" capability to work with it.
[*Note: this is not the same as the "mute user" built-in Discord function, which only applies to audio channels. This discusses the equivalent for text channels, for which there is no built-in.]
|Having sussed out a little more about YAGPDB custom commands, I determined after a little experimentation that this would be doable entirely within Yag's framework. The obscure part was how to pull out a valid username argument to target, because YAGPDB's approach to that is bizarre -- a factor that led me to Dyno for simpler command handling earlier on. Looking up a user by @-completion or whatever returns an object with several different fields, and we need to pull out the right one. Then, I couldn't tell if that was going to come back as a typical Discord long-number ID or a nickname or basename or what, and how reliable a match that was going to be once plugged back into any of the give/takeRole calls.|
|I asked Gail about this; she said that API interactions generally only pass the long-number identifiers, even when either a nickname or base username is given in the client UI. See, it was great to have her knowledge at that level as a sanity-check!|
|I proved this later on my test server, setting up two stupid little commands to give and take an arbitrary "blue" role for another user, which could even be myself. The bot didn't care; it was high enough in the role stack and armed with "manage roles", so the chosen victim could basically be anyone. The response from the bot did indeed spit out the long numeric "$uid", so I had evidently parsed out the correct thing. This was proof of concept, with great versatility in how to launch the command -- full regular expression match, simple "begins with" or "contains" text matches with or without the bot-prefix, and other methods. For greater stealth, the bot can not only be directed to take action silently, it can even delete the trigger command and make the channel look like nothing ever happened.|
|Still lacking useful decisions from above, I laid all this aside for the time being. Meanwhile, the Safety and IRT crews were being trained to simply grind through the UI -- if they would ultimately even be allowed to do this. Notwithstanding, we wanted to do a functional test of the process, so I offered up my "crash test" dummy and pretended to say offensive things in a channel. Vivian played the part of incident response deciding that they'd had enough of my crap, and began the procedure. At this point, the role in question had been named "On Hold". Not really subtle at all...|
|My test user's ability to spew garbage was suddenly halted, and within a few seconds I had a direct-message from Vivian. One of the ideas floated was having a bot do the direct message instead, but that wasn't going to work with a manual process. Anyway, even though I knew this was a rigged test, the rapidity and decisiveness with which it happened, plus the wording of the copy-n-paste "you're busted" message, made it quite the surprise! At that point it didn't matter who the message was from, clearly I had no choice but to go meekly beseech for forgiveness in #safety.|
|So the "mute role" worked great, for the most part. But there was a little sublety around some of the private channels, where the roles specifically permitted to interact in them actually took precedence over "On Hold", just because of the way Discord permissions work. It took me a while to figure out why, and it was sort of too late to go back and rework the base matrix for an active server with hundreds of users, so as a workaround I made the hold-role simply cut off the private channels entirely. Such as one of the Safer Spaces here, which also shows its "nondescript" gating role. A small compromise, for something we actually hoped would never be needed anyhow.|
We discussed how surprisingly effective the test had been, and how it would
be much more profound against someone genuinely acting up, so we called it
ready to rock.
I mentioned the few miscellaneous special-case fixups still needed, and
went off to take care of those.
But I still wanted to implement a simple handler for this, based on my previous testing. Once I got a breather from other activities, I went back to the bot panel and banged in refined versions of the commands.
|Another subtlety is that using a command with a username argument needs to be done in a *public* channel that the target user has at least view access to, or the @-mention won't complete and fill in the ID correctly. Rather than create some new and mysterious channel that everyone could see but not post to, I decided to super-stealth the whole thing into an existing read-only channel -- our short listing of "Discord 101" resources. The only permission that Safety/IRT would then need would be posting access to this one channel, and knowledge of the command format [which changed after this particular test]. The bot would silently do the dirty work and then delete the command. The channel might flag new activity, but anyone going to look wouldn't see anything different once the deed was done -- or later un-done.|
|The real command enhancement was a full audit of WHO had put WHO ELSE into the hold role or taken them out, because the native server log would only show that YAGPDB did something. So this is both bots chiming in; Dyno happening to notice the changes, and Yag actually dropping its results in the audit channel INSTEAD of the channel where the command was used. And now it even got the full basename#discriminator username syntax right for *both* involved parties.|
For the record, the production "put on hold" bot-code is nothing more than
and the "un-mute" complement invokes "takeRoleName" instead with a different result message. Full disclosure, it took quite a bit of doc reading and experimental thrashing to noodle out the right syntax for all this; it's really rather arbitrary. The base "language" is a variant of Go templates.
|I reported all this into the private IRT channel, but at this point it was Saturday of con, and nobody seemed interested in learning yet another procedure no matter how elegant and least-privilege it might be. It could have been that nobody even knew what the hell I was talking about. They seemed far more interested in the optics of WHO could perform these functions rather than the mechanics of HOW.|
While human-side receptivity was a little disappointing, it was a great
exercise in mastering one type of bot custom commands.
So all worthwhile, and gives me something to write up as a kind of
tutorial for anyone interested in doing more along these lines.
There's a lot more to it than this, of course, and this is only YAGPDB.
The minor limitations of the "on hold" role were still bugging me, though, and I found a couple of references on permissions precedence to try and work through what could be done differently. First of all, the users/members of a server don't have permissions. They are given roles that carry some set of base permissions, such as "Arisian" being allowed to see and send and react as a baseline. But then specific channel or group/category permissions can override those, in specific deny or allow fashion. The problem is that channel "allow" permissions take precedence over denies; thus, if an "Arisian" is generically blocked from a channel but their "Staff" role permits seeing and sending to staff areas, guess what. "On Hold" now has no effect either, because that's just another deny role that got overridden.
People have complained about this, with one person neatly summing up the problem in this support thread by saying "try and manage a firewall with 'allow all' as it last rule, and let us know how that goes." Flipping the precedence of allow and deny would certainly allow more restrictive postures, but a global change to how that's supposed to work would instantly break just about every server out there. If Discord was ever going to implement such a thing, there would have to be a very careful migration strategy -- perhaps servers could have a new control to select the order, but default to the original way until an admin was good and ready to try flipping it.
For a really effective global mute role, though, there's a workaround,
which I didn't really think through until I was nattering about this with
some folks post-con in the "Arisia All Year" secondary server.
Instead of trying to stomp existing send permission by piling another role
on top at the channel level, split the baseline functionality and
allow ANY sending permission via the "i" or "input" role alone and
don't modify channels for it.
That way, simple removal of "i" is a global mute everywhere, which one
or two special channels can still override.
That's an example of "lean permissions matrix" -- the fewest possible
non-neutral settings to accomplish the intent.
Then the problem becomes that an "accept" or "I've-read-the-rules" bot would need to assign a *pair* of roles instead of just one. That's where an "I accept" type of custom command handler could really shine. Anybody can click a react button; having to type an actual sentence and possibly have it logged somewhere might really make someone stop and think.