Files
YouTube.js/src/utils/DashUtils.ts
Daniel Wykerd 87ed3960ff refactor!: replace unnecessary classes with pure functions (#468)
* deps: update linkedom

* refactor!: remove YTNodeGenerator in favour of namespaced pure functions

BREAKING CHANGES:
- Removes `YTNodeGenerator` from `import('youtubei.js').Generator` and exposes its functions directly in `import('youtubei.js').Generator`

* refactor!: replace Parser class with pure functions

- Remove Parser class in favour of pure functions
- Merge duplicate classes `AppendContinuationItemsAction` into a single class
- Move continuation parsers into a seperate file
- Add better custom logging support to parser methods as per issue #460

* refactor!: replace Proto class with pure functions

* chore: update package-lock.json

* refactor!: replace FormatUtils with pure functions and JSX components

- Replace linkedom DASH manifest generation with a dependency free JSX implementation
- Remove FormatUtils class in favour of pure functions
- Remove DOMParser requirement
- Remove duplicate types

* refactor: implement changes from #462

* chore: lint

* fix: deno support

* fix: render valid xml document

* fix: wrong function call in DashUtils

* fix: typo in parser

Co-authored-by: LuanRT <luan.lrt4@gmail.com>

* refactor!: move streaming info logic into seperate function

This allows users to access the same data available in the dash manifest while also simplifying the manifest generation

* chore: lint

* refactor: readability improvements & fixes

Remove redundant getAudioTrackGroups
General readability improvements in StreamingInfo.ts
Share response object between `getBitrate` and `getMimeType` as to not make duplicate requests

* build: remove unnecessary step in deno build

Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>

* refactor: move types to `types` directory

* docs: add back comments lost during refactor

* chore: lint

---------

Co-authored-by: LuanRT <luan.lrt4@gmail.com>
Co-authored-by: absidue <48293849+absidue@users.noreply.github.com>
2023-08-18 06:49:58 -03:00

105 lines
2.9 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-namespace */
declare global {
namespace JSX {
interface IntrinsicElements {
[key: string]: DashProps;
}
}
}
export type DashChild = (DashNode | (DashNode | Promise<DashNode | DashNode[]>) | Promise<DashNode | DashNode[]>);
export interface DashProps {
[key: string]: unknown,
children?: DashChild[]
}
export interface DashNode {
type: string,
props: DashProps,
}
const XML_CHARACTER_MAP = {
'&': '&amp;',
'"': '&quot;',
'\'': '&apos;',
'<': '&lt;',
'>': '&gt;'
} as const;
function escapeXMLString(str: string) {
return str.replace(/([&"<>'])/g, (_, item: keyof typeof XML_CHARACTER_MAP) => {
return XML_CHARACTER_MAP[item];
});
}
function normalizeTag(tag: string) {
if (tag === 'mpd') return 'MPD';
if (tag === 'base-url') return 'BaseURL';
const sections = tag.split('-');
return sections.map((section) => section.charAt(0).toUpperCase() + section.slice(1)).join('');
}
export function createElement(
tagNameOrFunction: string | ((props: DashProps) => DashNode | Promise<DashNode>),
props: { [key: string] : unknown } | null | undefined,
...children: DashChild[]
): DashNode | Promise<DashNode> {
const normalizedChildren = children.flat().map((child) => typeof child === 'string' ? createTextElement(child) : child);
if (typeof tagNameOrFunction === 'function') {
return tagNameOrFunction({ ...props, children: normalizedChildren });
}
return {
type: normalizeTag(tagNameOrFunction),
props: {
...props,
children: normalizedChildren
}
};
}
export function createTextElement(text: string): DashNode {
return {
type: 'TEXT_ELEMENT',
props: { nodeValue: text }
};
}
export async function renderElementToString(element: DashNode): Promise<string> {
if (element.type === 'TEXT_ELEMENT')
return escapeXMLString(typeof element.props.nodeValue === 'string' ? element.props.nodeValue : '');
let dom = `<${element.type}`;
if (element.props) {
const properties = Object.keys(element.props)
.filter((key) => ![ 'children', 'nodeValue' ].includes(key) && element.props[key] !== undefined)
.map((name) => `${name}="${escapeXMLString(`${element.props[name]}`)}"`);
if (properties.length > 0)
dom += ` ${properties.join(' ')}`;
}
if (element.props.children) {
const children = await Promise.all((await Promise.all(element.props.children.flat())).flat().filter((child) => !!child).map((child) => renderElementToString(child)));
if (children.length > 0) {
dom += `>${children.join('')}</${element.type}>`;
return dom;
}
}
return `${dom}/>`;
}
export async function renderToString(root: DashNode | Promise<DashNode>) {
const dom = await renderElementToString(await root);
return `<?xml version="1.0" encoding="utf-8"?>${dom}`;
}
export function Fragment(props: DashProps) {
return props.children;
}