node-csfd-api 4.1.6 → 4.2.0-next.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.
- package/mcp-server.js +192 -0
- package/package.json +15 -6
- package/server.js +324 -0
package/mcp-server.js
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { z } from "zod";
|
|
5
|
+
import packageJson from "./package.json" with { type: "json" };
|
|
6
|
+
import { csfd } from "./index.mjs";
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: packageJson.name,
|
|
9
|
+
version: packageJson.version
|
|
10
|
+
});
|
|
11
|
+
server.tool(
|
|
12
|
+
"search",
|
|
13
|
+
"Searches for a movie, TV series, or person on CSFD.cz. Returns a list of results with IDs. Use this tool FIRST to find the ID needed for other tools.",
|
|
14
|
+
{
|
|
15
|
+
query: z.string().describe("Search query (movie title, series, or actor name)")
|
|
16
|
+
},
|
|
17
|
+
async ({ query }) => {
|
|
18
|
+
try {
|
|
19
|
+
const results = await csfd.search(query);
|
|
20
|
+
return {
|
|
21
|
+
content: [
|
|
22
|
+
{
|
|
23
|
+
type: "text",
|
|
24
|
+
text: JSON.stringify(results, null, 2)
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
};
|
|
28
|
+
} catch (error) {
|
|
29
|
+
return {
|
|
30
|
+
content: [{ type: "text", text: `Error during search: ${error}` }],
|
|
31
|
+
isError: true
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
server.tool(
|
|
37
|
+
"get_movie",
|
|
38
|
+
"Retrieves detailed information about a specific movie or series, including rating, plot, genres, and actors. Requires a numeric CSFD ID.",
|
|
39
|
+
{
|
|
40
|
+
id: z.number().describe("CSFD Movie ID (found using the 'search' tool)")
|
|
41
|
+
},
|
|
42
|
+
async ({ id }) => {
|
|
43
|
+
try {
|
|
44
|
+
const movie = await csfd.movie(id);
|
|
45
|
+
return {
|
|
46
|
+
content: [
|
|
47
|
+
{
|
|
48
|
+
type: "text",
|
|
49
|
+
text: JSON.stringify(movie, null, 2)
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
} catch (error) {
|
|
54
|
+
return {
|
|
55
|
+
content: [{ type: "text", text: `Error retrieving movie details: ${error}` }],
|
|
56
|
+
isError: true
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
);
|
|
61
|
+
server.tool(
|
|
62
|
+
"get_creator",
|
|
63
|
+
"Retrieves information about a specific creator (actor, director, etc.), including their biography and filmography. Requires a numeric CSFD ID.",
|
|
64
|
+
{
|
|
65
|
+
id: z.number().describe("CSFD Creator ID (found using the 'search' tool)")
|
|
66
|
+
},
|
|
67
|
+
async ({ id }) => {
|
|
68
|
+
try {
|
|
69
|
+
const creator = await csfd.creator(id);
|
|
70
|
+
return {
|
|
71
|
+
content: [
|
|
72
|
+
{
|
|
73
|
+
type: "text",
|
|
74
|
+
text: JSON.stringify(creator, null, 2)
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
};
|
|
78
|
+
} catch (error) {
|
|
79
|
+
return {
|
|
80
|
+
content: [{ type: "text", text: `Error retrieving creator details: ${error}` }],
|
|
81
|
+
isError: true
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
);
|
|
86
|
+
server.tool(
|
|
87
|
+
"get_user_ratings",
|
|
88
|
+
"Retrieves movie ratings from a specific CSFD user. Returns a list of movies with their user rating (0-5 stars). Supports pagination and filtering by film type.",
|
|
89
|
+
{
|
|
90
|
+
user: z.union([z.string(), z.number()]).describe("CSFD User ID (numeric) or username"),
|
|
91
|
+
page: z.number().optional().describe("Page number to fetch (default: 1)"),
|
|
92
|
+
allPages: z.boolean().optional().describe("Fetch all pages at once (use wisely, may be slow)"),
|
|
93
|
+
allPagesDelay: z.number().optional().describe("Delay in ms between page requests when using allPages"),
|
|
94
|
+
excludes: z.array(z.string()).optional().describe('Film types to exclude (e.g. "seri\xE1l", "TV film")'),
|
|
95
|
+
includesOnly: z.array(z.string()).optional().describe('Only include these film types (e.g. "film")')
|
|
96
|
+
},
|
|
97
|
+
async ({ user, page, allPages, allPagesDelay, excludes, includesOnly }) => {
|
|
98
|
+
try {
|
|
99
|
+
const results = await csfd.userRatings(user, {
|
|
100
|
+
page,
|
|
101
|
+
allPages,
|
|
102
|
+
allPagesDelay,
|
|
103
|
+
excludes,
|
|
104
|
+
includesOnly
|
|
105
|
+
});
|
|
106
|
+
return {
|
|
107
|
+
content: [
|
|
108
|
+
{
|
|
109
|
+
type: "text",
|
|
110
|
+
text: JSON.stringify(results, null, 2)
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
};
|
|
114
|
+
} catch (error) {
|
|
115
|
+
return {
|
|
116
|
+
content: [{ type: "text", text: `Error retrieving user ratings: ${error}` }],
|
|
117
|
+
isError: true
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
);
|
|
122
|
+
server.tool(
|
|
123
|
+
"get_user_reviews",
|
|
124
|
+
"Retrieves movie reviews written by a specific CSFD user. Returns a list of movies with their review text and rating. Supports pagination and filtering by film type.",
|
|
125
|
+
{
|
|
126
|
+
user: z.union([z.string(), z.number()]).describe("CSFD User ID (numeric) or username"),
|
|
127
|
+
page: z.number().optional().describe("Page number to fetch (default: 1)"),
|
|
128
|
+
allPages: z.boolean().optional().describe("Fetch all pages at once (use wisely, may be slow)"),
|
|
129
|
+
allPagesDelay: z.number().optional().describe("Delay in ms between page requests when using allPages"),
|
|
130
|
+
excludes: z.array(z.string()).optional().describe('Film types to exclude (e.g. "seri\xE1l", "TV film")'),
|
|
131
|
+
includesOnly: z.array(z.string()).optional().describe('Only include these film types (e.g. "film")')
|
|
132
|
+
},
|
|
133
|
+
async ({ user, page, allPages, allPagesDelay, excludes, includesOnly }) => {
|
|
134
|
+
try {
|
|
135
|
+
const results = await csfd.userReviews(user, {
|
|
136
|
+
page,
|
|
137
|
+
allPages,
|
|
138
|
+
allPagesDelay,
|
|
139
|
+
excludes,
|
|
140
|
+
includesOnly
|
|
141
|
+
});
|
|
142
|
+
return {
|
|
143
|
+
content: [
|
|
144
|
+
{
|
|
145
|
+
type: "text",
|
|
146
|
+
text: JSON.stringify(results, null, 2)
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
};
|
|
150
|
+
} catch (error) {
|
|
151
|
+
return {
|
|
152
|
+
content: [{ type: "text", text: `Error retrieving user reviews: ${error}` }],
|
|
153
|
+
isError: true
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
);
|
|
158
|
+
server.tool(
|
|
159
|
+
"get_cinemas",
|
|
160
|
+
"Retrieves cinema screenings for a given district in Czech Republic. Returns a list of cinemas with their current screenings, showtimes, and movie details.",
|
|
161
|
+
{
|
|
162
|
+
district: z.union([z.number(), z.string()]).describe("District ID (numeric) or name"),
|
|
163
|
+
period: z.enum(["today", "tomorrow", "weekend", "week", "month"]).describe("Time period for screenings")
|
|
164
|
+
},
|
|
165
|
+
async ({ district, period }) => {
|
|
166
|
+
try {
|
|
167
|
+
const results = await csfd.cinema(district, period);
|
|
168
|
+
return {
|
|
169
|
+
content: [
|
|
170
|
+
{
|
|
171
|
+
type: "text",
|
|
172
|
+
text: JSON.stringify(results, null, 2)
|
|
173
|
+
}
|
|
174
|
+
]
|
|
175
|
+
};
|
|
176
|
+
} catch (error) {
|
|
177
|
+
return {
|
|
178
|
+
content: [{ type: "text", text: `Error retrieving cinema data: ${error}` }],
|
|
179
|
+
isError: true
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
);
|
|
184
|
+
async function main() {
|
|
185
|
+
const transport = new StdioServerTransport();
|
|
186
|
+
await server.connect(transport);
|
|
187
|
+
console.error("CSFD MCP Server running on stdio...");
|
|
188
|
+
}
|
|
189
|
+
main().catch((error) => {
|
|
190
|
+
console.error("Fatal error in MCP server:", error);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "node-csfd-api",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.2.0-next.0",
|
|
4
4
|
"description": "ČSFD API in JavaScript. Amazing NPM library for scrapping csfd.cz :)",
|
|
5
5
|
"author": "BART! <bart@bartweb.cz>",
|
|
6
6
|
"publishConfig": {
|
|
@@ -8,8 +8,12 @@
|
|
|
8
8
|
"registry": "https://registry.npmjs.org"
|
|
9
9
|
},
|
|
10
10
|
"dependencies": {
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.26.0",
|
|
11
12
|
"cross-fetch": "^4.1.0",
|
|
12
|
-
"node-html-parser": "^7.0.2"
|
|
13
|
+
"node-html-parser": "^7.0.2",
|
|
14
|
+
"express": "^5.2.1",
|
|
15
|
+
"dotenv": "^17.2.4",
|
|
16
|
+
"zod": "^4.3.6"
|
|
13
17
|
},
|
|
14
18
|
"repository": {
|
|
15
19
|
"url": "git+https://github.com/bartholomej/node-csfd-api.git",
|
|
@@ -41,9 +45,14 @@
|
|
|
41
45
|
"types": "./index.d.ts",
|
|
42
46
|
"exports": {
|
|
43
47
|
".": {
|
|
44
|
-
"
|
|
45
|
-
"
|
|
48
|
+
"import": "./index.mjs",
|
|
49
|
+
"require": "./index.js"
|
|
46
50
|
},
|
|
47
51
|
"./package.json": "./package.json"
|
|
48
|
-
}
|
|
49
|
-
|
|
52
|
+
},
|
|
53
|
+
"bin": {
|
|
54
|
+
"csfd-mcp": "mcp-server.js",
|
|
55
|
+
"csfd-server": "server.js"
|
|
56
|
+
},
|
|
57
|
+
"sideEffects": false
|
|
58
|
+
}
|
package/server.js
ADDED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import "dotenv/config";
|
|
3
|
+
import express from "express";
|
|
4
|
+
import packageJson from "./package.json" with { type: "json" };
|
|
5
|
+
import { csfd } from "./index.mjs";
|
|
6
|
+
const LOG_COLORS = {
|
|
7
|
+
info: "\x1B[36m",
|
|
8
|
+
// cyan
|
|
9
|
+
warn: "\x1B[33m",
|
|
10
|
+
// yellow
|
|
11
|
+
error: "\x1B[31m",
|
|
12
|
+
// red
|
|
13
|
+
success: "\x1B[32m",
|
|
14
|
+
// green
|
|
15
|
+
reset: "\x1B[0m"
|
|
16
|
+
};
|
|
17
|
+
const LOG_SYMBOLS = {
|
|
18
|
+
info: "\u2139\uFE0F",
|
|
19
|
+
warn: "\u26A0\uFE0F",
|
|
20
|
+
error: "\u274C",
|
|
21
|
+
success: "\u2705"
|
|
22
|
+
};
|
|
23
|
+
const LOG_PADDED_SEVERITY = {
|
|
24
|
+
info: "INFO ",
|
|
25
|
+
warn: "WARN ",
|
|
26
|
+
error: "ERROR ",
|
|
27
|
+
success: "SUCCESS"
|
|
28
|
+
};
|
|
29
|
+
var Errors = /* @__PURE__ */ ((Errors2) => {
|
|
30
|
+
Errors2["API_KEY_MISSING"] = "API_KEY_MISSING";
|
|
31
|
+
Errors2["API_KEY_INVALID"] = "API_KEY_INVALID";
|
|
32
|
+
Errors2["ID_MISSING"] = "ID_MISSING";
|
|
33
|
+
Errors2["MOVIE_FETCH_FAILED"] = "MOVIE_FETCH_FAILED";
|
|
34
|
+
Errors2["CREATOR_FETCH_FAILED"] = "CREATOR_FETCH_FAILED";
|
|
35
|
+
Errors2["SEARCH_FETCH_FAILED"] = "SEARCH_FETCH_FAILED";
|
|
36
|
+
Errors2["USER_RATINGS_FETCH_FAILED"] = "USER_RATINGS_FETCH_FAILED";
|
|
37
|
+
Errors2["USER_REVIEWS_FETCH_FAILED"] = "USER_REVIEWS_FETCH_FAILED";
|
|
38
|
+
Errors2["CINEMAS_FETCH_FAILED"] = "CINEMAS_FETCH_FAILED";
|
|
39
|
+
Errors2["PAGE_NOT_FOUND"] = "PAGE_NOT_FOUND";
|
|
40
|
+
Errors2["TOO_MANY_REQUESTS"] = "TOO_MANY_REQUESTS";
|
|
41
|
+
return Errors2;
|
|
42
|
+
})(Errors || {});
|
|
43
|
+
function logMessage(severity, log, req) {
|
|
44
|
+
const time = (/* @__PURE__ */ new Date()).toISOString();
|
|
45
|
+
const reqInfo = req ? `${req.method}: ${req.originalUrl}` : "";
|
|
46
|
+
const reqIp = req ? req.headers["x-forwarded-for"] || req.socket.remoteAddress || req.ip || req.ips : "";
|
|
47
|
+
const msg = `${LOG_COLORS[severity]}[${LOG_PADDED_SEVERITY[severity]}]${LOG_COLORS.reset} ${time} | IP: ${reqIp} ${LOG_SYMBOLS[severity]} ${log.error ? log.error + ":" : ""} ${log.message} \u{1F517} ${reqInfo}`;
|
|
48
|
+
const logSuccessEnabled = process.env.VERBOSE === "true";
|
|
49
|
+
if (severity === "success") {
|
|
50
|
+
if (logSuccessEnabled) {
|
|
51
|
+
console.log(msg);
|
|
52
|
+
}
|
|
53
|
+
} else if (severity === "error") {
|
|
54
|
+
console.error(msg);
|
|
55
|
+
} else if (severity === "warn") {
|
|
56
|
+
console.warn(msg);
|
|
57
|
+
} else {
|
|
58
|
+
console.log(msg);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
var Endpoint = /* @__PURE__ */ ((Endpoint2) => {
|
|
62
|
+
Endpoint2["MOVIE"] = "/movie/:id";
|
|
63
|
+
Endpoint2["CREATOR"] = "/creator/:id";
|
|
64
|
+
Endpoint2["SEARCH"] = "/search/:query";
|
|
65
|
+
Endpoint2["USER_RATINGS"] = "/user-ratings/:id";
|
|
66
|
+
Endpoint2["USER_REVIEWS"] = "/user-reviews/:id";
|
|
67
|
+
Endpoint2["CINEMAS"] = "/cinemas";
|
|
68
|
+
return Endpoint2;
|
|
69
|
+
})(Endpoint || {});
|
|
70
|
+
const app = express();
|
|
71
|
+
const port = process.env.PORT || 3e3;
|
|
72
|
+
const API_KEY_NAME = process.env.API_KEY_NAME || "x-api-key";
|
|
73
|
+
const API_KEY = process.env.API_KEY;
|
|
74
|
+
const RAW_LANGUAGE = process.env.LANGUAGE;
|
|
75
|
+
const isSupportedLanguage = (value) => value === "cs" || value === "en" || value === "sk";
|
|
76
|
+
const BASE_LANGUAGE = isSupportedLanguage(RAW_LANGUAGE) ? RAW_LANGUAGE : void 0;
|
|
77
|
+
const API_KEYS_LIST = API_KEY ? API_KEY.split(/[,;\s]+/).map((k) => k.trim()).filter(Boolean) : [];
|
|
78
|
+
if (BASE_LANGUAGE) {
|
|
79
|
+
csfd.setOptions({ language: BASE_LANGUAGE });
|
|
80
|
+
}
|
|
81
|
+
app.use((req, res, next) => {
|
|
82
|
+
if (API_KEY) {
|
|
83
|
+
const apiKey = req.get(API_KEY_NAME)?.trim();
|
|
84
|
+
if (!apiKey) {
|
|
85
|
+
const log = {
|
|
86
|
+
error: "API_KEY_MISSING" /* API_KEY_MISSING */,
|
|
87
|
+
message: `Missing API key in request header: ${API_KEY_NAME}`
|
|
88
|
+
};
|
|
89
|
+
logMessage("error", log, req);
|
|
90
|
+
res.status(401).json(log);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
if (!API_KEYS_LIST.includes(apiKey)) {
|
|
94
|
+
const log = {
|
|
95
|
+
error: "API_KEY_INVALID" /* API_KEY_INVALID */,
|
|
96
|
+
message: `Invalid API key in request header: ${API_KEY_NAME}`
|
|
97
|
+
};
|
|
98
|
+
logMessage("error", log, req);
|
|
99
|
+
res.status(401).json(log);
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
next();
|
|
104
|
+
});
|
|
105
|
+
app.get("/", (_, res) => {
|
|
106
|
+
logMessage("info", { error: null, message: "/" });
|
|
107
|
+
res.json({
|
|
108
|
+
name: packageJson.name,
|
|
109
|
+
version: packageJson.version,
|
|
110
|
+
docs: packageJson.homepage,
|
|
111
|
+
links: Object.values(Endpoint)
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
app.get(["/movie/", "/creator/", "/search/", "/user-ratings/", "/user-reviews/"], (req, res) => {
|
|
115
|
+
const log = {
|
|
116
|
+
error: "ID_MISSING" /* ID_MISSING */,
|
|
117
|
+
message: `ID is missing. Provide ID like this: ${req.url}${req.url.endsWith("/") ? "" : "/"}1234`
|
|
118
|
+
};
|
|
119
|
+
logMessage("warn", log, req);
|
|
120
|
+
res.status(404).json(log);
|
|
121
|
+
});
|
|
122
|
+
app.get("/movie/:id" /* MOVIE */, async (req, res) => {
|
|
123
|
+
const rawLanguage = req.query.language;
|
|
124
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
125
|
+
try {
|
|
126
|
+
const movie = await csfd.movie(+req.params.id, { language });
|
|
127
|
+
res.json(movie);
|
|
128
|
+
logMessage(
|
|
129
|
+
"success",
|
|
130
|
+
{
|
|
131
|
+
error: null,
|
|
132
|
+
message: `${"/movie/:id" /* MOVIE */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
133
|
+
},
|
|
134
|
+
req
|
|
135
|
+
);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const log = {
|
|
138
|
+
error: "MOVIE_FETCH_FAILED" /* MOVIE_FETCH_FAILED */,
|
|
139
|
+
message: "Failed to fetch movie data: " + error
|
|
140
|
+
};
|
|
141
|
+
logMessage("error", log, req);
|
|
142
|
+
res.status(500).json(log);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
app.get("/creator/:id" /* CREATOR */, async (req, res) => {
|
|
146
|
+
const rawLanguage = req.query.language;
|
|
147
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
148
|
+
try {
|
|
149
|
+
const result = await csfd.creator(+req.params.id, { language });
|
|
150
|
+
res.json(result);
|
|
151
|
+
logMessage(
|
|
152
|
+
"success",
|
|
153
|
+
{
|
|
154
|
+
error: null,
|
|
155
|
+
message: `${"/creator/:id" /* CREATOR */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
156
|
+
},
|
|
157
|
+
req
|
|
158
|
+
);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
const log = {
|
|
161
|
+
error: "CREATOR_FETCH_FAILED" /* CREATOR_FETCH_FAILED */,
|
|
162
|
+
message: "Failed to fetch creator data: " + error
|
|
163
|
+
};
|
|
164
|
+
logMessage("error", log, req);
|
|
165
|
+
res.status(500).json(log);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
app.get("/search/:query" /* SEARCH */, async (req, res) => {
|
|
169
|
+
const rawLanguage = req.query.language;
|
|
170
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
171
|
+
try {
|
|
172
|
+
const result = await csfd.search(req.params.query, { language });
|
|
173
|
+
res.json(result);
|
|
174
|
+
logMessage(
|
|
175
|
+
"success",
|
|
176
|
+
{
|
|
177
|
+
error: null,
|
|
178
|
+
message: `${"/search/:query" /* SEARCH */}: ${req.params.query}${language ? ` [${language}]` : ""}`
|
|
179
|
+
},
|
|
180
|
+
req
|
|
181
|
+
);
|
|
182
|
+
} catch (error) {
|
|
183
|
+
const log = {
|
|
184
|
+
error: "SEARCH_FETCH_FAILED" /* SEARCH_FETCH_FAILED */,
|
|
185
|
+
message: "Failed to fetch search data: " + error
|
|
186
|
+
};
|
|
187
|
+
logMessage("error", log, req);
|
|
188
|
+
res.status(500).json(log);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
app.get("/user-ratings/:id" /* USER_RATINGS */, async (req, res) => {
|
|
192
|
+
const { allPages, allPagesDelay, excludes, includesOnly, page } = req.query;
|
|
193
|
+
const rawLanguage = req.query.language;
|
|
194
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
195
|
+
try {
|
|
196
|
+
const result = await csfd.userRatings(
|
|
197
|
+
req.params.id,
|
|
198
|
+
{
|
|
199
|
+
allPages: allPages === "true",
|
|
200
|
+
allPagesDelay: allPagesDelay ? +allPagesDelay : void 0,
|
|
201
|
+
excludes: excludes ? excludes.split(",") : void 0,
|
|
202
|
+
includesOnly: includesOnly ? includesOnly.split(",") : void 0,
|
|
203
|
+
page: page ? +page : void 0
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
language
|
|
207
|
+
}
|
|
208
|
+
);
|
|
209
|
+
res.json(result);
|
|
210
|
+
logMessage(
|
|
211
|
+
"success",
|
|
212
|
+
{
|
|
213
|
+
error: null,
|
|
214
|
+
message: `${"/user-ratings/:id" /* USER_RATINGS */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
215
|
+
},
|
|
216
|
+
req
|
|
217
|
+
);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
const log = {
|
|
220
|
+
error: "USER_RATINGS_FETCH_FAILED" /* USER_RATINGS_FETCH_FAILED */,
|
|
221
|
+
message: "Failed to fetch user-ratings data: " + error
|
|
222
|
+
};
|
|
223
|
+
logMessage("error", log, req);
|
|
224
|
+
res.status(500).json(log);
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
app.get("/user-reviews/:id" /* USER_REVIEWS */, async (req, res) => {
|
|
228
|
+
const { allPages, allPagesDelay, excludes, includesOnly, page } = req.query;
|
|
229
|
+
const rawLanguage = req.query.language;
|
|
230
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
231
|
+
try {
|
|
232
|
+
const result = await csfd.userReviews(
|
|
233
|
+
req.params.id,
|
|
234
|
+
{
|
|
235
|
+
allPages: allPages === "true",
|
|
236
|
+
allPagesDelay: allPagesDelay ? +allPagesDelay : void 0,
|
|
237
|
+
excludes: excludes ? excludes.split(",") : void 0,
|
|
238
|
+
includesOnly: includesOnly ? includesOnly.split(",") : void 0,
|
|
239
|
+
page: page ? +page : void 0
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
language
|
|
243
|
+
}
|
|
244
|
+
);
|
|
245
|
+
res.json(result);
|
|
246
|
+
logMessage(
|
|
247
|
+
"success",
|
|
248
|
+
{
|
|
249
|
+
error: null,
|
|
250
|
+
message: `${"/user-reviews/:id" /* USER_REVIEWS */}: ${req.params.id}${language ? ` [${language}]` : ""}`
|
|
251
|
+
},
|
|
252
|
+
req
|
|
253
|
+
);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
const log = {
|
|
256
|
+
error: "USER_REVIEWS_FETCH_FAILED" /* USER_REVIEWS_FETCH_FAILED */,
|
|
257
|
+
message: "Failed to fetch user-reviews data: " + error
|
|
258
|
+
};
|
|
259
|
+
logMessage("error", log, req);
|
|
260
|
+
res.status(500).json(log);
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
app.get("/cinemas" /* CINEMAS */, async (req, res) => {
|
|
264
|
+
const rawLanguage = req.query.language;
|
|
265
|
+
const language = isSupportedLanguage(rawLanguage) ? rawLanguage : void 0;
|
|
266
|
+
try {
|
|
267
|
+
const result = await csfd.cinema(1, "today", { language });
|
|
268
|
+
logMessage(
|
|
269
|
+
"success",
|
|
270
|
+
{ error: null, message: `${"/cinemas" /* CINEMAS */}${language ? ` [${language}]` : ""}` },
|
|
271
|
+
req
|
|
272
|
+
);
|
|
273
|
+
res.json(result);
|
|
274
|
+
} catch (error) {
|
|
275
|
+
const log = {
|
|
276
|
+
error: "CINEMAS_FETCH_FAILED" /* CINEMAS_FETCH_FAILED */,
|
|
277
|
+
message: "Failed to fetch cinemas data: " + error
|
|
278
|
+
};
|
|
279
|
+
logMessage("error", log, req);
|
|
280
|
+
res.status(500).json(log);
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
app.use((req, res) => {
|
|
284
|
+
const log = {
|
|
285
|
+
error: "PAGE_NOT_FOUND" /* PAGE_NOT_FOUND */,
|
|
286
|
+
message: "The requested endpoint could not be found."
|
|
287
|
+
};
|
|
288
|
+
logMessage("warn", log, req);
|
|
289
|
+
res.status(404).json(log);
|
|
290
|
+
});
|
|
291
|
+
app.listen(port, () => {
|
|
292
|
+
console.log(`
|
|
293
|
+
_ __ _ _
|
|
294
|
+
| | / _| | | (_)
|
|
295
|
+
_ __ ___ __| | ___ ___ ___| |_ __| | __ _ _ __ _
|
|
296
|
+
| '_ \\ / _ \\ / _\` |/ _ \\ / __/ __| _/ _\` | / _\` | '_ \\| |
|
|
297
|
+
| | | | (_) | (_| | __/ | (__\\__ \\ || (_| | | (_| | |_) | |
|
|
298
|
+
|_| |_|\\___/ \\__,_|\\___| \\___|___/_| \\__,_| \\__,_| .__/|_|
|
|
299
|
+
| |
|
|
300
|
+
|_|
|
|
301
|
+
`);
|
|
302
|
+
console.log(`node-csfd-api@${packageJson.version}
|
|
303
|
+
`);
|
|
304
|
+
console.log(`Docs: ${packageJson.homepage}`);
|
|
305
|
+
console.log(`Endpoints: ${Object.values(Endpoint).join(", ")}
|
|
306
|
+
`);
|
|
307
|
+
console.log(`API is running on: http://localhost:${port}`);
|
|
308
|
+
if (BASE_LANGUAGE) {
|
|
309
|
+
console.log(`Base language configured: ${BASE_LANGUAGE}
|
|
310
|
+
`);
|
|
311
|
+
}
|
|
312
|
+
if (API_KEYS_LIST.length === 0) {
|
|
313
|
+
console.log(
|
|
314
|
+
"\x1B[31m%s\x1B[0m",
|
|
315
|
+
"\u26A0\uFE0F Server is OPEN!\n- Your server will be open to the world and potentially everyone can use it without any restriction.\n- To enable some basic protection, set API_KEY environment variable (single value or comma-separated list) and provide the same value in request header: " + API_KEY_NAME
|
|
316
|
+
);
|
|
317
|
+
} else {
|
|
318
|
+
console.log(
|
|
319
|
+
"\x1B[32m%s\x1B[0m",
|
|
320
|
+
`\u2714\uFE0F Server is protected (somehow).
|
|
321
|
+
- ${API_KEYS_LIST.length} API key(s) are configured and will be checked for each request header: ${API_KEY_NAME}`
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
});
|