Skip to content

Instantly share code, notes, and snippets.

@A1ex-N
Created November 22, 2023 00:45
Show Gist options
  • Save A1ex-N/0b6c94954cf1754f23f370af2aa81cd1 to your computer and use it in GitHub Desktop.
Save A1ex-N/0b6c94954cf1754f23f370af2aa81cd1 to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Lookmovie premium
// @namespace https://lookmovie2.to
// @version 0.1
// @description allows you to copy stream (m3u8) URLs to clipboard
// @author https://github.com/A1ex-N
// @match *://www.lookmovie2.to/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=lookmovie169.xyz
// @grant GM.xmlHttpRequest
// @grant unsafeWindow
// @grant GM_setClipboard
// @grant GM_addElement
// @grant GM_addStyle
// @run-at document-idle
// ==/UserScript==
// styles for the URL copy buttons
GM_addStyle(`
.copy-url-btn {
margin-right: 10px; /* Adds space between buttons */
padding: 0.6em 2em;
border: none;
margin: 5px;
outline: none;
color: rgb(255, 255, 255);
background: #111;
cursor: pointer;
position: relative;
z-index: 0;
border-radius: 10px;
user-select: none;
-webkit-user-select: none;
touch-action: manipulation;
}
.copy-url-btn:before {
content: "";
background: linear-gradient(
45deg,
#ff0000,
#ff7300,
#fffb00,
#48ff00,
#00ffd5,
#002bff,
#7a00ff,
#ff00c8,
#ff0000
);
position: absolute;
top: -2px;
left: -2px;
background-size: 400%;
z-index: -1;
filter: blur(5px);
-webkit-filter: blur(5px);
width: calc(100% + 4px);
height: calc(100% + 4px);
animation: glowing-button-85 20s linear infinite;
transition: opacity 0.3s ease-in-out;
border-radius: 10px;
}
@keyframes glowing-button-85 {
0% {
background-position: 0 0;
}
50% {
background-position: 400% 0;
}
100% {
background-position: 0 0;
}
}
.copy-url-btn:after {
z-index: -1;
content: "";
position: absolute;
width: 100%;
height: 100%;
background: #222;
left: 0;
top: 0;
border-radius: 10px;
}
.copy-url-btn:active {
transform: scale(0.95); /* Reduce size slightly on click */
box-shadow: 0 0 5px rgba(0, 0, 0, 0.3); /* Add a box-shadow on click */
}
.copy-btn-div {
display: flex;
width: fit-content;
}
`)
class ElementFactory {
static clearPremiumAdvertisement() {
let displayElement = document.querySelector("#app > div.view-movie > div > div.container > div:nth-child(3)");
displayElement.innerHTML = "";
}
static createUrlCopyButtons(streams) {
let htmlToInsert = `<div class="copy-btn-div"><ul>`;
for (let stream of Object.keys(streams)) {
htmlToInsert += `<li><button class="copy-url-btn" onclick="window.setClipboardText('${streams[stream]}')">${stream}</button></li>`;
}
// element already on the page, below the video player
let displayElement = document.querySelector("#app > div.view-movie > div > div.container > div:nth-child(3)");
htmlToInsert += "</ul></div>";
// changing += to = will get rid of the copy stream buttons
displayElement.innerHTML += htmlToInsert;
}
static createButtonWithTextAndCallback(text, callback, id = "") {
let htmlToInsert = `<button id="${id}" class="copy-url-btn" onclick="${callback}">${text}</button>`;
// element already on the page, below the video player
let displayElement = document.querySelector("#app > div.view-movie > div > div.container > div:nth-child(3)");
displayElement.innerHTML += htmlToInsert;
}
}
let moviesAlreadyFetched = false;
unsafeWindow.Movie = class {
static setup() {
console.log("This is a movie")
ElementFactory.clearPremiumAdvertisement();
ElementFactory.createButtonWithTextAndCallback("Get movie stream URLs", "Movie.displayUrlCopyButtons()", "get-streams-btn");
}
static getStorage() {
return {
id: unsafeWindow.movie_storage.id_movie,
hash: unsafeWindow.movie_storage.hash,
expires: unsafeWindow.movie_storage.expires
}
}
static async displayUrlCopyButtons() {
if (moviesAlreadyFetched) { return }
const streams = await this.getStreams();
ElementFactory.createUrlCopyButtons(streams);
console.log(streams);
}
static async getStreams() {
if (moviesAlreadyFetched) { return }
const movieStorage = this.getStorage();
let r = await GM.xmlHttpRequest({
url: `/api/v1/security/movie-access?id_movie=${movieStorage.id}&hash=${movieStorage.hash}&expires=${movieStorage.expires}`
}).catch(e => alert(e));
moviesAlreadyFetched = true;
return JSON.parse(r.response).streams; // {1080p: "url", 720p: "url", ...}
}
}
unsafeWindow.Show = class {
static setup() {
console.log("This is a show");
const showStorage = this.getStorage();
let buttonText = `S${showStorage.zeroPaddedSeasonNumber}E${showStorage.zeroPaddedEpisodeNumber}`;
ElementFactory.clearPremiumAdvertisement();
ElementFactory.createButtonWithTextAndCallback(`Get ${buttonText} stream URLs`, "Show.displayUrlCopyButtons()");
Show.createGetAllStreamsInSeasonBtn();
unsafeWindow.addEventListener("ChangedEpisodeHash", () => {
this.setup();
})
}
static getStorage() {
const currentSeason_ = unsafeWindow.currentSeason;
const allEpisodes_ = unsafeWindow.show_storage.seasons;
return {
currentSeason: currentSeason_,
allEpisodes: allEpisodes_,
allEpisodesInCurrentSeason: this.getAllEpisodesForSeason(allEpisodes_, currentSeason_),
totalEpisodes: unsafeWindow.show_storage.total_episodes,
currentEpisodeNumber: unsafeWindow.currentEpisode,
zeroPaddedSeasonNumber: unsafeWindow.currentSeason.toString().padStart(2, '0'),
zeroPaddedEpisodeNumber: unsafeWindow.currentEpisode.toString().padStart(2, '0'),
scriptFriendlyTitle: unsafeWindow.show_storage.title.split(' ').join('.').toLowerCase(),
currentEpisodeID: unsafeWindow.currentEpisodeID,
currentEpisodeIndex: unsafeWindow.currentEpisodeIndex,
showTitle: unsafeWindow.show_storage.title,
hash: unsafeWindow.show_storage.hash,
expires: unsafeWindow.show_storage.expires
}
}
static getAllEpisodesForSeason(allEpisodes, seasonNumber) {
let allEpisodesInSeason = [];
for (const episode of allEpisodes) {
if (episode.season == seasonNumber) {
allEpisodesInSeason.push(episode);
}
}
return allEpisodesInSeason;
}
static async displayUrlCopyButtons() {
const streams = await this.getStreamsForCurrentEpisode();
ElementFactory.createUrlCopyButtons(streams);
console.log(streams);
}
static async copyAllStreamsToClipboard() {
let storage = this.getStorage();
let allStreams = await this.getAllStreamsInSeason(storage);
setClipboardText(allStreams.join('\n'));
}
static createGetAllStreamsInSeasonBtn() {
let storage = this.getStorage();
let initialButtonText = `Copy all streams in season ${storage.currentSeason} to clipboard`;
ElementFactory.createButtonWithTextAndCallback(initialButtonText, "Show.copyAllStreamsToClipboard()", "get-all-streams-btn");
let getAllStreamsBtn = document.getElementById("get-all-streams-btn");
document.addEventListener("fetching-streams", (e) => {
getAllStreamsBtn.innerText = `Fetching... ${e.detail}/${storage.allEpisodesInCurrentSeason.length}`;
getAllStreamsBtn.disabled = true;
});
document.addEventListener("done-fetching", () => {
getAllStreamsBtn.innerText = initialButtonText;
getAllStreamsBtn.disabled = false;
});
}
static async getStreamsForCurrentEpisode() {
const showStorage = this.getStorage();
let r = await GM.xmlHttpRequest({
url: `/api/v1/security/episode-access?id_episode=${showStorage.currentEpisodeID}&hash=${showStorage.hash}&expires=${showStorage.expires}`
}).catch(e => alert(e));
return JSON.parse(r.response).streams; // {1080p: "url", 720p: "url", ...}
}
static async getStreamsForSpecificEpisode(episode) {
const showStorage = this.getStorage();
console.log(`Trying to get info for episode ${episode.episode}`);
let r = await GM.xmlHttpRequest({
url: `/api/v1/security/episode-access?id_episode=${episode.id_episode}&hash=${showStorage.hash}&expires=${showStorage.expires}`
}).catch(e => console.log(e));
return JSON.parse(r.response).streams; // {1080p: "url", 720p: "url", ...}
}
static async getAllStreamsInSeason(storage) {
let allEpisodeStreams = [];
for (const episode of storage.allEpisodesInCurrentSeason) {
let fetchingEvent = new CustomEvent("fetching-streams", { detail: episode.episode });
document.dispatchEvent(fetchingEvent);
let streams = await this.getStreamsForSpecificEpisode(episode);
// Only get the highest quality stream
let highestQualityKey = Object.keys(streams)[Object.keys(streams).length - 1];
let highestQualityStream = streams[highestQualityKey];
let formattedString = `${highestQualityStream},${storage.scriptFriendlyTitle}.S${zeroPad(episode.season)}E${zeroPad(episode.episode)}.mp4`;
allEpisodeStreams.push(formattedString);
}
let doneEvent = new CustomEvent("done-fetching");
document.dispatchEvent(doneEvent);
return allEpisodeStreams;
}
}
function zeroPad(string) {
return string.padStart(2, '0');
}
unsafeWindow.setClipboardText = (data) => {
GM_setClipboard(data, "text", () => { console.log(`${data} copied to clipboard`); alert("url(s) copied to clipboard") });
}
function urlIncludes(str) {
return unsafeWindow.location.href.includes(str);
}
const IS_SHOW = urlIncludes("shows/play");
const IS_MOVIE = urlIncludes("movies/play");
(function () {
'use strict';
//Yii2App.userStatus = "paid"
if (IS_MOVIE) {
Movie.setup();
} else if (IS_SHOW) {
Show.setup();
} else {
console.log("This ain't a movie or a show");
}
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment