media-synopsis 0.1.1 → 1.0.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/LICENSE +1 -1
- package/README.md +47 -42
- package/dist/index.d.ts +8 -0
- package/dist/index.js +15 -0
- package/dist/text.d.ts +13 -0
- package/dist/text.js +71 -0
- package/dist/wikipedia.d.ts +3 -0
- package/dist/wikipedia.js +74 -0
- package/package.json +37 -14
- package/.npmignore +0 -25
- package/index.js +0 -84
package/LICENSE
CHANGED
package/README.md
CHANGED
|
@@ -1,70 +1,75 @@
|
|
|
1
|
-
Media Synopsis
|
|
2
|
-
==============
|
|
1
|
+
# Media Synopsis
|
|
3
2
|
|
|
4
|
-
Media Synopsis
|
|
3
|
+
Media Synopsis returns a short synopsis for a movie, book, anime, video game, or other media title. It reads the lead section of the matching English Wikipedia article and cleans it up for display.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
------------
|
|
5
|
+
Requires Node.js 20 or newer.
|
|
8
6
|
|
|
9
|
-
|
|
7
|
+
## Installation
|
|
10
8
|
|
|
11
|
-
|
|
12
|
-
|
|
9
|
+
```bash
|
|
10
|
+
npm install media-synopsis
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
The package is an ES module.
|
|
13
16
|
|
|
14
17
|
```javascript
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
synopsis('beyblade', ['anime', 'Japanese animated'], function(err, text) {
|
|
18
|
-
if (err) {
|
|
19
|
-
console.log(err);
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
console.log(text.title +
|
|
23
|
-
'\n=======================================\n\n' +
|
|
24
|
-
text.content);
|
|
25
|
-
});
|
|
26
|
-
```
|
|
18
|
+
import { synopsis } from 'media-synopsis';
|
|
27
19
|
|
|
20
|
+
const text = await synopsis('beyblade', ['anime', 'Japanese animated']);
|
|
21
|
+
console.log(text.title +
|
|
22
|
+
'\n=======================================\n\n' +
|
|
23
|
+
text.content);
|
|
24
|
+
```
|
|
28
25
|
|
|
29
|
-
Prints:
|
|
26
|
+
Prints something like:
|
|
30
27
|
|
|
31
28
|
```markdown
|
|
32
29
|
Beyblade
|
|
33
30
|
=======================================
|
|
34
31
|
|
|
35
|
-
Beyblade, known in Japan as
|
|
36
|
-
|
|
37
|
-
The manga is licensed for English language release in North America by Viz Media. An anime adaptation, also titled Beyblade and spanning 51 episodes, aired in Japan on TV Tokyo from January 8, 2001 to December 24, 2001. The second, Beyblade V-Force, ran for another 51 episodes from January 7, 2002 until December 30, 2002. Beyblade G-Revolution, the third and final adaptation, also spanned 52 episodes (the last two episodes were released together as a double-length special in Japan) and aired from January 6, 2003, until its conclusion on December 28, 2003. Nelvana licensed the anime for an English-language release. Takara Tomy also developed the Beyblade toy line.
|
|
32
|
+
Beyblade, known in Japan as Bakuten Shoot Beyblade, is a Japanese manga series written and illustrated by Takao Aoki, created to promote Takara's Beyblade spinning tops. The series focuses on a group of kids who form teams, which battle one another using Beyblades. It was serialized in Shogakukan's children's manga magazine CoroCoro Comic from August 1999 to June 2004, with its chapters collected and published in 14 tankōbon volumes. It was licensed for English release in North America by Viz Media and in Singapore, Australia and New Zealand by Chuang Yi.
|
|
38
33
|
|
|
39
|
-
|
|
34
|
+
An anime adaptation by Madhouse aired in Japan on TV Tokyo from January to December 2001 and was followed by two sequel series, Beyblade V-Force and Beyblade G-Revolution, and the 2002 film Beyblade: Fierce Battle all three by Nippon Animedia, a partnership between Nippon Animation, Takara, Agatsuma and Epoch. Nelvana licensed and produced English-language adaptations of the anime series and the film.
|
|
40
35
|
|
|
36
|
+
Source: [Wikipedia](https://en.wikipedia.org/wiki/Beyblade_(manga))
|
|
41
37
|
```
|
|
42
|
-
How it works?
|
|
43
|
-
-------------
|
|
44
38
|
|
|
45
|
-
|
|
39
|
+
The wording comes from the current Wikipedia article, so it will change as that article changes.
|
|
46
40
|
|
|
47
|
-
|
|
41
|
+
## How it works
|
|
48
42
|
|
|
49
|
-
|
|
43
|
+
`synopsis` searches the English Wikipedia API for the media title plus the first media type you passed. For the example above, the search is `beyblade anime`. It takes the first result and reads that page's lead section as plain text.
|
|
50
44
|
|
|
51
|
-
|
|
45
|
+
The lead is split into paragraphs. The first four are checked for any of the media types you provided. If one of those paragraphs mentions a type, every paragraph from the start through that one is kept. If none of them match, or you did not pass any types, only the first paragraph is kept.
|
|
52
46
|
|
|
53
|
-
|
|
54
|
-
---
|
|
47
|
+
The page title is cleaned up for use as a display name. A Wikipedia suffix is removed, `List of X episodes` becomes `X`, and a trailing disambiguator such as `(film)` or `(manga)` is removed. That last part is what turns a loose query such as `mahouka` into `The Irregular at Magic High School`.
|
|
55
48
|
|
|
56
|
-
|
|
49
|
+
The synopsis text drops Japanese and Hepburn pronunciation parentheses, and bracketed citation markers such as `[1]`. A Markdown link to the Wikipedia article is appended. Linking back to Wikipedia is what makes the placeholder text reasonable to show while a real description is still missing.
|
|
57
50
|
|
|
58
|
-
|
|
51
|
+
## API
|
|
59
52
|
|
|
60
|
-
|
|
53
|
+
### `synopsis(mediaTitle, [mediaTypes])`
|
|
61
54
|
|
|
62
|
-
|
|
63
|
-
------------
|
|
55
|
+
`mediaTitle` is a string.
|
|
64
56
|
|
|
65
|
-
|
|
57
|
+
`mediaTypes` is an optional array of strings. Only the first non-empty type is added to the Wikipedia search. All of them are used when choosing paragraphs.
|
|
66
58
|
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
The returned Promise resolves to an object with:
|
|
60
|
+
|
|
61
|
+
- `title` — the cleaned Wikipedia title
|
|
62
|
+
- `content` — the synopsis, including a `Source: [Wikipedia](...)` line
|
|
63
|
+
|
|
64
|
+
The Promise rejects with an `Error` when Wikipedia has no matching page or the request fails.
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
The cleanup rules are deliberately small. If a real title comes back with a pronunciation, citation, or disambiguator still attached, open an issue with the query, the Wikipedia URL, and what the title or synopsis should have been.
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm test
|
|
72
|
+
npm run lint
|
|
73
|
+
```
|
|
69
74
|
|
|
70
|
-
|
|
75
|
+
Vitest covers title cleanup, paragraph selection, pronunciation stripping, and the Wikipedia client with a stand-in for `fetch`. The tests do not call Wikipedia.
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Synopsis } from './text.js';
|
|
2
|
+
export type { Synopsis };
|
|
3
|
+
/**
|
|
4
|
+
* Look up a short synopsis for a movie, book, anime, game, or other media.
|
|
5
|
+
* Only the first non-empty media type is added to the Wikipedia search.
|
|
6
|
+
* Every non-empty type is used when choosing lead paragraphs.
|
|
7
|
+
*/
|
|
8
|
+
export declare function synopsis(mediaTitle: string, mediaTypes?: readonly string[]): Promise<Synopsis>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { buildSynopsis } from './text.js';
|
|
2
|
+
import { fetchWikipediaLead } from './wikipedia.js';
|
|
3
|
+
function mediaTypeList(mediaTypes) {
|
|
4
|
+
return [...mediaTypes];
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Look up a short synopsis for a movie, book, anime, game, or other media.
|
|
8
|
+
* Only the first non-empty media type is added to the Wikipedia search.
|
|
9
|
+
* Every non-empty type is used when choosing lead paragraphs.
|
|
10
|
+
*/
|
|
11
|
+
export async function synopsis(mediaTitle, mediaTypes = []) {
|
|
12
|
+
const types = mediaTypeList(mediaTypes);
|
|
13
|
+
const page = await fetchWikipediaLead(mediaTitle.trim(), types);
|
|
14
|
+
return buildSynopsis(page, types);
|
|
15
|
+
}
|
package/dist/text.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface Synopsis {
|
|
2
|
+
content: string;
|
|
3
|
+
title: string;
|
|
4
|
+
}
|
|
5
|
+
export interface LeadPage {
|
|
6
|
+
extract: string;
|
|
7
|
+
title: string;
|
|
8
|
+
url: string;
|
|
9
|
+
}
|
|
10
|
+
export declare function getMediaTitle(fullLinkText: string): string;
|
|
11
|
+
export declare function getContentText(text: string, mediaTypes: readonly string[]): string;
|
|
12
|
+
export declare function removeUnwantedChars(text: string, title: string): string;
|
|
13
|
+
export declare function buildSynopsis(page: LeadPage, mediaTypes: readonly string[]): Synopsis;
|
package/dist/text.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// Trailing disambiguator, such as " (film)" or " (manga)".
|
|
2
|
+
const TRAILING_DISAMBIGUATION = /\s+\([^)]*\)$/;
|
|
3
|
+
function escapeRegExp(value) {
|
|
4
|
+
// Characters that would otherwise change the meaning of a regular expression.
|
|
5
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
6
|
+
}
|
|
7
|
+
export function getMediaTitle(fullLinkText) {
|
|
8
|
+
const withoutSite = fullLinkText.split('- Wikipedia')[0].trim();
|
|
9
|
+
// "List of Naruto episodes" keeps "Naruto".
|
|
10
|
+
const seriesName = /List of (.*) episodes/i.exec(withoutSite)?.[1]?.trim();
|
|
11
|
+
if (seriesName) {
|
|
12
|
+
return seriesName;
|
|
13
|
+
}
|
|
14
|
+
return withoutSite.replace(TRAILING_DISAMBIGUATION, '').trim();
|
|
15
|
+
}
|
|
16
|
+
export function getContentText(text, mediaTypes) {
|
|
17
|
+
const paragraphs = text
|
|
18
|
+
// One or more newlines separate paragraphs.
|
|
19
|
+
.split(/\n+/)
|
|
20
|
+
.map((paragraph) => paragraph.trim())
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
const opening = paragraphs[0];
|
|
23
|
+
if (opening === undefined) {
|
|
24
|
+
return '';
|
|
25
|
+
}
|
|
26
|
+
const needles = mediaTypes
|
|
27
|
+
.map((type) => type.trim())
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
if (needles.length === 0) {
|
|
30
|
+
return opening;
|
|
31
|
+
}
|
|
32
|
+
const pattern = needles.map(escapeRegExp).join('|');
|
|
33
|
+
// Case-insensitive whole word or phrase for any supplied media type.
|
|
34
|
+
const mediaTypePattern = new RegExp(`\\b(?:${pattern})\\b`, 'i');
|
|
35
|
+
const limit = Math.min(4, paragraphs.length);
|
|
36
|
+
for (let index = 0; index < limit; index += 1) {
|
|
37
|
+
const paragraph = paragraphs[index];
|
|
38
|
+
if (paragraph !== undefined && mediaTypePattern.test(paragraph)) {
|
|
39
|
+
return paragraphs.slice(0, index + 1).join('\n\n');
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return opening;
|
|
43
|
+
}
|
|
44
|
+
export function removeUnwantedChars(text, title) {
|
|
45
|
+
// Parenthetical immediately after the title. `.` stops at a newline.
|
|
46
|
+
const titlePronunciation = new RegExp(`${escapeRegExp(title)}\\s*\\(.+?\\)`, 'i');
|
|
47
|
+
// Japanese or Hepburn pronunciation, including when it follows the original name. Stays on one line.
|
|
48
|
+
const namedPronunciation = /\s*\([^)\n]*(?:Japanese:|Hepburn:|[\u3040-\u30ff\u3400-\u9fff])[^)\n]*\)/g;
|
|
49
|
+
return text
|
|
50
|
+
.replace(namedPronunciation, '')
|
|
51
|
+
.replace(titlePronunciation, title)
|
|
52
|
+
// Citation markers such as [1].
|
|
53
|
+
.replace(/\[.*?\]/g, '')
|
|
54
|
+
// Runs of spaces or tabs left by the removals above.
|
|
55
|
+
.replace(/[ \t]{2,}/g, ' ')
|
|
56
|
+
// A space before punctuation, such as " ," or " .".
|
|
57
|
+
.replace(/[ \t]+([,.;:])/g, '$1')
|
|
58
|
+
.trim();
|
|
59
|
+
}
|
|
60
|
+
export function buildSynopsis(page, mediaTypes) {
|
|
61
|
+
const title = getMediaTitle(page.title);
|
|
62
|
+
const relevantParagraphs = getContentText(page.extract, mediaTypes);
|
|
63
|
+
const content = removeUnwantedChars(relevantParagraphs, title);
|
|
64
|
+
if (!content) {
|
|
65
|
+
throw new Error('No synopsis text found on the Wikipedia page');
|
|
66
|
+
}
|
|
67
|
+
return {
|
|
68
|
+
content: `${content}\n\nSource: [Wikipedia](${page.url})`,
|
|
69
|
+
title
|
|
70
|
+
};
|
|
71
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import pkg from '../package.json' with { type: 'json' };
|
|
2
|
+
const API_URL = 'https://en.wikipedia.org/w/api.php';
|
|
3
|
+
const USER_AGENT = `media-synopsis/${pkg.version} (https://github.com/thethomaseffect/node-media-synopsis; thethomaseffect@gmail.com)`;
|
|
4
|
+
export function searchQuery(mediaTitle, mediaTypes) {
|
|
5
|
+
const hint = mediaTypes
|
|
6
|
+
.map((type) => type.trim())
|
|
7
|
+
.find((type) => type !== '');
|
|
8
|
+
if (!hint) {
|
|
9
|
+
return mediaTitle;
|
|
10
|
+
}
|
|
11
|
+
return `${mediaTitle} ${hint}`;
|
|
12
|
+
}
|
|
13
|
+
function pageUrl(page) {
|
|
14
|
+
if (typeof page.fullurl === 'string' && page.fullurl) {
|
|
15
|
+
return page.fullurl;
|
|
16
|
+
}
|
|
17
|
+
const slug = encodeURIComponent((page.title ?? '').replace(/ /g, '_'));
|
|
18
|
+
return `https://en.wikipedia.org/wiki/${slug}`;
|
|
19
|
+
}
|
|
20
|
+
async function readJson(response) {
|
|
21
|
+
try {
|
|
22
|
+
return await response.json();
|
|
23
|
+
}
|
|
24
|
+
catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
function isWikiBody(value) {
|
|
29
|
+
return typeof value === 'object' && value !== null;
|
|
30
|
+
}
|
|
31
|
+
function failureMessage(response, body) {
|
|
32
|
+
if (isWikiBody(body) && typeof body.error?.info === 'string') {
|
|
33
|
+
return body.error.info;
|
|
34
|
+
}
|
|
35
|
+
return `Wikipedia request failed with status ${response.status}`;
|
|
36
|
+
}
|
|
37
|
+
export async function fetchWikipediaLead(mediaTitle, mediaTypes) {
|
|
38
|
+
const params = new URLSearchParams({
|
|
39
|
+
action: 'query',
|
|
40
|
+
explaintext: '1',
|
|
41
|
+
exintro: '1',
|
|
42
|
+
format: 'json',
|
|
43
|
+
formatversion: '2',
|
|
44
|
+
generator: 'search',
|
|
45
|
+
gsrlimit: '1',
|
|
46
|
+
gsrsearch: searchQuery(mediaTitle, mediaTypes),
|
|
47
|
+
inprop: 'url',
|
|
48
|
+
prop: 'extracts|info',
|
|
49
|
+
redirects: '1'
|
|
50
|
+
});
|
|
51
|
+
const response = await fetch(`${API_URL}?${params.toString()}`, {
|
|
52
|
+
headers: {
|
|
53
|
+
Accept: 'application/json',
|
|
54
|
+
'User-Agent': USER_AGENT
|
|
55
|
+
},
|
|
56
|
+
signal: AbortSignal.timeout(15000)
|
|
57
|
+
});
|
|
58
|
+
const body = await readJson(response);
|
|
59
|
+
if (!response.ok || (isWikiBody(body) && body.error)) {
|
|
60
|
+
throw new Error(failureMessage(response, body));
|
|
61
|
+
}
|
|
62
|
+
if (!isWikiBody(body)) {
|
|
63
|
+
throw new Error('Wikipedia returned a response that was not JSON');
|
|
64
|
+
}
|
|
65
|
+
const page = body.query?.pages?.[0];
|
|
66
|
+
if (!page || page.missing === true || typeof page.title !== 'string' || typeof page.extract !== 'string' || page.extract.trim() === '') {
|
|
67
|
+
throw new Error('No Wikipedia results found for that media title and media type');
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
extract: page.extract,
|
|
71
|
+
title: page.title,
|
|
72
|
+
url: pageUrl(page)
|
|
73
|
+
};
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,25 +1,36 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "media-synopsis",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Get a synopsis for a
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
"
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Get a synopsis for a movie, anime, book, or other media from Wikipedia.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist",
|
|
16
|
+
"LICENSE"
|
|
17
|
+
],
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=20"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsc -p tsconfig.build.json",
|
|
23
|
+
"test": "tsc --noEmit && npm run build && vitest run",
|
|
24
|
+
"lint": "eslint .",
|
|
25
|
+
"prepublishOnly": "npm run lint && npm test"
|
|
10
26
|
},
|
|
11
27
|
"repository": {
|
|
12
28
|
"type": "git",
|
|
13
|
-
"url": "https://github.com/thethomaseffect/node-media-synopsis"
|
|
29
|
+
"url": "git+https://github.com/thethomaseffect/node-media-synopsis.git"
|
|
14
30
|
},
|
|
15
31
|
"bugs": {
|
|
16
32
|
"url": "https://github.com/thethomaseffect/node-media-synopsis/issues"
|
|
17
33
|
},
|
|
18
|
-
"readmeFilename": "README.md",
|
|
19
|
-
"devDependencies": {},
|
|
20
|
-
"scripts": {
|
|
21
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
22
|
-
},
|
|
23
34
|
"keywords": [
|
|
24
35
|
"anime",
|
|
25
36
|
"movie",
|
|
@@ -33,5 +44,17 @@
|
|
|
33
44
|
"name": "Thomas Geraghty",
|
|
34
45
|
"email": "thethomaseffect@gmail.com"
|
|
35
46
|
},
|
|
36
|
-
"license": "MIT"
|
|
47
|
+
"license": "MIT",
|
|
48
|
+
"publishConfig": {
|
|
49
|
+
"access": "public"
|
|
50
|
+
},
|
|
51
|
+
"devDependencies": {
|
|
52
|
+
"@eslint/js": "^10.0.1",
|
|
53
|
+
"@types/node": "^26.6.5",
|
|
54
|
+
"eslint": "^10.12.0",
|
|
55
|
+
"jiti": "^2.7.0",
|
|
56
|
+
"typescript": "^6.0.3",
|
|
57
|
+
"typescript-eslint": "^8.71.2",
|
|
58
|
+
"vitest": "^5.0.4"
|
|
59
|
+
}
|
|
37
60
|
}
|
package/.npmignore
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Logs
|
|
2
|
-
logs
|
|
3
|
-
*.log
|
|
4
|
-
|
|
5
|
-
# Runtime data
|
|
6
|
-
pids
|
|
7
|
-
*.pid
|
|
8
|
-
*.seed
|
|
9
|
-
|
|
10
|
-
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
11
|
-
lib-cov
|
|
12
|
-
|
|
13
|
-
# Coverage directory used by tools like istanbul
|
|
14
|
-
coverage
|
|
15
|
-
|
|
16
|
-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
17
|
-
.grunt
|
|
18
|
-
|
|
19
|
-
# Compiled binary addons (http://nodejs.org/api/addons.html)
|
|
20
|
-
build/Release
|
|
21
|
-
|
|
22
|
-
# Dependency directory
|
|
23
|
-
# Deployed apps should consider commenting this line out:
|
|
24
|
-
# see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git
|
|
25
|
-
node_modules
|
package/index.js
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
var extractor = require('unfluff');
|
|
2
|
-
var request = require('request');
|
|
3
|
-
var google = require('google');
|
|
4
|
-
|
|
5
|
-
exports.synopsis = function(mediaTitle, mediaType, callback) {
|
|
6
|
-
if (mediaType === null || mediaType === undefined) {
|
|
7
|
-
mediaType = [''];
|
|
8
|
-
}
|
|
9
|
-
// Allow providing a string for mediaType but convert it to array
|
|
10
|
-
// to simplify code
|
|
11
|
-
if (typeof mediaType === 'string'){
|
|
12
|
-
mediaType = [mediaType];
|
|
13
|
-
}
|
|
14
|
-
// Only use first media type to keep things simple
|
|
15
|
-
google(mediaTitle + ' ' + mediaType[0] + ' site:wikipedia.org',
|
|
16
|
-
function(err, next, links){
|
|
17
|
-
if (err) {
|
|
18
|
-
callback(err);
|
|
19
|
-
return;
|
|
20
|
-
}
|
|
21
|
-
if (!links[0]) {
|
|
22
|
-
callback("No Wikipedia Results found for that mediaTitle and mediaType");
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
// Sometimes this will be 'List of x Episodes' for TV shows. Example: 're hamatora'
|
|
26
|
-
var title = getMediaTitle(links[0].title.trim());
|
|
27
|
-
request(links[0].link, function (error, response, body) {
|
|
28
|
-
if (!error && response.statusCode == 200) {
|
|
29
|
-
data = extractor(body);
|
|
30
|
-
getContentText(data.text, mediaType);
|
|
31
|
-
var releventParagraphs = getContentText(data.text, mediaType);
|
|
32
|
-
callback(undefined,
|
|
33
|
-
{
|
|
34
|
-
title: title,
|
|
35
|
-
content: removeUnwantedChars(releventParagraphs, title) +
|
|
36
|
-
'\n\nSource: [Wikipedia](' + links[0].link + ')'
|
|
37
|
-
});
|
|
38
|
-
} else {
|
|
39
|
-
callback(error);
|
|
40
|
-
}
|
|
41
|
-
});
|
|
42
|
-
});
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
function getMediaTitle(fullLinkText) {
|
|
47
|
-
var title = fullLinkText.split('- Wikipedia')[0];
|
|
48
|
-
var listOfEpsRegex = /.*List of (.*) episodes/.exec(title);
|
|
49
|
-
if(listOfEpsRegex) {
|
|
50
|
-
return listOfEpsRegex[1];
|
|
51
|
-
}
|
|
52
|
-
var filmRegex = /(.*) \(film\)/.exec(title);
|
|
53
|
-
if(filmRegex) {
|
|
54
|
-
return filmRegex[1];
|
|
55
|
-
}
|
|
56
|
-
return title;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
getContentText = function(text, mediaTypes) {
|
|
60
|
-
paragraphs = text.split('\n');
|
|
61
|
-
// This will build an OR type regex for each media type
|
|
62
|
-
var re = new RegExp('\\b' + mediaTypes.join('\\b|\\b') + '\\b', 'i');
|
|
63
|
-
// Media type(s) is in first paragraph, return it
|
|
64
|
-
if(re.test(paragraphs[0])) {
|
|
65
|
-
return paragraphs[0];
|
|
66
|
-
}
|
|
67
|
-
// Media type(s) in in first 4 parapraphs, return all
|
|
68
|
-
// paragraphs until it's mention
|
|
69
|
-
for(var i = 1; i < 4; i++) {
|
|
70
|
-
if(re.test(paragraphs[i])) {
|
|
71
|
-
return paragraphs.slice(0, i + 1).join('\n');
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
// Media type not found, return first paragraph
|
|
75
|
-
return paragraphs[0];
|
|
76
|
-
};
|
|
77
|
-
|
|
78
|
-
function removeUnwantedChars(text, title) {
|
|
79
|
-
// TODO: Replace [\s\S] with . once issue with unfluff is resolved
|
|
80
|
-
var AsianPronunciationsRegex = new RegExp(title + '\\([\\s\\S]*?\\) ', 'i');
|
|
81
|
-
return text
|
|
82
|
-
.replace(AsianPronunciationsRegex, title) // Removes Asian pronunciations
|
|
83
|
-
.replace(/(\[.*?\])/ig, ''); // Removes links to sources
|
|
84
|
-
}
|