UNPKG

@dan-uni/dan-any

Version:

A danmaku transformer lib, supporting danmaku from different platforms.

1,293 lines 92.3 kB
import { __webpack_require__ } from "./rslib-runtime.js"; import "reflect-metadata/lite"; import { IsDate, IsEmail, IsEnum, IsInt, IsNotEmpty, IsNumber, IsOptional, IsString, Max, Min, isEmail, isJSON, isObject, isString, validateOrReject } from "class-validator"; import { XMLBuilder, XMLParser } from "fast-xml-parser"; import json_bigint from "json-bigint"; import { create, fromBinary, toBinary } from "@bufbuild/protobuf"; import { file_google_protobuf_timestamp, timestampDate, timestampFromDate, timestampNow } from "@bufbuild/protobuf/wkt"; import { Expose, plainToInstance } from "class-transformer"; import hh_mm_ss from "hh-mm-ss"; import jssha from "jssha"; import { brotliCompressSync, brotliDecompressSync, gunzipSync, gzipSync } from "node:zlib"; import { decode, encode as external_base16384_encode } from "base16384"; import { fileDesc, messageDesc as codegenv2_messageDesc } from "@bufbuild/protobuf/codegenv2"; var dm_gen_namespaceObject = {}; __webpack_require__.r(dm_gen_namespaceObject); __webpack_require__.d(dm_gen_namespaceObject, { DMAttr: ()=>dm_gen_DMAttr, ExtraDanUniChapterAction: ()=>dm_gen_ExtraDanUniChapterAction, ExtraDanUniChapterType: ()=>dm_gen_ExtraDanUniChapterType, Modes: ()=>dm_gen_Modes, Pools: ()=>dm_gen_Pools, UniDM: ()=>UniDM }); var id_gen_namespaceObject = {}; __webpack_require__.r(id_gen_namespaceObject); __webpack_require__.d(id_gen_namespaceObject, { UniID: ()=>id_gen_UniID, createDMID: ()=>createDMID }); var platform_namespaceObject = {}; __webpack_require__.r(platform_namespaceObject); __webpack_require__.d(platform_namespaceObject, { PlatformDanmakuOnlySource: ()=>platform_PlatformDanmakuOnlySource, PlatformDanmakuOnlySources: ()=>PlatformDanmakuOnlySources, PlatformDanmakuSources: ()=>PlatformDanmakuSources, PlatformInfoSource: ()=>platform_PlatformInfoSource, PlatformInfoSources: ()=>PlatformInfoSources, PlatformSources: ()=>PlatformSources, PlatformVideoSource: ()=>platform_PlatformVideoSource, PlatformVideoSources: ()=>PlatformVideoSources }); var package_namespaceObject = JSON.parse('{"UU":"@dan-uni/dan-any","rE":"1.0.1","TB":"https://github.com/ani-uni/danuni/tree/master/packages/dan-any#readme"}'); const color_pad = (s)=>s.length < 2 ? `0${s}` : s; const decimalToHex = (n)=>color_pad(n.toString(16)); const isDarkColor = ({ r, g, b })=>0.299 * r + 0.587 * g + 0.114 * b < 0x30; const WHITE = { r: 255, g: 255, b: 255 }; const BLACK = { r: 0, g: 0, b: 0 }; const hexColorToRGB = (hex)=>{ if (0 === hex.indexOf('#')) hex = hex.slice(1); const [r, g, b] = 3 === hex.length ? hex.split('').map((c)=>c + c) : hex.match(/../g) || []; return { r: Number.parseInt(r, 16), g: Number.parseInt(g, 16), b: Number.parseInt(b, 16) }; }; const formatColor = ({ r, g, b }, opacity)=>{ const color = [ b, g, r ]; if (void 0 !== opacity) { const alpha = Math.round((1 - opacity) * 255); color.unshift(alpha); } return `&H${color.map(decimalToHex).join('').toUpperCase()}`; }; const getDecoratingColor = (color)=>isDarkColor(color) ? WHITE : BLACK; const isWhite = (color)=>255 === color.r && 255 === color.g && 255 === color.b; var platform_PlatformInfoSource = /*#__PURE__*/ function(PlatformInfoSource) { PlatformInfoSource["Bangumi"] = "bgm"; PlatformInfoSource["TMDB"] = "tmdb"; return PlatformInfoSource; }({}); const PlatformInfoSources = Object.values(platform_PlatformInfoSource); var platform_PlatformVideoSource = /*#__PURE__*/ function(PlatformVideoSource) { PlatformVideoSource["Acfun"] = "acfun"; PlatformVideoSource["Baha"] = "baha"; PlatformVideoSource["Bilibili"] = "bili"; PlatformVideoSource["BilibiliGlobal"] = "bglobal"; PlatformVideoSource["Iqiyi"] = "iqiyi"; PlatformVideoSource["Tencent"] = "tencent"; PlatformVideoSource["Youku"] = "youku"; return PlatformVideoSource; }({}); const PlatformVideoSources = Object.values(platform_PlatformVideoSource); var platform_PlatformDanmakuOnlySource = /*#__PURE__*/ function(PlatformDanmakuOnlySource) { PlatformDanmakuOnlySource["DanDanPlay"] = "ddplay"; PlatformDanmakuOnlySource["TuCao"] = "tucao"; return PlatformDanmakuOnlySource; }({}); const PlatformDanmakuOnlySources = Object.values(platform_PlatformDanmakuOnlySource); const PlatformDanmakuSources = [ ...PlatformVideoSources, ...PlatformDanmakuOnlySources ]; const PlatformSources = [ ...PlatformInfoSources, ...PlatformDanmakuSources ]; class id_gen_UniID { constructor(id, domain){ this.id = id; this.domain = domain; } static fromString(str) { const [id, domain] = str.split('@'); return new id_gen_UniID(id, domain); } toString() { return `${this.id}@${this.domain}`; } static fromNull(domain) { return new id_gen_UniID('runtime' === domain ? 'runtime' : 'anonymous', domain || 'danuni'); } static fromBili({ cid, mid, midHash }) { if (cid) return new id_gen_UniID(cid.toString(), platform_PlatformVideoSource.Bilibili); if (mid) return new id_gen_UniID(mid.toString(), platform_PlatformVideoSource.Bilibili); if (midHash) return new id_gen_UniID(midHash, platform_PlatformVideoSource.Bilibili); return this.fromNull(platform_PlatformVideoSource.Bilibili); } static fromUnknown(id, domain) { if (id) return new id_gen_UniID(id, domain); return this.fromNull(domain); } } function createDMID(dan, slice = 8) { return new jssha('SHA3-256', 'TEXT').update(`${dan.content}|${dan.mode}|${dan.pool}|${dan.platform}|${dan.extraStr}|${dan.senderID}|${UniDM.transCtime(dan.ctime).toISOString()}`).getHash('HEX').slice(0, slice); } function _ts_decorate(decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : null === desc ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if ("object" == typeof Reflect && "function" == typeof Reflect.decorate) r = Reflect.decorate(decorators, target, key, desc); else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; } function _ts_metadata(k, v) { if ("object" == typeof Reflect && "function" == typeof Reflect.metadata) return Reflect.metadata(k, v); } const dm_gen_JSON = json_bigint({ useNativeBigInt: true }); function cleanEmptyObjects(obj) { if (null === obj || 'object' != typeof obj) return obj; if (Array.isArray(obj)) return obj; const cleaned = {}; for (const [key, value] of Object.entries(obj)){ const cleanedValue = cleanEmptyObjects(value); if (void 0 !== cleanedValue && ('object' != typeof cleanedValue || 0 !== Object.keys(cleanedValue).length)) cleaned[key] = cleanedValue; } return Object.keys(cleaned).length > 0 ? cleaned : {}; } class SetBin { constructor(bin){ this.bin = bin; } set1(bit) { this.bin |= 1 << bit; } set0(bit) { this.bin &= ~(1 << bit); } } const toBits = (number)=>{ const bits = []; do { bits.unshift(!!(1 & number)); number >>= 1; }while (number); return bits.toReversed(); }; var dm_gen_DMAttr = /*#__PURE__*/ function(DMAttr) { DMAttr["Protect"] = "Protect"; DMAttr["FromLive"] = "FromLive"; DMAttr["HighLike"] = "HighLike"; DMAttr["Compatible"] = "Compatible"; DMAttr["Reported"] = "Reported"; DMAttr["Unchecked"] = "Unchecked"; DMAttr["HasEvent"] = "HasEvent"; DMAttr["Hide"] = "Hide"; return DMAttr; }({}); const DMAttrUtils = { fromBin (bin = 0, format) { const array = toBits(bin); const attr = []; if ('bili' === format) { if (array[0]) attr.push("Protect"); if (array[1]) attr.push("FromLive"); if (array[2]) attr.push("HighLike"); } return attr; }, toBin (attr = [], format) { const bin = new SetBin(0); if ('bili' === format) { if (attr.includes("Protect")) bin.set1(0); if (attr.includes("FromLive")) bin.set1(1); if (attr.includes("HighLike")) bin.set1(2); } return bin.bin; } }; var dm_gen_ExtraDanUniChapterType = /*#__PURE__*/ function(ExtraDanUniChapterType) { ExtraDanUniChapterType["Chapter"] = "ch"; ExtraDanUniChapterType["Review"] = "rev"; ExtraDanUniChapterType["OP"] = "op"; ExtraDanUniChapterType["Intermission"] = "int"; ExtraDanUniChapterType["ED"] = "ed"; ExtraDanUniChapterType["Preview"] = "prvw"; ExtraDanUniChapterType["Cut"] = "cut"; ExtraDanUniChapterType["Duplicate"] = "dup"; ExtraDanUniChapterType["AdBiz"] = "biz"; ExtraDanUniChapterType["AdUnpaid"] = "promo"; return ExtraDanUniChapterType; }({}); const ExtraDanUniChapterTypeDict = { chs: { ch: '其它片段', rev: '回顾', op: '片头', int: '中场', ed: '片尾', prvw: '预告', cut: '删减', dup: '补档', biz: '商业广告', promo: '推广' } }; var dm_gen_ExtraDanUniChapterAction = /*#__PURE__*/ function(ExtraDanUniChapterAction) { ExtraDanUniChapterAction[ExtraDanUniChapterAction["Disabled"] = -1] = "Disabled"; ExtraDanUniChapterAction[ExtraDanUniChapterAction["ShowOverlay"] = 0] = "ShowOverlay"; ExtraDanUniChapterAction[ExtraDanUniChapterAction["ManualSkip"] = 1] = "ManualSkip"; ExtraDanUniChapterAction[ExtraDanUniChapterAction["AutoSkip"] = 2] = "AutoSkip"; return ExtraDanUniChapterAction; }({}); var dm_gen_Modes = /*#__PURE__*/ function(Modes) { Modes[Modes["Normal"] = 0] = "Normal"; Modes[Modes["Bottom"] = 1] = "Bottom"; Modes[Modes["Top"] = 2] = "Top"; Modes[Modes["Reverse"] = 3] = "Reverse"; Modes[Modes["Ext"] = 4] = "Ext"; return Modes; }({}); var dm_gen_Pools = /*#__PURE__*/ function(Pools) { Pools[Pools["Def"] = 0] = "Def"; Pools[Pools["Sub"] = 1] = "Sub"; Pools[Pools["Adv"] = 2] = "Adv"; Pools[Pools["Ix"] = 3] = "Ix"; return Pools; }({}); class UniDM { init(options) { this.options = options || this.options; const def = new UniDM(); if (void 0 === this.options.dmid || true === this.options.dmid) this.options.dmid = createDMID; this.content = String(this.content); this.ctime = UniDM.transCtime(this.ctime); if (!this.SOID) this.SOID = def.SOID; if (!this.progress) this.progress = def.progress; if (!this.mode) this.mode = def.mode; if (!this.fontsize) this.fontsize = def.mode; if (!this.color) this.color = def.color; if (!this.senderID) this.senderID = def.senderID; if (!this.content) this.content = def.content; if (!this.ctime) this.ctime = def.ctime; if (!this.weight) this.weight = def.weight; if (!this.pool) this.pool = def.pool; if (!this.attr) this.attr = def.attr; if (!this.DMID && false !== this.options.dmid) this.DMID = this.toDMID(); this.progress = Number.parseFloat(this.progress.toFixed(3)); if (this.extraStr) this.extraStr = dm_gen_JSON.stringify(cleanEmptyObjects(dm_gen_JSON.parse(this.extraStr))); if ('{}' === this.extraStr) this.extraStr = void 0; else if (4 !== this.mode) { const checkExtraBili = (obj)=>obj ? [ 'adv', 'bas', 'code', 'command' ].some((k)=>obj[k]) : false; if (this.extra.artplayer || this.extra.danuni?.chapter || checkExtraBili(this.extra.bili)) this.mode = 4; } return this; } async validate() { return validateOrReject(this); } static create(pjson, options) { return pjson ? plainToInstance(UniDM, pjson.extra ? { ...pjson, extraStr: pjson.extra ? dm_gen_JSON.stringify(pjson.extra) : pjson.extraStr } : pjson, { excludeExtraneousValues: true }).init(options) : new UniDM(); } get extra() { const extra = dm_gen_JSON.parse(this.extraStr || '{}'); return extra; } get isFrom3rdPlatform() { if (this.platform && PlatformDanmakuSources.includes(this.platform)) return true; return false; } toDMID() { if (false === this.options.dmid) return; if (true === this.options.dmid) return createDMID(this); if ('number' == typeof this.options.dmid) return createDMID(this, this.options.dmid); return this.options.dmid(this); } isSameAs(dan, options) { if (this === dan) return true; if (4 === this.mode || 4 === dan.mode) return false; if (!options?.skipDanuniMerge && (this.extra.danuni?.merge || dan.extra.danuni?.merge)) return false; if (this.extra.bili?.dmid && dan.extra.bili?.dmid) { if (this.extra.bili.dmid && !dan.extra.bili.dmid || !this.extra.bili.dmid && dan.extra.bili.dmid) return false; if (this.extra.bili.dmid === dan.extra.bili.dmid) return true; return false; } if (this.extra.artplayer && !dan.extra.artplayer || !this.extra.artplayer && dan.extra.artplayer) return false; if (this.extra.artplayer && dan.extra.artplayer && (this.extra.artplayer.border !== dan.extra.artplayer.border || dm_gen_JSON.stringify(this.extra.artplayer.style) !== dm_gen_JSON.stringify(dan.extra.artplayer.style))) return false; const isSame = (k)=>this[k] === dan[k]; const checks = [ 'SOID', 'content', 'mode', 'pool', 'platform' ].every((k)=>isSame(k)); return checks; } minify() { const def = new UniDM(); const result = { SOID: this.SOID }; if (this.progress !== def.progress) result.progress = this.progress; if (this.mode !== def.mode) result.mode = this.mode; if (this.fontsize !== def.fontsize) result.fontsize = this.fontsize; if (this.color !== def.color) result.color = this.color; if (this.senderID !== def.senderID) result.senderID = this.senderID; if (this.content !== def.content) result.content = this.content; if (this.weight !== def.weight) result.weight = this.weight; if (this.pool !== def.pool) result.pool = this.pool; if (this.attr.length > 0) result.attr = this.attr; if (void 0 !== this.platform) result.platform = this.platform; if (this.extraStr && '{}' !== this.extraStr) result.extraStr = this.extraStr; if (void 0 !== this.DMID) result.DMID = this.DMID; return result; } downgradeAdvcancedDan({ include, exclude, cleanExtra = false } = {}) { if (!this.extra) return this; { if (!include) include = []; if (!exclude) exclude = []; const check = (k)=>include?.includes(k) || !exclude?.includes(k); const clone = UniDM.create(this); clone.mode = 2; if (check('danuni') && clone.extra.danuni) { const danuni = clone.extra.danuni; if (danuni.merge) { const merge = danuni.merge; clone.content = `${this.content} x${merge.count}`; } else if (danuni.chapter) { const chapter = danuni.chapter; if ("cut" === chapter.type) clone.content = `[提示]${clone.platform}源${ExtraDanUniChapterTypeDict.chs[chapter.type]}了${chapter.duration}秒`; else if ("dup" === chapter.type) clone.content = `[提示(${ExtraDanUniChapterTypeDict.chs[chapter.type]})]${clone.platform}源-${chapter.duration}秒`; else clone.content = `[空降(${ExtraDanUniChapterTypeDict.chs[chapter.type]})]${hh_mm_ss.fromS(clone.progress + chapter.duration)}`; } } else if (check('bili') && clone.extra.bili) { const bili = clone.extra.bili; if (7 === bili.mode && bili.adv) clone.content = `[B站高级弹幕]${dm_gen_JSON.parse(bili.adv)[4] || ''}`; else if (bili.command) { const command = bili.command; clone.content = `[B站指令弹幕]${command.content}`; clone.fontsize = 36; } } clone.senderID = 'compat[bot]@dan-any'; clone.attr.push("Compatible"); if (cleanExtra) clone.extraStr = void 0; return clone; } } static transCtime(oriCtime, tsUnit) { function isMsTs(ts) { if ('ms' === tsUnit) return true; if ('s' === tsUnit) return false; return ts < 100000000; } if ('number' == typeof oriCtime || 'bigint' == typeof oriCtime) if (isMsTs(oriCtime)) return new Date(Number(oriCtime)); else return new Date(1000 * Number(oriCtime)); if ('string' != typeof oriCtime) return oriCtime; if (/^\d+n$/.test(oriCtime)) return new Date(Number(oriCtime.slice(0, -1))); return new Date(oriCtime); } static transMode(oriMode, fmt) { let mode = 0; switch(fmt){ case 'bili': switch(oriMode){ case 4: mode = 1; break; case 5: mode = 2; break; case 6: mode = 3; break; case 7: mode = 4; break; case 8: mode = 4; break; case 9: mode = 4; break; } break; case 'dplayer': if (1 === oriMode) mode = 2; else if (2 === oriMode) mode = 1; break; case 'artplayer': if (1 === oriMode) mode = 2; else if (2 === oriMode) mode = 1; break; case 'ddplay': mode = this.transMode(oriMode, 'bili'); break; default: mode = 0; break; } return mode; } static parseBiliSingle(p, c) { const p_arr = p.split(','); return { content: c, progress: Number.parseFloat(p_arr[0]), mode: Number.parseInt(p_arr[1]), fontsize: Number.parseInt(p_arr[2]), color: Number.parseInt(p_arr[3]), ctime: BigInt(p_arr[4]), pool: Number.parseInt(p_arr[5]), midHash: p_arr[6], id: BigInt(p_arr[7]), weight: Number.parseInt(p_arr[8]) }; } static fromBili(args, cid, options, recSOID) { if (args.oid && !cid) cid = args.oid; const SOID = recSOID || `def_${platform_PlatformVideoSource.Bilibili}+${id_gen_UniID.fromBili({ cid })}`; const senderID = isEmail(args.midHash, { require_tld: false }) ? args.midHash : id_gen_UniID.fromBili({ midHash: args.midHash }); let mode = 0; const pool = args.pool; const extra = { bili: { mode: args.mode, pool: args.pool, dmid: args.id, attr: args.attr, mid: args.mid } }; switch(args.mode){ case 4: mode = 1; break; case 5: mode = 2; break; case 6: mode = 3; break; case 7: mode = 4; extra.bili.adv = args.content; break; case 8: mode = 4; extra.bili.code = args.content; break; case 9: mode = 4; extra.bili.bas = args.content; break; default: mode = 0; break; } return this.create({ ...args, SOID, mode, senderID: senderID.toString(), ctime: this.transCtime(args.ctime, 's'), weight: args.weight || (3 === pool ? 1 : 0), pool, attr: DMAttrUtils.fromBin(args.attr, platform_PlatformVideoSource.Bilibili), platform: platform_PlatformVideoSource.Bilibili, extra }, options); } toBiliXML(options) { if (options?.skipBiliCommand && this.extra.bili?.command) return null; const recMode = (mode, extra)=>{ switch(mode){ case 0: return 1; case 1: return 4; case 2: return 5; case 3: return 6; case 4: if (!extra) return 1; if (extra.adv) return 7; if (extra.code) return 8; else if (extra.bas) return 9; else return 1; default: return 1; } }; const rMode = this.extra.bili?.mode || recMode(this.mode, this.extra?.bili); let content; switch(rMode){ case 7: content = this.extra?.bili?.adv; break; case 8: content = this.extra?.bili?.code; break; case 9: content = this.extra?.bili?.bas; break; default: content = this.content; break; } return { '#text': content ?? this.content, '@_p': [ this.progress, rMode, this.fontsize, this.color, this.ctime.getTime() / 1000, this.extra.bili?.pool || this.pool, options?.avoidSenderIDWithAt ? this.senderID.replaceAll(`@${platform_PlatformVideoSource.Bilibili}`, '') : this.senderID, this.extra.bili?.dmid || this.DMID || this.toDMID(), this.weight ].join(',') }; } static fromBiliCommand(args, cid, options) { if (args.oid && !cid) cid = args.oid; const SOID = `def_${platform_PlatformVideoSource.Bilibili}+${id_gen_UniID.fromBili({ cid })}`; const senderID = id_gen_UniID.fromBili({ mid: args.mid }); return this.create({ ...args, SOID, mode: 4, senderID: senderID.toString(), ctime: new Date(`${args.ctime} GMT+0800`), weight: 10, pool: 2, attr: [ "Protect" ], platform: platform_PlatformVideoSource.Bilibili, extra: { bili: { command: args } } }, options); } static fromDplayer(args, playerID, domain, options) { const SOID = id_gen_UniID.fromUnknown(playerID, domain); const senderID = id_gen_UniID.fromUnknown(args.midHash, domain); return this.create({ ...args, SOID: SOID.toString(), mode: this.transMode(args.mode, 'dplayer'), senderID: senderID.toString(), platform: domain }, options); } toDplayer() { let mode = 0; if (2 === this.mode) mode = 1; else if (1 === this.mode) mode = 2; return { mode, progress: this.progress, color: this.color, midHash: this.senderID, content: this.content }; } static fromArtplayer(args, playerID, domain, options) { const SOID = id_gen_UniID.fromUnknown(playerID, domain); const senderID = id_gen_UniID.fromUnknown('', domain); let extra = args.border ? { artplayer: { border: args.border, style: {} } } : void 0; if (args.style) extra = extra ? { ...extra, artplayer: { ...extra.artplayer, style: args.style } } : { artplayer: { style: args.style } }; return this.create({ ...args, SOID: SOID.toString(), mode: this.transMode(args.mode, 'artplayer'), senderID: senderID.toString(), platform: domain, extra }, options); } toArtplayer() { let mode = 0; if (2 === this.mode) mode = 1; else if (1 === this.mode) mode = 2; return { progress: this.progress, mode, color: this.color, content: this.content, style: this.extra.artplayer?.style }; } static fromDDplay(args, episodeId, domain = platform_PlatformDanmakuOnlySource.DanDanPlay, options) { const SOID = id_gen_UniID.fromUnknown(`def_${platform_PlatformDanmakuOnlySource.DanDanPlay}+${episodeId}`, domain); return this.create({ ...args, SOID: SOID.toString(), mode: this.transMode(args.mode, 'ddplay'), senderID: args.uid, content: args.m, platform: domain, DMID: args.cid.toString() }, options); } toDDplay() { let mode = 1; if (2 === this.mode) mode = 5; else if (1 === this.mode) mode = 4; return { progress: this.progress, mode, color: this.color, uid: this.senderID, m: this.content, cid: this.DMID ? BigInt(`0x${Buffer.from(this.DMID).toString('hex')}`) : 0n }; } constructor(){ this.SOID = id_gen_UniID.fromNull().toString(); this.progress = 0; this.mode = 0; this.fontsize = 25; this.color = 16777215; this.senderID = id_gen_UniID.fromNull().toString(); this.content = ''; this.ctime = new Date(); this.weight = 0; this.pool = 0; this.attr = []; this.options = { dmid: createDMID }; } } _ts_decorate([ IsEmail({ require_tld: false }), IsString(), IsNotEmpty(), Expose(), _ts_metadata("design:type", String) ], UniDM.prototype, "SOID", void 0); _ts_decorate([ Min(0), IsNumber(), IsNotEmpty(), Expose(), _ts_metadata("design:type", Number) ], UniDM.prototype, "progress", void 0); _ts_decorate([ IsEnum(dm_gen_Modes), IsNotEmpty(), Expose(), _ts_metadata("design:type", Number) ], UniDM.prototype, "mode", void 0); _ts_decorate([ Max(64), Min(12), IsNumber(), IsNotEmpty(), Expose(), _ts_metadata("design:type", Number) ], UniDM.prototype, "fontsize", void 0); _ts_decorate([ IsNumber(), IsNotEmpty(), Expose(), _ts_metadata("design:type", Number) ], UniDM.prototype, "color", void 0); _ts_decorate([ IsEmail({ require_tld: false }), IsString(), IsNotEmpty(), Expose(), _ts_metadata("design:type", String) ], UniDM.prototype, "senderID", void 0); _ts_decorate([ IsString(), IsNotEmpty(), Expose(), _ts_metadata("design:type", String) ], UniDM.prototype, "content", void 0); _ts_decorate([ IsDate(), IsNotEmpty(), Expose(), _ts_metadata("design:type", "u" < typeof Date ? Object : Date) ], UniDM.prototype, "ctime", void 0); _ts_decorate([ Max(11), Min(0), IsInt(), IsNotEmpty(), Expose(), _ts_metadata("design:type", Number) ], UniDM.prototype, "weight", void 0); _ts_decorate([ IsEnum(dm_gen_Pools), IsNotEmpty(), Expose(), _ts_metadata("design:type", Number) ], UniDM.prototype, "pool", void 0); _ts_decorate([ IsEnum(dm_gen_DMAttr, { each: true }), IsNotEmpty(), Expose(), _ts_metadata("design:type", Array) ], UniDM.prototype, "attr", void 0); _ts_decorate([ IsString(), IsOptional(), Expose(), _ts_metadata("design:type", Object) ], UniDM.prototype, "platform", void 0); _ts_decorate([ IsString(), IsOptional(), Expose(), _ts_metadata("design:type", String) ], UniDM.prototype, "extraStr", void 0); _ts_decorate([ IsString(), IsOptional(), Expose(), _ts_metadata("design:type", String) ], UniDM.prototype, "DMID", void 0); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ "u" < typeof Options ? Object : Options ]), _ts_metadata("design:returntype", void 0) ], UniDM.prototype, "init", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", Promise) ], UniDM.prototype, "validate", null); _ts_decorate([ Expose(), _ts_metadata("design:type", "u" < typeof Extra ? Object : Extra), _ts_metadata("design:paramtypes", []) ], UniDM.prototype, "extra", null); _ts_decorate([ Expose(), _ts_metadata("design:type", void 0), _ts_metadata("design:paramtypes", []) ], UniDM.prototype, "isFrom3rdPlatform", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", void 0) ], UniDM.prototype, "toDMID", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ Object, Object ]), _ts_metadata("design:returntype", Boolean) ], UniDM.prototype, "isSameAs", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", void 0) ], UniDM.prototype, "minify", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ Object ]), _ts_metadata("design:returntype", void 0) ], UniDM.prototype, "downgradeAdvcancedDan", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", [ Object ]), _ts_metadata("design:returntype", void 0) ], UniDM.prototype, "toBiliXML", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", "u" < typeof DMDplayer ? Object : DMDplayer) ], UniDM.prototype, "toDplayer", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", "u" < typeof DMArtplayer ? Object : DMArtplayer) ], UniDM.prototype, "toArtplayer", null); _ts_decorate([ Expose(), _ts_metadata("design:type", Function), _ts_metadata("design:paramtypes", []), _ts_metadata("design:returntype", "u" < typeof DMDDplay ? Object : DMDDplay) ], UniDM.prototype, "toDDplay", null); const DanmakuType = { SCROLL: 1, BOTTOM: 2, TOP: 3 }; const FontSize = { SMALL: 0, NORMAL: 1, LARGE: 2 }; function decimalToRGB888(decimal) { const r = decimal >> 16 & 0xff; const g = decimal >> 8 & 0xff; const b = 0xff & decimal; return { r, g, b }; } function UniPool2DanmakuLists(UP) { const dans = UP.dans; let type = DanmakuType.SCROLL; return dans.map((d)=>{ if (d.mode === dm_gen_Modes.Bottom) type = DanmakuType.BOTTOM; else if (d.mode === dm_gen_Modes.Top) type = DanmakuType.TOP; return { time: d.progress, type, fontSizeType: d.fontsize, content: d.content, color: decimalToRGB888(d.color), extra: d }; }); } function DanmakuList2UniPool(d, options) { return new UniPool(d.map((d)=>UniDM.create(d.extra)), options); } const lang_assign = (source, ...targets)=>{ for (const target of targets)for (const key of Object.keys(target))source[key] = target[key]; return source; }; const arrayOfLength = (length, defaultValue)=>{ const array = new Array(length); for(let i = 0; i < length; i++)array[i] = defaultValue; return array; }; const computeScrollInTime = (rectWidth, screenWidth, scrollTime)=>{ const speed = (screenWidth + rectWidth) / scrollTime; return rectWidth / speed; }; const computeScrollOverTime = (rectWidth, screenWidth, scrollTime)=>{ const speed = (screenWidth + rectWidth) / scrollTime; return screenWidth / speed; }; const splitGrids = ({ fontSize, padding, playResY, bottomSpace })=>{ const defaultFontSize = fontSize[FontSize.NORMAL]; const paddingTop = padding[0]; const paddingBottom = padding[2]; const linesCount = Math.floor((playResY - bottomSpace) / (defaultFontSize + paddingTop + paddingBottom)); return { 1: arrayOfLength(linesCount, { start: 0, end: 0, width: 0 }), 3: arrayOfLength(linesCount, 0), 2: arrayOfLength(linesCount, 0) }; }; const measureTextWidthConstructor = (canvasContext)=>{ const supportTextMeasure = !!canvasContext.measureText('中'); if (supportTextMeasure) return (fontName, fontSize, bold, text)=>{ canvasContext.font = `${bold ? 'bold' : 'normal'} ${fontSize}px ${fontName}`; const textWidth = canvasContext.measureText(text).width; return Math.round(textWidth); }; console.warn('[Warn] node-canvas is installed without text measure support, layout may not be correct'); return (_fontName, fontSize, _bold, text)=>text.length * fontSize; }; const resolveAvailableFixGrid = (grids, time)=>{ for (const [i, grid] of grids.entries())if (grid <= time) return i; return -1; }; const resolveAvailableScrollGrid = (grids, rectWidth, screenWidth, time, duration)=>{ for (const [i, previous] of grids.entries()){ const previousInTime = previous.start + computeScrollInTime(previous.width, screenWidth, duration); const currentOverTime = time + computeScrollOverTime(rectWidth, screenWidth, duration); if (time >= previousInTime && currentOverTime >= previous.end) return i; } return -1; }; const initializeLayout = (config, canvasCtx)=>{ const { playResX, playResY, fontName, fontSize, bold, padding, scrollTime, fixTime, bottomSpace } = config; const [paddingTop, paddingRight, paddingBottom, paddingLeft] = padding; const defaultFontSize = fontSize[FontSize.NORMAL]; const grids = splitGrids(config); const gridHeight = defaultFontSize + paddingTop + paddingBottom; return (danmaku)=>{ const targetGrids = grids[danmaku.type]; const danmakuFontSize = fontSize[danmaku.fontSizeType]; const rectWidth = measureTextWidthConstructor(canvasCtx)(fontName, danmakuFontSize, bold, danmaku.content) + paddingLeft + paddingRight; const verticalOffset = paddingTop + Math.round((defaultFontSize - danmakuFontSize) / 2); if (danmaku.type === DanmakuType.SCROLL) { const scrollGrids = targetGrids; const gridNumber = resolveAvailableScrollGrid(scrollGrids, rectWidth, playResX, danmaku.time, scrollTime); if (gridNumber < 0) return null; targetGrids[gridNumber] = { width: rectWidth, start: danmaku.time, end: danmaku.time + scrollTime }; const top = gridNumber * gridHeight + verticalOffset; const start = playResX + paddingLeft; const end = -rectWidth; return { ...danmaku, top, start, end }; } { const gridNumber = resolveAvailableFixGrid(targetGrids, danmaku.time); if (gridNumber < 0) return null; if (danmaku.type === DanmakuType.TOP) { targetGrids[gridNumber] = danmaku.time + fixTime; const top = gridNumber * gridHeight + verticalOffset; const left = Math.round(playResX / 2); return { ...danmaku, top, left }; } targetGrids[gridNumber] = danmaku.time + fixTime; const top = playResY - bottomSpace - gridHeight * gridNumber - gridHeight + verticalOffset; const left = Math.round(playResX / 2); return { ...danmaku, top, left }; } }; }; const layoutDanmaku = (inputList, config, canvasCtx)=>{ const list = [ ...UniPool2DanmakuLists(inputList) ].toSorted((x, y)=>x.time - y.time); const layout = initializeLayout(config, canvasCtx); return DanmakuList2UniPool(list.map(layout).filter((danmaku)=>!!danmaku)); }; const formatTime = (seconds)=>{ const div = (i, j)=>Math.floor(i / j); const pad = (n)=>n < 10 ? `0${n}` : String(n); const integer = Math.floor(seconds); const hour = div(integer, 3600); const minute = div(integer, 60) % 60; const second = integer % 60; const minorSecond = Math.floor((seconds - integer) * 100); return `${hour}:${pad(minute)}:${pad(second)}.${minorSecond}`; }; const encode = (text)=>text.toString().replaceAll('{', '{').replaceAll('}', '}').replaceAll(/\r|\n/g, ''); const scrollCommand = ({ start, end, top })=>String.raw`\move(${start},${top},${end},${top})`; const fixCommand = ({ top, left })=>String.raw`\an8\pos(${left},${top})`; const colorCommand = (color)=>String.raw`\c${formatColor(color)}`; const borderColorCommand = (color)=>String.raw`\3c${formatColor(color)}`; const dialogue = (danmaku, config)=>{ const { fontSizeType, content, time } = danmaku; const { scrollTime, fixTime } = config; const commands = [ danmaku.type === DanmakuType.SCROLL ? scrollCommand({ start: danmaku.start, end: danmaku.end, top: danmaku.top }) : fixCommand({ top: danmaku.top, left: danmaku.left }), isWhite(danmaku.color) ? '' : colorCommand(danmaku.color), isWhite(danmaku.color) ? '' : borderColorCommand(getDecoratingColor(danmaku.color)) ]; const fields = [ 0, formatTime(time), formatTime(time + (danmaku.type === DanmakuType.SCROLL ? scrollTime : fixTime)), `F${fontSizeType}`, '', '0000', '0000', '0000', '', `{${commands.join('')}}${encode(content)}` ]; return `Dialogue: ${fields.join(',')}`; }; const calculateDanmakuPosition = (danmaku, config)=>{ const { playResX, playResY, scrollTime, fixTime } = config; switch(danmaku.type){ case DanmakuType.SCROLL: { const start = playResX; const end = -playResX / 10; const top = danmaku.fontSizeType * playResY / 20; return { ...danmaku, start, end, top, left: 0, duration: scrollTime }; } case DanmakuType.TOP: case DanmakuType.BOTTOM: { const left = playResX / 2; const top = danmaku.type === DanmakuType.TOP ? danmaku.fontSizeType * playResY / 20 : playResY - config.bottomSpace - danmaku.fontSizeType * playResY / 20; return { ...danmaku, start: 0, end: 0, top, left, duration: fixTime }; } default: throw new Error(`Unknown danmaku type: ${danmaku.type}`); } }; const event_event = (list, config)=>{ const content = [ '[Events]', 'Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text', ...list.map((danmaku)=>{ const positionedDanmaku = calculateDanmakuPosition(danmaku, config); return dialogue(positionedDanmaku, config); }) ]; return content.join('\n'); }; const info_info = ({ playResX, playResY }, { filename, title })=>{ const content = [ '[Script Info]', `Title: ${title}`, `Original Script: 根据 ${filename} 的弹幕信息,由 ${package_namespaceObject.TB} 生成`, 'ScriptType: v4.00+', 'Collisions: Reverse', `PlayResX: ${playResX}`, `PlayResY: ${playResY}`, 'Timer: 100.0000' ]; return content.join('\n'); }; const compressTypes = new Set([ 'brotli', 'gzip' ]); const baseTypes = new Set([ 'base64', 'base18384' ]); function fromUint16Array(array) { let result = ''; for (const element of array)result += String.fromCodePoint(element); return result; } function raw_raw(list, config, context, compressType = 'brotli', baseType = 'base18384') { const raw1 = { list, config, context }; const rawText = JSON.stringify(raw1); let compress; compress = 'brotli' === compressType ? brotliCompressSync(rawText) : gzipSync(rawText); return `;RawCompressType: ${compressType}\n;RawBaseType: ${baseType}\n;Raw: ${'base64' === baseType ? compress.toString('base64') : fromUint16Array(external_base16384_encode(compress))}`; } function deRaw(ass) { const arr = ass.split('\n'); const lineCompressType = arr.find((line)=>line.startsWith(';RawCompressType:')); const lineBaseType = arr.find((line)=>line.startsWith(';RawBaseType:')); const lineRaw = arr.find((line)=>line.startsWith(';Raw:')); if (!lineCompressType || !lineBaseType || !lineRaw) return; { let compressType = lineCompressType.replace(';RawCompressType: ', '').trim(); let baseType = lineBaseType.replace(';RawBaseType: ', '').trim(); if (!compressTypes.has(compressType)) compressType = 'gzip'; if (!baseTypes.has(baseType)) baseType = 'base64'; const text = lineRaw.replace(';Raw: ', '').trim(); const buffer = 'base64' === baseType ? Buffer.from(text, 'base64') : Buffer.from(decode(Buffer.from(text, 'utf8').toString('utf8'))); let decompress; decompress = 'brotli' === compressType ? brotliDecompressSync(buffer) : gunzipSync(buffer); try { return JSON.parse(decompress.toString('utf8')); } catch { return; } } } const style = ({ fontName, fontSize, color: configColor, outlineColor, backColor, bold, outline, shadow, opacity })=>{ const fields = [ 'Name', 'Fontname', 'Fontsize', 'PrimaryColour', 'SecondaryColour', 'OutlineColour', 'BackColour', 'Bold', 'Italic', 'Underline', 'StrikeOut', 'ScaleX', 'ScaleY', 'Spacing', 'Angle', 'BorderStyle', 'Outline', 'Shadow', 'Alignment', 'MarginL', 'MarginR', 'MarginV', 'Encoding' ]; const primaryColorValue = formatColor(hexColorToRGB(configColor), opacity); const secondaryColor = getDecoratingColor(hexColorToRGB(configColor)); const outlineColorValue = formatColor(outlineColor ? hexColorToRGB(outlineColor) : secondaryColor, opacity); const backColorValue = formatColor(backColor ? hexColorToRGB(backColor) : secondaryColor, opacity); const colorStyle = `${primaryColorValue},${primaryColorValue},${outlineColorValue},${backColorValue}`; const boldValue = bold ? '1' : '0'; const fontStyle = `${boldValue},0,0,0,100,100,0,0,1,${outline},${shadow},7,0,0,0,0`; const fontDeclaration = (size, i)=>`Style: F${i},${fontName},${size},${colorStyle},${fontStyle}`; const content = [ '[V4+ Styles]', `Format: ${fields.join(',')}`, ...fontSize.map(fontDeclaration) ]; return content.join('\n'); }; const default_context = { filename: 'unknown', title: 'unknown' }; const create_ass = (list, rawList, config, context = default_context, rawConfig)=>{ const Elist = UniPool2DanmakuLists(list); const ErawList = UniPool2DanmakuLists(rawList); const content = [ info_info(config, context), style(config), event_event(Elist, config) ]; if (config.includeRaw) content.push(raw_raw(ErawList, config, context, rawConfig?.compressType, rawConfig?.baseType)); return `${content.join('\n\n')}\n`; }; const getConfig = (overrides = {})=>{ const defaults = { fontSize: [ 25, 25, 36 ], fontName: 'SimHei', color: '#ffffff', outlineColor: void 0, backColor: void 0, outline: 2, shadow: 0, bold: false, padding: [ 2, 2, 2, 2 ], playResX: 1920, playResY: 1080, scrollTime: 8, fixTime: 4, opacity: 0.6, bottomSpace: 60, includeRaw: true, mergeIn: -1 }; const config = lang_assign(defaults, overrides); config.color = formatColor(hexColorToRGB(config.color)); config.outlineColor = config.outlineColor && formatColor(hexColorToRGB(config.outlineColor)); config.backColor = config.backColor && formatColor(hexColorToRGB(config.backColor)); return config; }; function generateASS(danmaku, options, canvasCtx) { const config = getConfig(options.substyle); const mergedList = danmaku.merge(config.mergeIn); const layoutList = layoutDanmaku(mergedList, config, canvasCtx); const content = create_ass(layoutList, danmaku, config, { filename: options?.filename || 'unknown', title: options?.title || 'unknown' }, options.raw); return content; } function parseAssRawField(ass, options) { const raw = deRaw(ass); if (raw) return DanmakuList2UniPool(raw.list, options); return UniPool.create(); } const file_bili_dm = /*@__PURE__*/ fileDesc("Cg1iaWxpL2RtLnByb3RvEiBiaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MSJkCgZBdmF0YXISCgoCaWQYASABKAkSCwoDdXJsGAIgASgJEkEKC2F2YXRhcl90eXBlGAMgASgOMiwuYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuQXZhdGFyVHlwZSIjCgZCdWJibGUSDAoEdGV4dBgBIAEoCRILCgN1cmwYAiABKAkixgEKCEJ1YmJsZVYyEgwKBHRleHQYASABKAkSCwoDdXJsGAIgASgJEkEKC2J1YmJsZV90eXBlGAMgASgOMiwuYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuQnViYmxlVHlwZRIVCg1leHBvc3VyZV9vbmNlGAQgASgIEkUKDWV4cG9zdXJlX3R5cGUYBSABKA4yLi5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5FeHBvc3VyZVR5cGUiWwoGQnV0dG9uEgwKBHRleHQYASABKAkSQwoGYWN0aW9uGAIgASgOMjMuYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuVG9hc3RGdW5jdGlvblR5cGUiWAoOQnV6endvcmRDb25maWcSRgoIa2V5d29yZHMYASADKAsyNC5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5CdXp6d29yZFNob3dDb25maWcieAoSQnV6endvcmRTaG93Q29uZmlnEgwKBG5hbWUYASABKAkSDgoGc2NoZW1hGAIgASgJEg4KBnNvdXJjZRgDIAEoBRIKCgJpZBgEIAEoAxITCgtidXp6d29yZF9pZBgFIAEoAxITCgtzY2hlbWFfdHlwZRgGIAEoBSJ7CghDaGVja0JveBIMCgR0ZXh0GAEgASgJEjwKBHR5cGUYAiABKA4yLi5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5DaGVja2JveFR5cGUSFQoNZGVmYXVsdF92YWx1ZRgDIAEoCBIMCgRzaG93GAQgASgIIm8KCkNoZWNrQm94VjISDAoEdGV4dBgBIAEoCRI8CgR0eXBlGAIgASgOMi4uYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuQ2hlY2tib3hUeXBlEhUKDWRlZmF1bHRfdmFsdWUYAyABKAgiggIKC0NsaWNrQnV0dG9uEhUKDXBvcnRyYWl0X3RleHQYASADKAkSFgoObGFuZHNjYXBlX3RleHQYAiADKAkSGwoTcG9ydHJhaXRfdGV4dF9mb2N1cxgDIAMoCRIcChRsYW5kc2NhcGVfdGV4dF9mb2N1cxgEIAMoCRJBCgtyZW5kZXJfdHlwZRgFIAEoDjIsLmJpbGliaWxpLmNvbW11bml0eS5zZXJ2aWNlLmRtLnYxLlJlbmRlclR5cGUSDAoEc2hvdxgGIAEoCBI4CgZidWJibGUYByABKAsyKC5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5CdWJibGUiswIKDUNsaWNrQnV0dG9uVjISFQoNcG9ydHJhaXRfdGV4dBgBIAMoCRIWCg5sYW5kc2NhcGVfdGV4dBgCIAMoCRIbChNwb3J0cmFpdF90ZXh0X2ZvY3VzGAMgAygJEhwKFGxhbmRzY2FwZV90ZXh0X2ZvY3VzGAQgAygJEkEKC3JlbmRlcl90eXBlGAUgASgOMiwuYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuUmVuZGVyVHlwZRIXCg90ZXh0X2lucHV0X3Bvc3QYBiABKAgSFQoNZXhwb3N1cmVfb25jZRgHIAEoCBJFCg1leHBvc3VyZV90eXBlGAggASgOMi4uYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuRXhwb3N1cmVUeXBlIksKB0NvbW1hbmQSQAoLY29tbWFuZF9kbXMYASADKAsyKy5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5Db21tYW5kRG0i5gEKCUNvbW1hbmREbRIKCgJpZBgBIAEoAxILCgNvaWQYAiABKAMSCwoDbWlkGAMgASgDEg8KB2NvbW1hbmQYBCABKAkSDwoHY29udGVudBgFIAEoCRIQCghwcm9ncmVzcxgGIAEoBRINCgVjdGltZRgHIAEoCRINCgVtdGltZRgIIAEoCRINCgVleHRyYRgJIAEoCRINCgVpZFN0chgKIAEoCRIMCgR0eXBlGAsgASgFEhMKC2F1dG9fY3JlYXRlGAwgASgIEhIKCmNvdW50X2Rvd24YDSABKAUSDAoEYXR0chgOIAEoBSJQCg1EYW5tYWt1QUlGbGFnEj8KCGRtX2ZsYWdzGAEgAygLMi0uYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuRGFubWFrdUZsYWciiwMKC0Rhbm1ha3VFbGVtEgoKAmlkGAEgASgDEhAKCHByb2dyZXNzGAIgASgFEgwKBG1vZGUYAyABKAUSEAoIZm9udHNpemUYBCABKAUSPwoFY29sb3IYBSABKA4yMC5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5EbUNvbG9yZnVsVHlwZRIQCghtaWRfaGFzaBgGIAEoCRIPCgdjb250ZW50GAcgASgJEg0KBWN0aW1lGAggASgDEg4KBndlaWdodBgJIAEoBRIOCgZhY3Rpb24YCiABKAkSDAoEcG9vbBgLIAEoBRIOCgZpZF9zdHIYDCABKAkSDAoEYXR0chgNIAEoBRIRCglhbmltYXRpb24YFiABKAkSDQoFZXh0cmEYFyABKAkSQgoIY29sb3JmdWwYGCABKA4yMC5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5EbUNvbG9yZnVsVHlwZRIMCgR0eXBlGBkgASgFEgsKA29pZBgaIAEoAyIpCgtEYW5tYWt1RmxhZxIMCgRkbWlkGAEgASgDEgwKBGZsYWcYAiABKAUiSwoRRGFubWFrdUZsYWdDb25maWcSEAoIcmVjX2ZsYWcYASABKAUSEAoIcmVjX3RleHQYAiABKAkSEgoKcmVjX3N3aXRjaBgDIAEoBSKLBwoYRGFubXVEZWZhdWx0UGxheWVyQ29uZmlnEikKIXBsYXllcl9kYW5tYWt1X3VzZV9kZWZhdWx0X2NvbmZpZxgBIAEoCB