mirror of
https://github.com/Chatterino/chatterino2.git
synced 2024-11-13 19:49:51 +01:00
Refactored and Migrated to Helix AutoMod message management (#2779)
This uses new Helix endpoint which requires new scopes and users need to reauthenticate to approve/deny AutoMod messages again.
This commit is contained in:
parent
170529e778
commit
e746201c4f
|
@ -11,6 +11,8 @@
|
|||
- Bugfix: `Login expired` message no longer highlights all tabs. (#2735)
|
||||
- Bugfix: Fix a deadlock that would occur during user badge loading. (#1704, #2756)
|
||||
- Bugfix: Tabbing in `Select a channel to open` is now consistent. (#1797)
|
||||
- Bugfix: Approving/denying AutoMod messages works again. (#2779)
|
||||
- Dev: Migrated AutoMod approve/deny endpoints to Helix. (#2779)
|
||||
|
||||
## 2.3.1
|
||||
|
||||
|
|
|
@ -3,12 +3,16 @@
|
|||
#include <QThread>
|
||||
|
||||
#include "Application.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/Env.hpp"
|
||||
#include "common/NetworkRequest.hpp"
|
||||
#include "common/Outcome.hpp"
|
||||
#include "common/QLogging.hpp"
|
||||
#include "controllers/accounts/AccountController.hpp"
|
||||
#include "messages/Message.hpp"
|
||||
#include "messages/MessageBuilder.hpp"
|
||||
#include "providers/IvrApi.hpp"
|
||||
#include "providers/irc/IrcMessageBuilder.hpp"
|
||||
#include "providers/twitch/TwitchCommon.hpp"
|
||||
#include "providers/twitch/TwitchUser.hpp"
|
||||
#include "providers/twitch/api/Helix.hpp"
|
||||
|
@ -354,42 +358,96 @@ SharedAccessGuard<const TwitchAccount::TwitchAccountEmoteData>
|
|||
}
|
||||
|
||||
// AutoModActions
|
||||
void TwitchAccount::autoModAllow(const QString msgID)
|
||||
void TwitchAccount::autoModAllow(const QString msgID, ChannelPtr channel)
|
||||
{
|
||||
QString url("https://api.twitch.tv/kraken/chat/twitchbot/approve");
|
||||
getHelix()->manageAutoModMessages(
|
||||
this->getUserId(), msgID, "ALLOW",
|
||||
[] {
|
||||
// success
|
||||
},
|
||||
[channel](auto error) {
|
||||
// failure
|
||||
QString errorMessage("Failed to allow AutoMod message - ");
|
||||
|
||||
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
|
||||
switch (error)
|
||||
{
|
||||
case HelixAutoModMessageError::MessageAlreadyProcessed: {
|
||||
errorMessage += "message has already been processed.";
|
||||
}
|
||||
break;
|
||||
|
||||
NetworkRequest(url, NetworkRequestType::Post)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Length", QByteArray::number(qba.size()))
|
||||
.payload(qba)
|
||||
case HelixAutoModMessageError::UserNotAuthenticated: {
|
||||
errorMessage += "you need to re-authenticate.";
|
||||
}
|
||||
break;
|
||||
|
||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||
.onError([=](NetworkResult result) {
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "[TwitchAccounts::autoModAllow] Error" << result.status();
|
||||
})
|
||||
.execute();
|
||||
case HelixAutoModMessageError::UserNotAuthorized: {
|
||||
errorMessage +=
|
||||
"you don't have permission to perform that action";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixAutoModMessageError::MessageNotFound: {
|
||||
errorMessage += "target message not found.";
|
||||
}
|
||||
break;
|
||||
|
||||
// This would most likely happen if the service is down, or if the JSON payload returned has changed format
|
||||
case HelixAutoModMessageError::Unknown:
|
||||
default: {
|
||||
errorMessage += "an unknown error occured.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchAccount::autoModDeny(const QString msgID)
|
||||
void TwitchAccount::autoModDeny(const QString msgID, ChannelPtr channel)
|
||||
{
|
||||
QString url("https://api.twitch.tv/kraken/chat/twitchbot/deny");
|
||||
getHelix()->manageAutoModMessages(
|
||||
this->getUserId(), msgID, "DENY",
|
||||
[] {
|
||||
// success
|
||||
},
|
||||
[channel](auto error) {
|
||||
// failure
|
||||
QString errorMessage("Failed to deny AutoMod message - ");
|
||||
|
||||
auto qba = (QString("{\"msg_id\":\"") + msgID + "\"}").toUtf8();
|
||||
switch (error)
|
||||
{
|
||||
case HelixAutoModMessageError::MessageAlreadyProcessed: {
|
||||
errorMessage += "message has already been processed.";
|
||||
}
|
||||
break;
|
||||
|
||||
NetworkRequest(url, NetworkRequestType::Post)
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Length", QByteArray::number(qba.size()))
|
||||
.payload(qba)
|
||||
case HelixAutoModMessageError::UserNotAuthenticated: {
|
||||
errorMessage += "you need to re-authenticate.";
|
||||
}
|
||||
break;
|
||||
|
||||
.authorizeTwitchV5(this->getOAuthClient(), this->getOAuthToken())
|
||||
.onError([=](NetworkResult result) {
|
||||
qCWarning(chatterinoTwitch)
|
||||
<< "[TwitchAccounts::autoModDeny] Error" << result.status();
|
||||
})
|
||||
.execute();
|
||||
case HelixAutoModMessageError::UserNotAuthorized: {
|
||||
errorMessage +=
|
||||
"you don't have permission to perform that action";
|
||||
}
|
||||
break;
|
||||
|
||||
case HelixAutoModMessageError::MessageNotFound: {
|
||||
errorMessage += "target message not found.";
|
||||
}
|
||||
break;
|
||||
|
||||
// This would most likely happen if the service is down, or if the JSON payload returned has changed format
|
||||
case HelixAutoModMessageError::Unknown:
|
||||
default: {
|
||||
errorMessage += "an unknown error occured.";
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
channel->addMessage(makeSystemMessage(errorMessage));
|
||||
});
|
||||
}
|
||||
|
||||
void TwitchAccount::loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
#include "common/Aliases.hpp"
|
||||
#include "common/Atomic.hpp"
|
||||
#include "common/Channel.hpp"
|
||||
#include "common/UniqueAccess.hpp"
|
||||
#include "controllers/accounts/Account.hpp"
|
||||
#include "messages/Emote.hpp"
|
||||
|
@ -114,8 +115,8 @@ public:
|
|||
SharedAccessGuard<const TwitchAccountEmoteData> accessEmotes() const;
|
||||
|
||||
// Automod actions
|
||||
void autoModAllow(const QString msgID);
|
||||
void autoModDeny(const QString msgID);
|
||||
void autoModAllow(const QString msgID, ChannelPtr channel);
|
||||
void autoModDeny(const QString msgID, ChannelPtr channel);
|
||||
|
||||
private:
|
||||
void loadEmoteSetData(std::shared_ptr<EmoteSet> emoteSet);
|
||||
|
|
|
@ -659,6 +659,68 @@ void Helix::updateChannel(QString broadcasterId, QString gameId,
|
|||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
void Helix::manageAutoModMessages(
|
||||
QString userID, QString msgID, QString action,
|
||||
std::function<void()> successCallback,
|
||||
std::function<void(HelixAutoModMessageError)> failureCallback)
|
||||
{
|
||||
QJsonObject payload;
|
||||
|
||||
payload.insert("user_id", userID);
|
||||
payload.insert("msg_id", msgID);
|
||||
payload.insert("action", action);
|
||||
|
||||
this->makeRequest("moderation/automod/message", QUrlQuery())
|
||||
.type(NetworkRequestType::Post)
|
||||
.header("Content-Type", "application/json")
|
||||
.payload(QJsonDocument(payload).toJson(QJsonDocument::Compact))
|
||||
.onSuccess([successCallback, failureCallback](auto result) -> Outcome {
|
||||
successCallback();
|
||||
return Success;
|
||||
})
|
||||
.onError([failureCallback, msgID, action](NetworkResult result) {
|
||||
switch (result.status())
|
||||
{
|
||||
case 400: {
|
||||
// Message was already processed
|
||||
failureCallback(
|
||||
HelixAutoModMessageError::MessageAlreadyProcessed);
|
||||
}
|
||||
break;
|
||||
|
||||
case 401: {
|
||||
// User is missing the required scope
|
||||
failureCallback(
|
||||
HelixAutoModMessageError::UserNotAuthenticated);
|
||||
}
|
||||
break;
|
||||
|
||||
case 403: {
|
||||
// Requesting user is not authorized to manage messages
|
||||
failureCallback(
|
||||
HelixAutoModMessageError::UserNotAuthorized);
|
||||
}
|
||||
break;
|
||||
|
||||
case 404: {
|
||||
// Message not found or invalid msgID
|
||||
failureCallback(HelixAutoModMessageError::MessageNotFound);
|
||||
}
|
||||
break;
|
||||
|
||||
default: {
|
||||
qCDebug(chatterinoTwitch)
|
||||
<< "Failed to manage automod message: " << action
|
||||
<< msgID << result.status() << result.getData();
|
||||
failureCallback(HelixAutoModMessageError::Unknown);
|
||||
}
|
||||
break;
|
||||
}
|
||||
})
|
||||
.execute();
|
||||
}
|
||||
|
||||
NetworkRequest Helix::makeRequest(QString url, QUrlQuery urlQuery)
|
||||
{
|
||||
assert(!url.startsWith("/"));
|
||||
|
|
|
@ -205,6 +205,14 @@ enum class HelixStreamMarkerError {
|
|||
UserNotAuthenticated,
|
||||
};
|
||||
|
||||
enum class HelixAutoModMessageError {
|
||||
Unknown,
|
||||
MessageAlreadyProcessed,
|
||||
UserNotAuthenticated,
|
||||
UserNotAuthorized,
|
||||
MessageNotFound,
|
||||
};
|
||||
|
||||
class Helix final : boost::noncopyable
|
||||
{
|
||||
public:
|
||||
|
@ -307,6 +315,12 @@ public:
|
|||
std::function<void(NetworkResult)> successCallback,
|
||||
HelixFailureCallback failureCallback);
|
||||
|
||||
// https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages
|
||||
void manageAutoModMessages(
|
||||
QString userID, QString msgID, QString action,
|
||||
std::function<void()> successCallback,
|
||||
std::function<void(HelixAutoModMessageError)> failureCallback);
|
||||
|
||||
void update(QString clientId, QString oauthToken);
|
||||
|
||||
static void initialize();
|
||||
|
|
|
@ -4,7 +4,7 @@ this folder describes what sort of API requests we do, what permissions are requ
|
|||
|
||||
## Kraken (V5)
|
||||
|
||||
We use a bunch of Kraken (V5) in Chatterino2.
|
||||
We use few Kraken endpoints in Chatterino2.
|
||||
|
||||
### Get Cheermotes
|
||||
|
||||
|
@ -23,18 +23,6 @@ Migration path: **Unknown**
|
|||
|
||||
- We use this in `providers/twitch/TwitchAccount.cpp loadEmotes` to figure out which emotes a user is allowed to use!
|
||||
|
||||
### AUTOMOD APPROVE
|
||||
|
||||
**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2
|
||||
|
||||
- We use this in `providers/twitch/TwitchAccount.cpp autoModAllow` to approve an automod deny/allow question
|
||||
|
||||
### AUTOMOD DENY
|
||||
|
||||
**Unofficial** documentation: https://discuss.dev.twitch.tv/t/allowing-others-aka-bots-to-use-twitchbot-reject/8508/2
|
||||
|
||||
- We use this in `providers/twitch/TwitchAccount.cpp autoModDeny` to deny an automod deny/allow question
|
||||
|
||||
## Helix
|
||||
|
||||
Full Helix API reference: https://dev.twitch.tv/docs/api/reference
|
||||
|
@ -160,6 +148,15 @@ URL: https://dev.twitch.tv/docs/api/reference#search-categories
|
|||
Used in:
|
||||
- `controllers/commands/CommandController.cpp` in `/setgame` command to fuzzy search for game titles
|
||||
|
||||
### Manage Held AutoMod Messages
|
||||
|
||||
URL: https://dev.twitch.tv/docs/api/reference#manage-held-automod-messages
|
||||
Requires `moderator:manage:automod` scope
|
||||
|
||||
- We implement this in `providers/twitch/api/Helix.cpp manageAutoModMessages`
|
||||
Used in:
|
||||
- `providers/twitch/TwitchAccount.cpp` to approve/deny held AutoMod messages
|
||||
|
||||
## TMI
|
||||
|
||||
The TMI api is undocumented.
|
||||
|
|
|
@ -2068,12 +2068,14 @@ void ChannelView::handleLinkClick(QMouseEvent *event, const Link &link,
|
|||
break;
|
||||
|
||||
case Link::AutoModAllow: {
|
||||
getApp()->accounts->twitch.getCurrent()->autoModAllow(link.value);
|
||||
getApp()->accounts->twitch.getCurrent()->autoModAllow(
|
||||
link.value, this->channel());
|
||||
}
|
||||
break;
|
||||
|
||||
case Link::AutoModDeny: {
|
||||
getApp()->accounts->twitch.getCurrent()->autoModDeny(link.value);
|
||||
getApp()->accounts->twitch.getCurrent()->autoModDeny(
|
||||
link.value, this->channel());
|
||||
}
|
||||
break;
|
||||
|
||||
|
|
Loading…
Reference in a new issue