Skip to content

Commit

Permalink
Merge pull request #2066 from DIYgod/master
Browse files Browse the repository at this point in the history
[pull] master from diygod:master
  • Loading branch information
pull[bot] authored Feb 2, 2025
2 parents 264ccf0 + 677df27 commit a94fd45
Show file tree
Hide file tree
Showing 6 changed files with 270 additions and 85 deletions.
9 changes: 7 additions & 2 deletions lib/routes/ehentai/favorites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,13 @@ import ConfigNotFoundError from '@/errors/types/config-not-found';
export const route: Route = {
path: '/favorites/:favcat?/:order?/:page?/:routeParams?',
categories: ['picture'],
example: '/ehentai/favorites/0/posted/1',
parameters: { favcat: 'Favorites folder number', order: '`posted`(Sort by gallery release time) , `favorited`(Sort by time added to favorites)', page: 'Page number', routeParams: 'Additional parameters, see the table above' },
example: '/ehentai/favorites/0/posted/0/bittorrent=true&embed_thumb=false',
parameters: {
favcat: 'Favorites folder number',
order: '`posted`(Sort by gallery release time) , `favorited`(Sort by time added to favorites)',
page: 'Page number, set 0 to get latest',
routeParams: 'Additional parameters, see the table above',
},
features: {
requireConfig: false,
requirePuppeteer: false,
Expand Down
4 changes: 2 additions & 2 deletions lib/routes/ehentai/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import EhAPI from './ehapi';
export const route: Route = {
path: '/search/:params?/:page?/:routeParams?',
categories: ['picture'],
example: '/ehentai/search/f_search=artist%3Amana%24/1',
parameters: { params: 'Search parameters. You can copy the content after `https://e-hentai.org/?`', page: 'Page number', routeParams: 'Additional parameters, see the table above' },
example: '/ehentai/search/f_cats=1021/0/bittorrent=true&embed_thumb=false',
parameters: { params: 'Search parameters. You can copy the content after `https://e-hentai.org/?`', page: 'Page number, set 0 to get latest', routeParams: 'Additional parameters, see the table above' },
features: {
requireConfig: false,
requirePuppeteer: false,
Expand Down
4 changes: 2 additions & 2 deletions lib/routes/ehentai/tag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import EhAPI from './ehapi';
export const route: Route = {
path: '/tag/:tag/:page?/:routeParams?',
categories: ['picture'],
example: '/ehentai/tag/language:chinese/1',
parameters: { tag: 'Tag', page: 'Page number', routeParams: 'Additional parameters, see the table above' },
example: '/ehentai/tag/language:chinese/0/bittorrent=true&embed_thumb=false',
parameters: { tag: 'Tag', page: 'Page number, set 0 to get latest', routeParams: 'Additional parameters, see the table above' },
features: {
requireConfig: false,
requirePuppeteer: false,
Expand Down
201 changes: 122 additions & 79 deletions lib/routes/gcores/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ import { getCurrentPath } from '@/utils/helpers';

import path from 'node:path';

export const __dirname = getCurrentPath(import.meta.url);
const __dirname = getCurrentPath(import.meta.url);

interface Style {
[key: string]: string;
}

interface BlockType {
element: string;
element: string | undefined;
parentElement?: string;
aliasedElements?: string[];
}
Expand Down Expand Up @@ -68,59 +68,31 @@ const BLOCK_TYPES: Readonly<Record<string, BlockType>> = {
'unordered-list-item': { element: 'li', parentElement: 'ul' },
'ordered-list-item': { element: 'li', parentElement: 'ol' },
blockquote: { element: 'blockquote' },
atomic: { element: 'figure' },
atomic: { element: undefined },
'code-block': { element: 'pre' },
unstyled: { element: 'div', aliasedElements: ['p'] },
unstyled: { element: 'p' },
};

/**
* Creates a styled HTML fragment for a given text and style object.
* @param text The text content of the fragment.
* @param style An object containing CSS styles (key-value pairs).
* @returns An HTML string representing the styled fragment.
* @param text - The text content.
* @param style - CSS styles as key-value pairs.
* @returns HTML string with applied styles.
*/
const createStyledFragment = (text: string, style: Record<string, string>): string =>
`<span style="${Object.entries(style)
.map(([key, value]) => `${key}: ${value};`)
.join('')}">${text}</span>`;

/**
* Applies inline styles to a text string.
* @param text The text to style.
* @param inlineStyleRanges An array of inline style ranges.
* @returns The styled text.
*/
const applyStyles = (text: string, inlineStyleRanges: readonly InlineStyleRange[]): string => {
if (!inlineStyleRanges || inlineStyleRanges.length === 0) {
return text;
}

const sortedRanges = [...inlineStyleRanges].sort((a, b) => a.offset - b.offset);

let lastOffset = 0;
const styledFragments = sortedRanges.map((range) => {
const style = STYLES[range.style];
if (!style) {
return text.substring(lastOffset, range.offset);
}

const styledText = createStyledFragment(text.substring(range.offset, range.offset + range.length), style);
const preText = text.substring(lastOffset, range.offset);
lastOffset = range.offset + range.length;
return preText + styledText;
});
let result = styledFragments.join('');
result += text.substring(lastOffset);
return result;
const createStyledFragment = (text: string, style: Readonly<Style>): string => {
const styleString = Object.entries(style)
.map(([key, value]) => `${key.replaceAll(/([A-Z])/g, '-$1').toLowerCase()}: ${value}`)
.join('; ');
return `<span style="${styleString}">${text}</span>`;
};

/**
* Creates an HTML element for a given entity.
* @param entity The entity to create an element for.
* @param block The current block the entity belongs to, for debugging purposes.
* @returns The HTML element string.
* @param entity - The entity to create an element for.
* @param text - The text content of the entity.
* @returns HTML element string.
*/
const createEntityElement = (entity: Entity, block: Block): string => {
const createEntityElement = (entity: Entity, text: string): string => {
switch (entity.type) {
case 'EMBED':
return entity.data.content.startsWith('http') ? `<a href="${entity.data.content}" target="_blank">${entity.data.content}</a>` : entity.data.content;
Expand All @@ -136,21 +108,21 @@ const createEntityElement = (entity: Entity, block: Block): string => {
},
]
: undefined,
});
}).replaceAll('\n', '');
case 'GALLERY':
if (!entity.data.images || !Array.isArray(entity.data.images)) {
return '';
}
return art(path.join(__dirname, 'templates/description.art'), {
images: entity.data.images.map((image: any) => ({
src: new URL(image.path, imageBaseUrl).href,
alt: image.caption,
alt: image.caption ?? entity.data.caption,
width: image.width,
height: image.height,
})),
});
}).replaceAll('\n', '');
case 'LINK':
return `<a href="${entity.data.href}" target="_blank">${block.text}</a>`;
return `<a href="${entity.data.url}" target="_blank">${text}</a>`;
case 'WIDGET':
return `<a href="${entity.data.url}" target="_blank">${entity.data.title}</a>`;
default:
Expand All @@ -159,37 +131,109 @@ const createEntityElement = (entity: Entity, block: Block): string => {
};

/**
* Parses a single content block into an HTML string.
* @param block The block to parse.
* @param entityMap The entity map.
* @returns The parsed HTML string.
* Parses a block into an HTML string with applied styles and entities.
* @param block - The block to parse.
* @param entityMap - The entity map.
* @returns HTML string representing the block.
*/
const parseBlock = (block: Block, entityMap: { [key: string]: Entity }): string => {
const parseBlock = (block: Block, entityMap: Readonly<Record<string, Entity>>): string => {
const blockType = BLOCK_TYPES[block.type];
if (!blockType) {
return '';
}

const usedElement = blockType.aliasedElements?.[0] ?? blockType.element;
const { text, inlineStyleRanges, entityRanges } = block;

// Combine and sort ranges
const combinedRanges: Array<{
offset: number;
length: number;
styles: Style[];
entity: Entity | null;
}> = [];

for (const range of inlineStyleRanges) {
combinedRanges.push({
...range,
styles: [STYLES[range.style]],
entity: null,
});
}

for (const range of entityRanges) {
combinedRanges.push({
...range,
styles: [],
entity: entityMap[range.key],
});
}

combinedRanges.sort((a, b) => a.offset - b.offset);

// Group ranges by offset and length
const groupedRangesMap = new Map<string, { offset: number; length: number; styles: Style[]; entities: Entity[] }>();
const groupedRanges: { offset: number; length: number; styles: Style[]; entities: Entity[] }[] = [];

for (const range of combinedRanges) {
const rangeKey = `${range.offset}-${range.length}`;
let existingRange = groupedRangesMap.get(rangeKey);
if (!existingRange) {
existingRange = {
offset: range.offset,
length: range.length,
styles: [],
entities: [],
};
groupedRangesMap.set(rangeKey, existingRange);
groupedRanges.push(existingRange);
}

if (range.styles.length > 0) {
existingRange.styles.push(...range.styles);
}
if (range.entity) {
existingRange.entities.push(range.entity);
}
}

// Apply styles and entities
const resultParts: string[] = [];
let lastOffset = 0;

let content = applyStyles(block.text, block.inlineStyleRanges);
for (const range of groupedRanges) {
resultParts.push(text.substring(lastOffset, range.offset));

if (block.entityRanges && block.entityRanges.length > 0) {
const entityElements = block.entityRanges
.map((range) => entityMap[range.key])
.filter(Boolean)
.map((entity) => createEntityElement(entity!, block));
let styledText = text.substring(range.offset, range.offset + range.length);

content = entityElements.join('');
if (range.styles.length > 0) {
const combinedStyle: Style = {};
for (const style of range.styles) {
for (const [key, value] of Object.entries(style)) {
combinedStyle[key] = value;
}
}
styledText = createStyledFragment(styledText, combinedStyle);
}

if (range.entities.length > 0) {
for (const entity of range.entities) {
styledText = createEntityElement(entity, styledText);
}
}

resultParts.push(styledText);
lastOffset = range.offset + range.length;
}

return `<${usedElement}>${content}</${usedElement}>`;
resultParts.push(text.substring(lastOffset));

return `${blockType.element ? `<${blockType.element}>` : ''}${resultParts.join('').replaceAll('\n', '<br>')}${blockType.element ? `</${blockType.element}>` : ''}`;
};

/**
* Parses a Content object into an HTML string using a for loop.
* @param content The Content object to parse.
* @returns The parsed HTML string.
* Parses a Content object into an HTML string.
* @param content - The Content object to parse.
* @returns HTML string representing the content.
*/
const parseContent = (content: Content): string => {
const { blocks, entityMap } = content;
Expand All @@ -198,44 +242,43 @@ const parseContent = (content: Content): string => {
return '';
}

let html = '';
const htmlParts: string[] = [];
let currentParent: string | undefined = undefined;
let parentContent = '';
let parentContent: string[] = [];

for (const block of blocks) {
const blockType = BLOCK_TYPES[block.type];
if (!blockType) {
continue;
}

const parentElement = blockType.parentElement;
const parsedBlock = parseBlock(block, entityMap);

if (parentElement) {
if (currentParent === parentElement) {
parentContent += parsedBlock;
if (blockType.parentElement) {
if (currentParent === blockType.parentElement) {
parentContent.push(parsedBlock);
} else {
if (currentParent) {
html += `<${currentParent}>${parentContent}</${currentParent}>`;
htmlParts.push(`<${currentParent}>${parentContent.join('')}</${currentParent}>`);
}
currentParent = parentElement;
parentContent = parsedBlock;
currentParent = blockType.parentElement;
parentContent = [parsedBlock];
}
} else {
if (currentParent) {
html += `<${currentParent}>${parentContent}</${currentParent}>`;
htmlParts.push(`<${currentParent}>${parentContent.join('')}</${currentParent}>`);
currentParent = undefined;
parentContent = '';
parentContent = [];
}
html += parsedBlock;
htmlParts.push(parsedBlock);
}
}

if (currentParent) {
html += `<${currentParent}>${parentContent}</${currentParent}>`;
htmlParts.push(`<${currentParent}>${parentContent.join('')}</${currentParent}>`);
}

return html;
return htmlParts.join('');
};

export { parseContent };
Loading

0 comments on commit a94fd45

Please sign in to comment.