epg-grabber 0.40.4 → 0.42.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/README.md +16 -9
  2. package/dist/cli.js +168 -0
  3. package/dist/index-Bcnk0U8G.js +1018 -0
  4. package/dist/index.d.ts +337 -0
  5. package/dist/index.js +15 -0
  6. package/eslint.config.js +5 -0
  7. package/package.json +38 -30
  8. package/src/cli.ts +209 -0
  9. package/src/core/channelList.ts +60 -0
  10. package/src/core/channelsParser.ts +30 -0
  11. package/src/core/client.ts +163 -0
  12. package/src/core/logger.ts +62 -0
  13. package/src/core/programsParser.ts +27 -0
  14. package/src/core/proxyParser.ts +31 -0
  15. package/src/core/siteConfig.ts +102 -0
  16. package/src/core/storage.ts +9 -0
  17. package/src/core/xmltvGenerator.ts +29 -0
  18. package/src/index.ts +114 -0
  19. package/src/models/channel.ts +78 -0
  20. package/src/models/index.ts +2 -0
  21. package/src/models/program.ts +539 -0
  22. package/src/types/channel.d.ts +11 -0
  23. package/src/types/channelList.d.ts +11 -0
  24. package/src/types/client.d.ts +19 -0
  25. package/src/types/index.d.ts +10 -0
  26. package/src/types/program.d.ts +118 -0
  27. package/src/types/siteConfig.d.ts +151 -0
  28. package/src/utils.ts +118 -0
  29. package/tests/cli.test.ts +96 -0
  30. package/tests/core/channelsParser.test.ts +102 -0
  31. package/tests/core/client.test.ts +82 -0
  32. package/tests/core/programsParser.test.ts +48 -0
  33. package/tests/core/proxyParser.test.ts +12 -0
  34. package/tests/core/xmltvGenerator.test.ts +194 -0
  35. package/tests/index.test.ts +124 -0
  36. package/tests/{Channel.test.js → models/channel.test.ts} +6 -5
  37. package/tests/{Program.test.js → models/program.test.ts} +34 -23
  38. package/tests/models/siteConfig.test.ts +63 -0
  39. package/tests/utils.test.ts +14 -0
  40. package/tsconfig.json +19 -0
  41. package/.github/workflows/test.yml +0 -15
  42. package/bin/epg-grabber.js +0 -192
  43. package/eslint.config.mjs +0 -25
  44. package/src/Channel.js +0 -25
  45. package/src/Program.js +0 -231
  46. package/src/client.js +0 -133
  47. package/src/config.js +0 -36
  48. package/src/file.js +0 -62
  49. package/src/index.d.ts +0 -124
  50. package/src/index.js +0 -101
  51. package/src/logger.js +0 -34
  52. package/src/parser.js +0 -56
  53. package/src/utils.js +0 -99
  54. package/src/xmltv.js +0 -215
  55. package/tests/__data__/expected/duplicates.guide.xml +0 -6
  56. package/tests/__data__/input/duplicates.config.js +0 -18
  57. package/tests/__data__/output/guide.xml +0 -10
  58. package/tests/__mocks__/axios.js +0 -3
  59. package/tests/bin.test.js +0 -130
  60. package/tests/client.test.js +0 -65
  61. package/tests/config.test.js +0 -26
  62. package/tests/index.test.js +0 -180
  63. package/tests/parser.test.js +0 -112
  64. package/tests/utils.test.js +0 -18
  65. package/tests/xmltv.test.js +0 -176
  66. /package/tests/__data__/input/{async.config.js → async.config.cjs} +0 -0
  67. /package/tests/__data__/input/{example.config.js → example.config.cjs} +0 -0
  68. /package/tests/__data__/input/{example_channels.config.js → example_channels.config.cjs} +0 -0
  69. /package/tests/__data__/input/{mini.config.js → mini.config.cjs} +0 -0
package/README.md CHANGED
@@ -118,9 +118,9 @@ module.exports = {
118
118
  },
119
119
 
120
120
  /**
121
- * @param {object} context
121
+ * @param {object} Request context
122
122
  *
123
- * @return {string} The function should return headers for each request (optional)
123
+ * @return {object} The function should return headers for each request (optional)
124
124
  */
125
125
  headers: function(context) {
126
126
  return {
@@ -130,9 +130,9 @@ module.exports = {
130
130
  },
131
131
 
132
132
  /**
133
- * @param {object} context
133
+ * @param {object} Request context
134
134
  *
135
- * @return {string} The function should return data for each request (optional)
135
+ * @return {any} The function should return data for each request (optional)
136
136
  */
137
137
  data: function(context) {
138
138
  const { channel, date } = context
@@ -146,7 +146,7 @@ module.exports = {
146
146
  },
147
147
 
148
148
  /**
149
- * @param {object} context
149
+ * @param {object} Request context
150
150
  *
151
151
  * @return {string} The function should return URL of the program page for the channel
152
152
  */
@@ -155,7 +155,7 @@ module.exports = {
155
155
  },
156
156
 
157
157
  /**
158
- * @param {object} context
158
+ * @param {object} Request context
159
159
  *
160
160
  * @return {string} The function should return URL of the channel logo (optional)
161
161
  */
@@ -164,7 +164,7 @@ module.exports = {
164
164
  },
165
165
 
166
166
  /**
167
- * @param {object} context
167
+ * @param {object} Parser context
168
168
  *
169
169
  * @return {array} The function should return an array of programs with their descriptions
170
170
  */
@@ -184,9 +184,16 @@ module.exports = {
184
184
  }
185
185
  ```
186
186
 
187
- ## Context Object
187
+ ## Request Context Object
188
188
 
189
- From each function in `config.js` you can access a `context` object containing the following data:
189
+ Inside `url()`, `logo()`, `request.data()`, `request.headers()` functions in `config.js` you can access a `context` object containing the following data:
190
+
191
+ - `channel`: The object describing the current channel (xmltv_id, site_id, name, lang)
192
+ - `date`: The 'dayjs' instance with the requested date
193
+
194
+ ## Parser Context Object
195
+
196
+ Inside `parser()` function in `config.js` you can access a `context` object containing the following data:
190
197
 
191
198
  - `channel`: The object describing the current channel (xmltv_id, site_id, name, lang)
192
199
  - `date`: The 'dayjs' instance with the requested date
package/dist/cli.js ADDED
@@ -0,0 +1,168 @@
1
+ #!/usr/bin/env node
2
+ import { n as name, v as version, d as description, p as parseNumber, L as Logger, E as EPGGrabber, g as getUTCDate, a as generateXMLTV } from './index-Bcnk0U8G.js';
3
+ import { Template, Collection } from '@freearhey/core';
4
+ import { Command, Option } from 'commander';
5
+ import { SocksProxyAgent } from 'socks-proxy-agent';
6
+ import { URL } from 'node:url';
7
+ import path from 'node:path';
8
+ import { TaskQueue } from 'cwait';
9
+ import Promise$1 from 'bluebird';
10
+ import fs from 'fs-extra';
11
+ import pako from 'pako';
12
+ import _ from 'lodash';
13
+ import 'dayjs';
14
+ import 'dayjs/plugin/utc.js';
15
+ import 'xml-js';
16
+ import 'glob';
17
+ import 'axios';
18
+ import 'curl-generator';
19
+ import 'axios-cache-interceptor';
20
+ import 'winston';
21
+ import 'path';
22
+
23
+ class ProxyParser {
24
+ static parse(proxy) {
25
+ const parsed = new URL(proxy);
26
+ const result = {
27
+ protocol: parsed.protocol.replace(":", "") || null,
28
+ host: parsed.hostname,
29
+ port: parsed.port ? parseInt(parsed.port) : null
30
+ };
31
+ if (parsed.username || parsed.password) {
32
+ result.auth = {};
33
+ if (parsed.username) result.auth.username = parsed.username;
34
+ if (parsed.password) result.auth.password = parsed.password;
35
+ }
36
+ return result;
37
+ }
38
+ }
39
+
40
+ class Storage {
41
+ static async loadJs(filepath) {
42
+ const absPath = path.resolve(filepath);
43
+ return (await import(absPath)).default;
44
+ }
45
+ }
46
+
47
+ const program = new Command();
48
+ program.name(name).version(version, "-v, --version").description(description).addOption(
49
+ new Option("-c, --config <config>", "Path to [site].config.js file").makeOptionMandatory()
50
+ ).addOption(new Option("-o, --output <output>", "Path to output file").default("guide.xml")).addOption(new Option("-x, --proxy <url>", "Use the specified proxy")).addOption(new Option("--channels <channels>", "Path to list of channels")).addOption(new Option("--lang <lang>", "Set default language for all programs")).addOption(
51
+ new Option("--days <days>", "Number of days for which to grab the program").argParser(parseNumber).default(1)
52
+ ).addOption(
53
+ new Option("--delay <delay>", "Delay between requests (in milliseconds)").argParser(parseNumber).default(3e3)
54
+ ).addOption(
55
+ new Option("--timeout <timeout>", "Set a timeout for each request (in milliseconds)").argParser(parseNumber).default(5e3)
56
+ ).addOption(
57
+ new Option(
58
+ "--max-connections <maxConnections>",
59
+ "Set a limit on the number of concurrent requests per site"
60
+ ).argParser(parseNumber).default(1)
61
+ ).addOption(
62
+ new Option(
63
+ "--cache-ttl <cacheTtl>",
64
+ "Maximum time for storing each request (in milliseconds)"
65
+ ).argParser(parseNumber)
66
+ ).addOption(new Option("--gzip", "Compress the output").default(false)).addOption(new Option("--debug", "Enable debug mode").default(false)).addOption(new Option("--curl", "Display request as CURL").default(false)).addOption(new Option("--log <log>", "Path to log file")).addOption(new Option("--log-level <level>", "Set log level").default("info")).parse(process.argv);
67
+ const options = program.opts();
68
+ const logger = new Logger({
69
+ log: options.log,
70
+ logLevel: options.debug ? "debug" : options.logLevel
71
+ });
72
+ async function main() {
73
+ logger.info("Starting...");
74
+ logger.debug(`Options: ${JSON.stringify(options, null, 2)}`);
75
+ logger.info(`Loading '${options.config}'...`);
76
+ let configObject = await Storage.loadJs(options.config);
77
+ configObject = _.merge(configObject, {
78
+ filepath: path.resolve(options.config),
79
+ channels: typeof options.channels === "string" ? path.resolve(options.channels) : void 0,
80
+ request: {}
81
+ });
82
+ if (configObject.output === void 0) configObject.output = options.output;
83
+ if (configObject.days === void 0) configObject.days = options.days;
84
+ if (configObject.delay === void 0) configObject.delay = options.delay;
85
+ if (configObject.curl === void 0) configObject.curl = options.curl;
86
+ if (configObject.debug === void 0) configObject.debug = options.debug;
87
+ if (configObject.gzip === void 0) configObject.gzip = options.gzip;
88
+ if (configObject.maxConnections === void 0)
89
+ configObject.maxConnections = options.maxConnections;
90
+ if (configObject.request.timeout === void 0) configObject.request.timeout = options.timeout;
91
+ if (options.cacheTtl !== void 0) configObject.request.cache = { ttl: options.cacheTtl };
92
+ if (options.proxy !== void 0) {
93
+ const proxy = ProxyParser.parse(options.proxy);
94
+ if (proxy.protocol && ["socks", "socks5", "socks5h", "socks4", "socks4a"].includes(String(proxy.protocol))) {
95
+ const socksProxyAgent = new SocksProxyAgent(options.proxy);
96
+ configObject.request = {
97
+ ...configObject.request,
98
+ ...{ httpAgent: socksProxyAgent, httpsAgent: socksProxyAgent }
99
+ };
100
+ } else {
101
+ configObject.request = { ...configObject.request, ...{ proxy } };
102
+ }
103
+ }
104
+ const grabber = new EPGGrabber(configObject, { logger });
105
+ logger.info("Loading channel list...");
106
+ const channelList = await grabber.loadChannels();
107
+ const template = new Template(configObject.output);
108
+ const groups = channelList.getGroups({ template });
109
+ if (channelList.getChannels().isEmpty()) {
110
+ logger.info("No channels found");
111
+ logger.info("Exit");
112
+ return;
113
+ }
114
+ logger.info("Processing...");
115
+ for (let groupId of groups.keys()) {
116
+ const group = groups.get(groupId);
117
+ const channels = new Collection(group);
118
+ let programs = new Collection();
119
+ let index = 1;
120
+ let days = configObject.days;
121
+ const maxConnections = configObject.maxConnections;
122
+ const total = channels.count() * days;
123
+ const utcDate = getUTCDate(process.env.CURR_DATE);
124
+ const dates = Array.from({ length: days }, (_2, i) => utcDate.add(i, "d"));
125
+ let queue = new Collection();
126
+ channels.forEach((channel) => {
127
+ for (let date of dates) {
128
+ queue.add({ channel, date });
129
+ }
130
+ });
131
+ const taskQueue = new TaskQueue(Promise$1, maxConnections);
132
+ const requests = queue.map(
133
+ taskQueue.wrap(async (queueItem) => {
134
+ const { channel, date } = queueItem;
135
+ if (!channel.logo && configObject.logo) {
136
+ channel.logo = await grabber.loadLogo({ channel, date });
137
+ }
138
+ const _programs = await grabber.grab(channel, date, (context, error) => {
139
+ const { channel: channel2, date: date2, programs: programs2 } = context;
140
+ logger.info(
141
+ `[${index}/${total}] ${configObject.site} - ${channel2.xmltv_id || channel2.site_id} - ${date2.format("MMM D, YYYY")} (${programs2.length} programs)`
142
+ );
143
+ if (error) logger.error(error.message);
144
+ if (index < total) index++;
145
+ });
146
+ programs.concat(new Collection(_programs));
147
+ })
148
+ );
149
+ await Promise$1.all(requests.all());
150
+ programs = programs.uniqBy((program2) => program2.start + program2.channel);
151
+ const xml = generateXMLTV(channels.all(), programs.all(), utcDate);
152
+ const channelSample = channels.sample();
153
+ let outputPath = template.format(channelSample.toObject());
154
+ const outputDir = path.dirname(outputPath);
155
+ fs.mkdirSync(outputDir, { recursive: true });
156
+ if (options.gzip) {
157
+ const compressed = pako.gzip(xml);
158
+ outputPath = outputPath || "guide.xml.gz";
159
+ fs.writeFileSync(outputPath, compressed);
160
+ } else {
161
+ outputPath = outputPath || "guide.xml";
162
+ fs.writeFileSync(outputPath, xml);
163
+ }
164
+ logger.info(`File '${outputPath}' successfully saved`);
165
+ }
166
+ logger.info("Finished");
167
+ }
168
+ main();