mirror of
https://github.com/Zaarrg/stremio-community-v5.git
synced 2026-01-11 20:10:31 +00:00
Added advanced Browser Extensions Support
- Added extension domain whitelist gotten from extensions.json on github for allowed domains to navigate for browser extension usage. App allow these domain to be navigated to other are open externally. - Added script Queue to run scripts on navigation complete - Added "Home" navigate event arg, to navigate back to home page - Added Back to Stremio button when not on Stremio page - Partial permid support with custom patch. Still some features not working.
This commit is contained in:
parent
81e0ce17cb
commit
416dab85cf
10 changed files with 285 additions and 3 deletions
|
|
@ -4,5 +4,8 @@
|
|||
<option name="pythonIntegrationState" value="YES" />
|
||||
</component>
|
||||
<component name="CMakeWorkspace" PROJECT_DIR="$PROJECT_DIR$" />
|
||||
<component name="WestSettings"><![CDATA[{}]]></component>
|
||||
<component name="JavaScriptSettings">
|
||||
<option name="languageLevel" value="FLOW" />
|
||||
</component>
|
||||
<component name="WestSettings">{}</component>
|
||||
</project>
|
||||
|
|
@ -52,6 +52,8 @@ set(SOURCES
|
|||
src/ui/mainwindow.h
|
||||
src/ui/mainwindow.cpp
|
||||
src/resource.h
|
||||
src/utils/extensions.cpp
|
||||
src/utils/extensions.h
|
||||
)
|
||||
|
||||
add_executable(${PROJECT_NAME} WIN32 ${SOURCES})
|
||||
|
|
@ -63,6 +65,7 @@ target_link_libraries(${PROJECT_NAME} PRIVATE
|
|||
Shcore.lib
|
||||
Msimg32.lib
|
||||
winhttp.lib
|
||||
shlwapi.lib
|
||||
nlohmann_json::nlohmann_json
|
||||
unofficial::webview2::webview2
|
||||
OpenSSL::SSL
|
||||
|
|
|
|||
|
|
@ -15,7 +15,10 @@ std::vector<std::wstring> g_webuiUrls = {
|
|||
L"https://zaarrg.github.io/stremio-web-shell-fixes/",
|
||||
L"https://web.stremio.com/"
|
||||
};
|
||||
std::vector<std::wstring> g_domainWhitelist;
|
||||
std::string g_updateUrl= "https://raw.githubusercontent.com/Zaarrg/stremio-desktop-v5/refs/heads/webview-windows/version/version.json";
|
||||
std::wstring g_extensionsDetailsUrl= L"https://raw.githubusercontent.com/Zaarrg/stremio-desktop-v5/refs/heads/webview-windows/extensions/extensions.json";
|
||||
std::wstring g_webuiUrl;
|
||||
|
||||
// Command-line args
|
||||
bool g_streamingServer = true;
|
||||
|
|
@ -85,6 +88,7 @@ std::atomic<bool> g_waitStarted(false);
|
|||
|
||||
// Extensions
|
||||
std::map<std::wstring, std::wstring> g_extensionMap;
|
||||
std::vector<std::wstring> g_scriptQueue;
|
||||
|
||||
// Updater
|
||||
std::atomic_bool g_updaterRunning = false;
|
||||
|
|
|
|||
|
|
@ -42,7 +42,10 @@ extern HANDLE g_hMutex;
|
|||
extern HHOOK g_hMouseHook;
|
||||
|
||||
extern std::vector<std::wstring> g_webuiUrls;
|
||||
extern std::vector<std::wstring> g_domainWhitelist;
|
||||
extern std::string g_updateUrl;
|
||||
extern std::wstring g_extensionsDetailsUrl;
|
||||
extern std::wstring g_webuiUrl;
|
||||
|
||||
// Args
|
||||
extern bool g_streamingServer;
|
||||
|
|
@ -131,6 +134,7 @@ extern std::atomic<bool> g_waitStarted;
|
|||
|
||||
// Extensions
|
||||
extern std::map<std::wstring, std::wstring> g_extensionMap;
|
||||
extern std::vector<std::wstring> g_scriptQueue;
|
||||
|
||||
// Updater
|
||||
extern std::atomic_bool g_updaterRunning;
|
||||
|
|
|
|||
|
|
@ -221,7 +221,11 @@ void HandleEvent(const std::string &ev, std::vector<std::string> &args)
|
|||
ShellExecuteW(nullptr, L"open", uri.c_str(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
} else if (ev=="navigate") {
|
||||
std::wstring uri(args[0].begin(), args[0].end());
|
||||
g_webview->Navigate(uri.c_str());
|
||||
if (args[0] == "home") {
|
||||
g_webview->Navigate(g_webuiUrl.c_str());
|
||||
} else {
|
||||
g_webview->Navigate(uri.c_str());
|
||||
}
|
||||
} else {
|
||||
std::cout<<"Unknown event="<<ev<<"\n";
|
||||
}
|
||||
|
|
|
|||
39
src/utils/extensions.cpp
Normal file
39
src/utils/extensions.cpp
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
#include "extensions.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <string>
|
||||
#include "../core/globals.h"
|
||||
|
||||
void HandlePremidLogin(const std::wstring& finalUri) {
|
||||
if (finalUri.rfind(L"https://login.premid.app", 0) == 0 && finalUri.rfind(L"https://discord.com", 0) != 0) {
|
||||
std::wstring extensionId;
|
||||
auto it = std::find_if(g_extensionMap.begin(), g_extensionMap.end(),
|
||||
[](const std::pair<std::wstring, std::wstring>& p) -> bool {
|
||||
return p.first.find(L"premid") != std::wstring::npos;
|
||||
});
|
||||
if (it != g_extensionMap.end()) {
|
||||
extensionId = it->second;
|
||||
} else {
|
||||
std::wcout << L"[EXTENSIONS]: Extension id not found\n";
|
||||
g_webview->Navigate(g_webuiUrl.c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
std::wstring codeParam;
|
||||
size_t codePos = finalUri.find(L"code=");
|
||||
if (codePos != std::wstring::npos) {
|
||||
codePos += 5; // Move past "code="
|
||||
size_t ampPos = finalUri.find(L'&', codePos);
|
||||
if (ampPos == std::wstring::npos) {
|
||||
codeParam = finalUri.substr(codePos);
|
||||
} else {
|
||||
codeParam = finalUri.substr(codePos, ampPos - codePos);
|
||||
}
|
||||
}
|
||||
std::wstring script = L"globalThis.getAuthorizationCode(\"" + codeParam + L"\");";
|
||||
g_scriptQueue.push_back(script);
|
||||
|
||||
std::wstring uri = L"chrome-extension://" + extensionId + L"/popup.html";
|
||||
g_webview->Navigate(uri.c_str());
|
||||
}
|
||||
}
|
||||
8
src/utils/extensions.h
Normal file
8
src/utils/extensions.h
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
#ifndef EXTENSIONS_H
|
||||
#define EXTENSIONS_H
|
||||
|
||||
#include <string>
|
||||
|
||||
void HandlePremidLogin(const std::wstring& finalUri);
|
||||
|
||||
#endif //EXTENSIONS_H
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
#include "helpers.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <tlhelp32.h>
|
||||
#include <winhttp.h>
|
||||
#include "../core/globals.h"
|
||||
|
|
@ -189,4 +191,97 @@ std::wstring GetFirstReachableUrl() {
|
|||
}
|
||||
// Fallback to first URL or handle error
|
||||
return g_webuiUrls.empty() ? L"" : g_webuiUrls[0];
|
||||
}
|
||||
|
||||
bool URLContainsAny(const std::wstring& url) {
|
||||
if (std::find(g_domainWhitelist.begin(), g_domainWhitelist.end(), g_webuiUrl) == g_domainWhitelist.end()) {
|
||||
g_domainWhitelist.push_back(g_webuiUrl);
|
||||
}
|
||||
return std::any_of(g_domainWhitelist.begin(), g_domainWhitelist.end(), [&](const std::wstring& sub) {
|
||||
return url.find(sub) != std::wstring::npos;
|
||||
});
|
||||
}
|
||||
|
||||
bool FetchAndParseWhitelist() {
|
||||
HINTERNET hSession = WinHttpOpen(L"DomainWhitelist Updater",
|
||||
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, NULL, NULL, 0);
|
||||
if (!hSession) return false;
|
||||
|
||||
WinHttpSetTimeouts(hSession, 3000, 3000, 3000, 3000);
|
||||
|
||||
URL_COMPONENTS urlComp = { sizeof(URL_COMPONENTS) };
|
||||
urlComp.dwHostNameLength = (DWORD)-1;
|
||||
urlComp.dwUrlPathLength = (DWORD)-1;
|
||||
urlComp.dwSchemeLength = (DWORD)-1;
|
||||
|
||||
if (!WinHttpCrackUrl(g_extensionsDetailsUrl.c_str(), (DWORD)g_extensionsDetailsUrl.length(), 0, &urlComp)) {
|
||||
WinHttpCloseHandle(hSession);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring host(urlComp.lpszHostName, urlComp.dwHostNameLength);
|
||||
std::wstring path(urlComp.lpszUrlPath, urlComp.dwUrlPathLength);
|
||||
INTERNET_PORT port = urlComp.nPort;
|
||||
bool useSSL = (urlComp.nScheme == INTERNET_SCHEME_HTTPS);
|
||||
|
||||
HINTERNET hConnect = WinHttpConnect(hSession, host.c_str(), port, 0);
|
||||
if (!hConnect) {
|
||||
WinHttpCloseHandle(hSession);
|
||||
return false;
|
||||
}
|
||||
|
||||
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"GET", path.c_str(),
|
||||
NULL, NULL, NULL, useSSL ? WINHTTP_FLAG_SECURE : 0);
|
||||
if (!hRequest) {
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WinHttpSendRequest(hRequest, NULL, 0, NULL, 0, 0, 0)) {
|
||||
WinHttpCloseHandle(hRequest);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!WinHttpReceiveResponse(hRequest, NULL)) {
|
||||
WinHttpCloseHandle(hRequest);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string response;
|
||||
DWORD bytesRead = 0;
|
||||
do {
|
||||
char buffer[4096];
|
||||
if (!WinHttpReadData(hRequest, buffer, sizeof(buffer), &bytesRead)) {
|
||||
break;
|
||||
}
|
||||
if (bytesRead > 0) {
|
||||
response.append(buffer, bytesRead);
|
||||
}
|
||||
} while (bytesRead > 0);
|
||||
|
||||
WinHttpCloseHandle(hRequest);
|
||||
WinHttpCloseHandle(hConnect);
|
||||
WinHttpCloseHandle(hSession);
|
||||
|
||||
try {
|
||||
json j = json::parse(response);
|
||||
if (j.contains("domains") && j["domains"].is_array()) {
|
||||
g_domainWhitelist.clear();
|
||||
for (const auto& domain : j["domains"]) {
|
||||
if (domain.is_string()) {
|
||||
g_domainWhitelist.push_back(Utf8ToWstring(domain.get<std::string>()));
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
} catch (...) {
|
||||
std::wcout << L"[HELPER]: Failed json parsing of domain whitelist for extensions..." << std::endl;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
|
@ -15,5 +15,7 @@ bool FileExists(const std::wstring& path);
|
|||
bool DirectoryExists(const std::wstring& dirPath);
|
||||
bool IsDuplicateProcessRunning(const std::vector<std::wstring>& targetProcesses);
|
||||
bool isSubtitle(const std::wstring& filePath);
|
||||
bool URLContainsAny(const std::wstring& url);
|
||||
bool FetchAndParseWhitelist();
|
||||
|
||||
#endif // HELPERS_H
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
#include <string>
|
||||
#include <thread>
|
||||
#include <cmath>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <Shlwapi.h>
|
||||
#include <wrl.h>
|
||||
|
|
@ -9,6 +10,7 @@
|
|||
#include "../utils/crashlog.h"
|
||||
#include "../utils/helpers.h"
|
||||
#include "../ui/mainwindow.h"
|
||||
#include "../utils/extensions.h"
|
||||
|
||||
static const wchar_t* EXEC_SHELL_SCRIPT = LR"JS_CODE(
|
||||
try {
|
||||
|
|
@ -86,6 +88,78 @@ static const wchar_t* INJECTED_KEYDOWN_SCRIPT = LR"JS(
|
|||
})();
|
||||
)JS";
|
||||
|
||||
static const wchar_t* INJECTED_BUTTON_SCRIPT = LR"JS(
|
||||
(function() {
|
||||
// Create the button element
|
||||
var btn = document.createElement('button');
|
||||
btn.id = 'goBackStremioBtn';
|
||||
|
||||
// Style the button:
|
||||
btn.style.position = 'fixed';
|
||||
btn.style.bottom = '15px';
|
||||
btn.style.right = '15px';
|
||||
btn.style.zIndex = '9999';
|
||||
btn.style.backgroundColor = '#121024'; // Purple
|
||||
btn.style.color = 'white';
|
||||
btn.style.border = 'none';
|
||||
btn.style.borderRadius = '30px';
|
||||
btn.style.padding = '12px 20px';
|
||||
btn.style.fontSize = '16px';
|
||||
btn.style.fontWeight = 'bold';
|
||||
btn.style.display = 'flex';
|
||||
btn.style.alignItems = 'center';
|
||||
btn.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
|
||||
btn.style.cursor = 'pointer';
|
||||
btn.style.transition = 'background-color 0.3s ease';
|
||||
|
||||
// Hover effect:
|
||||
btn.addEventListener('mouseenter', function() {
|
||||
btn.style.backgroundColor = '#211e39'; // Gray
|
||||
});
|
||||
btn.addEventListener('mouseleave', function() {
|
||||
btn.style.backgroundColor = '#121024';
|
||||
});
|
||||
|
||||
// Create an image element for the logo
|
||||
var img = document.createElement('img');
|
||||
img.src = 'https://stremio.zarg.me/images/stremio_symbol.png';
|
||||
img.alt = 'Logo';
|
||||
img.style.height = '24px';
|
||||
img.style.width = '24px';
|
||||
img.style.marginRight = '8px';
|
||||
|
||||
img.addEventListener('error', function() {
|
||||
img.style.display = 'none';
|
||||
});
|
||||
|
||||
// Create a text element
|
||||
var txt = document.createElement('span');
|
||||
txt.textContent = 'Back to Stremio';
|
||||
|
||||
// Append the logo and text to the button
|
||||
btn.appendChild(img);
|
||||
btn.appendChild(txt);
|
||||
|
||||
// On click go home
|
||||
btn.addEventListener('click', function() {
|
||||
const payload = {
|
||||
type: 6,
|
||||
object: "transport",
|
||||
method: "handleInboundJSON",
|
||||
id: 666,
|
||||
args: [
|
||||
"navigate",
|
||||
[ "home" ]
|
||||
]
|
||||
};
|
||||
window.chrome.webview.postMessage(JSON.stringify(payload));
|
||||
});
|
||||
|
||||
// Append the button to the document body
|
||||
document.body.appendChild(btn);
|
||||
})();
|
||||
)JS";
|
||||
|
||||
void WaitAndRefreshIfNeeded()
|
||||
{
|
||||
std::thread([](){
|
||||
|
|
@ -222,7 +296,9 @@ void InitWebView2(HWND hWnd)
|
|||
std::wcout << L"[WEBVIEW]: Checking web ui endpoints..." << std::endl;
|
||||
std::wstring foundUrl = GetFirstReachableUrl();
|
||||
std::wstring* pResult = new std::wstring(foundUrl);
|
||||
g_webuiUrl = foundUrl;
|
||||
PostMessage(g_hWnd, WM_REACHABILITY_DONE, (WPARAM)pResult, 0);
|
||||
FetchAndParseWhitelist();
|
||||
}).detach();
|
||||
return S_OK;
|
||||
}).Get()
|
||||
|
|
@ -250,14 +326,34 @@ static void SetupWebMessageHandler()
|
|||
{
|
||||
BOOL isSuccess;
|
||||
args->get_IsSuccess(&isSuccess);
|
||||
|
||||
// Retrieve the final URL
|
||||
wil::unique_cotaskmem_string rawUri;
|
||||
sender->get_Source(&rawUri);
|
||||
std::wstring finalUri = rawUri ? rawUri.get() : L"";
|
||||
std::wcout << L"[WEBVIEW]: Navigation try to " << finalUri << std::endl;
|
||||
|
||||
// Add back to stremio button if not on stremio
|
||||
if (finalUri.find(g_webuiUrl) == std::wstring::npos) {
|
||||
sender->ExecuteScript(INJECTED_BUTTON_SCRIPT, nullptr);
|
||||
}
|
||||
|
||||
if(isSuccess) {
|
||||
std::cout<<"[WEBVIEW]: Navigation Complete - Success\n";
|
||||
sender->ExecuteScript(EXEC_SHELL_SCRIPT, nullptr);
|
||||
// Flush the script queue.
|
||||
if (!g_scriptQueue.empty()) {
|
||||
for (const auto &script : g_scriptQueue) {
|
||||
sender->ExecuteScript(script.c_str(), nullptr);
|
||||
}
|
||||
g_scriptQueue.clear();
|
||||
}
|
||||
} else {
|
||||
std::cout<<"[WEBVIEW]: Navigation failed\n";
|
||||
if(g_hSplash && !g_waitStarted.exchange(true)) {
|
||||
WaitAndRefreshIfNeeded();
|
||||
}
|
||||
HandlePremidLogin(finalUri);
|
||||
}
|
||||
return S_OK;
|
||||
}).Get(),
|
||||
|
|
@ -422,6 +518,10 @@ static void SetupWebMessageHandler()
|
|||
SendToJS("FileDropped", j);
|
||||
return S_OK;
|
||||
}
|
||||
if (URLContainsAny(wuri)) {
|
||||
g_webview->Navigate(wuri.c_str());
|
||||
return S_OK;
|
||||
}
|
||||
// For non-file URIs, open externally
|
||||
ShellExecuteW(nullptr, L"open", uri.get(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
|
|
@ -431,6 +531,26 @@ static void SetupWebMessageHandler()
|
|||
&newWindowToken
|
||||
);
|
||||
|
||||
// For redirects like window.location.replace
|
||||
EventRegistrationToken navstartedToken;
|
||||
g_webview->add_NavigationStarting(
|
||||
Microsoft::WRL::Callback<ICoreWebView2NavigationStartingEventHandler>(
|
||||
[](ICoreWebView2* sender, ICoreWebView2NavigationStartingEventArgs* args) -> HRESULT
|
||||
{
|
||||
wil::unique_cotaskmem_string uri;
|
||||
args->get_Uri(&uri);
|
||||
std::wstring destination(uri.get());
|
||||
|
||||
if (!URLContainsAny(destination)) {
|
||||
args->put_Cancel(TRUE);
|
||||
ShellExecuteW(nullptr, L"open", uri.get(), nullptr, nullptr, SW_SHOWNORMAL);
|
||||
}
|
||||
return S_OK;
|
||||
}
|
||||
).Get(),
|
||||
&navstartedToken
|
||||
);
|
||||
|
||||
// FullScreen
|
||||
EventRegistrationToken cfeToken;
|
||||
g_webview->add_ContainsFullScreenElementChanged(
|
||||
|
|
@ -526,4 +646,4 @@ void refreshWeb(const bool refreshAll) {
|
|||
if (g_webview) {
|
||||
g_webview->Reload();
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue