Skip to content

Commit

Permalink
Moving to hono
Browse files Browse the repository at this point in the history
  • Loading branch information
Your Name committed Oct 19, 2024
1 parent 2466e8a commit 760cc98
Show file tree
Hide file tree
Showing 5 changed files with 142 additions and 860 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
<img src="https://i.imgur.com/FIGZdR3.png">
</p>

Current version: **3.3.4**
Current version: **3.3.5**

# About
This takes ESPN/ESPN+, FOX Sports, Paramount+, MSG+, NFL+, B1G+, NESN, Mountain West, FloSports, CBS Sports, or MLB.tv programming and transforms it into a "live TV" experience with virtual linear channels. It will discover what is on, and generate a schedule of channels that will give you M3U and XMLTV files that you can import into something like [Jellyfin](https://jellyfin.org) or [Channels](https://getchannels.com).
Expand Down
135 changes: 66 additions & 69 deletions index.ts → index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import express from 'express';
import {Context, Hono} from 'hono';
import {serve} from '@hono/node-server';
import {BlankEnv, BlankInput} from 'hono/types';
import moment from 'moment';

import {generateM3u} from './services/generate-m3u';
Expand All @@ -24,16 +26,26 @@ import {useLinear} from './services/channels';

import {version} from './package.json';

const notFound = (_req, res) => {
res.writeHead(404, {
const notFound = (c: Context<BlankEnv, '', BlankInput>) => {
return c.text('404 not found', 404, {
'X-Tuner-Error': 'EPlusTV: Error getting content',
});
res.write('404 not found');
res.end();
};

const shutDown = () => process.exit(0);

const getUri = (c: Context<BlankEnv, '', BlankInput>): string => {
if (process.env.BASE_URL) {
return process.env.BASE_URL;
}

const protocol = c.req.header('x-forwarded-proto') || 'http';
const host = c.req.header('host') || '';


return `${protocol}://${host}`;
};

const schedule = async () => {
console.log('=== Getting events ===');
await espnHandler.getSchedule();
Expand All @@ -55,78 +67,66 @@ const schedule = async () => {
console.log('=== Done building the schedule ===');
};

const app = express();
const app = new Hono();

app.get('/channels.m3u', (req, res) => {
const uri = process.env.BASE_URL || `${req.protocol}://${req.headers.host}`;
const m3uFile = generateM3u(uri);
app.get('/channels.m3u', c => {
const m3uFile = generateM3u(getUri(c));

if (!m3uFile) {
notFound(req, res);
return;
return notFound(c);
}

res.writeHead(200, {
return c.body(m3uFile, 200, {
'Content-Type': 'application/x-mpegurl',
});
res.end(m3uFile, 'utf-8');
});

app.get('/linear-channels.m3u', (req, res) => {
app.get('/linear-channels.m3u', c => {
if (!useLinear) {
notFound(req, res);
return;
return notFound(c);
}

const uri = process.env.BASE_URL || `${req.protocol}://${req.headers.host}`;
const m3uFile = generateM3u(uri, true);
const m3uFile = generateM3u(getUri(c), true);

if (!m3uFile) {
notFound(req, res);
return;
return notFound(c);
}

res.writeHead(200, {
return c.body(m3uFile, 200, {
'Content-Type': 'application/x-mpegurl',
});
res.end(m3uFile, 'utf-8');
});

app.get('/xmltv.xml', async (req, res) => {
app.get('/xmltv.xml', async c => {
const xmlFile = await generateXml();

if (!xmlFile) {
notFound(req, res);
return;
return notFound(c);
}

res.writeHead(200, {
return c.body(xmlFile, 200, {
'Content-Type': 'application/xml',
});
res.end(xmlFile, 'utf-8');
});

app.get('/linear-xmltv.xml', async (req, res) => {
app.get('/linear-xmltv.xml', async c => {
if (!useLinear) {
notFound(req, res);
return;
return notFound(c);
}

const xmlFile = await generateXml(true);

if (!xmlFile) {
notFound(req, res);
return;
return notFound(c);
}

res.writeHead(200, {
return c.body(xmlFile, 200, {
'Content-Type': 'application/xml',
});
res.end(xmlFile, 'utf-8');
});

app.get('/channels/:id.m3u8', async (req, res) => {
const {id} = req.params;
app.get('/channels/:id{.+\\.m3u8$}', async c => {
const id = c.req.param('id').split('.m3u8')[0];

let contents: string | undefined;

Expand All @@ -135,7 +135,7 @@ app.get('/channels/:id.m3u8', async (req, res) => {
appStatus.channels[id] = {};
}

const uri = process.env.BASE_URL || `${req.protocol}://${req.headers.host}`;
const uri = getUri(c);

if (!appStatus.channels[id].player?.playlist) {
try {
Expand All @@ -154,27 +154,25 @@ app.get('/channels/:id.m3u8', async (req, res) => {

removeChannelStatus(id);

notFound(req, res);
return;
return notFound(c);
}

appStatus.channels[id].heartbeat = new Date();

res.writeHead(200, {
return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'application/vnd.apple.mpegurl',
});
res.end(contents, 'utf-8');
});

app.get('/chunklist/:id/:chunklistid.m3u8', async (req, res) => {
const {id, chunklistid} = req.params;
app.get('/chunklist/:id/:chunklistid{.+\\.m3u8$}', async c => {
const id = c.req.param('id');
const chunklistid = c.req.param('chunklistid').split('.m3u8')[0];

let contents: string | null;
let contents: string | undefined;

if (!appStatus.channels[id]?.player?.playlist) {
notFound(req, res);
return;
return notFound(c);
}

try {
Expand All @@ -183,71 +181,65 @@ app.get('/chunklist/:id/:chunklistid.m3u8', async (req, res) => {

if (!contents) {
console.log(`Could not get chunklist for channel #${id}.`);
notFound(req, res);
return;
return notFound(c);
}

appStatus.channels[id].heartbeat = new Date();

res.writeHead(200, {
return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'application/vnd.apple.mpegurl',
});
res.end(contents, 'utf-8');
});

app.get('/channels/:id/:part.key', async (req, res) => {
const {id, part} = req.params;
app.get('/channels/:id/:part{.+\\.key$}', async c => {
const id = c.req.param('id');
const part = c.req.param('part').split('.key')[0];

let contents: ArrayBuffer | undefined;

try {
contents = await appStatus.channels[id].player?.getSegmentOrKey(part);
} catch (e) {
notFound(req, res);
return;
return notFound(c);
}

if (!contents) {
notFound(req, res);
return;
return notFound(c);
}

appStatus.channels[id].heartbeat = new Date();

res.writeHead(200, {
return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'application/octet-stream',
});
res.end(contents, 'utf-8');
});

app.get('/channels/:id/:part.ts', async (req, res) => {
const {id, part} = req.params;
app.get('/channels/:id/:part{.+\\.ts$}', async c => {
const id = c.req.param('id');
const part = c.req.param('part').split('.ts')[0];

let contents: ArrayBuffer | undefined;

try {
contents = await appStatus.channels[id].player?.getSegmentOrKey(part);
} catch (e) {
notFound(req, res);
return;
return notFound(c);
}

if (!contents) {
notFound(req, res);
return;
return notFound(c);
}

res.writeHead(200, {
return c.body(contents, 200, {
'Cache-Control': 'no-cache',
'Content-Type': 'video/MP2T',
});
res.end(contents, 'utf-8');
});

// 404 Handler
app.use(notFound);
app.notFound(notFound);

process.on('SIGTERM', shutDown);
process.on('SIGINT', shutDown);
Expand Down Expand Up @@ -288,8 +280,13 @@ process.on('SIGINT', shutDown);

await schedule();

console.log('=== Starting Server ===');
app.listen(SERVER_PORT, () => console.log(`Server started on port ${SERVER_PORT}`));
serve(
{
fetch: app.fetch,
port: SERVER_PORT,
},
() => console.log(`Server started on port ${SERVER_PORT}`),
);
})();

// Check for events every 4 hours and set the schedule
Expand Down
Loading

0 comments on commit 760cc98

Please sign in to comment.