-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
A lot of improvements that make the app more reliable and more usable.
- Now relies on [Stremio Service](https://github.com/Stremio/stremio-service) to run (if stremio service isn't installed it will guide the user to install it). This is means you will get a similar streaming experience to the official app. - Checks if the installed version is the latest version or not. Startup checking can be disabled from the settings (not tested yet). - Made a new plugin called "BetterEpisodesList." The idea is to add a new season option in shows that says "all." When the option is chosen, the plugin will take all of the episodes available in a show (except specials) and put them in one list for you, numbered as they are in one list. This is very useful for shows like One Piece, Where in other platforms the episodes are listed as they are in one season. This plugin brings that to Stremio. The plugin also adds a search bar for the user to search episodes, which is a feature apparent in [Stremio Web](https://web.stremio.com/). - Now shows the list of plugins/themes in more details. It will show the details provided in the meta data (the first few comment lines). You can check examples/BetterEpisodesList.plugin.js. - The source is a bit more organized now. - Fixed the flag for --devtools. Now if the user launches stremio-enhanced with the flag --devtools it will open the F12 devtools on launch properly. - Added the hotkey Ctrl+Shift+I to open the devtools. - Added the flag --no-stremio-service for users who don't want to use stremio service and override the streaming server URL from the settings. - Now sets the background to black on startup, so you don't get flashbanged on launch. *Note: I've only tested the update on Windows.*
- Loading branch information
1 parent
dd1752c
commit ff3cb78
Showing
21 changed files
with
20,589 additions
and
165,877 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,192 @@ | ||
/** | ||
* @name BetterEpisodeList | ||
* @description Adds a search bar to search episodes either by name or number, and adds an option to show all episodes in one list. | ||
* @updateUrl https://raw.githubusercontent.com/REVENGE977/BetterEpisodeList/main/BetterEpisodeList.plugin.js | ||
* @version 1.0.0 | ||
* @author REVENGE977 | ||
*/ | ||
|
||
//regex pattern for shows and movies urls | ||
const regexPattern = /\/detail\/(series|movie)\/[a-zA-Z0-9]+(?:\/[a-zA-Z0-9]+)?(?:\/tt[0-9]+%3A\d+:\d+)?(?:\/[^\/]+)?\/?$/; | ||
|
||
window.addEventListener('popstate', () => { | ||
console.log("[ BetterEpisodeList ] URL changed: " + window.location.href); | ||
waitForElm('[name="season"]').then(() => { | ||
if(regexPattern.test(window.location.href)) { | ||
console.log("[ BetterEpisodeList ] regex matches!"); | ||
if(document.getElementById("episode-search-field")) document.getElementById("episode-search-field").remove(); | ||
if(document.getElementById("AllEpisodesPlugin")) document.getElementById("AllEpisodesPlugin").remove(); | ||
|
||
//insert episode search bar | ||
addSearchBar(); | ||
|
||
//insert all option into the season selection | ||
const seasonSelectMenu = document.getElementsByName("season")[0]; | ||
addAllOption(seasonSelectMenu); | ||
|
||
seasonSelectMenu.addEventListener('change', (e) => { | ||
let pluginAddedEpisodes = document.getElementsByName("allEpisodesPlugin-episode"); | ||
|
||
if (e.target.value === 'all') { | ||
console.log('[ BetterEpisodeList ] Option All is selected!'); | ||
let injector = angular.injector(['ng']); | ||
let compile = injector.get('$compile'); | ||
let container = document.getElementsByClassName('episodes-list')[0]; | ||
let scope = angular.element(container).scope() | ||
|
||
scope.$apply(() => { | ||
if(pluginAddedEpisodes.length > 0) { | ||
pluginAddedEpisodes.forEach(element => { | ||
if(element) element.style.display = "flex"; | ||
}) | ||
} else { | ||
hideAddedEpisodes(); | ||
addEpisodesToAll(compile, scope); | ||
} | ||
}); | ||
} else if (e.target.value != 'all' && pluginAddedEpisodes.length > 0) { | ||
console.log("[BetterEpisodeList] Hiding all episodes.") | ||
pluginAddedEpisodes.forEach(element => { | ||
if(element) element.style.display = "none"; | ||
}) | ||
} | ||
}); | ||
} | ||
}) | ||
}); | ||
|
||
function addSearchBar() { | ||
let injector = angular.injector(['ng']); | ||
let compile = injector.get('$compile'); | ||
|
||
let heading = document.querySelector(".episodes > .heading"); | ||
let headingScope = angular.element(heading).scope(); | ||
|
||
let inputElm = document.createElement('input'); | ||
inputElm.setAttribute("id", "episode-search-field"); | ||
inputElm.setAttribute("tabindex", "-1"); | ||
inputElm.setAttribute("placeholder", "Search episode by name or number"); | ||
inputElm.setAttribute("type", "text"); | ||
inputElm.setAttribute("class", "ng-pristine ng-valid ng-isolate-scope ng-empty ng-touched"); | ||
inputElm.setAttribute("style", ` | ||
width: -webkit-fill-available; | ||
position: relative; | ||
margin-top: 5%;`); | ||
|
||
heading.appendChild(inputElm); | ||
compile(inputElm)(headingScope); | ||
|
||
inputElm.addEventListener("input", () => { | ||
searchEpisodes(inputElm.value); | ||
}) | ||
} | ||
|
||
function addAllOption(seasonSelectMenu) { | ||
let allOption = document.createElement('option'); | ||
allOption.value = 'all'; | ||
allOption.text = 'All'; | ||
allOption.id = "AllEpisodesPlugin"; | ||
|
||
seasonSelectMenu.insertBefore(allOption, seasonSelectMenu.firstChild); | ||
|
||
return seasonSelectMenu; | ||
} | ||
|
||
function addEpisodesToAll(compile, scope) { | ||
const container = document.getElementsByClassName('episodes-list')[0]; | ||
const episodes = scope.info.availableEpisodes || []; | ||
|
||
let episodesCounter = 0; | ||
|
||
episodes.forEach(episode => { | ||
let isSpecialEpisode = episode.season == 0; | ||
if(isSpecialEpisode) return; | ||
episodesCounter++; | ||
|
||
let childScope = scope.$new(); | ||
|
||
childScope.libItem = scope.item; | ||
childScope.metaItem = scope.info; | ||
childScope.intent = "player"; | ||
childScope.episode = episode; | ||
|
||
let li = document.createElement('li'); | ||
|
||
li.setAttribute('ng-click', `open({ libItem: libItem, metaItem: metaItem, video: episode, intent: "player" })`); | ||
li.setAttribute('tabindex', '-1'); | ||
li.setAttribute('title', `${episode.name}: Season ${episode.season}, Episode ${episode.number}, AllEpisode: ${episodesCounter}`); | ||
li.setAttribute("ng-right-click", "!isUpcoming(episode) && contextMenuVideo(item, info, episode, $event)"); | ||
li.setAttribute("name", "allEpisodesPlugin-episode"); | ||
|
||
li.innerHTML = ` | ||
<div class="thumbnail"> | ||
<img stremio-image="::{ url: ${episode.thumbnail} }" data-image="${episode.thumbnail}" loading="lazy" src="${episode.thumbnail}"> | ||
</div> | ||
<div class="episode-details"> | ||
<div class="title ng-binding">${episodesCounter}. ${episode.title}</div> | ||
<div class="footer"> | ||
<div class="date ng-isolate-scope">${formatTimestamp(episode.released.getTime())}</div> | ||
<div class="date ng-isolate-scope">Season ${episode.season}, Ep. ${episode.number}</div> | ||
</div> | ||
</div>`; | ||
|
||
container.appendChild(li); | ||
compile(li)(childScope); | ||
}); | ||
} | ||
|
||
function formatTimestamp(timestamp) { | ||
try { | ||
const date = new Date(timestamp); | ||
const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']; | ||
const formattedDate = `${date.getDate()} ${months[date.getMonth()]} ${date.getFullYear()}`; | ||
return formattedDate; | ||
} catch { | ||
return 'N/A'; | ||
} | ||
} | ||
|
||
function waitForElm(selector) { | ||
return new Promise(resolve => { | ||
if (document.querySelector(selector)) { | ||
return resolve(document.querySelector(selector)); | ||
} | ||
|
||
const observer = new MutationObserver(() => { | ||
if (document.querySelector(selector)) { | ||
observer.disconnect(); | ||
resolve(document.querySelector(selector)); | ||
} | ||
}); | ||
|
||
observer.observe(document.body, { | ||
childList: true, | ||
subtree: true | ||
}); | ||
}); | ||
} | ||
|
||
function hideAddedEpisodes(){ | ||
const container = document.getElementsByClassName('episodes-list')[0]; | ||
var elements = container.children; | ||
|
||
for (var i = 0; i < elements.length; i++) { | ||
elements[i].style.display = 'none'; | ||
} | ||
} | ||
|
||
function searchEpisodes(query) { | ||
let liElements = document.querySelectorAll('li'); | ||
let allEpisodes = Array.from(liElements).filter((liElement) => { | ||
return liElement.querySelector('div.episode-details') !== null; | ||
}); | ||
|
||
allEpisodes.forEach((episode) => { | ||
let titleAttribute = episode.getAttribute('title').toLowerCase(); | ||
if (titleAttribute && titleAttribute.includes(query.toLowerCase())) { | ||
episode.style.display = 'flex'; | ||
} else { | ||
episode.style.display = 'none'; | ||
} | ||
}); | ||
} |
Oops, something went wrong.