fix: image uploader setting import (#6387)

Co-authored-by: Rasmus Karlsson <rasmus.karlsson@pajlada.com>
This commit is contained in:
x3rt
2025-08-28 16:25:26 -06:00
committed by GitHub
parent f87aac7011
commit c7a7740bfe
8 changed files with 178 additions and 10 deletions
+1 -1
View File
@@ -20,7 +20,7 @@
- Minor: Made commands searchable using the Settings dialog search bar. (#5891)
- Minor: Added hotkey Action for opening account selector. (#6192)
- Minor: Add a setting to change the emote and badge thumbnail size. (#6126)
- Minor: Add an import and export button to image uploader settings. (#6284, #6394)
- Minor: Add an import and export button to image uploader settings. (#6284, #6394, #6387)
- Minor: Message character length label is now monospaced. (#6366)
- Minor: Add a setting under Moderation -> Logs to customize the timestamp used for chat logs. (#6338)
- Minor: Add a setting under Moderation -> Logs to use server timestamp from the message instead of the local clock time for logging. (#6346)
+65 -6
View File
@@ -4,11 +4,68 @@
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
namespace chatterino::imageuploader::detail {
namespace {
/**
* @brief Converts ShareX-style tokens throughout the URL to the expected format.
*
* Examples:
* - https://example.com/{json:foo.bar[1]}/{json:foo.bar[2]}
* -> https://example.com/{foo.bar.1}/{foo.bar.2}
* - {json:{response}|foo.bar[1]}
* -> {foo.bar.1}
*
* @see https://github.com/ShareX/ShareX/blob/8c0fac3bdf6c6ecb9756584096332175f290072c/ShareX.UploadersLib/CustomUploader/ShareXSyntaxParser.cs
* @see https://github.com/ShareX/ShareX/blob/8c0fac3bdf6c6ecb9756584096332175f290072c/ShareX.UploadersLib/CustomUploader/Functions/CustomUploaderFunctionJson.cs
*/
QString parseUrl(const QString &url)
{
if (url.isEmpty() || url.compare("{response}", Qt::CaseInsensitive) == 0)
{
return {};
}
static const QRegularExpression tokenRegex(
R"(\{json:([^{}]*(?:\{[^{}]*\}[^{}]*)*)\}*)",
QRegularExpression::CaseInsensitiveOption);
static const QRegularExpression arrayRegex(R"(\[(\d+)\])");
QString out;
qsizetype last = 0;
bool changed = false;
auto it = tokenRegex.globalMatch(url);
while (it.hasNext())
{
auto match = it.next();
out += url.mid(last, match.capturedStart() - last);
QString inner = match.captured(1).trimmed();
// Split by last '|' to support {json:input|jsonPath}
// We assume that the preceding parameter was `{response}` as other ShareX functions are not supported
qsizetype pipeIndex = inner.lastIndexOf('|');
if (pipeIndex != -1)
{
inner = inner.mid(pipeIndex + 1).trimmed();
}
inner.replace(arrayRegex, R"(.\1)");
out += '{' + inner + '}';
last = match.capturedEnd();
changed = true;
}
out += url.mid(last);
return changed ? out : QString(url);
}
QStringList parseHeaders(const QJsonObject &headersObj)
{
QStringList headerLines;
@@ -41,7 +98,7 @@ QJsonObject exportSettings(const Settings &s)
if (!headers.isEmpty())
{
QJsonObject headersObj;
QStringList headerLines = headers.split('\n', Qt::SkipEmptyParts);
QStringList headerLines = headers.split(';', Qt::SkipEmptyParts);
for (const QString &line : headerLines)
{
QStringList parts = line.split(':', Qt::SkipEmptyParts);
@@ -74,12 +131,13 @@ bool importSettings(const QJsonObject &settingsObj, Settings &s)
s.imageUploaderUrl = settingsObj["RequestURL"].toString();
s.imageUploaderFormField = settingsObj["FileFormName"].toString();
s.imageUploaderLink = settingsObj["URL"].toString();
s.imageUploaderLink = parseUrl(settingsObj["URL"].toString());
if (settingsObj.contains("DeletionURL") &&
settingsObj["DeletionURL"].isString())
{
s.imageUploaderDeletionLink = settingsObj["DeletionURL"].toString();
s.imageUploaderDeletionLink =
parseUrl(settingsObj["DeletionURL"].toString());
}
if (settingsObj.contains("Headers") && settingsObj["Headers"].isObject())
@@ -87,7 +145,7 @@ bool importSettings(const QJsonObject &settingsObj, Settings &s)
QStringList headers = parseHeaders(settingsObj["Headers"].toObject());
if (!headers.isEmpty())
{
s.imageUploaderHeaders = headers.join('\n');
s.imageUploaderHeaders = headers.join(';');
}
}
@@ -124,9 +182,10 @@ ExpectedStr<QJsonObject> validateImportJson(const QString &clipboardText)
return nonstd::make_unexpected("JSON must contain the 'Version' key");
}
if (!settingsObj.contains("Name"))
if (!settingsObj.contains("RequestURL"))
{
return nonstd::make_unexpected("JSON must contain the 'Name' key");
return nonstd::make_unexpected(
"JSON must contain the 'RequestURL' key");
}
return settingsObj;
@@ -11,8 +11,9 @@
"DeletionURL": "{more}",
"FileFormName": "file",
"Headers": {
"My-Header": "Foo ; Bar : Baz ; KeyOnly",
"X-My-Header": "1"
"Bar": "Baz",
"KeyOnly\nX-My-Header": "1",
"My-Header": "Foo"
},
"Name": "Chatterino Image Uploader Settings",
"RequestMethod": "POST",
@@ -0,0 +1,27 @@
{
"input": {
"Body": "MultipartFormData",
"DeletionURL": "{json:delete}",
"DestinationType": "ImageUploader, FileUploader",
"ErrorMessage": "{json:message}",
"FileFormName": "file",
"Headers": {
"X-bing": "bong",
"X-foo": "bar"
},
"Parameters": {
"urlfoo": "urlbar"
},
"RequestMethod": "POST",
"RequestURL": "https://kappa.lol/api/upload",
"URL": "{json:link}",
"Version": "14.0.1"
},
"output": {
"deletionLink": "{delete}",
"formField": "file",
"headers": "X-bing: bong;X-foo: bar",
"link": "{link}",
"url": "https://kappa.lol/api/upload"
}
}
@@ -17,7 +17,7 @@
"output": {
"deletionLink": "{more}",
"formField": "file",
"headers": "Another: header\nMy-Header: Foo ; Bar : Baz ; KeyOnly\nX-My-Header: 1",
"headers": "Another: header;My-Header: Foo ; Bar : Baz ; KeyOnly;X-My-Header: 1",
"link": "foo{bar}baz",
"url": "https://example.com"
}
@@ -0,0 +1,27 @@
{
"input": {
"Body": "MultipartFormData",
"DeletionURL": "{json:delete}",
"DestinationType": "ImageUploader, FileUploader",
"ErrorMessage": "{json:message}",
"FileFormName": "file",
"Headers": {
"X-bing": "bong",
"X-foo": "bar"
},
"Parameters": {
"urlfoo": "urlbar"
},
"RequestMethod": "POST",
"RequestURL": "https://kappa.lol/api/upload",
"URL": "{json:files[0].url}",
"Version": "14.0.1"
},
"output": {
"deletionLink": "{delete}",
"formField": "file",
"headers": "X-bing: bong;X-foo: bar",
"link": "{files.0.url}",
"url": "https://kappa.lol/api/upload"
}
}
@@ -0,0 +1,27 @@
{
"input": {
"Body": "MultipartFormData",
"DeletionURL": "{json:delete}",
"DestinationType": "ImageUploader, FileUploader",
"ErrorMessage": "{json:message}",
"FileFormName": "file",
"Headers": {
"X-bing": "bong",
"X-foo": "bar"
},
"Parameters": {
"urlfoo": "urlbar"
},
"RequestMethod": "POST",
"RequestURL": "https://kappa.lol/api/upload",
"URL": "{json:response|}",
"Version": "14.0.1"
},
"output": {
"deletionLink": "{delete}",
"formField": "file",
"headers": "X-bing: bong;X-foo: bar",
"link": "{}",
"url": "https://kappa.lol/api/upload"
}
}
@@ -0,0 +1,27 @@
{
"input": {
"Body": "MultipartFormData",
"DeletionURL": "{json:delete}",
"DestinationType": "ImageUploader, FileUploader",
"ErrorMessage": "{json:message}",
"FileFormName": "file",
"Headers": {
"X-bing": "bong",
"X-foo": "bar"
},
"Parameters": {
"urlfoo": "urlbar"
},
"RequestMethod": "POST",
"RequestURL": "https://kappa.lol/api/upload",
"URL": "{json:response|files[0].url}",
"Version": "14.0.1"
},
"output": {
"deletionLink": "{delete}",
"formField": "file",
"headers": "X-bing: bong;X-foo: bar",
"link": "{files.0.url}",
"url": "https://kappa.lol/api/upload"
}
}