Skip to content
Merged
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
82 changes: 76 additions & 6 deletions plugins/english/wtrlab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ class WTRLAB implements Plugin.PluginBase {
id = 'WTRLAB';
name = 'WTR-LAB';
site = 'https://wtr-lab.com/';
version = '1.2.2';
version = '1.2.3';
icon = 'src/en/wtrlab/icon.png';
sourceLang = 'en/';
baggage = '';
Expand Down Expand Up @@ -62,6 +62,52 @@ class WTRLAB implements Plugin.PluginBase {
return (storage.get<string>('sessionCookie') || '').trim();
}

/**
* Resolve the chapter payload.
*
* The reader API used to carry the body inline at data.data. It now returns
* an envelope with a `content_url` pointing at the real payload, whose inner
* shape (body / glossary_data / model) is unchanged. Handle both so the
* plugin works whichever the server returns.
*/
async resolveChapterContent(
parsed: ReaderResponse,
cookie: string,
): Promise<ChapterContent | null> {
const inline = parsed?.data?.data;
if (inline && inline.body !== undefined) return inline;

const contentUrl = parsed?.content_url;
if (!contentUrl) return null;

const url = /^https?:\/\//i.test(contentUrl)
? contentUrl
: this.site.replace(/\/$/, '') + contentUrl;

// Only ever send the session cookie to the source's own hosts. The server
// controls content_url, and an absolute one could point anywhere.
const hostOf = (value: string) =>
(value.match(/^https?:\/\/([^/?#]+)/i)?.[1] || '').toLowerCase();
const siteHost = hostOf(this.site);
const targetHost = hostOf(url);
const sameSite =
!!siteHost &&
(targetHost === siteHost || targetHost.endsWith('.' + siteHost));

const res = await fetchApi(url, {
headers: {
'Accept': 'application/json',
...(cookie && sameSite ? { Cookie: cookie } : {}),
},
});
const text = await res.text();
try {
return JSON.parse(text)?.data?.data ?? null;
} catch (e) {
return null;
}
}

/** Only attempt the sign-in link once per app run: the links are single-use. */
signInAttempted = false;

Expand Down Expand Up @@ -682,6 +728,7 @@ class WTRLAB implements Plugin.PluginBase {

const attemptLog: string[] = [];
let parsedJson;
let content: ChapterContent | null = null;
let usedType: string | null = null;

// Establish a session from the emailed link before asking for a chapter.
Expand Down Expand Up @@ -748,6 +795,18 @@ class WTRLAB implements Plugin.PluginBase {
continue;
}

// Resolve the body here, not after the loop: a healthy envelope does
// not guarantee a payload, and if this mode has none the next one
// (typically Web) should still get its turn.
const resolved = await this.resolveChapterContent(candidate, cookie);
if (!resolved || resolved.body === undefined) {
attemptLog.push(
`"${type}": request succeeded but no chapter content was returned`,
);
continue;
}

content = resolved;
usedType = type;
break;
}
Expand All @@ -759,19 +818,23 @@ class WTRLAB implements Plugin.PluginBase {
? `session cookie sent (${cookie.length} chars)`
: 'no session cookie set';

if (!usedType || !parsedJson?.data?.data) {
if (!usedType || !content || content.body === undefined) {
const errorMsg =
`None of the requested translations could be loaded [${cookieState}]. ` +
(attemptLog.length
? attemptLog.join(' | ')
: 'The server returned no usable response.');
: usedType
? 'The server accepted the request but returned no chapter content.'
: 'The server returned no usable response.');
console.error(errorMsg);
throw new Error(errorMsg);
}
let chapterContent: any = parsedJson.data.data.body;
const chapterTitle: string | undefined = parsedJson?.chapter?.title;

let chapterContent: any = content.body;
const chapterTitle: string | undefined =
parsedJson?.chapter?.title || content.title;
const chapterGlossary: ChapterContent['glossary_data'] | undefined =
parsedJson?.data?.data?.glossary_data;
content.glossary_data;

let htmlString = '';
if (chapterTitle) {
Expand Down Expand Up @@ -1973,6 +2036,13 @@ type ApiChapter = {
type ChapterData = {
data: ChapterContent;
};
type ReaderResponse = {
success?: boolean;
content_url?: string;
chapter?: { title?: string; locked?: boolean };
data?: { data?: ChapterContent };
};

type ChapterContent = {
title: string;
body: string;
Expand Down
Loading