On Thu, Jan 08, 2026 at 02:59:02PM -0500, Chris Frey wrote:
> If so, then this commit in the nheko dependency "mtxclient" should fix it:
>
> Implement MSC3916 (auth for media)
>
> https://github.com/Nheko-Reborn/mtxclient/commit/a70753812e3fb832df12b24ee0ee6c70d68060d5
I gave it a test, and while the patch from the commit did not apply
cleanly to bookworm's version of nheko, what did apply fixed my issue
with image downloads.
I've attached the patch I tested.
- Chris
diff -ru nheko-0.11.3/mtxclient/lib/http/client.cpp nheko-0.11.3-patched/mtxclient/lib/http/client.cpp
--- nheko-0.11.3/mtxclient/lib/http/client.cpp 2023-02-20 00:56:06.000000000 +0000
+++ nheko-0.11.3-patched/mtxclient/lib/http/client.cpp 2026-01-09 03:59:21.867062653 +0000
@@ -740,29 +740,45 @@
params.emplace("method", opts.method);
auto mxc = mtx::client::utils::parse_mxc_url(opts.mxc_url);
- const auto api_path = "/media/v3/thumbnail/" + mxc.server + "/" + mxc.media_id + "?" +
+ const auto api_path = "/client/v1/media/thumbnail/" + mxc.server + "/" + mxc.media_id + "?" +
client::utils::query_params(params);
+ auto fallback_api_path = "/media/v3/thumbnail/" + mxc.server + "/" + mxc.media_id + "?" +
+ client::utils::query_params(params);
+
get<std::string>(
api_path,
[callback = std::move(callback),
try_download,
mxc = std::move(mxc),
- _this = shared_from_this()](const std::string &res, HeaderFields, RequestErr err) {
- if (err && try_download) {
- const int status_code = static_cast<int>(err->status_code);
-
- if (status_code == 404) {
- _this->download(mxc.server,
- mxc.media_id,
- [callback](const std::string &res,
- const std::string &, // content_type
- const std::string &, // original_filename
- RequestErr err) { callback(res, err); });
- return;
- }
+ _this = shared_from_this(),
+ fallback_api_path =
+ std::move(fallback_api_path)](const std::string &res, HeaderFields, RequestErr err) {
+ if (!err || !(err->status_code == 404 || err->status_code == 400)) {
+ callback(res, err);
+ } else if (err && try_download && err->status_code == 404) {
+ _this->download(mxc.server,
+ mxc.media_id,
+ [callback](const std::string &res,
+ const std::string &, // content_type
+ const std::string &, // original_filename
+ RequestErr err) { callback(res, err); });
+ } else {
+ _this->get<std::string>(fallback_api_path,
+ [callback = callback, try_download, mxc, _this](
+ const std::string &res, HeaderFields, RequestErr err) {
+ if (err && try_download && err->status_code == 404) {
+ _this->download(
+ mxc.server,
+ mxc.media_id,
+ [callback](const std::string &res,
+ const std::string &, // content_type
+ const std::string &, // original_filename
+ RequestErr err) { callback(res, err); });
+ } else {
+ callback(res, err);
+ }
+ });
}
-
- callback(res, err);
});
}
@@ -774,32 +790,44 @@
const std::string &original_filename,
RequestErr err)> callback)
{
- const auto api_path = "/media/v3/download/" + server + "/" + media_id;
- get<std::string>(
- api_path,
- [callback =
- std::move(callback)](const std::string &res, HeaderFields fields, RequestErr err) {
- std::string content_type, original_filename;
-
- if (fields) {
- if (fields->find("Content-Type") != fields->end())
- content_type = fields->at("Content-Type");
- if (fields->find("Content-Disposition") != fields->end()) {
- auto value = fields->at("Content-Disposition");
-
- if (auto pos = value.find("filename"); pos != std::string::npos) {
- if (auto start = value.find('"', pos); start != std::string::npos) {
- auto end = value.find('"', start + 1);
- original_filename = value.substr(start + 1, end - start - 2);
- } else if (start = value.find('='); start != std::string::npos) {
- original_filename = value.substr(start + 1);
- }
- }
- }
- }
-
- callback(res, content_type, original_filename, err);
- });
+ auto cb = [callback =
+ std::move(callback)](const std::string &res, HeaderFields fields, RequestErr err) {
+ std::string content_type, original_filename;
+
+ if (fields) {
+ if (fields->find("Content-Type") != fields->end())
+ content_type = fields->at("Content-Type");
+ if (fields->find("Content-Disposition") != fields->end()) {
+ auto value = fields->at("Content-Disposition");
+
+ if (auto pos = value.find("filename"); pos != std::string::npos) {
+ if (auto start = value.find('"', pos); start != std::string::npos) {
+ auto end = value.find('"', start + 1);
+ original_filename = value.substr(start + 1, end - start - 2);
+ } else if (start = value.find('='); start != std::string::npos) {
+ original_filename = value.substr(start + 1);
+ }
+ }
+ }
+ }
+
+ callback(res, content_type, original_filename, err);
+ };
+
+ const auto api_path = "/client/v1/media/download/" + client::utils::url_encode(server) + "/" +
+ client::utils::url_encode(media_id);
+ get<std::string>(api_path,
+ [_this = shared_from_this(), cb = std::move(cb), server, media_id](
+ const std::string &res, HeaderFields fields, RequestErr err) {
+ if (!err || !(err->status_code == 404 || err->status_code == 400)) {
+ cb(res, fields, err);
+ } else {
+ const auto api_path = "/media/v3/download/" +
+ client::utils::url_encode(server) + "/" +
+ client::utils::url_encode(media_id);
+ _this->get<std::string>(api_path, std::move(cb));
+ }
+ });
}
void