UNPKG

ddnet

Version:

A typescript npm package for interacting with data from ddnet.org

828 lines 20.8 kB
/** * Wrapper class for the {@link Error} built-in class, used as a catch all option and to also provide error context. */ export class DDNetError extends Error { context; /** * Create a new instance of {@link DDNetError} */ constructor( /** * The reason for this error. */ reason, /** * Context for this error, usually an {@link Error} object, array or a string, ultimately unknown type. */ context) { super(reason ?? 'No error message provided, see context.'); this.context = context; } } /** * Converts a number of seconds to a DDNet finish time string. * * @example "03:23" */ export function timeString( /** * The time in seconds to convert. */ totalSeconds) { if (totalSeconds < 0) return '--:--'; const pad = (s) => (s.length < 2 ? `0${s}` : s); const hours = Math.floor(totalSeconds / 3600).toString(); const remainingSecondsAfterHours = totalSeconds % 3600; const minutes = Math.floor(remainingSecondsAfterHours / 60).toString(); const seconds = Math.floor(remainingSecondsAfterHours % 60).toString(); return hours === '0' ? `${pad(minutes)}:${pad(seconds)}` : `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`; } /** * Slugifies a name to safely use it in a url. * * @see * https://github.com/ddnet/ddnet-scripts/blob/master/servers/scripts/ddnet.py#L185 */ export function slugify(name) { const x = '[\t !"#$%&\'()*\\-/<=>?@[\\]^_`{|},.:]+'; let string = ''; for (const c of name) { if (c.match(x) || c.charCodeAt(0) >= 128) { string += `-${c.charCodeAt(0)}-`; } else { string += c; } } return string; } /** * Converts a python date time string or timestamp into a valid javascript timestamp. */ export function dePythonifyTime( /** * The time to convert. */ time) { if (typeof time === 'string') return new Date(time).getTime(); const d = new Date(time); // hacky fix if (d.getFullYear() < 2000) return new Date(time * 1000).getTime(); return d.getTime(); } /** * Represents the different DDNet server types. */ export var Type; (function (Type) { Type["novice"] = "Novice"; Type["moderate"] = "Moderate"; Type["brutal"] = "Brutal"; Type["insane"] = "Insane"; Type["dummy"] = "Dummy"; Type["ddmaxEasy"] = "DDmaX.Easy"; Type["ddmaxNext"] = "DDmaX.Next"; Type["ddmaxPro"] = "DDmaX.Pro"; Type["ddmaxNut"] = "DDmaX.Nut"; Type["oldschool"] = "Oldschool"; Type["solo"] = "Solo"; Type["race"] = "Race"; Type["fun"] = "Fun"; Type["unknown"] = "UNKNOWN"; })(Type || (Type = {})); /** * Represents server regions. * * @see * https://github.com/ddnet/ddnet-web/tree/master/www/countryflags */ export var ServerRegion; (function (ServerRegion) { ServerRegion["ARG"] = "ARG"; ServerRegion["AUS"] = "AUS"; ServerRegion["BRA"] = "BRA"; ServerRegion["CAN"] = "CAN"; ServerRegion["CHL"] = "CHL"; ServerRegion["CHN"] = "CHN"; ServerRegion["COL"] = "COL"; ServerRegion["CRI"] = "CRI"; ServerRegion["EUR"] = "EUR"; ServerRegion["GER"] = "GER"; ServerRegion["FIN"] = "FIN"; ServerRegion["IND"] = "IND"; ServerRegion["IRN"] = "IRN"; ServerRegion["JAP"] = "JAP"; ServerRegion["KOR"] = "KOR"; ServerRegion["KSA"] = "KSA"; ServerRegion["MEX"] = "MEX"; ServerRegion["NLD"] = "NLD"; ServerRegion["PER"] = "PER"; ServerRegion["POL"] = "POL"; ServerRegion["RUS"] = "RUS"; ServerRegion["SAU"] = "SAU"; ServerRegion["SGP"] = "SGP"; ServerRegion["TUR"] = "TUR"; ServerRegion["TWN"] = "TWN"; ServerRegion["UAE"] = "UAE"; ServerRegion["UKR"] = "UKR"; ServerRegion["UNK"] = "UNK"; ServerRegion["USA"] = "USA"; ServerRegion["ZAF"] = "ZAF"; })(ServerRegion || (ServerRegion = {})); /** * Represents server regions where ranks are available online. * * @remarks * It's similar to {@link ServerRegion} but some are missing and some don't exist. * * @see * https://github.com/ddnet/ddnet-scripts/blob/master/servers/scripts/maps.py#L178 */ export var RankAvailableRegion; (function (RankAvailableRegion) { RankAvailableRegion["NLD"] = "NLD"; RankAvailableRegion["FRA"] = "FRA"; RankAvailableRegion["GER"] = "GER"; RankAvailableRegion["POL"] = "POL"; RankAvailableRegion["FIN"] = "FIN"; RankAvailableRegion["UKR"] = "UKR"; RankAvailableRegion["RUS"] = "RUS"; RankAvailableRegion["TUR"] = "TUR"; RankAvailableRegion["IRN"] = "IRN"; RankAvailableRegion["BHR"] = "BHR"; RankAvailableRegion["CHL"] = "CHL"; RankAvailableRegion["BRA"] = "BRA"; RankAvailableRegion["ARG"] = "ARG"; RankAvailableRegion["PER"] = "PER"; RankAvailableRegion["USA"] = "USA"; RankAvailableRegion["CHN"] = "CHN"; RankAvailableRegion["KOR"] = "KOR"; RankAvailableRegion["TWN"] = "TWN"; RankAvailableRegion["SGP"] = "SGP"; RankAvailableRegion["ZAF"] = "ZAF"; RankAvailableRegion["IND"] = "IND"; RankAvailableRegion["AUS"] = "AUS"; RankAvailableRegion["OLD"] = "OLD"; RankAvailableRegion["UNK"] = "UNK"; })(RankAvailableRegion || (RankAvailableRegion = {})); /** * Represents player countries. * * @see * https://github.com/ddnet/ddnet/tree/master/data/countryflags */ export var Country; (function (Country) { Country["AD"] = "AD"; Country["AE"] = "AE"; Country["AF"] = "AF"; Country["AG"] = "AG"; Country["AI"] = "AI"; Country["AL"] = "AL"; Country["AM"] = "AM"; Country["AO"] = "AO"; Country["AQ"] = "AQ"; Country["AR"] = "AR"; Country["AS"] = "AS"; Country["AT"] = "AT"; Country["AU"] = "AU"; Country["AW"] = "AW"; Country["AX"] = "AX"; Country["AZ"] = "AZ"; Country["BA"] = "BA"; Country["BB"] = "BB"; Country["BD"] = "BD"; Country["BE"] = "BE"; Country["BF"] = "BF"; Country["BG"] = "BG"; Country["BH"] = "BH"; Country["BI"] = "BI"; Country["BJ"] = "BJ"; Country["BL"] = "BL"; Country["BM"] = "BM"; Country["BN"] = "BN"; Country["BO"] = "BO"; Country["BR"] = "BR"; Country["BS"] = "BS"; Country["BT"] = "BT"; Country["BW"] = "BW"; Country["BY"] = "BY"; Country["BZ"] = "BZ"; Country["CA"] = "CA"; Country["CC"] = "CC"; Country["CD"] = "CD"; Country["CF"] = "CF"; Country["CG"] = "CG"; Country["CH"] = "CH"; Country["CI"] = "CI"; Country["CK"] = "CK"; Country["CL"] = "CL"; Country["CM"] = "CM"; Country["CN"] = "CN"; Country["CO"] = "CO"; Country["CR"] = "CR"; Country["CU"] = "CU"; Country["CV"] = "CV"; Country["CW"] = "CW"; Country["CX"] = "CX"; Country["CY"] = "CY"; Country["CZ"] = "CZ"; Country["DE"] = "DE"; Country["DJ"] = "DJ"; Country["DK"] = "DK"; Country["DM"] = "DM"; Country["DO"] = "DO"; Country["DZ"] = "DZ"; Country["EC"] = "EC"; Country["EE"] = "EE"; Country["EG"] = "EG"; Country["EH"] = "EH"; Country["ER"] = "ER"; Country["ES-CT"] = "ES-CT"; Country["ES-GA"] = "ES-GA"; Country["ES"] = "ES"; Country["ET"] = "ET"; Country["EU"] = "EU"; Country["FI"] = "FI"; Country["FJ"] = "FJ"; Country["FK"] = "FK"; Country["FM"] = "FM"; Country["FO"] = "FO"; Country["FR"] = "FR"; Country["GA"] = "GA"; Country["GB-ENG"] = "GB-ENG"; Country["GB-NIR"] = "GB-NIR"; Country["GB-SCT"] = "GB-SCT"; Country["GB-WLS"] = "GB-WLS"; Country["GB"] = "GB"; Country["GD"] = "GD"; Country["GE"] = "GE"; Country["GF"] = "GF"; Country["GG"] = "GG"; Country["GH"] = "GH"; Country["GI"] = "GI"; Country["GL"] = "GL"; Country["GM"] = "GM"; Country["GN"] = "GN"; Country["GP"] = "GP"; Country["GQ"] = "GQ"; Country["GR"] = "GR"; Country["GS"] = "GS"; Country["GT"] = "GT"; Country["GU"] = "GU"; Country["GW"] = "GW"; Country["GY"] = "GY"; Country["HK"] = "HK"; Country["HN"] = "HN"; Country["HR"] = "HR"; Country["HT"] = "HT"; Country["HU"] = "HU"; Country["ID"] = "ID"; Country["IE"] = "IE"; Country["IL"] = "IL"; Country["IM"] = "IM"; Country["IN"] = "IN"; Country["IO"] = "IO"; Country["IQ"] = "IQ"; Country["IR"] = "IR"; Country["IS"] = "IS"; Country["IT"] = "IT"; Country["JE"] = "JE"; Country["JM"] = "JM"; Country["JO"] = "JO"; Country["JP"] = "JP"; Country["KE"] = "KE"; Country["KG"] = "KG"; Country["KH"] = "KH"; Country["KI"] = "KI"; Country["KM"] = "KM"; Country["KN"] = "KN"; Country["KP"] = "KP"; Country["KR"] = "KR"; Country["KW"] = "KW"; Country["KY"] = "KY"; Country["KZ"] = "KZ"; Country["LA"] = "LA"; Country["LB"] = "LB"; Country["LC"] = "LC"; Country["LI"] = "LI"; Country["LK"] = "LK"; Country["LR"] = "LR"; Country["LS"] = "LS"; Country["LT"] = "LT"; Country["LU"] = "LU"; Country["LV"] = "LV"; Country["LY"] = "LY"; Country["MA"] = "MA"; Country["MC"] = "MC"; Country["MD"] = "MD"; Country["ME"] = "ME"; Country["MF"] = "MF"; Country["MG"] = "MG"; Country["MH"] = "MH"; Country["MK"] = "MK"; Country["ML"] = "ML"; Country["MM"] = "MM"; Country["MN"] = "MN"; Country["MO"] = "MO"; Country["MP"] = "MP"; Country["MQ"] = "MQ"; Country["MR"] = "MR"; Country["MS"] = "MS"; Country["MT"] = "MT"; Country["MU"] = "MU"; Country["MV"] = "MV"; Country["MW"] = "MW"; Country["MX"] = "MX"; Country["MY"] = "MY"; Country["MZ"] = "MZ"; Country["NA"] = "NA"; Country["NC"] = "NC"; Country["NE"] = "NE"; Country["NF"] = "NF"; Country["NG"] = "NG"; Country["NI"] = "NI"; Country["NL"] = "NL"; Country["NO"] = "NO"; Country["NP"] = "NP"; Country["NR"] = "NR"; Country["NU"] = "NU"; Country["NZ"] = "NZ"; Country["OM"] = "OM"; Country["PA"] = "PA"; Country["PE"] = "PE"; Country["PF"] = "PF"; Country["PG"] = "PG"; Country["PH"] = "PH"; Country["PK"] = "PK"; Country["PL"] = "PL"; Country["PM"] = "PM"; Country["PN"] = "PN"; Country["PR"] = "PR"; Country["PS"] = "PS"; Country["PT"] = "PT"; Country["PW"] = "PW"; Country["PY"] = "PY"; Country["QA"] = "QA"; Country["RE"] = "RE"; Country["RO"] = "RO"; Country["RS"] = "RS"; Country["RU"] = "RU"; Country["RW"] = "RW"; Country["SA"] = "SA"; Country["SB"] = "SB"; Country["SC"] = "SC"; Country["SD"] = "SD"; Country["SE"] = "SE"; Country["SG"] = "SG"; Country["SH"] = "SH"; Country["SI"] = "SI"; Country["SK"] = "SK"; Country["SL"] = "SL"; Country["SM"] = "SM"; Country["SN"] = "SN"; Country["SO"] = "SO"; Country["SR"] = "SR"; Country["SS"] = "SS"; Country["ST"] = "ST"; Country["SV"] = "SV"; Country["SX"] = "SX"; Country["SY"] = "SY"; Country["SZ"] = "SZ"; Country["TC"] = "TC"; Country["TD"] = "TD"; Country["TF"] = "TF"; Country["TG"] = "TG"; Country["TH"] = "TH"; Country["TJ"] = "TJ"; Country["TK"] = "TK"; Country["TL"] = "TL"; Country["TM"] = "TM"; Country["TN"] = "TN"; Country["TO"] = "TO"; Country["TR"] = "TR"; Country["TT"] = "TT"; Country["TV"] = "TV"; Country["TW"] = "TW"; Country["TZ"] = "TZ"; Country["UA"] = "UA"; Country["UG"] = "UG"; Country["US"] = "US"; Country["UY"] = "UY"; Country["UZ"] = "UZ"; Country["VA"] = "VA"; Country["VC"] = "VC"; Country["VE"] = "VE"; Country["VG"] = "VG"; Country["VI"] = "VI"; Country["VN"] = "VN"; Country["VU"] = "VU"; Country["WF"] = "WF"; Country["WS"] = "WS"; Country["YE"] = "YE"; Country["ZA"] = "ZA"; Country["ZM"] = "ZM"; Country["ZW"] = "ZW"; Country["default"] = "default"; })(Country || (Country = {})); // TODO: Merge this into Country directly /** * Map object which holds flag id's for {@link Country} enum members * * @see * https://github.com/ddnet/ddnet/blob/master/data/countryflags/index.txt */ export const CountryFlagsMap = { 'default': -1, 'GB-ENG': 901, 'GB-NIR': 902, 'GB-SCT': 903, 'GB-WLS': 904, 'ES-CT': 906, 'ES-GA': 907, 'EU': 905, 'AF': 4, 'AX': 248, 'AL': 8, 'DZ': 12, 'AS': 16, 'AD': 20, 'AO': 24, 'AQ': 10, 'AI': 660, 'AG': 28, 'AR': 32, 'AM': 51, 'AW': 533, 'AU': 36, 'AT': 40, 'AZ': 31, 'BS': 44, 'BH': 48, 'BD': 50, 'BB': 52, 'BY': 112, 'BE': 56, 'BZ': 84, 'BJ': 204, 'BM': 60, 'BT': 64, 'BO': 68, 'BA': 70, 'BW': 72, 'BR': 76, 'IO': 86, 'BN': 96, 'BG': 100, 'BF': 854, 'BI': 108, 'KH': 116, 'CM': 120, 'CA': 124, 'CV': 132, 'KY': 136, 'CF': 140, 'TD': 148, 'CL': 152, 'CN': 156, 'CX': 162, 'CC': 166, 'CO': 170, 'KM': 174, 'CG': 178, 'CD': 180, 'CK': 184, 'CR': 188, 'CI': 384, 'HR': 191, 'CU': 192, 'CW': 531, 'CY': 196, 'CZ': 203, 'DK': 208, 'DJ': 262, 'DM': 212, 'DO': 214, 'EC': 218, 'EG': 818, 'SV': 222, 'GQ': 226, 'ER': 232, 'EE': 233, 'ET': 231, 'FK': 238, 'FO': 234, 'FJ': 242, 'FI': 246, 'FR': 250, 'GF': 254, 'PF': 258, 'TF': 260, 'GA': 266, 'GM': 270, 'GE': 268, 'DE': 276, 'GH': 288, 'GI': 292, 'GR': 300, 'GL': 304, 'GD': 308, 'GP': 312, 'GU': 316, 'GT': 320, 'GG': 831, 'GN': 324, 'GW': 624, 'GY': 328, 'HT': 332, 'VA': 336, 'HN': 340, 'HK': 344, 'HU': 348, 'IS': 352, 'IN': 356, 'ID': 360, 'IR': 364, 'IQ': 368, 'IE': 372, 'IM': 833, 'IL': 376, 'IT': 380, 'JM': 388, 'JP': 392, 'JE': 832, 'JO': 400, 'KZ': 398, 'KE': 404, 'KI': 296, 'KP': 408, 'KR': 410, 'KW': 414, 'KG': 417, 'LA': 418, 'LV': 428, 'LB': 422, 'LS': 426, 'LR': 430, 'LY': 434, 'LI': 438, 'LT': 440, 'LU': 442, 'MO': 446, 'MK': 807, 'MG': 450, 'MW': 454, 'MY': 458, 'MV': 462, 'ML': 466, 'MT': 470, 'MH': 584, 'MQ': 474, 'MR': 478, 'MU': 480, 'MX': 484, 'FM': 583, 'MD': 498, 'MC': 492, 'MN': 496, 'ME': 499, 'MS': 500, 'MA': 504, 'MZ': 508, 'MM': 104, 'NA': 516, 'NR': 520, 'NP': 524, 'NL': 528, 'NC': 540, 'NZ': 554, 'NI': 558, 'NE': 562, 'NG': 566, 'NU': 570, 'NF': 574, 'MP': 580, 'NO': 578, 'OM': 512, 'PK': 586, 'PW': 585, 'PA': 591, 'PG': 598, 'PY': 600, 'PE': 604, 'PH': 608, 'PN': 612, 'PL': 616, 'PT': 620, 'PR': 630, 'PS': 275, 'QA': 634, 'RE': 638, 'RO': 642, 'RU': 643, 'RW': 646, 'BL': 652, 'SH': 654, 'KN': 659, 'LC': 662, 'MF': 663, 'PM': 666, 'VC': 670, 'WS': 882, 'SM': 674, 'ST': 678, 'SA': 682, 'SN': 686, 'RS': 688, 'SC': 690, 'SL': 694, 'SG': 702, 'SX': 534, 'SK': 703, 'SI': 705, 'SB': 90, 'SO': 706, 'SS': 737, 'ZA': 710, 'GS': 239, 'ES': 724, 'LK': 144, 'SD': 736, 'SR': 740, 'SZ': 748, 'SE': 752, 'CH': 756, 'SY': 760, 'TW': 158, 'TJ': 762, 'TZ': 834, 'TH': 764, 'TL': 626, 'TG': 768, 'TK': 772, 'TO': 776, 'TT': 780, 'TN': 788, 'TR': 792, 'TM': 795, 'TC': 796, 'TV': 798, 'UG': 800, 'UA': 804, 'AE': 784, 'GB': 826, 'US': 840, 'UY': 858, 'UZ': 860, 'VU': 548, 'VE': 862, 'VN': 704, 'VG': 92, 'VI': 850, 'WF': 876, 'EH': 732, 'YE': 887, 'ZM': 894, 'ZW': 716 }; /** * Helper function to translate {@link Country} enum member keys to country id's and vice-versa */ export function getCountryOrId(identifier) { if (typeof identifier === 'number') { const n = Object.entries(CountryFlagsMap).find(entry => entry[1] === identifier)?.[0]; if (!n) return Country.default; return Country[n]; } return CountryFlagsMap[identifier]; } /** * Represents map tiles. * * @see * https://github.com/ddnet/ddnet-web/tree/master/www/tiles */ export var Tile; (function (Tile) { Tile["BONUS"] = "BONUS"; Tile["BOOST"] = "BOOST"; Tile["CHECKPOINT_FIRST"] = "CHECKPOINT_FIRST"; Tile["CRAZY_SHOTGUN"] = "CRAZY_SHOTGUN"; Tile["DEATH"] = "DEATH"; Tile["DFREEZE"] = "DFREEZE"; Tile["DOOR"] = "DOOR"; Tile["DRAGGER"] = "DRAGGER"; Tile["EHOOK_START"] = "EHOOK_START"; Tile["HIT_END"] = "HIT_END"; Tile["JETPACK_START"] = "JETPACK_START"; Tile["JUMP"] = "JUMP"; Tile["LASER_STOP"] = "LASER_STOP"; Tile["NPC_START"] = "NPC_START"; Tile["NPH_START"] = "NPH_START"; Tile["OLDLASER"] = "OLDLASER"; Tile["PLASMAE"] = "PLASMAE"; Tile["PLASMAF"] = "PLASMAF"; Tile["PLASMAU"] = "PLASMAU"; Tile["POWERUP_NINJA"] = "POWERUP_NINJA"; Tile["SOLO_START"] = "SOLO_START"; Tile["STOP"] = "STOP"; Tile["SUPER_START"] = "SUPER_START"; Tile["SWITCH"] = "SWITCH"; Tile["SWITCH_TIMED"] = "SWITCH_TIMED"; Tile["TELECHECK"] = "TELECHECK"; Tile["TELECHECKIN"] = "TELECHECKIN"; Tile["TELEIN"] = "TELEIN"; Tile["TELEINEVIL"] = "TELEINEVIL"; Tile["TELEINHOOK"] = "TELEINHOOK"; Tile["TELEINWEAPON"] = "TELEINWEAPON"; Tile["TELE_GRENADE"] = "TELE_GRENADE"; Tile["TELE_GUN"] = "TELE_GUN"; Tile["TELE_LASER"] = "TELE_LASER"; Tile["THROUGH"] = "THROUGH"; Tile["THROUGH_ALL"] = "THROUGH_ALL"; Tile["TUNE"] = "TUNE"; Tile["WALLJUMP"] = "WALLJUMP"; Tile["WEAPON_GRENADE"] = "WEAPON_GRENADE"; Tile["WEAPON_RIFLE"] = "WEAPON_RIFLE"; Tile["WEAPON_SHOTGUN"] = "WEAPON_SHOTGUN"; Tile["UNKNOWN_TILE"] = "UNKNOWN_TILE"; })(Tile || (Tile = {})); /** * @param kind The kind of image wanted, this is used at run-time to determine which url gets returned for overlapping members. * @typeParam T Used in combination with the {@link kind} param to also determine the kind of image wanted at compile-time. */ export function getImageUrl(eMem, kind) { if (kind === 'country') return `https://raw.githubusercontent.com/ddnet/ddnet/master/data/countryflags/${eMem}.png`; if (kind === 'region') return `https://raw.githubusercontent.com/ddnet/ddnet-web/master/www/countryflags/${eMem}.png`; if (kind === 'tile') return `https://raw.githubusercontent.com/ddnet/ddnet-web/master/www/tiles/${eMem}.png`; throw new DDNetError(`Unknown error.`); } /** * Splits a mapper name string into an array of mapper names. * * @see * https://github.com/ddnet/ddnet-scripts/blob/8e0909edbeb5d7a6446349dc66a3beb0f5ddccc7/servers/scripts/ddnet.py#L213 */ export function splitMappers(mapperNames) { let names = mapperNames.split(', '); if (names.length) { names = names.slice(0, -1).concat(names[names.length - 1].split(' & ')); } return names; } /** * Calculates map points reward based on difficulty (server type) and star count. * * @see * https://ddnet.org/ranks/fun/#points */ export function calculatePoints(type, stars) { // [multiplier, offset] const multiplierAndOffsetMap = { [Type.novice]: [1, 0], [Type.moderate]: [2, 5], [Type.brutal]: [3, 15], [Type.insane]: [4, 30], [Type.dummy]: [5, 5], [Type.ddmaxEasy]: [4, 0], [Type.ddmaxNext]: [4, 0], [Type.ddmaxPro]: [4, 0], [Type.ddmaxNut]: [4, 0], [Type.oldschool]: [6, 0], [Type.solo]: [4, 0], [Type.race]: [2, 0], [Type.fun]: [0, 0], [Type.unknown]: [-1, -1] }; const [multiplier, offset] = multiplierAndOffsetMap[type]; return stars * multiplier + offset; } /** * Formats an array of strings into a list. * * @example * ```ts * const authors = ["Sans3108", "urg"]; * console.log(formatStringList(authors)); // 'Sans3108 & urg' * * const authors = ["Sans3108", "urg", "Meloƞ"]; * console.log(formatStringList(authors)); // 'Sans3108, urg & Meloƞ' * ``` */ export function formatStringList(strings) { if (strings.length === 0) return ''; if (strings.length === 1) return strings[0]; if (strings.length === 2) return `${strings[0]} & ${strings[1]}`; return `${strings.slice(0, strings.length - 1).join(', ')} & ${strings[strings.length - 1]}`; } //# sourceMappingURL=util.js.map