My-Streams/Google Script And Install Info/google_apps_script
2025-03-06 09:51:10 -05:00

395 lines
17 KiB
Text

function doGet(e) {
const params = e.parameter;
const region = (params.region || 'us').toLowerCase().trim();
const service = params.service;
const sort = params.sort || 'name';
if (!service) {
return handleError('Error: No service type provided');
}
// Handle Pluto TV service
if (service.toLowerCase() === 'plutotv') {
return handlePluto(region, sort);
}
// Handle Plex service with updated User-Agent
if (service.toLowerCase() === 'plex') {
return handlePlex(region, sort);
}
// Handle SamsungTVPlus service
if (service.toLowerCase() === 'samsungtvplus') {
return handleSamsungTVPlus(region, sort);
}
// Handle Roku service
if (service.toLowerCase() === 'roku') {
return handleRoku(sort);
}
// Handle Stirr service
if (service.toLowerCase() === 'stirr') {
return handleStirr(sort);
}
// Handle Tubi service with new URL
if (service.toLowerCase() === 'tubi') {
return handleTubi(service);
}
// Handle PBSKids service
if (service.toLowerCase() === 'pbskids') {
return handlePBSKids(service);
}
// Handle PBS service
if (service.toLowerCase() === 'pbs') {
return handlePBS();
}
// If no matching service was found, return an error
return handleError('Error: Unsupported service type provided');
}
//------ Service Functions ------//
function handlePluto(region, sort) {
const PLUTO_URL = 'https://github.com/matthuisman/i.mjh.nz/raw/refs/heads/master/PlutoTV/.channels.json.gz';
const STREAM_URL_TEMPLATE = 'https://jmp2.uk/plu-{id}.m3u8';
sort = sort || 'name';
let data;
try {
Logger.log('Fetching new Pluto data from URL: ' + PLUTO_URL);
const response = UrlFetchApp.fetch(PLUTO_URL);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
Logger.log('Data successfully extracted and parsed.');
} catch (error) {
Logger.log('Error fetching or processing Pluto data: ' + error.stack);
return handleError('Error fetching Pluto data: ' + error.message);
}
let output = `#EXTM3U url-tvg="https://github.com/matthuisman/i.mjh.nz/raw/master/PlutoTV/${region}.xml.gz"\n`;
const regionNameMap = {
ar: "Argentina", br: "Brazil", ca: "Canada", cl: "Chile", de: "Germany",
dk: "Denmark", es: "Spain", fr: "France", gb: "United Kingdom", it: "Italy",
mx: "Mexico", no: "Norway", se: "Sweden", us: "United States"
};
let channels = {};
if (region === 'all') {
for (const regionKey in data.regions) {
const regionData = data.regions[regionKey];
const regionFullName = regionNameMap[regionKey] || regionKey.toUpperCase();
for (const channelKey in regionData.channels) {
const channel = { ...regionData.channels[channelKey], region: regionFullName };
const uniqueChannelId = `${channelKey}-${regionKey}`;
channels[uniqueChannelId] = channel;
}
}
} else {
if (!data.regions[region]) {
return handleError(`Error: Region '${region}' not found in Pluto data.`);
}
channels = data.regions[region].channels || {};
}
const sortedChannelIds = Object.keys(channels).sort((a, b) => {
const channelA = channels[a];
const channelB = channels[b];
if (sort === 'chno') {
return channelA.chno - channelB.chno;
} else {
return channelA.name.localeCompare(channelB.name);
}
});
sortedChannelIds.forEach(channelId => {
const channel = channels[channelId];
const { chno, name, logo, group, region: channelRegion } = channel;
const groupTitle = region === 'all' ? `${channelRegion}` : group;
output += `#EXTINF:-1 channel-id="${channelId}" tvg-id="${channelId}" tvg-chno="${chno}" tvg-name="${name}" tvg-logo="${logo}" group-title="${groupTitle}", ${name}\n`;
output += STREAM_URL_TEMPLATE.replace('{id}', channelId.split('-')[0]) + '\n';
});
output = output.replace(/tvg-id="(.*?)-\w{2}"/g, 'tvg-id="$1"');
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
}
function handlePlex(region, sort) {
const PLEX_URL = 'https://github.com/matthuisman/i.mjh.nz/raw/refs/heads/master/Plex/.channels.json.gz';
const CHANNELS_JSON_URL = 'https://raw.githubusercontent.com/Mikoshi-nyudo/plex-channels-list/refs/heads/main/plex/channels.json';
const STREAM_URL_TEMPLATE = 'https://jmp2.uk/plex-{id}.m3u8';
const USER_AGENT = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36';
sort = sort || 'name';
let data;
let plexChannels = [];
try {
Logger.log('Fetching new Plex data from URL: ' + PLEX_URL);
const options = {
'headers': { 'User-Agent': USER_AGENT }
};
const response = UrlFetchApp.fetch(PLEX_URL, options);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
Logger.log('Fetching new channels.json data from URL: ' + CHANNELS_JSON_URL);
const channelsResponse = UrlFetchApp.fetch(CHANNELS_JSON_URL, options);
plexChannels = JSON.parse(channelsResponse.getContentText());
} catch (error) {
Logger.log('Error fetching Plex or channels data: ' + error.stack);
return handleError('Error fetching Plex or channels data: ' + error.message);
}
let output = `#EXTM3U url-tvg="https://github.com/matthuisman/i.mjh.nz/raw/master/Plex/${region}.xml.gz"\n`;
const regionNameMap = {
us: "United States", mx: "Mexico", es: "Spain", ca: "Canada", au: "Australia", nz: "New Zealand"
};
let channels = {};
if (region === 'all') {
for (const regionKey in data.regions) {
const regionData = data.regions[regionKey];
const regionFullName = regionNameMap[regionKey] || regionKey.toUpperCase();
for (const channelKey in data.channels) {
const channel = data.channels[channelKey];
if (channel.regions.includes(regionKey)) {
const uniqueChannelId = `${channelKey}-${regionKey}`;
channels[uniqueChannelId] = { ...channel, region: regionFullName, group: regionFullName, originalId: channelKey };
}
}
}
} else {
if (!data.regions[region]) {
return handleError(`Error: Region '${region}' not found in Plex data.`);
}
for (const channelKey in data.channels) {
const channel = data.channels[channelKey];
if (channel.regions.includes(region)) {
const matchingChannel = plexChannels.find(ch => ch.Title === channel.name);
const genre = matchingChannel && matchingChannel.Genre ? matchingChannel.Genre : 'Uncategorized';
channels[channelKey] = { ...channel, group: genre, originalId: channelKey };
}
}
}
const sortedChannelIds = Object.keys(channels).sort((a, b) => {
const channelA = channels[a];
const channelB = channels[b];
return sort === 'chno' ? (channelA.chno - channelB.chno) : channelA.name.localeCompare(channelB.name);
});
sortedChannelIds.forEach(channelId => {
const channel = channels[channelId];
const { chno, name, logo, group, originalId } = channel;
output += `#EXTINF:-1 channel-id="${channelId}" tvg-id="${channelId}" tvg-chno="${chno || ''}" tvg-name="${name}" tvg-logo="${logo}" group-title="${group}", ${name}\n`;
output += STREAM_URL_TEMPLATE.replace('{id}', originalId) + '\n';
});
output = output.replace(/tvg-id="(.*?)-\w{2}"/g, 'tvg-id="$1"');
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
}
function handleSamsungTVPlus(region, sort) {
const SAMSUNG_URL = 'https://github.com/matthuisman/i.mjh.nz/raw/refs/heads/master/SamsungTVPlus/.channels.json.gz';
const STREAM_URL_TEMPLATE = 'https://jmp2.uk/sam-{id}.m3u8';
sort = sort || 'name';
let data;
try {
Logger.log('Fetching new SamsungTVPlus data from URL: ' + SAMSUNG_URL);
const response = UrlFetchApp.fetch(SAMSUNG_URL);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
} catch (error) {
Logger.log('Error fetching or processing SamsungTVPlus data: ' + error.stack);
return handleError('Error fetching SamsungTVPlus data: ' + error.message);
}
let output = `#EXTM3U url-tvg="https://github.com/matthuisman/i.mjh.nz/raw/master/SamsungTVPlus/${region}.xml.gz"\n`;
let channels = {};
if (region === 'all') {
for (const regionKey in data.regions) {
const regionData = data.regions[regionKey];
const regionFullName = regionData.name || regionKey.toUpperCase();
for (const channelKey in regionData.channels) {
const channel = { ...regionData.channels[channelKey], region: regionFullName };
const uniqueChannelId = `${channelKey}-${regionKey}`;
channels[uniqueChannelId] = channel;
}
}
} else {
if (!data.regions[region]) {
return handleError(`Error: Region '${region}' not found in SamsungTVPlus data.`);
}
channels = data.regions[region].channels || {};
}
const sortedChannelIds = Object.keys(channels).sort((a, b) => {
const channelA = channels[a];
const channelB = channels[b];
if (sort === 'chno') {
return channelA.chno - channelB.chno;
} else {
return channelA.name.localeCompare(channelB.name);
}
});
sortedChannelIds.forEach(channelId => {
const channel = channels[channelId];
const { chno, name, logo, group, region: channelRegion } = channel;
const groupTitle = region === 'all' ? `${channelRegion}` : group;
output += `#EXTINF:-1 channel-id="${channelId}" tvg-id="${channelId}" tvg-chno="${chno}" tvg-name="${name}" tvg-logo="${logo}" group-title="${groupTitle}", ${name}\n`;
output += STREAM_URL_TEMPLATE.replace('{id}', channelId.split('-')[0]) + '\n';
});
output = output.replace(/tvg-id="(.*?)-\w{2}"/g, 'tvg-id="$1"');
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
}
function handleRoku(sort) {
const ROKU_URL = 'https://github.com/matthuisman/i.mjh.nz/raw/refs/heads/master/Roku/.channels.json.gz';
const STREAM_URL_TEMPLATE = 'https://jmp2.uk/rok-{id}.m3u8';
sort = sort || 'name';
let data;
try {
Logger.log('Fetching new Roku data from URL: ' + ROKU_URL);
const response = UrlFetchApp.fetch(ROKU_URL);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
} catch (error) {
Logger.log('Error fetching or processing Roku data: ' + error.stack);
return handleError('Error fetching Roku data: ' + error.message);
}
let output = `#EXTM3U url-tvg="https://github.com/matthuisman/i.mjh.nz/raw/master/Roku/all.xml.gz"\n`;
let channels = data.channels || {};
const sortedChannelIds = Object.keys(channels).sort((a, b) => {
const channelA = channels[a];
const channelB = channels[b];
if (sort === 'chno') {
return channelA.chno - channelB.chno;
} else {
return channelA.name.localeCompare(channelB.name);
}
});
sortedChannelIds.forEach(channelId => {
const channel = channels[channelId];
const { chno, name, logo, groups } = channel;
const groupTitle = groups && groups.length > 0 ? groups[0] : 'Uncategorized';
output += `#EXTINF:-1 channel-id="${channelId}" tvg-id="${channelId}" tvg-chno="${chno}" tvg-name="${name}" tvg-logo="${logo}" group-title="${groupTitle}", ${name}\n`;
output += STREAM_URL_TEMPLATE.replace('{id}', channelId) + '\n';
});
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
}
function handleStirr(sort) {
const STIRR_URL = 'https://github.com/matthuisman/i.mjh.nz/raw/refs/heads/master/Stirr/.channels.json.gz';
sort = sort || 'name';
let data;
try {
Logger.log('Fetching new Stirr data from URL: ' + STIRR_URL);
const response = UrlFetchApp.fetch(STIRR_URL);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
} catch (error) {
Logger.log('Error fetching or processing Stirr data: ' + error.stack);
return handleError('Error fetching Stirr data: ' + error.message);
}
let output = `#EXTM3U url-tvg="https://github.com/matthuisman/i.mjh.nz/raw/refs/heads/master/Stirr/all.xml.gz"\n`;
let channels = data.channels || {};
const sortedChannelIds = Object.keys(channels).sort((a, b) => {
const channelA = channels[a];
const channelB = channels[b];
if (sort === 'chno') {
return channelA.chno - channelB.chno;
} else {
return channelA.name.localeCompare(channelB.name);
}
});
sortedChannelIds.forEach(channelId => {
const channel = channels[channelId];
const { chno, name, logo, groups } = channel;
const groupTitle = groups && groups.length > 0 ? groups.join(', ') : 'Uncategorized';
const streamUrl = `https://jmp2.uk/str-${channelId}.m3u8`;
output += `#EXTINF:-1 channel-id="${channelId}" tvg-id="${channelId}" tvg-chno="${chno}" tvg-name="${name}" tvg-logo="${logo}" group-title="${groupTitle}", ${name}\n`;
output += `${streamUrl}\n`;
});
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
}
function handleTubi(service) {
let data;
try {
Logger.log('Fetching new Tubi data');
const playlistUrl = 'https://raw.githubusercontent.com/BuddyChewChew/tubi-scraper/refs/heads/main/tubi_playlist.m3u';
const options = {
'headers': {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36'
}
};
const response = UrlFetchApp.fetch(playlistUrl, options);
data = response.getContentText();
let epgUrl = 'https://raw.githubusercontent.com/BuddyChewChew/tubi-scraper/refs/heads/main/tubi_epg.xml';
let output = `#EXTM3U url-tvg="${epgUrl}"\n`;
output += data;
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
} catch (error) {
Logger.log('Error fetching Tubi data: ' + error.stack);
return handleError('Error fetching Tubi data: ' + error.message);
}
}
function handlePBSKids(service) {
if (service.toLowerCase() !== 'pbskids') return;
let data;
try {
Logger.log('Fetching new PBS Kids data');
const APP_URL = 'https://i.mjh.nz/PBS/.kids_app.json.gz';
const response = UrlFetchApp.fetch(APP_URL);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
let output = `#EXTM3U url-tvg="https://github.com/matthuisman/i.mjh.nz/raw/master/PBS/kids_all.xml.gz"\n`;
const sortedKeys = Object.keys(data.channels).sort((a, b) => {
const channelA = data.channels[a].name.toLowerCase();
const channelB = data.channels[b].name.toLowerCase();
return channelA.localeCompare(channelB);
});
sortedKeys.forEach(key => {
const channel = data.channels[key];
const { logo, name, url } = channel;
output += `#EXTINF:-1 channel-id="pbskids-${key}" tvg-id="${key}" tvg-name="${name}" tvg-logo="${logo}", ${name}\n${url}\n`;
});
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
} catch (error) {
Logger.log('Error fetching PBS Kids data: ' + error.stack);
return handleError('Error fetching PBS Kids data: ' + error.message);
}
}
function handlePBS() {
const DATA_URL = 'https://i.mjh.nz/PBS/.app.json.gz';
const EPG_URL = 'https://i.mjh.nz/PBS/all.xml.gz';
let data;
try {
Logger.log('Fetching new PBS data from URL: ' + DATA_URL);
const response = UrlFetchApp.fetch(DATA_URL);
let gzipBlob = response.getBlob();
gzipBlob = gzipBlob.setContentType('application/x-gzip');
const extractedBlob = Utilities.ungzip(gzipBlob);
const extractedData = extractedBlob.getDataAsString();
data = JSON.parse(extractedData);
} catch (error) {
Logger.log('Error fetching or processing PBS data: ' + error.stack);
return handleError('Error fetching PBS data: ' + error.message);
}
let output = `#EXTM3U x-tvg-url="${EPG_URL}"\n`;
Object.keys(data.channels).forEach(key => {
const channel = data.channels[key];
output += `#EXTINF:-1 channel-id="pbs-${key}" tvg-id="${key}" tvg-name="${channel.name}" tvg-logo="${channel.logo}", ${channel.name}\n`;
output += `#KODIPROP:inputstream.adaptive.manifest_type=mpd\n`;
output += `#KODIPROP:inputstream.adaptive.license_type=com.widevine.alpha\n`;
output += `#KODIPROP:inputstream.adaptive.license_key=${channel.license}|Content-Type=application%2Foctet-stream&user-agent=okhttp%2F4.9.0|R{SSM}|\n`;
output += `${channel.url}|user-agent=okhttp%2F4.9.0\n`;
});
return ContentService.createTextOutput(output).setMimeType(ContentService.MimeType.TEXT);
}
//------ Other Functions ------//
function handleError(errorMessage) {
return ContentService.createTextOutput(errorMessage)
.setMimeType(ContentService.MimeType.TEXT);
}