mirror of
https://github.com/Viren070/stremio-addon-manager.git
synced 2025-12-01 23:25:50 +01:00
init commit
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
# Stremio Addon Manager
|
||||
Manage your Stremio addons with ease.
|
||||
|
||||
**WARNING: Use this at your own risk. This is not an official Stremio product and may break your Stremio installation. No support or warranty is given.**
|
||||
|
||||
## Features
|
||||
- Re-order your addons (including Cinemeta)
|
||||
- Remove non-protected addons
|
||||
|
||||
## Installation
|
||||
1. Clone this repository
|
||||
2. Open `web/index.html` in your browser
|
||||
3. Follow instructions on the page
|
||||
|
||||
## Thanks
|
||||
Big thank you to `Sleeyax` and `<Code/>` for the conversations and code snippets that made this really easy to implement.
|
||||
+198
@@ -0,0 +1,198 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="description" content="Stremio Addon Manager">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stremio Addon Manager</title>
|
||||
<link rel="stylesheet" href="https://unpkg.com/chota@latest">
|
||||
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v4.0.0/css/line.css">
|
||||
<script src="script.js" defer></script>
|
||||
<style>
|
||||
body>.container {
|
||||
max-width: 720px;
|
||||
}
|
||||
|
||||
.logo {
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
margin-top: 10%;
|
||||
}
|
||||
|
||||
.logo svg {
|
||||
width: 100%;
|
||||
max-width: 300px;
|
||||
}
|
||||
|
||||
.logo h3 {
|
||||
font-weight: 300;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
body>footer {
|
||||
background-color: #fafafa;
|
||||
padding: 4rem;
|
||||
}
|
||||
|
||||
body.dark>footer {
|
||||
background-color: #262626;
|
||||
}
|
||||
|
||||
section {
|
||||
margin: 5rem auto;
|
||||
}
|
||||
|
||||
.install button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.install {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* sortable list/addon list specifics */
|
||||
.sortable-list {
|
||||
padding: 25px;
|
||||
background: #fff;
|
||||
border-radius: 7px;
|
||||
padding: 30px 25px 20px;
|
||||
box-shadow: 0 15px 30px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.sortable-list .item {
|
||||
list-style: none;
|
||||
display: flex;
|
||||
cursor: move;
|
||||
background: #fff;
|
||||
align-items: center;
|
||||
border-radius: 5px;
|
||||
padding: 10px 13px;
|
||||
margin-bottom: 11px;
|
||||
/* box-shadow: 0 2px 4px rgba(0,0,0,0.06); */
|
||||
border: 1px solid #ccc;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.item .details {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.item .details img {
|
||||
height: 43px;
|
||||
width: 43px;
|
||||
pointer-events: none;
|
||||
margin-right: 12px;
|
||||
object-fit: contain;
|
||||
object-position: center;
|
||||
border-radius: 50%;
|
||||
background-color: #262626;
|
||||
|
||||
}
|
||||
|
||||
.item.dragging {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.item.dragging :where(.details, i) {
|
||||
opacity: 0;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body id="top" class="dark">
|
||||
<div class="container">
|
||||
<div class="hero">
|
||||
<div class="logo is-center is-vertical-align">
|
||||
<h1>Stremio Addon Manager</h1>
|
||||
<h3>Re-order and remove <strong>Stremio</strong> addons from your profile.
|
||||
</h3>
|
||||
<small><em>For power users - use it at your own risk.</em></small>
|
||||
</div>
|
||||
</div>
|
||||
<section id="features">
|
||||
<h2>Why?</h2>
|
||||
<p>
|
||||
Stremio currently doesn't allow users to change the order that their installed addons appear on the home
|
||||
screen.
|
||||
As a work around, it is common for users to remove and re-install addons in the order they want
|
||||
them to appear. This is a tedious and cumbersome process. This addon uses the internal Stremio API to
|
||||
manipulate addon order without having to add/remove them.
|
||||
</p>
|
||||
<p>
|
||||
This is a <strong>workaround</strong> and not a <strong>solution</strong>. It is not recommended to
|
||||
use this tool unless you are comfortable with the risks involved.
|
||||
<strong>No support or warranty is given.</strong>
|
||||
</p>
|
||||
<h3>How?</h3>
|
||||
<ol>
|
||||
<li>Login to <a href="https://web.stremio.com/">https://web.stremio.com/</a> using your Stremio
|
||||
credentials in your browser.</li>
|
||||
<li>
|
||||
Open the developer console and paste the follow code snippet
|
||||
<code>JSON.parse(localStorage.getItem("profile")).auth.key</code>
|
||||
</li>
|
||||
<li>Take the output value and paste it into the form below.</li>
|
||||
<li>Click 'Load Addons' to load your profiles addons.</li>
|
||||
<li>Re-order your addons as you like.</li>
|
||||
<li>Click the 'Sync To Stremio' button to sync the changes back to your profile.</li>
|
||||
</ol>
|
||||
</section>
|
||||
<section id="configure">
|
||||
<h2>Configure</h2>
|
||||
<form onsubmit="return false;">
|
||||
<fieldset id="form_step0">
|
||||
<legend>Step 0: Paste Stremio AuthKey</legend>
|
||||
<p class="grouped">
|
||||
<input type="password" id="stremio_authkey_input_field"
|
||||
placeholder="Paste Stremio AuthKey here...">
|
||||
<button class="button primary" id="load_addons_button">Load Addons</button>
|
||||
</p>
|
||||
</fieldset>
|
||||
<fieldset id="form_step1">
|
||||
<legend>Step 1: Re-Order Addons</legend>
|
||||
<ul class="sortable-list">
|
||||
<!-- Placeholder for addon items -->
|
||||
</ul>
|
||||
</fieldset>
|
||||
<fieldset id="form_step2">
|
||||
<legend>Step 2: Sync Addons</legend>
|
||||
<button type="button" class="button primary icon" id="sync_addons_button">Sync to Stremio
|
||||
<img src="https://icongr.am/feather/loader.svg?size=16&color=ffffff" alt="icon">
|
||||
</button>
|
||||
</fieldset>
|
||||
</form>
|
||||
|
||||
</section>
|
||||
<section id="thanks">
|
||||
<h3>Thank you</h3>
|
||||
<p>Big thank you to <code>Sleeyax</code> and <code><Code/></code> for the conversations and code
|
||||
snippets that made this really easy to implement.</p>
|
||||
|
||||
</section>
|
||||
|
||||
<section id="faq">
|
||||
<h2>Frequently Asked Questions</h2>
|
||||
<details>
|
||||
<summary>
|
||||
What risks are involved?
|
||||
</summary>
|
||||
<p>
|
||||
This was whipped together in a couple of hours. It is not well tested and may break your Stremio
|
||||
profile. Stremio functionality relies on the existence of certain addons (ie, Cinemeta). It is
|
||||
possible that the order of addons is also important to some functionality.
|
||||
</p>
|
||||
<p>
|
||||
There is currently no way to 'reset' your addons to previous configurations or default
|
||||
configurations.
|
||||
</p>
|
||||
</details>
|
||||
</section>
|
||||
<div>
|
||||
<p>Made with ❤️, 🤖 and 🥞's - enjoy!</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
+195
@@ -0,0 +1,195 @@
|
||||
const addedAddonsListElement = document.querySelector(".sortable-list");
|
||||
const loadAddonButton = document.getElementById("load_addons_button");
|
||||
const syncAddonsButton = document.getElementById("sync_addons_button");
|
||||
|
||||
addonList = [];
|
||||
|
||||
const initSortableList = (e) => {
|
||||
e.preventDefault();
|
||||
const draggingItem = document.querySelector(".dragging");
|
||||
// Getting all items except currently dragging and making array of them
|
||||
let siblings = [...addedAddonsListElement.querySelectorAll(".item:not(.dragging)")];
|
||||
|
||||
// Finding the sibling after which the dragging item should be placed
|
||||
let nextSibling = siblings.find(sibling => {
|
||||
return e.clientY <= sibling.getBoundingClientRect().top;
|
||||
});
|
||||
|
||||
// Inserting the dragging item before the found sibling
|
||||
addedAddonsListElement.insertBefore(draggingItem, nextSibling);
|
||||
|
||||
// Update local addon list
|
||||
syncLocalAddonList();
|
||||
}
|
||||
|
||||
function removeAddon(url) {
|
||||
// Remove addon from list
|
||||
addonList = addonList.filter(addon => addon.transportUrl != url);
|
||||
// Remove addon from list
|
||||
removeAddonFromList(url);
|
||||
}
|
||||
|
||||
function getAuthKey() {
|
||||
// Strip "'s and spaces from AuthKey in case user pasted them in
|
||||
return document.getElementById("stremio_authkey_input_field").value.replaceAll('"', '').trim();
|
||||
}
|
||||
|
||||
async function loadAddons() {
|
||||
var authKey = getAuthKey();
|
||||
if (!authKey) {
|
||||
alert("Please enter your Stremio auth key");
|
||||
throw new Error("No auth key");
|
||||
}
|
||||
var json = JSON.stringify({
|
||||
type: 'AddonCollectionGet',
|
||||
authKey,
|
||||
update: true,
|
||||
});
|
||||
var addons = await fetch('https://api.strem.io/api/addonCollectionGet', {
|
||||
method: 'POST',
|
||||
body: json,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => data.result.addons);
|
||||
return addons;
|
||||
}
|
||||
|
||||
async function syncAddons() {
|
||||
var authKey = getAuthKey();
|
||||
var json = JSON.stringify({
|
||||
type: 'AddonCollectionSet',
|
||||
authKey,
|
||||
addons: addonList,
|
||||
});
|
||||
var addons = await fetch('https://api.strem.io/api/addonCollectionSet', {
|
||||
method: 'POST',
|
||||
body: json,
|
||||
})
|
||||
.then((res) => res.json())
|
||||
.then((data) => {
|
||||
if (!("result" in data) || data.result == null) {
|
||||
alert("Failed to sync addons with unknown error");
|
||||
} else if (!data.result.success) {
|
||||
alert("Failed to sync addons: " + data.result.error);
|
||||
} else {
|
||||
alert("Successfully synced addons!");
|
||||
}
|
||||
});
|
||||
return addons;
|
||||
}
|
||||
|
||||
function addAddonToList(addon) {
|
||||
const item = document.createElement("li");
|
||||
item.className = "item";
|
||||
item.draggable = true;
|
||||
item.setAttribute("data-url", addon.transportUrl);
|
||||
item.setAttribute("data-addon", JSON.stringify(addon));
|
||||
item.innerHTML = `
|
||||
<div class="col-8">
|
||||
<div class="details">
|
||||
<div class="logo_container">
|
||||
</div>
|
||||
<span>${addon.manifest.name}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
`;
|
||||
|
||||
// Populate logo or use a placeholder if manifest doesn't define one
|
||||
if (addon.manifest.logo) {
|
||||
item.querySelector(".logo_container").innerHTML = `<img src="${addon.manifest.logo}" alt="${addon.manifest.name}" />`;
|
||||
} else {
|
||||
item.querySelector(".logo_container").innerHTML = `<img src="https://icongr.am/feather/box.svg?size=12" alt="${addon.manifest.name}" />`;
|
||||
}
|
||||
|
||||
addonConfigurable = false;
|
||||
if (addon.manifest.behaviorHints && addon.manifest.behaviorHints.configurable) {
|
||||
addonConfigurable = true;
|
||||
item.innerHTML += `
|
||||
<button class="button icon-only visit-url" title="Open addon configuration page in new window">
|
||||
<img src="https://icongr.am/feather/arrow-up-right.svg?size=12">
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
item.innerHTML += `
|
||||
<button class="button icon-only copy-url" title="Copy addon manifest URL to clipboard">
|
||||
<img src="https://icongr.am/feather/clipboard.svg?size=12">
|
||||
</button>
|
||||
`;
|
||||
// Only show delete button if addon is not protected
|
||||
addonDeletable = true;
|
||||
if ("flags" in addon && addon.flags.protected) {
|
||||
addonDeletable = false;
|
||||
} else {
|
||||
item.innerHTML += `
|
||||
<button class="button icon-only delete" title="Remove addon from list">
|
||||
<img src="https://icongr.am/feather/trash-2.svg?size=12">
|
||||
</button>
|
||||
`;
|
||||
}
|
||||
item.innerHTML += `
|
||||
<i class="uil uil-draggabledots"></i>
|
||||
</div >
|
||||
`;
|
||||
addedAddonsListElement.appendChild(item);
|
||||
|
||||
// Add elem event listeners
|
||||
item.addEventListener("dragstart", () => {
|
||||
// Adding dragging class to item after a delay
|
||||
setTimeout(() => item.classList.add("dragging"), 0);
|
||||
});
|
||||
// Removing dragging class from item on dragend event
|
||||
item.addEventListener("dragend", () => item.classList.remove("dragging"));
|
||||
// Add copy url button event listener
|
||||
item.querySelector(".button.icon-only.copy-url").addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
navigator.clipboard.writeText(addon.transportUrl);
|
||||
});
|
||||
// Add delete button event listener
|
||||
if (addonDeletable) {
|
||||
item.querySelector(".button.icon-only.delete").addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
removeAddon(addon.transportUrl);
|
||||
});
|
||||
}
|
||||
// Add visit addon configuration endpoint
|
||||
if (addonConfigurable) {
|
||||
// Add visit url button event listener - replacing protocol with https and path with /configure
|
||||
item.querySelector(".button.icon-only.visit-url").addEventListener("click", e => {
|
||||
e.preventDefault();
|
||||
window.open(addon.transportUrl.replace("stremio://", "https://").replace("/manifest.json", "/configure"));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function removeAddonFromList(url) {
|
||||
const item = addedAddonsListElement.querySelector(`[data-url="${url}"]`);
|
||||
item.remove();
|
||||
}
|
||||
|
||||
function syncLocalAddonList() {
|
||||
const items = addedAddonsListElement.querySelectorAll(".item");
|
||||
addonList = [];
|
||||
items.forEach(item => {
|
||||
addonList.push(JSON.parse(item.getAttribute("data-addon")));
|
||||
});
|
||||
}
|
||||
|
||||
async function main() {
|
||||
addedAddonsListElement.addEventListener("dragover", initSortableList);
|
||||
addedAddonsListElement.addEventListener("dragenter", e => e.preventDefault());
|
||||
|
||||
syncAddonsButton.addEventListener("click", syncAddons);
|
||||
loadAddonButton.addEventListener("click", async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
await loadAddons().then((addons) => {
|
||||
addons.forEach((addon) => {
|
||||
addAddonToList(addon);
|
||||
addonList.push(addon);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
main().then(() => console.log('done')).catch(console.error);
|
||||
Reference in New Issue
Block a user