@fibercom/routeros-api
Version:
Robust MikroTik RouterOS API client for Node.js and TypeScript
689 lines • 48.7 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.MikrotikAPI = void 0;
const RouterOSAPI_1 = require("./RouterOSAPI");
const is_ip_1 = require("is-ip");
const ip = require("ip");
/**
* Extended RouterOS API class with additional functionality
*/
class MikrotikAPI extends RouterOSAPI_1.RouterOSAPI {
constructor(options) {
super(options);
}
/**
* Get system information
*/
getSystemInfo() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/system/routerboard/print");
});
}
/**
* Get system resources
*/
getSystemResources() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/system/resource/print");
});
}
/**
* Get interface list
*/
getInterfaces() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/interface/print");
});
}
/**
* Get IP addresses
*/
getIPAddresses() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/ip/address/print");
});
}
/**
* Adds a static DHCP lease.
*
* @param {Object} lease - The lease details.
* @param {string} lease.address - The IPv4 address to assign.
* @param {string} lease.macAddress - The MAC address of the device.
* @param {string} lease.server - The name of the DHCP server.
* @param {string} [lease.comment] - Optional comment for the lease (will be uppercased).
* @param {boolean} [lease.disabled] - Optional: Set to true to disable the lease.
* @returns {Promise<IRosGenericResponse[]>} The response from the RouterOS API.
* @throws {Error} If required parameters are missing or invalid.
*/
addDhcpLease(lease) {
return __awaiter(this, void 0, void 0, function* () {
if (!lease ||
!lease.address ||
!(0, is_ip_1.isIPv4)(lease.address) ||
!lease.macAddress ||
!lease.server) {
throw new Error("A valid IP address, macAddress, and server are required!");
}
const command = [
"/ip/dhcp-server/lease/add",
`=address=${lease.address.trim()}`,
`=mac-address=${lease.macAddress.trim()}`,
`=server=${lease.server.trim()}`,
];
if (lease.comment)
command.push(`=comment=${lease.comment.trim().toUpperCase()}`);
if (typeof lease.disabled === "boolean") {
command.push(`=disabled=${lease.disabled ? "yes" : "no"}`);
}
return this.write(command);
});
}
/**
* Retrieve DHCP leases, optionally filtering by DHCP server name.
*
* @param {string} [serverName] - (Optional) Name of the DHCP server to filter leases.
* @returns {Promise<IRosGenericResponse[]>} List of DHCP leases.
*/
getDhcpLeases(serverName) {
return __awaiter(this, void 0, void 0, function* () {
const command = ["/ip/dhcp-server/lease/print"];
if (serverName && serverName.trim()) {
command.push(`?server=${serverName.trim()}`);
}
// Returns all leases if no server specified
return this.write(command);
});
}
/**
* Updates a DHCP lease by ID with the provided parameters.
*
* @param {string} id - The internal ID of the DHCP lease (must start with '*').
* @param {Object} params - Lease parameters to update.
* @param {string} [params.address] - The IP address to assign.
* @param {string} [params.macAddress] - The MAC address of the device.
* @param {string} [params.server] - The DHCP server name.
* @param {string} [params.comment] - A comment for the lease.
* @param {boolean} [params.disabled] - Whether to disable the lease.
* @returns {Promise<IRosGenericResponse[]>} The response from the RouterOS API.
* @throws {Error} If the ID is invalid or IP address is invalid.
*/
setDhcpLease(id, params) {
return __awaiter(this, void 0, void 0, function* () {
if (!id || !id.startsWith("*"))
throw new Error("Lease ID required for setDhcpLease");
if (params.address && !(0, is_ip_1.isIPv4)(params.address))
throw new Error("Invalid IP Address!");
// Map camelCase keys to RouterOS keys
const fieldMap = {
macAddress: "mac-address",
address: "address",
server: "server",
comment: "comment",
disabled: "disabled",
};
const command = ["/ip/dhcp-server/lease/set", `=.id=${id}`];
for (const [key, value] of Object.entries(params)) {
if (typeof value === "undefined")
continue; // skip unset
const rosKey = fieldMap[key] || key;
if (rosKey === "comment" && typeof value === "string") {
command.push(`=comment=${value.toUpperCase()}`);
}
else if (rosKey === "disabled" && typeof value === "boolean") {
command.push(`=disabled=${value ? "yes" : "no"}`);
}
else {
command.push(`=${rosKey}=${value}`);
}
}
return this.write(command);
});
}
/**
* Updates a DHCP lease by ID and updates all firewall address-list entries
* (of any list) from the old IP to the new IP (preserving lists, updating comment if given).
*
* @param {string} id - The internal ID of the DHCP lease (must start with '*').
* @param {Object} params - Lease parameters to update.
* @param {string} [params.address] - The new IP address.
* @param {string} [params.macAddress] - The MAC address.
* @param {string} [params.server] - The DHCP server.
* @param {string} [params.comment] - Optional comment for the lease/address-list.
* @param {boolean} [params.disabled] - Disable the lease.
* @returns {Promise<boolean>} The response from RouterOS API.
*/
setDhcpLeaseAndUpdateAddressLists(id, params) {
return __awaiter(this, void 0, void 0, function* () {
if (!id || !id.startsWith("*"))
throw new Error("Lease ID required for setDhcpLease");
if (params.address && !(0, is_ip_1.isIPv4)(params.address))
throw new Error("Invalid IP Address!");
// 1. Get the old lease (for current IP and comment)
const oldLeaseArr = yield this.write([
"/ip/dhcp-server/lease/print",
`?.id=${id}`,
]);
if (!oldLeaseArr.length)
throw new Error(`Lease with ID ${id} not found`);
const oldLease = oldLeaseArr[0];
const oldIp = oldLease.address;
// 2. Update the lease
// -- param mapping as before --
const fieldMap = {
macAddress: "mac-address",
address: "address",
server: "server",
comment: "comment",
disabled: "disabled",
};
const command = ["/ip/dhcp-server/lease/set", `=.id=${id}`];
for (const [key, valueRaw] of Object.entries(params)) {
if (typeof valueRaw === "undefined")
continue;
const rosKey = fieldMap[key] || key;
const value = typeof valueRaw === "string" ? valueRaw.trim() : valueRaw;
if (rosKey === "comment" && typeof value === "string") {
command.push(`=comment=${value.toUpperCase()}`);
}
else if (rosKey === "disabled" && typeof value === "boolean") {
command.push(`=disabled=${value ? "yes" : "no"}`);
}
else {
command.push(`=${rosKey}=${value}`);
}
}
const response = yield this.write(command);
// 3. If IP changes, update all address-list entries with old IP
const newIp = params.address;
if (newIp && newIp !== oldIp) {
// Find all address-list entries with the old IP
const oldAddrEntries = yield this.write([
"/ip/firewall/address-list/print",
`?address=${oldIp}`,
]);
for (const entry of oldAddrEntries) {
if (entry[".id"]) {
// Use param comment > new lease comment > old comment > ""
let updatedComment = "";
if (typeof params.comment === "string" && params.comment.trim()) {
updatedComment = params.comment.trim();
}
else if (typeof oldLease.comment === "string" &&
oldLease.comment.trim()) {
updatedComment = oldLease.comment.trim();
}
else if (typeof entry.comment === "string" &&
entry.comment.trim()) {
updatedComment = entry.comment.trim();
}
yield this.write([
"/ip/firewall/address-list/set",
`=.id=${entry[".id"]}`,
`=address=${newIp}`,
`=comment=${updatedComment}`,
]);
}
}
}
return true;
});
}
/**
* Remove a DHCP lease by ID and remove the associated IP from all firewall address-lists.
*
* @param {string} id - The internal ID of the DHCP lease (must start with '*').
* @returns {Promise<boolean>} True if the lease (and related firewall entries) were removed.
* @throws {Error} If the ID is invalid or the lease is not found.
*/
removeDhcpLease(id) {
return __awaiter(this, void 0, void 0, function* () {
if (!id || !id.startsWith("*")) {
throw new Error("A valid lease ID is required!");
}
// Step 1: Lookup IP address for the lease ID
const leaseData = yield this.write([
"/ip/dhcp-server/lease/print",
`?.id=${id}`,
]);
if (!leaseData.length || !leaseData[0].address) {
throw new Error(`Lease with ID ${id} not found or missing address`);
}
const ip = leaseData[0].address;
// Step 2: Find all firewall address-list entries with this IP
const firewallEntries = yield this.write([
"/ip/firewall/address-list/print",
`?address=${ip}`,
]);
for (const entry of firewallEntries) {
if (entry[".id"]) {
yield this.write([
"/ip/firewall/address-list/remove",
`=.id=${entry[".id"]}`,
]);
}
}
// Step 3: Remove the DHCP lease itself
yield this.write(["/ip/dhcp-server/lease/remove", `=.id=${id}`]);
return true;
});
}
/**
* Fetches all DHCP servers along with IP addresses belonging to their interfaces.
*
* @returns {Promise<IRosGenericResponse[]>}
*/
getDhcpServersWithGateways() {
return __awaiter(this, void 0, void 0, function* () {
// 1. Get all DHCP servers
const servers = yield this.write(["/ip/dhcp-server/print"]);
if (!servers.length)
return [];
// 2. Get all IP addresses
const addresses = yield this.write(["/ip/address/print"]);
// 3. Map interface -> all its addresses (in form address + "/" + netmask or just as 'address')
const ifaceToAddresses = {};
for (const addr of addresses) {
const iface = addr.interface;
const ipCidr = addr.address; // e.g., "192.168.1.1/24"
if (!iface || !ipCidr)
continue;
if (!ifaceToAddresses[iface])
ifaceToAddresses[iface] = [];
ifaceToAddresses[iface].push(ipCidr);
}
// 4. Merge info per server
return servers.map((server) => {
const iface = server.interface;
const gateways = iface && ifaceToAddresses[iface] ? ifaceToAddresses[iface] : [];
return Object.assign(Object.assign({}, server), { gateway: gateways });
});
});
}
/**
* Returns all free (unleased) IP addresses in a DHCP server's network, excluding the gateway itself.
*
* @param {string} serverName - The DHCP server name (e.g., "dhcp1").
* @param {string} gatewayCidr - The network in CIDR format (e.g., "192.168.1.1/24").
* @returns {Promise<string[]>} Array of free IP addresses as strings.
* @throws {Error} If params are missing or invalid.
*/
getFreeIpsForDhcpServer(serverName, gatewayCidr) {
return __awaiter(this, void 0, void 0, function* () {
// --- Validate params ---
if (typeof serverName !== "string" || !serverName.trim()) {
throw new Error("serverName is required and must be a non-empty string.");
}
if (typeof gatewayCidr !== "string" ||
!/^(\d{1,3}\.){3}\d{1,3}\/\d{1,2}$/.test(gatewayCidr)) {
throw new Error("gatewayCidr is required and must be in valid CIDR format, e.g. '192.168.1.1/24'.");
}
// 1. Get all leases for the DHCP server
const leases = yield this.write([
"/ip/dhcp-server/lease/print",
`?server=${serverName.trim()}`,
]);
const leasedIps = new Set((leases || []).map((l) => l.address).filter(Boolean));
// Extract the gateway IP and prefix
const [gatewayIp, prefixStr] = gatewayCidr.split("/");
const prefix = Number(prefixStr);
// Prefix range check: only allow reasonable DHCP networks
if (isNaN(prefix) || prefix < 16 || prefix > 30) {
throw new Error("Invalid CIDR prefix. Only prefixes between /16 and /30 are supported.");
}
// 2. Generate all usable IPs in the subnet
const subnet = ip.cidrSubnet(gatewayCidr);
// Defensive: If CIDR is invalid, ip.cidrSubnet may produce .firstAddress/.lastAddress undefined
if (!subnet.firstAddress || !subnet.lastAddress) {
throw new Error("Invalid CIDR subnet provided.");
}
const allIps = [];
let curr = ip.toLong(subnet.firstAddress) + 1;
const last = ip.toLong(subnet.lastAddress);
while (curr <= last) {
allIps.push(ip.fromLong(curr));
curr++;
}
// Exclude leased/reserved IPs AND the gateway itself
return allIps.filter((addr) => !leasedIps.has(addr) && addr !== gatewayIp);
});
}
/**
* Finds a DHCP lease by its ID, gets its IP address, and adds that IP to the "Blocked" firewall address list.
* Checks for duplicates and supports optional comment.
*
* @param {string} leaseId - The ID of the DHCP lease (must start with '*')
* @param {string} [comment] - Optional comment for the address-list entry.
* @returns {Promise<boolean>} True if the IP was added, false if already present.
* @throws {Error} If the lease is not found or has no address.
*/
blockDhcpLeaseById(leaseId, comment) {
return __awaiter(this, void 0, void 0, function* () {
// --- Validate input ---
if (typeof leaseId !== "string" || !leaseId.startsWith("*")) {
throw new Error("A valid lease ID is required (must start with '*').");
}
// 1. Get the lease info by ID
const leaseArr = yield this.write([
"/ip/dhcp-server/lease/print",
`?.id=${leaseId}`,
]);
if (!leaseArr.length || !leaseArr[0].address) {
throw new Error(`Lease with ID ${leaseId} not found or has no address.`);
}
const ipAddress = leaseArr[0].address;
// Use comment: param > lease comment > blank
let entryComment = "Blocked by Billing";
if (typeof comment === "string" && comment.trim()) {
entryComment = comment;
}
else if (typeof leaseArr[0].comment === "string" &&
leaseArr[0].comment.trim()) {
entryComment = leaseArr[0].comment;
}
// 2. Check if already in address-list "Blocked"
const existing = yield this.write([
"/ip/firewall/address-list/print",
`?address=${ipAddress}`,
`?list=Blocked`,
]);
if (existing.length) {
// Already present, do not add again
return false;
}
// 3. Add to address-list "Blocked"
yield this.write([
"/ip/firewall/address-list/add",
`=address=${ipAddress}`,
`=list=Blocked`,
`=comment=${entryComment}`,
]);
return true;
});
}
/**
* Finds a DHCP lease by its ID, gets its IP address, and removes that IP from the "Blocked" firewall address list (if present).
*
* @param {string} leaseId - The ID of the DHCP lease (must start with '*')
* @returns {Promise<boolean>} True if an address-list entry was removed, false if not present.
* @throws {Error} If the lease is not found or has no address.
*/
unblockDhcpLeaseById(leaseId) {
return __awaiter(this, void 0, void 0, function* () {
// --- Validate input ---
if (typeof leaseId !== "string" || !leaseId.startsWith("*")) {
throw new Error("A valid lease ID is required (must start with '*').");
}
// 1. Get the lease info by ID
const leaseArr = yield this.write([
"/ip/dhcp-server/lease/print",
`?.id=${leaseId}`,
]);
if (!leaseArr.length || !leaseArr[0].address) {
throw new Error(`Lease with ID ${leaseId} not found or has no address.`);
}
const ipAddress = leaseArr[0].address;
// 2. Check for address-list entry in "Blocked"
const existing = yield this.write([
"/ip/firewall/address-list/print",
`?address=${ipAddress}`,
`?list=Blocked`,
]);
if (!existing.length) {
// Not present
return false;
}
// 3. Remove all matching entries (just in case there are duplicates)
for (const entry of existing) {
if (entry[".id"]) {
yield this.write([
"/ip/firewall/address-list/remove",
`=.id=${entry[".id"]}`,
]);
}
}
return true;
});
}
/**
* Exports the current MikroTik configuration to a dated .rsc file on the device.
*
* @param {string} [filename] - Optional base filename (without date or extension).
* @returns {Promise<string>} The actual filename used for the export (e.g., "backup_2025-06-10.rsc").
* @throws {Error} If writing to the router fails.
*/
exportConfig(filename) {
return __awaiter(this, void 0, void 0, function* () {
// Format date as yyyy-mm-dd using toISOString
const dateStr = new Date().toISOString().slice(0, 10); // 'yyyy-mm-dd'
// Use provided filename or "backup" as default
let baseName = filename && filename.trim() ? filename.trim() : "backup";
// Remove .rsc extension if present
if (baseName.toLowerCase().endsWith(".rsc")) {
baseName = baseName.slice(0, -4);
}
// Replace illegal/special characters with "_"
baseName = baseName.replace(/[^a-zA-Z0-9_\-]/g, "_");
// Compose the final filename
const finalName = `${baseName}_${dateStr}.rsc`;
// Send export command to MikroTik API
yield this.write(["/export", `=file=${finalName}`]);
return finalName;
});
}
/**
* Imports a MikroTik .rsc configuration file using the API.
*
* @param {string} filename - The filename to import (must end with .rsc and already exist on the device).
* @returns {Promise<boolean>} True if the command was sent successfully.
* @throws {Error} If the filename is missing or invalid.
*/
importConfig(filename) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof filename !== "string" ||
!filename.trim() ||
!filename.trim().toLowerCase().endsWith(".rsc")) {
throw new Error("A valid filename ending with '.rsc' is required.");
}
const safeFilename = filename.trim().replace(/[^a-zA-Z0-9_\-\.]/g, "_");
yield this.write(["/import", `=file=${safeFilename}`]);
return true;
});
}
/**
* Get firewall rules
*/
getFirewallRules(chain) {
return __awaiter(this, void 0, void 0, function* () {
const params = ["/ip/firewall/filter/print"];
if (chain) {
params.push(`?chain=${chain}`);
}
return this.write(params);
});
}
/**
* Add a firewall rule
*/
addFirewallRule(chain, action, srcAddress, dstAddress, protocol, port) {
return __awaiter(this, void 0, void 0, function* () {
const params = [
"/ip/firewall/filter/add",
`=chain=${chain}`,
`=action=${action}`,
];
if (srcAddress)
params.push(`=src-address=${srcAddress}`);
if (dstAddress)
params.push(`=dst-address=${dstAddress}`);
if (protocol)
params.push(`=protocol=${protocol}`);
if (port)
params.push(`=dst-port=${port}`);
return this.write(params);
});
}
/**
* Get wireless interfaces
*/
getWirelessInterfaces() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/interface/wireless/print");
});
}
/**
* Get wireless registration table
*/
getWirelessRegistrations() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/interface/wireless/registration-table/print");
});
}
/**
* Stream wireless registration table
*/
streamWirelessRegistrations(callback) {
return this.stream("/interface/wireless/registration-table/print", callback);
}
/**
* Get bridge interfaces
*/
getBridges() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/interface/bridge/print");
});
}
/**
* Get routing table
*/
getRoutes() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/ip/route/print");
});
}
/**
* Add a route
*/
addRoute(dstAddress, gateway, distance) {
return __awaiter(this, void 0, void 0, function* () {
const params = [
"/ip/route/add",
`=dst-address=${dstAddress}`,
`=gateway=${gateway}`,
];
if (distance !== undefined) {
params.push(`=distance=${distance}`);
}
return this.write(params);
});
}
/**
* Get system logs
*/
getSystemLogs(topics) {
return __awaiter(this, void 0, void 0, function* () {
const params = ["/log/print"];
if (topics && topics.length > 0) {
params.push(`?topics=${topics.join(",")}`);
}
return this.write(params);
});
}
/**
* Stream system logs
*/
streamSystemLogs(callback) {
return this.stream("/log/print", "?follow=yes", callback);
}
/**
* Reboot the device
*/
reboot() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/system/reboot");
});
}
/**
* Get system identity
*/
getSystemIdentity() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/system/identity/print");
});
}
/**
* Set system identity
*/
setSystemIdentity(name) {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/system/identity/set", `=name=${name}`);
});
}
/**
* Get user list
*/
getUsers() {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/user/print");
});
}
/**
* Add a user
*/
addUser(name, password, group) {
return __awaiter(this, void 0, void 0, function* () {
const params = ["/user/add", `=name=${name}`, `=password=${password}`];
if (group) {
params.push(`=group=${group}`);
}
return this.write(params);
});
}
/**
* Execute a script
*/
runScript(scriptName) {
return __awaiter(this, void 0, void 0, function* () {
return this.write("/system/script/run", `=.id=${scriptName}`);
});
}
/**
* Get interface statistics
*/
getInterfaceStats(interfaceName) {
return __awaiter(this, void 0, void 0, function* () {
const params = ["/interface/monitor-traffic"];
if (interfaceName) {
params.push(`=interface=${interfaceName}`);
}
params.push("=once=yes");
return this.write(params);
});
}
/**
* Stream interface statistics with customizable parameters
*/
streamInterfaceStats(params, callback) {
const command = [
"/interface/monitor-traffic",
`=interface=${params.interface}`,
];
if (params.interval !== undefined) {
command.push(`=interval=${params.interval}`);
}
if (params.duration !== undefined) {
command.push(`=duration=${params.duration}`);
}
return this.stream(command, callback);
}
}
exports.MikrotikAPI = MikrotikAPI;
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiTWlrcm90aWtBUEkuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvTWlrcm90aWtBUEkudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6Ijs7Ozs7Ozs7Ozs7O0FBQUEsK0NBQTRDO0FBSTVDLGlDQUErQjtBQUMvQix5QkFBeUI7QUFFekI7O0dBRUc7QUFDSCxNQUFhLFdBQVksU0FBUSx5QkFBVztJQUMxQyxZQUFZLE9BQW9CO1FBQzlCLEtBQUssQ0FBQyxPQUFPLENBQUMsQ0FBQztJQUNqQixDQUFDO0lBRUQ7O09BRUc7SUFDVSxhQUFhOztZQUN4QixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsMkJBQTJCLENBQUMsQ0FBQztRQUNqRCxDQUFDO0tBQUE7SUFFRDs7T0FFRztJQUNVLGtCQUFrQjs7WUFDN0IsT0FBTyxJQUFJLENBQUMsS0FBSyxDQUFDLHdCQUF3QixDQUFDLENBQUM7UUFDOUMsQ0FBQztLQUFBO0lBRUQ7O09BRUc7SUFDVSxhQUFhOztZQUN4QixPQUFPLElBQUksQ0FBQyxLQUFLLENBQUMsa0JBQWtCLENBQUMsQ0FBQztRQUN4QyxDQUFDO0tBQUE7SUFFRDs7T0FFRztJQUNVLGNBQWM7O1lBQ3pCLE9BQU8sSUFBSSxDQUFDLEtBQUssQ0FBQyxtQkFBbUIsQ0FBQyxDQUFDO1FBQ3pDLENBQUM7S0FBQTtJQUVEOzs7Ozs7Ozs7OztPQVdHO0lBQ1UsWUFBWSxDQUFDLEtBTXpCOztZQUNDLElBQ0UsQ0FBQyxLQUFLO2dCQUNOLENBQUMsS0FBSyxDQUFDLE9BQU87Z0JBQ2QsQ0FBQyxJQUFBLGNBQU0sRUFBQyxLQUFLLENBQUMsT0FBTyxDQUFDO2dCQUN0QixDQUFDLEtBQUssQ0FBQyxVQUFVO2dCQUNqQixDQUFDLEtBQUssQ0FBQyxNQUFNLEVBQ2IsQ0FBQztnQkFDRCxNQUFNLElBQUksS0FBSyxDQUNiLDBEQUEwRCxDQUMzRCxDQUFDO1lBQ0osQ0FBQztZQUNELE1BQU0sT0FBTyxHQUFHO2dCQUNkLDJCQUEyQjtnQkFDM0IsWUFB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