init commit

This commit is contained in:
Pan Cake
2024-02-01 13:45:18 +10:00
commit f03fcec5f3
3 changed files with 409 additions and 0 deletions
+16
View File
@@ -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
View File
@@ -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&amp;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>&#60;Code/&#62;</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
View File
@@ -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);