Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion plugin/_locales/en/messages.json
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@
"message": "Publisher",
"description": "Label for Epub publisher"
},
"__MSG_label_Metadata_date_published__": {
"message": "Published",
"description": "Label for the original publication date"
},
"__MSG_label_Custom_Filename__": {
"message": "Custom Filename",
"description": "Custom Filename input"
Expand Down Expand Up @@ -822,4 +826,4 @@
}
}
}
}
}
2 changes: 1 addition & 1 deletion plugin/js/EpubMetaInfo.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ class EpubMetaInfo {
this.subject = "";
this.description = "";
this.publisher = "";
this.datePublished = null;
this.seriesName = null;
this.seriesIndex = null;
this.styleSheet = EpubMetaInfo.getDefaultStyleSheet();
Expand Down Expand Up @@ -253,4 +254,3 @@ class EpubAddMetaInfo {
this.author = "";
}
}

3 changes: 2 additions & 1 deletion plugin/js/EpubPacker.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,8 @@ class EpubPacker {
opf.documentElement.appendChild(metadata);
this.createAndAppendChildNS(metadata, dc_ns, "dc:title", this.metaInfo.title);
this.createAndAppendChildNS(metadata, dc_ns, "dc:language", this.metaInfo.language);
this.createAndAppendChildNS(metadata, dc_ns, "dc:date", this.getDateForMetaData());
let datePublished = this.metaInfo.datePublished || this.getDateForMetaData();
this.createAndAppendChildNS(metadata, dc_ns, "dc:date", datePublished);
if (!util.isNullOrEmpty(this.metaInfo.subject)) {
this.createAndAppendChildNS(metadata, dc_ns, "dc:subject", this.metaInfo.subject);
}
Expand Down
13 changes: 13 additions & 0 deletions plugin/js/Parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,13 @@ class Parser {
return publisher?.content ?? "";
}

extractDatePublished(dom) {
let published = dom.querySelector(
"meta[property='article:published_time'], time[itemprop='datePublished']"
);
return published?.content ?? published?.dateTime ?? null;
}

/**
* default implementation, Derived classes will override
*/
Expand Down Expand Up @@ -392,6 +399,12 @@ class Parser {
catch (err) {
metaInfo.publisher = "";
}
try {
metaInfo.datePublished = this.extractDatePublished(dom);
}
catch (err) {
metaInfo.datePublished = null;
}
this.extractSeriesInfo(dom, metaInfo);
return metaInfo;
}
Expand Down
16 changes: 10 additions & 6 deletions plugin/js/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ var main = (function() {

// details
let initialWebPage = null;
let initialMetaInfo = null;
let parser = null;
let userPreferences = null;
let library = new Library;
Expand Down Expand Up @@ -47,6 +48,7 @@ var main = (function() {
try {
await parser.loadEpubMetaInfo(dom);
let metaInfo = parser.getEpubMetaInfo(dom, userPreferences.useFullTitle.value);
initialMetaInfo = metaInfo;
populateMetaInfo(metaInfo);
setUiToDefaultState();
parser.populateUI(dom);
Expand Down Expand Up @@ -80,6 +82,7 @@ var main = (function() {
setUiFieldToValue("subjectInput", metaInfo.subject);
setUiFieldToValue("descriptionInput", metaInfo.description);
setUiFieldToValue("publisherInput", metaInfo.publisher);
setUiFieldToValue("datePublishedInput", metaInfo.datePublished);
if (metaInfo.seriesName !== null) {
document.getElementById("seriesRow").hidden = false;
document.getElementById("volumeRow").hidden = false;
Expand Down Expand Up @@ -110,6 +113,7 @@ var main = (function() {
metaInfo.subject = getValueFromUiField("subjectInput");
metaInfo.description = getValueFromUiField("descriptionInput");
metaInfo.publisher = getValueFromUiField("publisherInput");
metaInfo.datePublished = getValueFromUiField("datePublishedInput");

if (document.getElementById("seriesRow").hidden === false) {
metaInfo.seriesName = getValueFromUiField("seriesNameInput");
Expand All @@ -134,12 +138,13 @@ var main = (function() {

async function fetchContentAndPackEpub() {
let libclick = this;
if (document.getElementById("noAdditionalMetadataCheckbox").checked == true) {
setUiFieldToValue("subjectInput", "");
setUiFieldToValue("descriptionInput", "");
setUiFieldToValue("publisherInput", "");
}
let metaInfo = metaInfoFromControls();
if (document.getElementById("noAdditionalMetadataCheckbox").checked == true
&& initialMetaInfo != null) {
metaInfo.subject = initialMetaInfo.subject;
metaInfo.description = initialMetaInfo.description;
metaInfo.publisher = initialMetaInfo.publisher;
}

if ("yes" == libclick.dataset.libclick) {
if (document.getElementById("chaptersPageInChapterListCheckbox").checked) {
Expand Down Expand Up @@ -661,4 +666,3 @@ var main = (function() {
getUserPreferences: () => userPreferences,
};
})();

79 changes: 75 additions & 4 deletions plugin/js/parsers/LiteroticaParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,79 @@ class LiteroticaParser extends Parser {
dom = (await HttpClient.wrapFetch(randomChapter[0].href)).responseXML;
}
}
this.title = dom.querySelector("div[data-tab=\"tabpanel-series\"] a")?.textContent??dom.querySelector("h1");
this.author = [...dom.querySelectorAll("a")].filter(a => a.href.includes("https://www.literotica.com/authors/"))?.[0].title??"";
this.description = [...dom.querySelectorAll("div[data-tab=\"tabpanel-info\"] div")]?.[0]?.textContent??"";
this.tags = [...dom.querySelectorAll("div[data-tab=\"tabpanel-tags\"] a")]?.map(a => a.textContent)??[];
let article = LiteroticaParser.articleMetadata(dom);
this.title = dom.querySelector("div[data-tab=\"tabpanel-series\"] a")?.textContent
?? article?.headline
?? dom.querySelector("h1");
this.author = LiteroticaParser.authorName(article?.author)
|| [...dom.querySelectorAll("a[href*='/authors/']")]
.map(a => a.textContent.trim() || a.title?.trim())
.find(name => name)
|| "";
this.description = article?.description
?? dom.querySelector("div[data-tab=\"tabpanel-info\"] div")?.textContent
?? "";
this.tags = LiteroticaParser.tagNames(article?.keywords);
if (this.tags.length === 0) {
this.tags = LiteroticaParser.tagNames(
dom.querySelector("meta[name='keywords']")?.content
);
}
if (this.tags.length === 0) {
this.tags = [...dom.querySelectorAll("div[data-tab=\"tabpanel-tags\"] a")]
.map(a => a.textContent.trim())
.filter(tag => tag !== "");
}
this.datePublished = article?.datePublished
?? dom.querySelector("meta[property='article:published_time']")?.content
?? null;
return;
}

static articleMetadata(dom) {
for (let element of dom.querySelectorAll("script[type='application/ld+json']")) {
try {
let data = JSON.parse(element.textContent);
let entries = Array.isArray(data) ? data : (data["@graph"] ?? [data]);
let article = entries.find(entry => {
let types = Array.isArray(entry?.["@type"])
? entry["@type"] : [entry?.["@type"]];
return types.includes("Article");
});
if (article != null) {
return article;
}
} catch (error) {
// Ignore unrelated or malformed structured data and use the DOM fallbacks.
}
}
return null;
}

static authorName(author) {
if (Array.isArray(author)) {
return author.map(a => LiteroticaParser.authorName(a)).filter(a => a).join(", ");
}
return (typeof author === "string") ? author.trim() : author?.name?.trim();
}

static tagNames(keywords) {
if (Array.isArray(keywords)) {
return keywords
.map(tag => LiteroticaParser.tagName(tag))
.filter(tag => tag !== "");
}
return (typeof keywords === "string")
? keywords.split(",").map(tag => tag.trim()).filter(tag => tag !== "")
: [];
}

static tagName(tag) {
if (typeof tag === "string") {
return tag.trim();
}
return `${tag?.name ?? tag?.tag ?? tag?.keyword ?? ""}`.trim();
}

extractTitleImpl() {
return this.title;
Expand All @@ -74,6 +141,10 @@ class LiteroticaParser extends Parser {
return this.description.trim();
}

extractDatePublished() {
return this.datePublished;
}

findCoverImageUrl() {
return "";
}
Expand Down
4 changes: 4 additions & 0 deletions plugin/popup.html
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,10 @@ <h3>Instructions</h3>
<td>__MSG_label_Metadata_publisher__</td>
<td><input id="publisherInput" type="text" name="publisherInput" /></td>
</tr>
<tr>
<td>__MSG_label_Metadata_date_published__</td>
<td><input id="datePublishedInput" type="text" name="datePublishedInput" /></td>
</tr>
<tr id="seriesRow">
<td>__MSG_label_Series__</td>
<td><input id="seriesNameInput" type="text" name="titleInput" /></td>
Expand Down
9 changes: 9 additions & 0 deletions unitTest/UtestEpubPacker.js
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,15 @@ test("buildEpub3ContentOpf", function (assert) {
);
});

test("buildContentOpf uses publication date", function (assert) {
let epubPacker = makePacker();
epubPacker.metaInfo.datePublished = "2019-12-23T00:00:00.000Z";
epubPacker.getDateForMetaData = function () { return "2026-07-31T12:34:56.789Z"; };
let contentOpf = epubPacker.buildContentOpf(makeEpubItemSupplier());

assert.ok(contentOpf.includes("<dc:date>2019-12-23T00:00:00.000Z</dc:date>"));
});

test("buildContentOpfWithCover", function (assert) {
let image = new ImageInfo("http://bp.org/thepic.jpeg", 0, "http://bp.org/thepic.jpeg");
image.isCover = true;
Expand Down
70 changes: 70 additions & 0 deletions unitTest/UtestLiteroticaParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,53 @@ QUnit.test("chaptersFromMemberPage", function (assert) {
assert.strictEqual(chapters[1].newArc, null);
});

QUnit.test("getEpubMetaInfo-fromArticleJsonLd", async function (assert) {
let dom = new DOMParser().parseFromString(
LiteroticaStoryMetadataSample, "text/html");
let parser = new LiteroticaParser();

await parser.loadEpubMetaInfo(dom);
let metaInfo = parser.getEpubMetaInfo(dom);

assert.equal(metaInfo.title, "Case 3319");
assert.equal(metaInfo.author, "Mesmerciless");
assert.equal(metaInfo.datePublished, "2019-12-23T00:00:00.000Z");
assert.equal(metaInfo.subject, "mind control, transformation");
assert.equal(metaInfo.description, "From streaming star to brainless bimbo.");
});

QUnit.test("tagNames-supportsAlternateMetadataShapes", function (assert) {
assert.deepEqual(
LiteroticaParser.tagNames([
{name: "mind control"},
{tag: "transformation"},
{keyword: "gamer"}
]),
["mind control", "transformation", "gamer"]
);
assert.deepEqual(
LiteroticaParser.tagNames("mind control, transformation"),
["mind control", "transformation"]
);
});

QUnit.test("loadEpubMetaInfo-tagsFromMetaFallback", async function (assert) {
let dom = new DOMParser().parseFromString(`
<html><head>
<base href="https://www.literotica.com/s/example-story">
<meta name="keywords" content="mind control, transformation">
<script type="application/ld+json">
{"@type":"Article", "headline":"Example", "author":{"name":"Writer"}}
</script>
</head><body><h1>Example</h1></body></html>
`, "text/html");
let parser = new LiteroticaParser();

await parser.loadEpubMetaInfo(dom);

assert.deepEqual(parser.tags, ["mind control", "transformation"]);
});

let LiteroticaToCSamplePage1 =
/*html*/
`<!DOCTYPE html>
Expand Down Expand Up @@ -58,3 +105,26 @@ let LiteroticaToCSamplePage2 =
</body>
</html>
`

let LiteroticaStoryMetadataSample =
/*html*/
`<!DOCTYPE html>
<html lang="en">
<head>
<base href="https://www.literotica.com/s/case-3319" />
<script type="application/ld+json">not valid JSON</script>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@type": "Article",
"headline": "Case 3319",
"description": "From streaming star to brainless bimbo.",
"author": {"@type": "Person", "name": "Mesmerciless"},
"datePublished": "2019-12-23T00:00:00.000Z",
"keywords": ["mind control", "transformation"]
}
</script>
</head>
<body><h1>Fallback title</h1></body>
</html>
`
Loading