forked from Mystique/Mystique
Introduce cultural groups & readme updates
This commit is contained in:
parent
6ad1422eec
commit
3da59045d9
3 changed files with 260 additions and 16 deletions
188
.forgejo/scripts/cultural-groups.js
Normal file
188
.forgejo/scripts/cultural-groups.js
Normal file
|
@ -0,0 +1,188 @@
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// Define cultural groups by country
|
||||||
|
const culturalGroups = {
|
||||||
|
anglosphere: [
|
||||||
|
'united kingdom', 'uk', 'britain', 'england', 'scotland', 'wales', 'northern ireland',
|
||||||
|
'united states', 'usa', 'america', 'canada', 'australia', 'new zealand', 'ireland'
|
||||||
|
],
|
||||||
|
francophone: [
|
||||||
|
'france', 'belgium', 'switzerland', 'quebec', 'monaco',
|
||||||
|
'luxembourg', 'haiti', 'ivory coast', 'senegal', 'cameroon'
|
||||||
|
],
|
||||||
|
hispanic: [
|
||||||
|
'spain', 'mexico', 'argentina', 'chile', 'colombia', 'peru',
|
||||||
|
'venezuela', 'ecuador', 'guatemala', 'cuba', 'dominican republic',
|
||||||
|
'honduras', 'el salvador', 'nicaragua', 'costa rica', 'panama'
|
||||||
|
],
|
||||||
|
lusophone: [
|
||||||
|
'portugal', 'brazil', 'angola', 'mozambique',
|
||||||
|
'cape verde', 'guinea-bissau', 'sao tome and principe'
|
||||||
|
],
|
||||||
|
arabic: [
|
||||||
|
'saudi arabia', 'egypt', 'uae', 'united arab emirates', 'qatar',
|
||||||
|
'kuwait', 'oman', 'bahrain', 'yemen', 'iraq', 'syria',
|
||||||
|
'jordan', 'lebanon', 'palestine', 'libya', 'tunisia',
|
||||||
|
'algeria', 'morocco', 'sudan'
|
||||||
|
],
|
||||||
|
germanosphere: [
|
||||||
|
'germany', 'austria', 'switzerland', 'luxembourg', 'liechtenstein'
|
||||||
|
],
|
||||||
|
slavic: [
|
||||||
|
'russia', 'ukraine', 'belarus', 'poland', 'czech republic',
|
||||||
|
'slovakia', 'serbia', 'croatia', 'bosnia', 'montenegro',
|
||||||
|
'slovenia', 'bulgaria', 'north macedonia'
|
||||||
|
],
|
||||||
|
sinosphere: [
|
||||||
|
'china', 'hong kong', 'taiwan', 'singapore', 'macau'
|
||||||
|
],
|
||||||
|
indosphere: [
|
||||||
|
'india', 'pakistan', 'bangladesh', 'nepal', 'sri lanka',
|
||||||
|
'bhutan', 'maldives'
|
||||||
|
],
|
||||||
|
turkic: [
|
||||||
|
'turkey', 'azerbaijan', 'uzbekistan', 'kazakhstan',
|
||||||
|
'kyrgyzstan', 'turkmenistan'
|
||||||
|
],
|
||||||
|
nordic: [
|
||||||
|
'sweden', 'norway', 'denmark', 'finland', 'iceland',
|
||||||
|
'faroe islands', 'greenland'
|
||||||
|
],
|
||||||
|
baltic: [
|
||||||
|
'estonia', 'latvia', 'lithuania'
|
||||||
|
],
|
||||||
|
hellenic: [
|
||||||
|
'greece', 'cyprus'
|
||||||
|
],
|
||||||
|
benelux: [
|
||||||
|
'netherlands', 'belgium', 'luxembourg'
|
||||||
|
],
|
||||||
|
persian: [
|
||||||
|
'iran', 'afghanistan', 'tajikistan'
|
||||||
|
],
|
||||||
|
malaysphere: [
|
||||||
|
'malaysia', 'brunei', 'indonesia'
|
||||||
|
],
|
||||||
|
korean: [
|
||||||
|
'south korea', 'korea', 'north korea'
|
||||||
|
],
|
||||||
|
japanese: [
|
||||||
|
'japan'
|
||||||
|
],
|
||||||
|
vietnamese: [
|
||||||
|
'vietnam'
|
||||||
|
],
|
||||||
|
thai: [
|
||||||
|
'thailand'
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
function getCulturalGroup(channelInfo) {
|
||||||
|
// Look specifically for group-title (country information)
|
||||||
|
const groupMatch = channelInfo.match(/group-title="([^"]*)"/) || [];
|
||||||
|
const groupTitle = (groupMatch[1] || '').toLowerCase();
|
||||||
|
|
||||||
|
// Check if the country belongs to any cultural group
|
||||||
|
for (const [group, countries] of Object.entries(culturalGroups)) {
|
||||||
|
if (countries.some(country => groupTitle.includes(country))) {
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Return null instead of 'other' for non-matching channels
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitByCulturalGroup(filePath) {
|
||||||
|
const content = fs.readFileSync(filePath, 'utf8');
|
||||||
|
const lines = content.split('\n');
|
||||||
|
|
||||||
|
// Create cultural groups directory
|
||||||
|
const groupsDir = path.join(path.dirname(filePath), 'cultural-groups');
|
||||||
|
if (!fs.existsSync(groupsDir)) {
|
||||||
|
fs.mkdirSync(groupsDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get existing cultural group files
|
||||||
|
const existingFiles = fs.readdirSync(groupsDir)
|
||||||
|
.filter(file => file.endsWith('.m3u'))
|
||||||
|
.map(file => file.toLowerCase());
|
||||||
|
|
||||||
|
const groups = {};
|
||||||
|
let currentExtinf = null;
|
||||||
|
|
||||||
|
// Verify M3U header
|
||||||
|
const header = lines[0];
|
||||||
|
if (!header.startsWith('#EXTM3U')) {
|
||||||
|
throw new Error('Invalid M3U file: Missing #EXTM3U header');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process lines
|
||||||
|
lines.forEach(line => {
|
||||||
|
line = line.trim();
|
||||||
|
if (!line) return;
|
||||||
|
|
||||||
|
if (line.startsWith('#EXTINF')) {
|
||||||
|
currentExtinf = line;
|
||||||
|
const culturalGroup = getCulturalGroup(line);
|
||||||
|
|
||||||
|
// Only add to a group if there's a match
|
||||||
|
if (culturalGroup) {
|
||||||
|
if (!groups[culturalGroup]) {
|
||||||
|
groups[culturalGroup] = ['#EXTM3U'];
|
||||||
|
}
|
||||||
|
groups[culturalGroup].push(line);
|
||||||
|
}
|
||||||
|
} else if (currentExtinf && !line.startsWith('#')) {
|
||||||
|
const culturalGroup = getCulturalGroup(currentExtinf);
|
||||||
|
// Only add the URL line if the channel belonged to a group
|
||||||
|
if (culturalGroup) {
|
||||||
|
groups[culturalGroup].push(line);
|
||||||
|
}
|
||||||
|
currentExtinf = null;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get list of current cultural group files
|
||||||
|
const currentGroupFiles = Object.keys(groups).map(groupTitle =>
|
||||||
|
`${groupTitle.toLowerCase()}.m3u`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Remove obsolete files
|
||||||
|
existingFiles.forEach(existingFile => {
|
||||||
|
if (!currentGroupFiles.includes(existingFile)) {
|
||||||
|
const fileToRemove = path.join(groupsDir, existingFile);
|
||||||
|
fs.unlinkSync(fileToRemove);
|
||||||
|
console.log(`Removed obsolete cultural group playlist: ${existingFile}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Write cultural group files
|
||||||
|
Object.entries(groups).forEach(([groupTitle, groupLines]) => {
|
||||||
|
const groupFilePath = path.join(groupsDir, `${groupTitle.toLowerCase()}.m3u`);
|
||||||
|
fs.writeFileSync(groupFilePath, groupLines.join('\n') + '\n');
|
||||||
|
console.log(`Created/updated cultural group playlist: ${groupFilePath}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate summary
|
||||||
|
const summary = Object.entries(groups).map(([group, lines]) => {
|
||||||
|
const channelCount = (lines.length - 1) / 2; // Subtract header and divide by 2 (EXTINF + URL)
|
||||||
|
return `${group}: ${channelCount} channels`;
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('\nCultural group split summary:');
|
||||||
|
console.log(summary.join('\n'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const filePath = process.argv[2];
|
||||||
|
if (!filePath) {
|
||||||
|
console.error('Please provide the path to the M3U file');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
splitByCulturalGroup(filePath);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error splitting M3U file:', error.message);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
|
@ -2,9 +2,13 @@ const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
function updateReadme(m3uPath) {
|
function updateReadme(m3uPath) {
|
||||||
// Get list of group files
|
// Get directories for both types of groups
|
||||||
const groupsDir = path.join(path.dirname(m3uPath), 'countries');
|
const countriesDir = path.join(path.dirname(m3uPath), 'countries');
|
||||||
const groups = {};
|
const culturalDir = path.join(path.dirname(m3uPath), 'cultural-groups');
|
||||||
|
const groups = {
|
||||||
|
countries: {},
|
||||||
|
cultural: {}
|
||||||
|
};
|
||||||
|
|
||||||
// Read the main M3U to get original group names and channel counts
|
// Read the main M3U to get original group names and channel counts
|
||||||
const content = fs.readFileSync(m3uPath, 'utf8');
|
const content = fs.readFileSync(m3uPath, 'utf8');
|
||||||
|
@ -16,33 +20,82 @@ function updateReadme(m3uPath) {
|
||||||
totalChannels++;
|
totalChannels++;
|
||||||
const groupMatch = line.match(/group-title="([^"]*)"/);
|
const groupMatch = line.match(/group-title="([^"]*)"/);
|
||||||
const groupTitle = groupMatch ? groupMatch[1] : 'Unknown';
|
const groupTitle = groupMatch ? groupMatch[1] : 'Unknown';
|
||||||
groups[groupTitle] = (groups[groupTitle] || 0) + 1;
|
groups.countries[groupTitle] = (groups.countries[groupTitle] || 0) + 1;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Start building README content
|
// Get cultural group counts
|
||||||
let readmeContent = '# Mystique IPTV\n\n';
|
if (fs.existsSync(culturalDir)) {
|
||||||
|
fs.readdirSync(culturalDir)
|
||||||
|
.filter(file => file.endsWith('.m3u'))
|
||||||
|
.forEach(file => {
|
||||||
|
const culturalContent = fs.readFileSync(path.join(culturalDir, file), 'utf8');
|
||||||
|
const culturalLines = culturalContent.split('\n');
|
||||||
|
const channelCount = culturalLines.filter(line => line.startsWith('#EXTINF')).length;
|
||||||
|
const groupName = file.replace('.m3u', '');
|
||||||
|
groups.cultural[groupName] = channelCount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build README content
|
||||||
|
let readmeContent = '# Mystique\n\n';
|
||||||
|
|
||||||
|
// Add description and features
|
||||||
|
readmeContent += '## About\n\n';
|
||||||
|
readmeContent += 'Mystique provides a curated collection of IPTV channels from around the world. ';
|
||||||
|
readmeContent += 'The channels are organized by both countries and cultural groups for easy access.\n\n';
|
||||||
|
|
||||||
|
// Add usage section
|
||||||
|
readmeContent += '## Usage\n\n';
|
||||||
|
readmeContent += '1. **Complete Playlist**: Use the main `mystique.m3u` file for access to all channels\n';
|
||||||
|
readmeContent += '2. **Country-Specific**: Individual country playlists are available in the `countries/` directory\n';
|
||||||
|
readmeContent += '3. **Cultural Groups**: Cultural/linguistic group playlists are available in the `cultural-groups/` directory\n\n';
|
||||||
|
|
||||||
|
// Add statistics
|
||||||
|
readmeContent += '## Statistics\n\n';
|
||||||
|
readmeContent += `- Total Channels: ${totalChannels}\n`;
|
||||||
|
readmeContent += `- Countries Available: ${Object.keys(groups.countries).length}\n`;
|
||||||
|
readmeContent += `- Cultural Groups: ${Object.keys(groups.cultural).length}\n\n`;
|
||||||
|
|
||||||
|
// Add playlists table
|
||||||
readmeContent += '## Available Playlists\n\n';
|
readmeContent += '## Available Playlists\n\n';
|
||||||
readmeContent += '| Playlist | Channels | Link |\n';
|
readmeContent += '| Playlist | Channels | Link |\n';
|
||||||
readmeContent += '|----------|-----------|------|\n';
|
readmeContent += '|----------|-----------|------|\n';
|
||||||
|
|
||||||
// Add main playlist first
|
// Add main playlist
|
||||||
const mainPlaylistName = path.basename(m3uPath);
|
const mainPlaylistName = path.basename(m3uPath);
|
||||||
readmeContent += `| **Complete (All countries)** | ${totalChannels} | [${mainPlaylistName}](${mainPlaylistName}) |\n`;
|
readmeContent += `| **Complete (All channels)** | ${totalChannels} | [${mainPlaylistName}](${mainPlaylistName}) |\n`;
|
||||||
|
|
||||||
// Sort groups alphabetically
|
// Add countries
|
||||||
const sortedGroups = Object.entries(groups).sort((a, b) => a[0].localeCompare(b[0]));
|
readmeContent += '| **───────── Countries ─────────** | | |\n';
|
||||||
|
const sortedCountryGroups = Object.entries(groups.countries).sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
// Add each group to the table
|
sortedCountryGroups.forEach(([groupName, channelCount]) => {
|
||||||
sortedGroups.forEach(([groupName, channelCount]) => {
|
|
||||||
const safeGroupName = groupName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
const safeGroupName = groupName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
|
||||||
readmeContent += `| ${groupName} | ${channelCount} | [${safeGroupName}.m3u](countries/${safeGroupName}.m3u) |\n`;
|
readmeContent += `| ${groupName} | ${channelCount} | [${safeGroupName}.m3u](countries/${safeGroupName}.m3u) |\n`;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Add cultural groups
|
||||||
|
if (Object.keys(groups.cultural).length > 0) {
|
||||||
|
readmeContent += '| **───────── Cultural Groups ─────────** | | |\n';
|
||||||
|
const sortedCulturalGroups = Object.entries(groups.cultural).sort((a, b) => a[0].localeCompare(b[0]));
|
||||||
|
sortedCulturalGroups.forEach(([groupName, channelCount]) => {
|
||||||
|
const displayName = groupName
|
||||||
|
.split('-')
|
||||||
|
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
||||||
|
.join(' ');
|
||||||
|
readmeContent += `| ${displayName} | ${channelCount} | [${groupName}.m3u](cultural-groups/${groupName}.m3u) |\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Add note about legal usage
|
||||||
|
readmeContent += '## Legal Notice\n\n';
|
||||||
|
readmeContent += 'This playlist is a collection of publicly available IPTV streams. ';
|
||||||
|
readmeContent += 'Please check your local laws regarding IPTV streaming before using this playlist.\n';
|
||||||
|
|
||||||
const readmePath = path.join(path.dirname(m3uPath), 'README.md');
|
const readmePath = path.join(path.dirname(m3uPath), 'README.md');
|
||||||
|
|
||||||
fs.writeFileSync(readmePath, readmeContent);
|
fs.writeFileSync(readmePath, readmeContent);
|
||||||
console.log('README.md has been updated with playlist and group information');
|
console.log('README.md has been updated with comprehensive playlist information');
|
||||||
}
|
}
|
||||||
|
|
||||||
const filePath = process.argv[2];
|
const filePath = process.argv[2];
|
||||||
|
|
|
@ -29,9 +29,12 @@ jobs:
|
||||||
- name: Run M3U linter
|
- name: Run M3U linter
|
||||||
run: m3u-linter --config .forgejo/scripts/m3u-linter.config.json mystique.m3u
|
run: m3u-linter --config .forgejo/scripts/m3u-linter.config.json mystique.m3u
|
||||||
|
|
||||||
- name: Split into group playlists
|
- name: Split into country playlists
|
||||||
run: node .forgejo/scripts/split-m3u.js mystique.m3u
|
run: node .forgejo/scripts/split-m3u.js mystique.m3u
|
||||||
|
|
||||||
|
- name: Split into cultural group playlists
|
||||||
|
run: node .forgejo/scripts/cultural-groups.js mystique.m3u
|
||||||
|
|
||||||
- name: Update README
|
- name: Update README
|
||||||
run: node .forgejo/scripts/readme-m3u.js mystique.m3u
|
run: node .forgejo/scripts/readme-m3u.js mystique.m3u
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue