<commit_before>import net from 'net';
import crypto from 'crypto';
import ip from 'ip';
import url from 'urijs';

export const ATYP_V4 = 1;
export const ATYP_DOMAIN = 3;
export const ATYP_V6 = 4;

export const BYTE_ORDER_BE = 0;
export const BYTE_ORDER_LE = 1;

/**
 * convert a number to a buffer with specified length in specified byte order
 * @param num
 * @param len
 * @param byteOrder
 * @returns {Buffer}
 */
export function numberToBuffer(num, len = 2, byteOrder = BYTE_ORDER_BE) {
  if (len < 1) {
    throw Error('len must be greater than 0');
  }

  const isOutOfRange = num > parseInt(`0x${'ff'.repeat(len)}`);
  if (isOutOfRange) {
    throw Error(`Number ${num} is too long to store in a '${len}' length buffer`);
  }

  const buf = Buffer.alloc(len);
  if (byteOrder === BYTE_ORDER_BE) {
    buf.writeUIntBE(num, 0, len);
  } else {
    buf.writeUIntLE(num, 0, len);
  }
  return buf;
}

/**
 * convert a http(s) url to an address with type, host and port
 * @param uri
 * @returns {{type: Number, host: Buffer, port: Buffer}}
 */
export function parseURI(uri) {
  let _uri = uri;
  let _port = null;

  if (_uri.startsWith('http://')) {
    _uri = _uri.substr(7);
    _port = 80;
  }

  if (_uri.startsWith('https://')) {
    _uri = _uri.substr(8);
    _port = 443;
  }

  const parts = {};
  url.parseHost(_uri, parts);

  const {hostname, port} = parts;
  const addrType = net.isIP(hostname) ? (net.isIPv4(hostname) ? ATYP_V4 : ATYP_V6) : ATYP_DOMAIN;

  return {
    type: addrType,
    host: net.isIP(hostname) ? ip.toBuffer(hostname) : Buffer.from(hostname),
    port: numberToBuffer(port || _port || 80)
  };
}

/**
 * returns a random integer in [min, max].
 * @param min
 * @param max
 * @returns {Number}
 */
export function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.ceil(max);
  return Math.floor(crypto.randomBytes(1)[0] / 0xff * (max - min + 1)) + min;
}

/**
 * split buffer into chunks, each chunk size is picked randomly from [min, max]
 * @param buffer
 * @param min
 * @param max
 * @returns {Array<Buffer>}
 */
export function getRandomChunks(buffer, min, max) {
  const totalLen = buffer.length;
  const bufs = [];
  let ptr = 0;
  while (ptr < totalLen - 1) {
    const offset = getRandomInt(min, max);
    bufs.push(buffer.slice(ptr, ptr + offset));
    ptr += offset;
  }
  if (ptr < totalLen) {
    bufs.push(buffer.slice(ptr));
  }
  return bufs;
}

/**
 * split buffer into chunks, the max chunk size is maxSize
 * @param buffer
 * @param maxSize
 * @returns {Array<Buffer>}
 */
export function getChunks(buffer, maxSize) {
  const totalLen = buffer.length;
  const bufs = [];
  let ptr = 0;
  while (ptr < totalLen - 1) {
    bufs.push(buffer.slice(ptr, ptr + maxSize));
    ptr += maxSize;
  }
  if (ptr < totalLen) {
    bufs.push(buffer.slice(ptr));
  }
  return bufs;
}

/**
 * return UTC timestamp as buffer
 * @returns {Buffer}
 */
export function getUTC() {
  const ts = Math.floor((new Date()).getTime() / 1e3);
  return numberToBuffer(ts, 4, BYTE_ORDER_BE);
}

/**
 * convert string to buffer
 * @param str
 * @returns {Buffer}
 */
export function hexStringToBuffer(str) {
  return Buffer.from(str, 'hex');
}

/**
 * verify hostname
 *
 * @param hostname
 * @returns {boolean}
 *
 * @reference
 *   http://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php
 */
export function isValidHostname(hostname) {
  // overall length check
  if (hostname.length < 1 || hostname.length > 253) {
    return false;
  }
  // valid chars check
  if (/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname) === false) {
    return false;
  }
  // length of each label
  if (/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname) === false) {
    return false;
  }
  return true;
}

/**
 * whether a port is valid or not
 * @param port
 * @returns {boolean}
 */
export function isValidPort(port) {
  if (typeof port !== 'number') {
    return false;
  }
  if (port < 0 || port > 65535) {
    return false;
  }
  return true;
}

/**
 * message digest
 * @param algorithm
 * @param buffer
 * @returns {*}
 */
export function hash(algorithm, buffer) {
  const hs = crypto.createHash(algorithm);
  hs.update(buffer);
  return hs.digest();
}

/**
 * calculate the HMAC from key and message
 * @param algorithm
 * @param key
 * @param buffer
 * @returns {Buffer}
 */
export function hmac(algorithm, key, buffer) {
  const hmac = crypto.createHmac(algorithm, key);
  return hmac.update(buffer).digest();
}

/**
 * EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt
 *
 * @algorithm
 *   D_i = HASH^count(D_(i-1) || data || salt)
 */
export function EVP_BytesToKey(password, keyLen, ivLen) {
  let _data = Buffer.from(password);
  let i = 0;
  const bufs = [];
  while (Buffer.concat(bufs).length < (keyLen + ivLen)) {
    if (i > 0) {
      _data = Buffer.concat([bufs[i - 1], Buffer.from(password)]);
    }
    bufs.push(hash('md5', _data));
    i += 1;
  }
  return Buffer.concat(bufs).slice(0, keyLen);
}

/**
 * HMAC-based Extract-and-Expand Key Derivation Function
 * @param hash, the message digest algorithm
 * @param salt, a non-secret random value
 * @param ikm, input keying material
 * @param info, optional context and application specific information
 * @param length, length of output keying material in octets
 * @returns {Buffer}
 */
export function HKDF(hash, salt, ikm, info, length) {
  // Step 1: "extract" to fixed length pseudo-random key(prk)
  const prk = hmac(hash, salt, ikm);
  // Step 2: "expand" prk to several pseudo-random keys(okm)
  let t = Buffer.alloc(0);
  let okm = Buffer.alloc(0);
  for (let i = 0; i < Math.ceil(length / prk.length); ++i) {
    t = hmac(hash, prk, Buffer.concat([t, info, Buffer.alloc(1, i + 1)]));
    okm = Buffer.concat([okm, t]);
  }
  // Step 3: crop okm to desired length
  return okm.slice(0, length);
}
<commit_msg>utils: add Xor()<commit_after>import net from 'net';
import crypto from 'crypto';
import ip from 'ip';
import url from 'urijs';

export const ATYP_V4 = 1;
export const ATYP_DOMAIN = 3;
export const ATYP_V6 = 4;

export const BYTE_ORDER_BE = 0;
export const BYTE_ORDER_LE = 1;

/**
 * convert a number to a buffer with specified length in specified byte order
 * @param num
 * @param len
 * @param byteOrder
 * @returns {Buffer}
 */
export function numberToBuffer(num, len = 2, byteOrder = BYTE_ORDER_BE) {
  if (len < 1) {
    throw Error('len must be greater than 0');
  }

  const isOutOfRange = num > parseInt(`0x${'ff'.repeat(len)}`);
  if (isOutOfRange) {
    throw Error(`Number ${num} is too long to store in a '${len}' length buffer`);
  }

  const buf = Buffer.alloc(len);
  if (byteOrder === BYTE_ORDER_BE) {
    buf.writeUIntBE(num, 0, len);
  } else {
    buf.writeUIntLE(num, 0, len);
  }
  return buf;
}

/**
 * convert a http(s) url to an address with type, host and port
 * @param uri
 * @returns {{type: Number, host: Buffer, port: Buffer}}
 */
export function parseURI(uri) {
  let _uri = uri;
  let _port = null;

  if (_uri.startsWith('http://')) {
    _uri = _uri.substr(7);
    _port = 80;
  }

  if (_uri.startsWith('https://')) {
    _uri = _uri.substr(8);
    _port = 443;
  }

  const parts = {};
  url.parseHost(_uri, parts);

  const {hostname, port} = parts;
  const addrType = net.isIP(hostname) ? (net.isIPv4(hostname) ? ATYP_V4 : ATYP_V6) : ATYP_DOMAIN;

  return {
    type: addrType,
    host: net.isIP(hostname) ? ip.toBuffer(hostname) : Buffer.from(hostname),
    port: numberToBuffer(port || _port || 80)
  };
}

/**
 * returns a random integer in [min, max].
 * @param min
 * @param max
 * @returns {Number}
 */
export function getRandomInt(min, max) {
  min = Math.ceil(min);
  max = Math.ceil(max);
  return Math.floor(crypto.randomBytes(1)[0] / 0xff * (max - min + 1)) + min;
}

/**
 * split buffer into chunks, each chunk size is picked randomly from [min, max]
 * @param buffer
 * @param min
 * @param max
 * @returns {Array<Buffer>}
 */
export function getRandomChunks(buffer, min, max) {
  const totalLen = buffer.length;
  const bufs = [];
  let ptr = 0;
  while (ptr < totalLen - 1) {
    const offset = getRandomInt(min, max);
    bufs.push(buffer.slice(ptr, ptr + offset));
    ptr += offset;
  }
  if (ptr < totalLen) {
    bufs.push(buffer.slice(ptr));
  }
  return bufs;
}

/**
 * split buffer into chunks, the max chunk size is maxSize
 * @param buffer
 * @param maxSize
 * @returns {Array<Buffer>}
 */
export function getChunks(buffer, maxSize) {
  const totalLen = buffer.length;
  const bufs = [];
  let ptr = 0;
  while (ptr < totalLen - 1) {
    bufs.push(buffer.slice(ptr, ptr + maxSize));
    ptr += maxSize;
  }
  if (ptr < totalLen) {
    bufs.push(buffer.slice(ptr));
  }
  return bufs;
}

/**
 * return UTC timestamp as buffer
 * @returns {Buffer}
 */
export function getUTC() {
  const ts = Math.floor((new Date()).getTime() / 1e3);
  return numberToBuffer(ts, 4, BYTE_ORDER_BE);
}

/**
 * convert string to buffer
 * @param str
 * @returns {Buffer}
 */
export function hexStringToBuffer(str) {
  return Buffer.from(str, 'hex');
}

/**
 * verify hostname
 *
 * @param hostname
 * @returns {boolean}
 *
 * @reference
 *   http://stackoverflow.com/questions/1755144/how-to-validate-domain-name-in-php
 */
export function isValidHostname(hostname) {
  // overall length check
  if (hostname.length < 1 || hostname.length > 253) {
    return false;
  }
  // valid chars check
  if (/^([a-z\d](-*[a-z\d])*)(\.([a-z\d](-*[a-z\d])*))*$/i.test(hostname) === false) {
    return false;
  }
  // length of each label
  if (/^[^.]{1,63}(\.[^.]{1,63})*$/.test(hostname) === false) {
    return false;
  }
  return true;
}

/**
 * whether a port is valid or not
 * @param port
 * @returns {boolean}
 */
export function isValidPort(port) {
  if (typeof port !== 'number') {
    return false;
  }
  if (port < 0 || port > 65535) {
    return false;
  }
  return true;
}

/**
 * message digest
 * @param algorithm
 * @param buffer
 * @returns {*}
 */
export function hash(algorithm, buffer) {
  const hs = crypto.createHash(algorithm);
  hs.update(buffer);
  return hs.digest();
}

/**
 * calculate the HMAC from key and message
 * @param algorithm
 * @param key
 * @param buffer
 * @returns {Buffer}
 */
export function hmac(algorithm, key, buffer) {
  const hmac = crypto.createHmac(algorithm, key);
  return hmac.update(buffer).digest();
}

/**
 * EVP_BytesToKey with the digest algorithm set to MD5, one iteration, and no salt
 *
 * @algorithm
 *   D_i = HASH^count(D_(i-1) || data || salt)
 */
export function EVP_BytesToKey(password, keyLen, ivLen) {
  let _data = Buffer.from(password);
  let i = 0;
  const bufs = [];
  while (Buffer.concat(bufs).length < (keyLen + ivLen)) {
    if (i > 0) {
      _data = Buffer.concat([bufs[i - 1], Buffer.from(password)]);
    }
    bufs.push(hash('md5', _data));
    i += 1;
  }
  return Buffer.concat(bufs).slice(0, keyLen);
}

/**
 * HMAC-based Extract-and-Expand Key Derivation Function
 * @param hash, the message digest algorithm
 * @param salt, a non-secret random value
 * @param ikm, input keying material
 * @param info, optional context and application specific information
 * @param length, length of output keying material in octets
 * @returns {Buffer}
 */
export function HKDF(hash, salt, ikm, info, length) {
  // Step 1: "extract" to fixed length pseudo-random key(prk)
  const prk = hmac(hash, salt, ikm);
  // Step 2: "expand" prk to several pseudo-random keys(okm)
  let t = Buffer.alloc(0);
  let okm = Buffer.alloc(0);
  for (let i = 0; i < Math.ceil(length / prk.length); ++i) {
    t = hmac(hash, prk, Buffer.concat([t, info, Buffer.alloc(1, i + 1)]));
    okm = Buffer.concat([okm, t]);
  }
  // Step 3: crop okm to desired length
  return okm.slice(0, length);
}

/**
 * xor two Buffer/Array
 * @param buf1
 * @param buf2
 * @returns {*}
 */
export function Xor(buf1, buf2) {
  if (buf1.length === buf2.length) {
    const outBuf = [];
    for (let i = 0; i < buf1.length; ++i) {
      outBuf[i] = buf1[i] ^ buf2[i];
    }
    if (buf1 instanceof Buffer) {
      return Buffer.from(outBuf);
    } else {
      return outBuf;
    }
  }
  return null;
}
<|endoftext|>


<commit_before>import CoreCombatLogParser from 'parser/core/CombatLogParser';
import LowHealthHealing from 'parser/shared/modules/features/LowHealthHealing';
import ManaLevelChart from 'parser/shared/modules/resources/mana/ManaLevelChart';
import ManaUsageChart from 'parser/shared/modules/resources/mana/ManaUsageChart';

import LightOfDawnNormalizer from './normalizers/LightOfDawn';
import DivinePurposeNormalizer from './normalizers/DivinePurpose';
import BeaconOfVirtueNormalizer from './normalizers/BeaconOfVirtue';

import BeaconTransferFactor from './modules/beacons/BeaconTransferFactor';
import BeaconHealSource from './modules/beacons/BeaconHealSource';
import BeaconHealingDone from './modules/beacons/BeaconHealingDone';
import BeaconTargets from './modules/beacons/BeaconTargets';
import MissingBeacons from './modules/beacons/MissingBeacons';
import FailedBeaconTransfers from './modules/beacons/FailedBeaconTransfers';
import DirectBeaconHealing from './modules/beacons/DirectBeaconHealing';

import PaladinAbilityTracker from './modules/core/PaladinAbilityTracker';
import CastBehavior from './modules/CastBehavior';
import Overhealing from './modules/Overhealing';
import FillerLightOfTheMartyrs from './modules/FillerLightOfTheMartyrs';
import InefficientLightOfTheMartyrs from './modules/InefficientLightOfTheMartyrs';
import FillerFlashOfLight from './modules/FillerFlashOfLight';
import LightOfDawn from './modules/LightOfDawn';
import LightOfDawnIndexer from './modules/LightOfDawnIndexer';
import SpellManaCost from './modules/core/SpellManaCost';

import Abilities from './modules/Abilities';
import Buffs from './modules/Buffs';
import Checklist from './modules/checklist/Module';
import MasteryEffectiveness from './modules/MasteryEffectiveness';
import AlwaysBeCasting from './modules/AlwaysBeCasting';
import CooldownThroughputTracker from './modules/CooldownThroughputTracker';
import StatValues from './modules/StatValues';

import MightOfTheMountain from './modules/MightOfTheMountain';

import RuleOfLaw from './modules/talents/RuleOfLaw';
import DevotionAuraDamageReduction from './modules/talents/DevotionAuraDamageReduction';
import AuraOfSacrificeDamageReduction from './modules/talents/AuraOfSacrificeDamageReduction';
import AuraOfMercy from './modules/talents/AuraOfMercy';
import HolyAvenger from './modules/talents/HolyAvenger';
import DivinePurpose from './modules/talents/DivinePurpose';
import CrusadersMight from './modules/talents/CrusadersMight';
import AvengingCrusader from './modules/talents/AvengingCrusader';

// azurite traits //
import GraceOfTheJusticar from './modules/azeritetraits/GraceOfTheJusticar';
import GlimmerOfLight from './modules/azeritetraits/GlimmerOfLight';
import RadiantIncandescence from './modules/azeritetraits/RadiantIncandescence';

import { ABILITIES_AFFECTED_BY_HEALING_INCREASES } from './constants';

class CombatLogParser extends CoreCombatLogParser {
  static abilitiesAffectedByHealingIncreases = ABILITIES_AFFECTED_BY_HEALING_INCREASES;

  static specModules = {
    // Normalizers
    lightOfDawnNormalizer: LightOfDawnNormalizer,
    divinePurposeNormalizer: DivinePurposeNormalizer,
    beaconOfVirtueNormalizer: BeaconOfVirtueNormalizer,

    // Override the ability tracker so we also get stats for IoL and beacon healing
    abilityTracker: PaladinAbilityTracker,
    lowHealthHealing: LowHealthHealing,

    // PaladinCore
    beaconTransferFactor: BeaconTransferFactor,
    beaconHealSource: BeaconHealSource,
    beaconHealingDone: BeaconHealingDone,
    beaconTargets: BeaconTargets,
    missingBeacons: MissingBeacons,
    failedBeaconTransfers: FailedBeaconTransfers,
    directBeaconHealing: DirectBeaconHealing,
    castBehavior: CastBehavior,
    overhealing: Overhealing,
    fillerLightOfTheMartyrs: FillerLightOfTheMartyrs,
    inefficientLightOfTheMartyrs: InefficientLightOfTheMartyrs,
    fillerFlashOfLight: FillerFlashOfLight,
    lightOfDawn: LightOfDawn,
    lightOfDawnIndexer: LightOfDawnIndexer,
    spellManaCost: SpellManaCost,

    // Generic healer things
    manaLevelChart: ManaLevelChart,
    manaUsageChart: ManaUsageChart,

    // Features
    checklist: Checklist,
    abilities: Abilities,
    buffs: Buffs,
    masteryEffectiveness: MasteryEffectiveness,
    alwaysBeCasting: AlwaysBeCasting,
    cooldownThroughputTracker: CooldownThroughputTracker,
    statValues: StatValues,

    // Racials
    mightOfTheMountain: MightOfTheMountain,

    // Talents
    ruleOfLaw: RuleOfLaw,
    devotionAuradamageReduction: DevotionAuraDamageReduction,
    auraOfSacrificeDamageReduction: AuraOfSacrificeDamageReduction,
    auraOfMercy: AuraOfMercy,
    holyAvenger: HolyAvenger,
    divinePurpose: DivinePurpose,
    crusadersMight: CrusadersMight,
    avengingCrusader: AvengingCrusader,

    // Azureite Traits //
    graceOfTheJusticar: GraceOfTheJusticar,
    radiantIncandescence: RadiantIncandescence,
    glimmerOfLight: GlimmerOfLight,
  };
}

export default CombatLogParser;
<commit_msg>Fix azerite traits typo<commit_after>import CoreCombatLogParser from 'parser/core/CombatLogParser';
import LowHealthHealing from 'parser/shared/modules/features/LowHealthHealing';
import ManaLevelChart from 'parser/shared/modules/resources/mana/ManaLevelChart';
import ManaUsageChart from 'parser/shared/modules/resources/mana/ManaUsageChart';

import LightOfDawnNormalizer from './normalizers/LightOfDawn';
import DivinePurposeNormalizer from './normalizers/DivinePurpose';
import BeaconOfVirtueNormalizer from './normalizers/BeaconOfVirtue';

import BeaconTransferFactor from './modules/beacons/BeaconTransferFactor';
import BeaconHealSource from './modules/beacons/BeaconHealSource';
import BeaconHealingDone from './modules/beacons/BeaconHealingDone';
import BeaconTargets from './modules/beacons/BeaconTargets';
import MissingBeacons from './modules/beacons/MissingBeacons';
import FailedBeaconTransfers from './modules/beacons/FailedBeaconTransfers';
import DirectBeaconHealing from './modules/beacons/DirectBeaconHealing';

import PaladinAbilityTracker from './modules/core/PaladinAbilityTracker';
import CastBehavior from './modules/CastBehavior';
import Overhealing from './modules/Overhealing';
import FillerLightOfTheMartyrs from './modules/FillerLightOfTheMartyrs';
import InefficientLightOfTheMartyrs from './modules/InefficientLightOfTheMartyrs';
import FillerFlashOfLight from './modules/FillerFlashOfLight';
import LightOfDawn from './modules/LightOfDawn';
import LightOfDawnIndexer from './modules/LightOfDawnIndexer';
import SpellManaCost from './modules/core/SpellManaCost';

import Abilities from './modules/Abilities';
import Buffs from './modules/Buffs';
import Checklist from './modules/checklist/Module';
import MasteryEffectiveness from './modules/MasteryEffectiveness';
import AlwaysBeCasting from './modules/AlwaysBeCasting';
import CooldownThroughputTracker from './modules/CooldownThroughputTracker';
import StatValues from './modules/StatValues';

import MightOfTheMountain from './modules/MightOfTheMountain';

import RuleOfLaw from './modules/talents/RuleOfLaw';
import DevotionAuraDamageReduction from './modules/talents/DevotionAuraDamageReduction';
import AuraOfSacrificeDamageReduction from './modules/talents/AuraOfSacrificeDamageReduction';
import AuraOfMercy from './modules/talents/AuraOfMercy';
import HolyAvenger from './modules/talents/HolyAvenger';
import DivinePurpose from './modules/talents/DivinePurpose';
import CrusadersMight from './modules/talents/CrusadersMight';
import AvengingCrusader from './modules/talents/AvengingCrusader';

// azerite traits //
import GraceOfTheJusticar from './modules/azeritetraits/GraceOfTheJusticar';
import GlimmerOfLight from './modules/azeritetraits/GlimmerOfLight';
import RadiantIncandescence from './modules/azeritetraits/RadiantIncandescence';

import { ABILITIES_AFFECTED_BY_HEALING_INCREASES } from './constants';

class CombatLogParser extends CoreCombatLogParser {
  static abilitiesAffectedByHealingIncreases = ABILITIES_AFFECTED_BY_HEALING_INCREASES;

  static specModules = {
    // Normalizers
    lightOfDawnNormalizer: LightOfDawnNormalizer,
    divinePurposeNormalizer: DivinePurposeNormalizer,
    beaconOfVirtueNormalizer: BeaconOfVirtueNormalizer,

    // Override the ability tracker so we also get stats for IoL and beacon healing
    abilityTracker: PaladinAbilityTracker,
    lowHealthHealing: LowHealthHealing,

    // PaladinCore
    beaconTransferFactor: BeaconTransferFactor,
    beaconHealSource: BeaconHealSource,
    beaconHealingDone: BeaconHealingDone,
    beaconTargets: BeaconTargets,
    missingBeacons: MissingBeacons,
    failedBeaconTransfers: FailedBeaconTransfers,
    directBeaconHealing: DirectBeaconHealing,
    castBehavior: CastBehavior,
    overhealing: Overhealing,
    fillerLightOfTheMartyrs: FillerLightOfTheMartyrs,
    inefficientLightOfTheMartyrs: InefficientLightOfTheMartyrs,
    fillerFlashOfLight: FillerFlashOfLight,
    lightOfDawn: LightOfDawn,
    lightOfDawnIndexer: LightOfDawnIndexer,
    spellManaCost: SpellManaCost,

    // Generic healer things
    manaLevelChart: ManaLevelChart,
    manaUsageChart: ManaUsageChart,

    // Features
    checklist: Checklist,
    abilities: Abilities,
    buffs: Buffs,
    masteryEffectiveness: MasteryEffectiveness,
    alwaysBeCasting: AlwaysBeCasting,
    cooldownThroughputTracker: CooldownThroughputTracker,
    statValues: StatValues,

    // Racials
    mightOfTheMountain: MightOfTheMountain,

    // Talents
    ruleOfLaw: RuleOfLaw,
    devotionAuradamageReduction: DevotionAuraDamageReduction,
    auraOfSacrificeDamageReduction: AuraOfSacrificeDamageReduction,
    auraOfMercy: AuraOfMercy,
    holyAvenger: HolyAvenger,
    divinePurpose: DivinePurpose,
    crusadersMight: CrusadersMight,
    avengingCrusader: AvengingCrusader,

    // Azureite Traits //
    graceOfTheJusticar: GraceOfTheJusticar,
    radiantIncandescence: RadiantIncandescence,
    glimmerOfLight: GlimmerOfLight,
  };
}

export default CombatLogParser;
<|endoftext|>


<commit_before>/**
* Generated by PluginGenerator from webgme on Mon Apr 28 2014 13:58:17 GMT-0500 (Central Daylight Time).
*/

define(['plugin/PluginConfig',
    'plugin/PluginBase',
    'jszip',
    'xmljsonconverter'], function (PluginConfig, PluginBase, JSZip, Converter) {
    'use strict';

    /**
    * Initializes a new instance of FmuImporter.
    * @class
    * @augments {PluginBase}
    * @classdesc This class represents the plugin FmuImporter.
    * @constructor
    */
    var FmuImporter = function () {
        // Call base class' constructor.
        PluginBase.call(this);
    };

    // Prototypal inheritance from PluginBase.
    FmuImporter.prototype = Object.create(PluginBase.prototype);
    FmuImporter.prototype.constructor = FmuImporter;

    /**
    * Gets the name of the FmuImporter.
    * @returns {string} The name of the plugin.
    * @public
    */
    FmuImporter.prototype.getName = function () {
        return "FMU Model Importer";
    };

    /**
    * Gets the semantic version (semver.org) of the FmuImporter.
    * @returns {string} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getVersion = function () {
        return "0.1.0";
    };

    /**
    * Gets the description of the FmuImporter.
    * @returns {string} The description of the plugin.
    * @public
    */
    FmuImporter.prototype.getDescription = function () {
        return "Creates WebGME models for one or more uploaded FMUs";
    };

    /**
    * Gets the configuration structure for the FmuImporter.
    * The ConfigurationStructure defines the configuration for the plugin
    * and will be used to populate the GUI when invoking the plugin from webGME.
    * @returns {object} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getConfigStructure = function () {
        return [
            {
                "name": "FMU",
                "displayName": "FmuPackage",
                "description": 'Click and drag an existing compiled FMU',
                "value": "", // this is the 'default config'
                "valueType": "asset",
                "readOnly": false
            }
        ];
    };

    /**
    * Main function for the plugin to execute. This will perform the execution.
    * Notes:
    * - Always log with the provided logger.[error,warning,info,debug].
    * - Do NOT put any user interaction logic UI, etc. inside this method.
    * - callback always has to be called even if error happened.
    *
    * @param {function(string, plugin.PluginResult)} mainCallback - the result callback
    */
    FmuImporter.prototype.main = function (mainCallback) {
        // Use self to access core, project, result, logger etc from PluginBase.
        // These are all instantiated at this point.
        var self = this,
            selectedNode = self.activeNode,
            currentConfig = self.getCurrentConfig(),
            currentConfigString = JSON.stringify(currentConfig, null, 4),
            fmuHash = currentConfig.FMU,
            fmuJsonDict;

        self.logger.debug('Entering FmuImporter main');

        self.logger.debug('CurrentConfig:');
        self.logger.debug(currentConfigString);

//        var newFmuObject = core.createNode({parent: rootNode, base: '/1822302751/902541625'});  //FMU base object
//        core.newFmuObject(newFmuObject, 'resource', fmuHash);
//        core.newFmuObject(newFmuObject, 'name', 'Here_is_a_brand_new_FMU');

        // TODO: Zsolt please upzip the FMU associated with 'fmuHash' and get the XML only
        self.getFmuModelDescriptionXml(fmuHash, function (err, fmuModelDescriptionXml) {
            if (err) {
                self.logger.error(err);
                return;
            }

            // TODO: Patrik please convert the XML to JSON
            fmuJsonDict = self.convertXml2Json(fmuModelDescriptionXml);
        });

        var testDownloadArtifact = self.blobClient.createArtifact('testArtifact');

        var addHashCallback = function (err, fileHash) {
            if (err) {
                self.result.setSuccess(false);
                mainCallback(err, self.result);
                return;
            }

            self.logger.debug('Added hash ' + fileHash + 'to testArtifact.');

            var artifactSaveCallback = function (err, artifactHash) {
                if (err) {
                    self.result.setSuccess(false);
                    mainCallback(err, self.result);
                    return;
                }

                self.result.addArtifact(artifactHash);

                // This will save the changes. If you don't want to save;
                // exclude self.save and call callback directly from this scope.
                self.result.setSuccess(true);
                self.save('Saving FmuImporter results to database...', function (err) {
                    mainCallback(null, self.result);
                });
            };

            testDownloadArtifact.save(artifactSaveCallback);
        };

        testDownloadArtifact.addHash('aNewFmu.fmu', fmuHash, addHashCallback);
    };

    FmuImporter.prototype.getFmuModelDescriptionXml = function (fmuHash, callback) {
        var self = this;

        self.blobClient.getObject(fmuHash, function (err, content) {
            if (err) {
                callback(err);
                return;
            }

            // TODO: what if the content is not a ZIP? TODO: check metadata
            var zip = new JSZip(content);

            // extract fmu zip
            var modelDescriptionXml = zip.file("modelDescription.xml").asText();

            // return .\modelDescription.xml
            callback(null, modelDescriptionXml);
        });
    };

    FmuImporter.prototype.convertXml2Json = function (modelDescriptionXml) {
        // TODO: what if modelDescriptionXml is NOT an xml?

        var self = this,
            converter = new Converter.Xml2json(null, {skipWSText: true}),
            obj = converter.xmlStr2json(modelDescriptionXml);

        self.logger.info(JSON.stringify(obj, null, 4));

        return obj;
    };

    /**
     * Finds and returns the node object defining the meta type for the given node.
     * @param node - Node to be check for type.
     * @returns {Object} - Node object defining the meta type of node.
     */
    FmuImporter.prototype.getMetaType = function (node) {
        var self = this,
            name;
        while (node) {
            name = self.core.getAttribute(node, 'name');
            if (self.META.hasOwnProperty(name) && self.core.getPath(self.META[name]) === self.core.getPath(node)) {
                break;
            }
            node = self.core.getBase(node);
        }
        return node;
    };

    return FmuImporter;
});<commit_msg>try to handle multi FMUs<commit_after>/**
* Generated by PluginGenerator from webgme on Mon Apr 28 2014 13:58:17 GMT-0500 (Central Daylight Time).
*/

define(['plugin/PluginConfig',
    'plugin/PluginBase',
    'jszip',
    'xmljsonconverter'], function (PluginConfig, PluginBase, JSZip, Converter) {
    'use strict';

    /**
    * Initializes a new instance of FmuImporter.
    * @class
    * @augments {PluginBase}
    * @classdesc This class represents the plugin FmuImporter.
    * @constructor
    */
    var FmuImporter = function () {
        // Call base class' constructor.
        PluginBase.call(this);
    };

    // Prototypal inheritance from PluginBase.
    FmuImporter.prototype = Object.create(PluginBase.prototype);
    FmuImporter.prototype.constructor = FmuImporter;

    /**
    * Gets the name of the FmuImporter.
    * @returns {string} The name of the plugin.
    * @public
    */
    FmuImporter.prototype.getName = function () {
        return "FMU Model Importer";
    };

    /**
    * Gets the semantic version (semver.org) of the FmuImporter.
    * @returns {string} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getVersion = function () {
        return "0.1.0";
    };

    /**
    * Gets the description of the FmuImporter.
    * @returns {string} The description of the plugin.
    * @public
    */
    FmuImporter.prototype.getDescription = function () {
        return "Creates WebGME models for one or more uploaded FMUs";
    };

    /**
    * Gets the configuration structure for the FmuImporter.
    * The ConfigurationStructure defines the configuration for the plugin
    * and will be used to populate the GUI when invoking the plugin from webGME.
    * @returns {object} The version of the plugin.
    * @public
    */
    FmuImporter.prototype.getConfigStructure = function () {
        return [
            {
                "name": "FMU",
                "displayName": "FmuPackage",
                "description": 'Click and drag an existing compiled FMU',
                "value": "", // this is the 'default config'
                "valueType": "asset",
                "readOnly": false
            }
        ];
    };

    /**
    * Main function for the plugin to execute. This will perform the execution.
    * Notes:
    * - Always log with the provided logger.[error,warning,info,debug].
    * - Do NOT put any user interaction logic UI, etc. inside this method.
    * - callback always has to be called even if error happened.
    *
    * @param {function(string, plugin.PluginResult)} mainCallback - the result callback
    */
    FmuImporter.prototype.main = function (mainCallback) {
        // Use self to access core, project, result, logger etc from PluginBase.
        // These are all instantiated at this point.
        var self = this,
            selectedNode = self.activeNode,
            currentConfig = self.getCurrentConfig(),
            currentConfigString = JSON.stringify(currentConfig, null, 4),
            fmuHash = "46f9efe35185b3f19cfeeefbf98d22107bbd1b8f",
            numFmus,
            i;

        self.logger.debug('Entering FmuImporter main');

        self.logger.debug('CurrentConfig:');
        self.logger.debug(currentConfigString);

//        var newFmuObject = core.createNode({parent: rootNode, base: '/1822302751/902541625'});  //FMU base object
//        core.newFmuObject(newFmuObject, 'resource', fmuHash);
//        core.newFmuObject(newFmuObject, 'name', 'Here_is_a_brand_new_FMU');

        self.getFmuModelDescriptions(fmuHash, function (err, fmuModelDescriptions) {
            if (err) {
                self.logger.error(err);
                return;
            }

            numFmus = fmuModelDescriptions.length;

            for (i = 0; i < numFmus; i += 1) {
                self.createNewFmu(fmuModelDescriptions[i]);
            }

            // This will save the changes. If you don't want to save;
            // exclude self.save and call callback directly from this scope.
            self.result.setSuccess(true);
            self.save('Saving FmuImporter results to database...', function (err) {
                mainCallback(null, self.result);
            });
        });

    };

    FmuImporter.prototype.getFmuModelDescriptions = function (uploadedFileHash, getFmuModelDescriptionsCallback) {
        var self = this,
            zip,
            fmuZip,
            fmuFileHash = 32,
            modelDescriptionXml,
            modelDescriptionJson,
            modelDescriptionMap = {},
            fmusWithinZip,
            numFmus,
            i;

        var blobGetObjectCallback = function (err, content) {
            if (err) {
                getFmuModelDescriptionsCallback(err);
                return;
            }

            // TODO: what if the content is not a ZIP? TODO: check metadata
            zip = new JSZip(content);

            modelDescriptionXml = zip.file("modelDescription.xml");

            if (modelDescriptionXml === null) {
                // we might have a zip with multiple fmus within
                fmusWithinZip = zip.file(/\.fmu/);
                numFmus = fmusWithinZip.length;

                for (i = 0; i < numFmus; i += 1) {
                    fmuZip = fmusWithinZip[i];
                    fmuFileHash += 1;
                    modelDescriptionXml = fmuZip.file("modelDescription.xml");
                    if (modelDescriptionXml != null) {
                        modelDescriptionJson = self.convertXml2Json(modelDescriptionXml.asText());
                        modelDescriptionMap[fmuFileHash] = modelDescriptionJson;
                    } else {
                        self.logger.error('Could not extract fmu modelDescription');
                        continue;
                    }
                }
            } else {
                modelDescriptionJson = self.convertXml2Json(modelDescriptionXml.asText());
                modelDescriptionMap[uploadedFileHash] = modelDescriptionJson;
            }

            // return .\modelDescription.xml
            getFmuModelDescriptionsCallback(null, modelDescriptionMap);
        };

        self.blobClient.getObject(uploadedFileHash, blobGetObjectCallback);
    };

    FmuImporter.prototype.convertXml2Json = function (modelDescriptionXml) {
        // TODO: what if modelDescriptionXml is NOT an xml?

        var self = this,
            converter = new Converter.Xml2json(null, {skipWSText: true}),
            obj = converter.xmlStr2json(modelDescriptionXml);

        return obj;
    };

    /**
     * Finds and returns the node object defining the meta type for the given node.
     * @param node - Node to be check for type.
     * @returns {Object} - Node object defining the meta type of node.
     */
    FmuImporter.prototype.getMetaType = function (node) {
        var self = this,
            name;
        while (node) {
            name = self.core.getAttribute(node, 'name');
            if (self.META.hasOwnProperty(name) && self.core.getPath(self.META[name]) === self.core.getPath(node)) {
                break;
            }
            node = self.core.getBase(node);
        }
        return node;
    };

    return FmuImporter;
});<|endoftext|>


<commit_before>var words = [
  ["enseñar", "to teach"],
  ["pared", "wall"],
  ["pizarra", "whiteboard"],
  ["casado", "married"],
  ["cara", "face"],
  ["venir", "to come"],
  ["cartera", "wallet"],
  ["lentillas", "contacts"],
  ["pajina", "page"],
  ["explicar", "to explain"],
  ["lugar", "place"],
  ["hacer", "to do, to make"],
  ["desde cuándo", "how long"],
  ["-verbo- desde hace -tiempo-", "I have -verb- since -length of time-"],
  ["llegar", "to arrive"],
  ["casi", "almost"],
  ["traducir", "to translate"],
  ["contar", "to tell"],
  ["recordar", "remember"],
  ["ancha", "wide"],
  ["al lado", "next to"],
  ["parecer", "to seem"],
  ["le cuesta/n", "to have trouble with"],
  ["libreta, cuaderno", "notebook"],
  ["echo de menus", "to miss"],
  ["perder", "to lose"],
  ["volver", "to return"],
  ["decir", "to say"],
  ["olvidar", "to forget"],
  ["pedir", "to ask for"],
  ["segundo", "second (time)"],
  ["lejos", "far"],
  ["cordero", "lamb"],
  ["pierna", "leg"],
  ["pie", "foot"],
  ["brazo", "arm"],
  ["dedo", "finger"],
  ["cuerpo", "body"],
  ["estómago", "stomach"],
  ["espalda", "back"],
  ["pecho", "chest"],
  ["enfermera", "nurse"],
  ["enfermo", "sick"],
  ["encargado", "manager"],
  ["deber", "to owe, must, should, ought to"],
  ["quedar", "to stay, remain"],
  ["dejar", "to leave, abandon, to let, allow"],
  ["seguir", "to follow, continue"],
  ["encontrar", "to find, encounter"],
  ["llamar", "to call, to name"],
  ["pensar", "to think"],
  ["conocer", "to know (people, places)"],
  ["sentir", "to feel, regret"],
  ["tratar", "to treat, handle"],
  ["existir", "to exist"],
  ["recibir", "to receive, to welcome, greet"],
  ["terminar", "to finish, end"],
  ["aparecer", "to appear, show up"],
  ["conseguir", "to get, obtain"],
  ["comenzar", "to begin, start, commence"],
  ["sacar", "to take out, stick out"],
  ["caer", "to fall"],
  ["acabar", "to finish, end"],
  ["traer", "to bring, to get, fetch, to carry"],
  ["suponer", "to suppose"],
  ["reconocer", "to recognize"],
  ["alcanzar", "to reach, catch up"],
  ["ofrecer", "to offer"],
  ["levantar", "to raise, to lift"],
];
<commit_msg>Nueva palabras<commit_after>var words = [
  ["pared", "wall"],
  ["pizarra", "whiteboard"],
  ["venir", "to come"],
  ["cartera", "wallet"],
  ["lugar", "place"],
  ["desde cuándo", "how long"],
  ["-verbo- desde hace -tiempo-", "I have -verb- since -length of time-"],
  ["llegar", "to arrive"],
  ["casi", "almost"],
  ["traducir", "to translate"],
  ["contar", "to tell"],
  ["recordar", "remember"],
  ["parecer", "to seem"],
  ["le cuesta/n", "to have trouble with"],
  ["libreta, cuaderno", "notebook"],
  ["echo de menus", "to miss"],
  ["perder", "to lose"],
  ["volver", "to return"],
  ["decir", "to say"],
  ["olvidar", "to forget"],
  ["pedir", "to ask for"],
  ["segundo", "second (time)"],
  ["lejos", "far"],
  ["cordero", "lamb"],
  ["pierna", "leg"],
  ["brazo", "arm"],
  ["cuerpo", "body"],
  ["espalda", "back"],
  ["pecho", "chest"],
  ["encargado", "manager"],
  ["deber", "to owe, must, should, ought to"],
  ["quedar", "to stay, remain"],
  ["dejar", "to leave, abandon, to let, allow"],
  ["seguir", "to follow, continue"],
  ["encontrar", "to find, encounter"],
  ["llamar", "to call, to name"],
  ["pensar", "to think"],
  ["conocer", "to know (people, places)"],
  ["sentir", "to feel, regret"],
  ["tratar", "to treat, handle"],
  ["recibir", "to receive, to welcome, greet"],
  ["aparecer", "to appear, show up"],
  ["conseguir", "to get, obtain"],
  ["comenzar", "to begin, start, commence"],
  ["caer", "to fall"],
  ["traer", "to bring, to get, fetch, to carry"],
  ["suponer", "to suppose"],
  ["reconocer", "to recognize"],
  ["alcanzar", "to reach, catch up"],
  ["ofrecer", "to offer"],
  ["navigador", "browser"],
  ["mitad", "half"],
  ["inicio", "half"],
  ["excusa", "excuse"],
  ["cantidad", "quantity"],
  ["durante", "during"],
  ["césped", "grass"],
  ["luego, entonces", "then"],
  ["nacer", "to be born"],
  ["util", "useful"],
  ["corregir", "edit"],
  ["castigar", "to punish"],
  ["pisar", "to step on"],
  ["detener, parar", "to stop"],
  ["acá", "over here"],
  ["allí", "there"],
  ["allá", "over there"],
  ["lavar", "to wash"],
  ["levantarse", "to get up"],
  ["despertar", "to wake"],
  ["bañera", "bath"],
  ["nevera", "fridge"],
  ["lavabo", "sink"],
  ["esos/esas", "those"],
  ["ese/esa", "that"],
  ["esto/esta", "this"],
  ["driveway", "entrada de coches"],
  ["levantar", "to raise, to lift"],
];
<|endoftext|>


<commit_before>import { get_examples } from './_examples.js';

let cached;

export function get(req, res) {
	try {
		if (!cached || process.env.NODE_ENV !== 'production') {
			cached = JSON.stringify(get_examples());
		}

		res.writeHead(200, {
			'Content-Type': 'application/json'
		});

		res.end(cached);
	} catch (e) {
		res.writeHead(e.status || 500, {
			'Content-Type': 'application/json'
		});

		res.end(JSON.stringify({ message: e.message }));
	}
}<commit_msg>site: in examples.json don't return sections with no title<commit_after>import { get_examples } from './_examples.js';

let cached;

export function get(req, res) {
	try {
		if (!cached || process.env.NODE_ENV !== 'production') {
			cached = JSON.stringify(get_examples().filter(section => section.title));
		}

		res.writeHead(200, {
			'Content-Type': 'application/json'
		});

		res.end(cached);
	} catch (e) {
		res.writeHead(e.status || 500, {
			'Content-Type': 'application/json'
		});

		res.end(JSON.stringify({ message: e.message }));
	}
}<|endoftext|>


<commit_before>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var RelayHandler = require('../relay_handler');

var REGISTER_GRACE_PERIOD = 1000;
var REGISTER_TTL = 1000;
var DEFAULT_LOG_GRACE_PERIOD = 5 * 60 * 1000;

function ServiceDispatchHandler(options) {
    if (!(this instanceof ServiceDispatchHandler)) {
        return new ServiceDispatchHandler(options);
    }
    var self = this;

    self.options = options;
    assert(options, 'service dispatch handler options not actually optional');
    self.channel = self.options.channel;
    self.config = self.options.config;
    self.logger = self.options.logger;
    self.statsd = self.options.statsd;
    self.egressNodes = self.options.egressNodes;
    self.createdAt = self.channel.timers.now();
    self.logGracePeriod = self.options.logGracePeriod ||
        DEFAULT_LOG_GRACE_PERIOD;

    self.egressNodes.on('membershipChanged', onMembershipChanged);

    self.registerTTL = numberOrDefault(self.config,
        'core.exitNode.registerTTL', REGISTER_TTL);
    self.registrationGracePeriod = numberOrDefault(self.config,
        'core.exitNode.registrationGracePeriod', REGISTER_GRACE_PERIOD);

    function onMembershipChanged() {
        self.updateServiceChannels();
    }
}

ServiceDispatchHandler.prototype.type = 'tchannel.hyperbahn.service-dispatch-handler';

ServiceDispatchHandler.prototype.handleRequest =
function handleRequest(req, buildRes) {
    /* eslint max-statements:[2,20] */
    var self = this;
    if (!req.serviceName) {
        self.logger.error('Got incoming req with no service', {
            serviceName: req.serviceName,
            arg1: String(req.arg1)
        });

        buildRes().sendError('BadRequest', 'no service name given');
        return;
    }

    if (!req.streamed) {
        var name = String(req.arg1);
        self.onEndpointHandled(req.serviceName, name);
    } else {
        var statsdKey = 'server.request-stream.' + req.serviceName;
        self.statsd.increment(statsdKey);
    }

    var chan = self.channel.subChannels[req.serviceName];
    if (chan) {
        // Temporary hack. Need to set json by default because
        // we want to upgrade without breaking ncar
        chan.handler.handleRequest(req, buildRes);
    } else {
        self.handleDefault(req, buildRes);
    }
};

ServiceDispatchHandler.prototype.onEndpointHandled =
function onEndpointHandled(service, name) {
    var self = this;

    var logRequest = self.config.get('server.logRequest');

    var endpointName = name
        .replace(/\//g, '_')
        .replace(/:/g, '_');

    var statsdKey = 'server.request.' + service +
        '.' + endpointName;

    self.statsd.increment(statsdKey);

    if (logRequest && logRequest[name] === false) {
        return;
    }
};

ServiceDispatchHandler.prototype.handleDefault =
function handleDefault(req, buildRes) {
    var self = this;
    var svcchan = self.getOrCreateServiceChannel(req.serviceName);
    svcchan.handler.handleRequest(req, buildRes);
};

ServiceDispatchHandler.prototype.getOrCreateServiceChannel =
function getOrCreateServiceChannel(serviceName) {
    var self = this;
    return self.getServiceChannel(serviceName, true);
};

ServiceDispatchHandler.prototype.getServiceChannel =
function getServiceChannel(serviceName, create) {
    var self = this;
    var svcchan = self.channel.subChannels[serviceName];
    if (!svcchan && create) {
        var now = self.channel.timers.now();
        if (now >= self.createdAt + self.logGracePeriod) {
            self.logger.info('Creating new sub channel', {
                serviceName: serviceName
            });
        }

        svcchan = self.createServiceChannel(serviceName);
    }
    return svcchan;
};

ServiceDispatchHandler.prototype.getServicePeer =
function getServicePeer(serviceName, hostPort) {
    var self = this;
    var svcchan = self.getOrCreateServiceChannel(serviceName);
    return self._getServicePeer(svcchan, hostPort);
};

ServiceDispatchHandler.prototype._getServicePeer =
function _getServicePeer(svcchan, hostPort) {
    var peer = svcchan.peers.get(hostPort);
    if (!peer) {
        peer = svcchan.peers.add(hostPort);
    }
    if (!peer.serviceProxyServices) {
        peer.serviceProxyServices = {};
    }
    peer.serviceProxyServices[svcchan.serviceName] = true;
    return peer;
};

ServiceDispatchHandler.prototype.createServiceChannel =
function createServiceChannel(serviceName) {
    var self = this;

    var exitNodes = self.egressNodes.exitsFor(serviceName);
    var isExit = self.egressNodes.isExitFor(serviceName);
    var mode = isExit ? 'exit' : 'forward';
    var svcchan = self.channel.makeSubChannel({
        serviceName: serviceName
    });
    svcchan.serviceProxyMode = mode; // duck: punched

    if (mode === 'forward') {
        var exitNames = Object.keys(exitNodes);
        for (var i = 0; i < exitNames.length; i++) {
            self._getServicePeer(svcchan, exitNames[i]);
        }
    }

    svcchan.handler = new RelayHandler(svcchan);

    return svcchan;
};

ServiceDispatchHandler.prototype.updateServiceChannels =
function updateServiceChannels() {
    var self = this;
    var serviceNames = Object.keys(self.channel.subChannels);
    for (var i = 0; i < serviceNames.length; i++) {
        var serviceName = serviceNames[i];
        var chan = self.channel.subChannels[serviceName];
        if (chan.serviceProxyMode) {
            self.updateServiceChannel(chan);
        }
    }
};

ServiceDispatchHandler.prototype.updateServiceChannel =
function updateServiceChannel(svcchan) {
    var self = this;
    var exitNodes = self.egressNodes.exitsFor(svcchan.serviceName);
    var isExit = self.egressNodes.isExitFor(svcchan.serviceName);
    if (isExit && svcchan.serviceProxyMode === 'forward') {
        self.changeToExit(exitNodes, svcchan);
    } else if (!isExit) {
        if (svcchan.serviceProxyMode === 'exit') {
            self.changeToForward(exitNodes, svcchan);
        } else {
            self.updateExitNodes(exitNodes, svcchan);
        }
    }
};

ServiceDispatchHandler.prototype.changeToExit =
function changeToExit(exitNodes, svcchan) {
    var self = this;

    var oldMode = svcchan.serviceProxyMode;
    svcchan.serviceProxyMode = 'exit';
    svcchan.peers.clear();
    self.emit('roleTransition', svcchan, {
        oldMode: oldMode,
        newMode: 'exit'
    });

    self.logger.info('Changing to exit node', {
        oldMode: oldMode,
        newMode: 'exit',
        serviceName: svcchan.serviceName
    });
};

ServiceDispatchHandler.prototype.changeToForward =
function changeToForward(exitNodes, svcchan) {
    var self = this;
    var oldMode = svcchan.serviceProxyMode;
    svcchan.serviceProxyMode = 'forward';

    // TODO make sure we close all connections.
    svcchan.peers.clear();
    // TODO: transmit prior known registration data to new owner(s) to
    // speed convergence / deal with transitions better:
    //     var oldPeers = svcchan.peers.clear();
    //     ... send rpc to new exit nodes
    var exitNames = Object.keys(exitNodes);
    for (var i = 0; i < exitNames.length; i++) {
        self._getServicePeer(svcchan, exitNames[i]);
    }
    self.emit('roleTransition', svcchan, {
        oldMode: oldMode,
        newMode: 'forward'
    });

    self.logger.info('Changing to forward node', {
        oldMode: oldMode,
        newMode: 'forward',
        serviceName: svcchan.serviceName
    });
};

ServiceDispatchHandler.prototype.updateExitNodes =
function updateExitNodes(exitNodes, svcchan) {
    var self = this;
    var i;
    var oldNames = svcchan.peers.keys();
    for (i = 0; i < oldNames.length; i++) {
        if (!exitNodes[oldNames[i]]) {
            svcchan.peers.delete(oldNames[i]);
        }
    }
    var exitNames = Object.keys(exitNodes);
    for (i = 0; i < exitNames.length; i++) {
        self._getServicePeer(svcchan, exitNames[i]);
    }
};

// TODO Consider sharding by hostPort and indexing exit exitNodes by hostPort.
// We also have to shard by serviceName and store the serviceName <-> hostPort
// information under the "service exitNodes".  This means that sharding by
// hostPort gives an even spread of socket distribution. i.e. if we shard
// dispatch to 5 exit exitNodes and some small lulzy service to 5 exit
// exitNodes we wont have massive imbalance of dispatch having 500 workers and
// the small service having 2 workers.  We would need two hops to find an exit
// node though

function numberOrDefault(config, key, defaultValue) {
    var value = config.get(key);
    if (typeof value === 'number') {
        return value;
    }
    return defaultValue;
}

module.exports = ServiceDispatchHandler;
<commit_msg>service_proxy: cleanup dead configuration code<commit_after>// Copyright (c) 2015 Uber Technologies, Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.

'use strict';

var assert = require('assert');
var RelayHandler = require('../relay_handler');

var DEFAULT_LOG_GRACE_PERIOD = 5 * 60 * 1000;

function ServiceDispatchHandler(options) {
    if (!(this instanceof ServiceDispatchHandler)) {
        return new ServiceDispatchHandler(options);
    }
    var self = this;

    self.options = options;
    assert(options, 'service dispatch handler options not actually optional');
    self.channel = self.options.channel;
    self.logger = self.options.logger;
    self.statsd = self.options.statsd;
    self.egressNodes = self.options.egressNodes;
    self.createdAt = self.channel.timers.now();
    self.logGracePeriod = self.options.logGracePeriod ||
        DEFAULT_LOG_GRACE_PERIOD;

    self.egressNodes.on('membershipChanged', onMembershipChanged);

    function onMembershipChanged() {
        self.updateServiceChannels();
    }
}

ServiceDispatchHandler.prototype.type = 'tchannel.hyperbahn.service-dispatch-handler';

ServiceDispatchHandler.prototype.handleRequest =
function handleRequest(req, buildRes) {
    /* eslint max-statements:[2,20] */
    var self = this;
    if (!req.serviceName) {
        self.logger.error('Got incoming req with no service', {
            serviceName: req.serviceName,
            arg1: String(req.arg1)
        });

        buildRes().sendError('BadRequest', 'no service name given');
        return;
    }

    if (!req.streamed) {
        var name = String(req.arg1);
        self.onEndpointHandled(req.serviceName, name);
    } else {
        var statsdKey = 'server.request-stream.' + req.serviceName;
        self.statsd.increment(statsdKey);
    }

    var chan = self.channel.subChannels[req.serviceName];
    if (chan) {
        // Temporary hack. Need to set json by default because
        // we want to upgrade without breaking ncar
        chan.handler.handleRequest(req, buildRes);
    } else {
        self.handleDefault(req, buildRes);
    }
};

ServiceDispatchHandler.prototype.onEndpointHandled =
function onEndpointHandled(service, name) {
    var self = this;

    var logRequest = self.config.get('server.logRequest');

    var endpointName = name
        .replace(/\//g, '_')
        .replace(/:/g, '_');

    var statsdKey = 'server.request.' + service +
        '.' + endpointName;

    self.statsd.increment(statsdKey);

    if (logRequest && logRequest[name] === false) {
        return;
    }
};

ServiceDispatchHandler.prototype.handleDefault =
function handleDefault(req, buildRes) {
    var self = this;
    var svcchan = self.getOrCreateServiceChannel(req.serviceName);
    svcchan.handler.handleRequest(req, buildRes);
};

ServiceDispatchHandler.prototype.getOrCreateServiceChannel =
function getOrCreateServiceChannel(serviceName) {
    var self = this;
    return self.getServiceChannel(serviceName, true);
};

ServiceDispatchHandler.prototype.getServiceChannel =
function getServiceChannel(serviceName, create) {
    var self = this;
    var svcchan = self.channel.subChannels[serviceName];
    if (!svcchan && create) {
        var now = self.channel.timers.now();
        if (now >= self.createdAt + self.logGracePeriod) {
            self.logger.info('Creating new sub channel', {
                serviceName: serviceName
            });
        }

        svcchan = self.createServiceChannel(serviceName);
    }
    return svcchan;
};

ServiceDispatchHandler.prototype.getServicePeer =
function getServicePeer(serviceName, hostPort) {
    var self = this;
    var svcchan = self.getOrCreateServiceChannel(serviceName);
    return self._getServicePeer(svcchan, hostPort);
};

ServiceDispatchHandler.prototype._getServicePeer =
function _getServicePeer(svcchan, hostPort) {
    var peer = svcchan.peers.get(hostPort);
    if (!peer) {
        peer = svcchan.peers.add(hostPort);
    }
    if (!peer.serviceProxyServices) {
        peer.serviceProxyServices = {};
    }
    peer.serviceProxyServices[svcchan.serviceName] = true;
    return peer;
};

ServiceDispatchHandler.prototype.createServiceChannel =
function createServiceChannel(serviceName) {
    var self = this;

    var exitNodes = self.egressNodes.exitsFor(serviceName);
    var isExit = self.egressNodes.isExitFor(serviceName);
    var mode = isExit ? 'exit' : 'forward';
    var svcchan = self.channel.makeSubChannel({
        serviceName: serviceName
    });
    svcchan.serviceProxyMode = mode; // duck: punched

    if (mode === 'forward') {
        var exitNames = Object.keys(exitNodes);
        for (var i = 0; i < exitNames.length; i++) {
            self._getServicePeer(svcchan, exitNames[i]);
        }
    }

    svcchan.handler = new RelayHandler(svcchan);

    return svcchan;
};

ServiceDispatchHandler.prototype.updateServiceChannels =
function updateServiceChannels() {
    var self = this;
    var serviceNames = Object.keys(self.channel.subChannels);
    for (var i = 0; i < serviceNames.length; i++) {
        var serviceName = serviceNames[i];
        var chan = self.channel.subChannels[serviceName];
        if (chan.serviceProxyMode) {
            self.updateServiceChannel(chan);
        }
    }
};

ServiceDispatchHandler.prototype.updateServiceChannel =
function updateServiceChannel(svcchan) {
    var self = this;
    var exitNodes = self.egressNodes.exitsFor(svcchan.serviceName);
    var isExit = self.egressNodes.isExitFor(svcchan.serviceName);
    if (isExit && svcchan.serviceProxyMode === 'forward') {
        self.changeToExit(exitNodes, svcchan);
    } else if (!isExit) {
        if (svcchan.serviceProxyMode === 'exit') {
            self.changeToForward(exitNodes, svcchan);
        } else {
            self.updateExitNodes(exitNodes, svcchan);
        }
    }
};

ServiceDispatchHandler.prototype.changeToExit =
function changeToExit(exitNodes, svcchan) {
    var self = this;

    var oldMode = svcchan.serviceProxyMode;
    svcchan.serviceProxyMode = 'exit';
    svcchan.peers.clear();
    self.emit('roleTransition', svcchan, {
        oldMode: oldMode,
        newMode: 'exit'
    });

    self.logger.info('Changing to exit node', {
        oldMode: oldMode,
        newMode: 'exit',
        serviceName: svcchan.serviceName
    });
};

ServiceDispatchHandler.prototype.changeToForward =
function changeToForward(exitNodes, svcchan) {
    var self = this;
    var oldMode = svcchan.serviceProxyMode;
    svcchan.serviceProxyMode = 'forward';

    // TODO make sure we close all connections.
    svcchan.peers.clear();
    // TODO: transmit prior known registration data to new owner(s) to
    // speed convergence / deal with transitions better:
    //     var oldPeers = svcchan.peers.clear();
    //     ... send rpc to new exit nodes
    var exitNames = Object.keys(exitNodes);
    for (var i = 0; i < exitNames.length; i++) {
        self._getServicePeer(svcchan, exitNames[i]);
    }
    self.emit('roleTransition', svcchan, {
        oldMode: oldMode,
        newMode: 'forward'
    });

    self.logger.info('Changing to forward node', {
        oldMode: oldMode,
        newMode: 'forward',
        serviceName: svcchan.serviceName
    });
};

ServiceDispatchHandler.prototype.updateExitNodes =
function updateExitNodes(exitNodes, svcchan) {
    var self = this;
    var i;
    var oldNames = svcchan.peers.keys();
    for (i = 0; i < oldNames.length; i++) {
        if (!exitNodes[oldNames[i]]) {
            svcchan.peers.delete(oldNames[i]);
        }
    }
    var exitNames = Object.keys(exitNodes);
    for (i = 0; i < exitNames.length; i++) {
        self._getServicePeer(svcchan, exitNames[i]);
    }
};

// TODO Consider sharding by hostPort and indexing exit exitNodes by hostPort.
// We also have to shard by serviceName and store the serviceName <-> hostPort
// information under the "service exitNodes".  This means that sharding by
// hostPort gives an even spread of socket distribution. i.e. if we shard
// dispatch to 5 exit exitNodes and some small lulzy service to 5 exit
// exitNodes we wont have massive imbalance of dispatch having 500 workers and
// the small service having 2 workers.  We would need two hops to find an exit
// node though

function numberOrDefault(config, key, defaultValue) {
    var value = config.get(key);
    if (typeof value === 'number') {
        return value;
    }
    return defaultValue;
}

module.exports = ServiceDispatchHandler;
<|endoftext|>


<commit_before>"use strict";

const deepl = require("deepl-node");
const fs = require("fs");
const PO = require("pofile");
const GoogleTranslate = require("@google-cloud/translate").v3.TranslationServiceClient;

let keys = fs.existsSync("./keys.json") ? JSON.parse(fs.readFileSync("./keys.json")) : null;

const deepl_translator = keys ? new deepl.Translator(keys.deepl_api_key) : null;
const googleTranslate = keys
    ? new GoogleTranslate({
          projectId: keys.google_credentials.project_id,
          credentials: keys.google_credentials,
      })
    : null;

let limit = 1;

async function main() {
    //const countries = JSON.parse(await fs.promises.readFile("./build/countries.json", "utf-8"));
    //const ui_keys = JSON.parse(await fs.promises.readFile("./build/ogs-ui-keys.json", "utf-8"));
    if (!fs.existsSync("./autotranslations.json")) {
        await fs.promises.writeFile("./autotranslations.json", "{}");
    }

    const autotranslations = JSON.parse(
        await fs.promises.readFile("./autotranslations.json", "utf-8"),
    );
    const languages = JSON.parse(await fs.promises.readFile("./languages.json", "utf-8"));
    let translations_missing = {};
    let vandalized_languages = {};
    let autotranslations_needed = {};

    for (let lang in languages) {
        let conv = lang.replace(/([a-z]+)-([a-zA-Z]+)/, (_, a, b) => `${a}_${b.toUpperCase()}`);
        const po = await asyncLoadPoFile(`./locale/${conv}.po`);

        let missing = 0;
        let missing_strings = [];
        let errors = 0;
        let vandalizations = 0;
        for (let item of po.items) {
            if (item.obsolete) {
                continue;
            }

            for (let str of [item.msgid, item.msgid_plural]) {
                if (!str) {
                    continue;
                }

                let label_replacements = str.match(/([{][{][a-zA-Z_]+[}][}])/g);
                let num_percent_s = str.match(/%[sd]/g) ? str.match(/%[sd]/g).length : 0;

                for (let msg of item.msgstr) {
                    let auto_translated = "";
                    if (msg.trim() === "") {
                        missing++;
                        missing_strings.push(str);
                        if (!(lang in autotranslations_needed)) {
                            autotranslations_needed[lang] = {};
                        }
                        autotranslations_needed[lang][str] = str;

                        if (!(lang in autotranslations) || !(str in autotranslations[lang])) {
                            continue;
                        } else {
                            msg = autotranslations[lang][str];
                            auto_translated = "[autotranslated] ";
                            //console.log("Using autotranslation", str, " -> ", msg);
                        }
                    }

                    if (looks_vandalized(lang, msg)) {
                        console.log(
                            `${auto_translated}${conv}.po: vandalization: ${str} -> ${msg}`,
                        );
                        vandalized_languages[lang] = ++vandalizations;
                    }

                    let m = msg.match(/%[sd]/g);
                    if (num_percent_s != (m ? m.length : 0)) {
                        console.log(`${auto_translated}${conv}.po: %s mismatch: ${str} -> ${msg}`);
                        errors++;
                    }

                    if (label_replacements) {
                        for (let replacement of label_replacements) {
                            if (replacement === "{{num}}" && msg.indexOf("{{") < 0) {
                                // {{num}} point often goes to "1" in the case where num == 1, this is a fine translation
                                // so allow this, but if we see any {{ in the string then it's probably likely that the
                                // translator actually translated "num", which is a problem.
                                continue;
                            }
                            if (msg.indexOf(replacement) < 0) {
                                console.log(
                                    `${auto_translated}${conv}.po: label replacement for ${replacement} missing: ${str} -> ${msg}`,
                                );
                                errors++;
                            }
                        }
                    }
                }
            }
        }

        if (vandalizations > 0) {
            console.log(`*** VANDALIZED *** ${vandalizations} vandalizations`);
        } else if (errors > 0) {
            console.log(`** BAD ** ${lang}: ${missing} missing ${errors} errors`);
            //exit_code = 1;
        } else if (missing > 0) {
            console.log(`MISSING ${lang}: ${missing} missing`);
            /*
            if (missing < 10) {
                for (let str of missing_strings) {
                    console.log(`    ${str}`);
                }
            }
            */
        } else {
            console.log(`GOOD ${lang}`);
        }

        translations_missing[lang] = missing;

        if (vandalizations === 0) {
            let data = await fs.promises.readFile(`./locale/${lang}.js`, "utf8");
            if (/window.ogs_missing_translation_count = [0-9]+;/.test(data)) {
                //console.log("Replace missing")
                data = data.replace(
                    /window.ogs_missing_translation_count = [0-9]+;/,
                    `window.ogs_missing_translation_count = ${missing};`,
                );
            } else {
                //console.log("Add missing")
                data += `window.ogs_missing_translation_count = ${missing};`;
            }
            await fs.promises.writeFile(`./locale/${lang}.js`, data);
        }
    }

    await fs.promises.writeFile("./locale/translations_missing.json", JSON.stringify(translations_missing, null, 4));

    if (deepl_translator && googleTranslate) {
        if (Object.keys(vandalized_languages).length > 0) {
            console.error(
                `Critical error: ${
                    Object.keys(vandalized_languages).length
                } languages have been vandalized`,
            );
            console.error(JSON.stringify(vandalized_languages, undefined, 4));
            process.exit(1);
        }

        let num_placeholders = 0;
        const placeholder_a_to_n = { "%s": "8888888", "%d": "9999999" };
        const placeholder_n_to_a = { 8888888: "%s", 9999999: "%d" };

        for (let lang in autotranslations_needed) {
            for (let str in autotranslations_needed[lang]) {
                if (lang in autotranslations && str in autotranslations[lang]) {
                    delete autotranslations_needed[lang][str];
                } else {
                    let label_replacements = str.match(/([{][{][a-zA-Z_]+[}][}])/g);
                    if (label_replacements) {
                        for (let replacement of label_replacements) {
                            if (!(replacement in placeholder_a_to_n)) {
                                let zeropadded = ("987654321000" + num_placeholders).slice(-8);
                                let k = `${zeropadded}`;
                                placeholder_a_to_n[replacement] = k;
                                placeholder_n_to_a[k] = replacement;
                                num_placeholders++;
                            }
                        }
                    }
                }
            }
            if (Object.keys(autotranslations_needed[lang]).length === 0) {
                delete autotranslations_needed[lang];
            }
        }

        const googleSupportedLanguages = {};
        const googleTargetLanguages = await googleTranslate.getSupportedLanguages({
            parent: "projects/" + keys.google_credentials.project_id + "/locations/global",
            displayLanguageCode: "en",
        });
        for (let lang of googleTargetLanguages[0].languages) {
            googleSupportedLanguages[lang.languageCode] = lang.displayName;
        }

        const deeplTargetLanguages = await deepl_translator.getTargetLanguages();
        const deeplSupportedLanguages = {};
        for (let e of deeplTargetLanguages) {
            deeplSupportedLanguages[e.code.toLowerCase()] = true;
        }

        for (let lang in autotranslations_needed) {
            if (lang in deeplSupportedLanguages || lang in googleSupportedLanguages) {
                if (!(lang in autotranslations)) {
                    autotranslations[lang] = {};
                }

                console.log(`Missing translations for ${lang}:`);

                const orig_strs = Object.keys(autotranslations_needed[lang]);
                const strs = orig_strs.map((s) => replace_placeholders(s, placeholder_a_to_n));

                let results = [];

                if (lang in deeplSupportedLanguages) {
                    results = (await deepl_translator.translateText(strs, "en", lang)).map(
                        (r) => r.text,
                    );
                }
                if (lang in googleSupportedLanguages) {
                    const todo = strs.map((s) => s);

                    while (todo.length > 0) {
                        const batch_size = Math.min(100, todo.length);
                        const batch = todo.splice(0, batch_size);
                        const batch_results = await googleTranslate.translateText({
                            parent:
                                "projects/" +
                                keys.google_credentials.project_id +
                                "/locations/global",
                            contents: batch,
                            mimeType: "text/plain",
                            sourceLanguageCode: "en",
                            targetLanguageCode: lang,
                        });

                        for (let i = 0; i < batch.length; i++) {
                            results.push(batch_results[0].translations[i].translatedText);
                        }
                        console.log(`Google Translated ${batch.length} strings`);
                    }
                }

                for (let i = 0; i < orig_strs.length; ++i) {
                    autotranslations[lang][orig_strs[i]] = replace_placeholders(
                        results[i],
                        placeholder_n_to_a,
                    );
                }
            } else {
                console.error("Failed to find a translator for " + lang);
            }
        }

        /*
    await autotranslate(lang, str);
    */

        await fs.promises.writeFile(
            "./autotranslations.json",
            JSON.stringify(autotranslations, undefined, 4),
        );
    }
    else {
        console.log("No autotranslation support on this system, skipping autotranslations");
    }
}

function replace_placeholders(str, dict) {
    for (let k in dict) {
        while (str.indexOf(k) >= 0) {
            str = str.replace(k, dict[k]);
        }
    }
    return str;
}

async function asyncLoadPoFile(filename) {
    return new Promise((resolve, reject) => {
        PO.load(filename, (err, po) => {
            if (err) {
                reject(err);
            } else {
                resolve(po);
            }
        });
    });
}

function encode(obj) {
    return btoa(unescape(encodeURIComponent(JSON.stringify(obj)))).replace(/.{76}(?=.)/g, "$&");
}

function decode(str) {
    return JSON.parse(decodeURIComponent(escape(atob(str))));
}

let bad_symbols = decode(
    "WyLljZAiLCLikrYiLCLimK0iLCLinK8iLCLimK4iLCLinKEiLCLljY0iLCLgv5UiLCLgv5YiLCLi" +
        "nJkiLCLgv5ciLCLgv5giLCLqlqYiLCLwn4+0Iiwi4ZuL4ZuLIl0=",
);

let profanity_regex = {};

let profanity_dictionary = decode(
    "eyJlbiI6WyJuaWdnZXIiLCJjdWNrIiwiZnVjayIsInNoaXQiLCJuYXppIiwiMTQ4OCIsImhpdGxl" +
        "ciIsInN0cmFpZ2h0IHByaWRlIiwiZ2F5IHByaWRlIiwiZ2F5IiwiZ2F5cyJdfQ==",
);

for (let lang in profanity_dictionary) {
    profanity_regex[lang] = new RegExp(
        "(" + profanity_dictionary[lang].map((s) => "\\b" + s + "\\b").join("|") + ")",
        "gui",
    );
}

function looks_vandalized(lang, msg) {
    if (detect_profanity("en", msg)) {
        console.error("en Profanity detected");
        return true;
    }
    if (detect_profanity(lang, msg)) {
        console.error(lang, " Profanity detected");
        return true;
    }

    let lower = msg.toLowerCase();

    for (let entry of bad_symbols) {
        if (lower.indexOf(entry) >= 0) {
            return true;
        }
    }

    return false;
}

function detect_profanity(lang, msg) {
    if (lang in profanity_regex) {
        return profanity_regex[lang].test(msg);
    }

    return false;
}

main()
    .then(() => console.log("Done"))
    .catch((err) => console.error(err));
<commit_msg>Don't google translate if deepl translation is supported<commit_after>"use strict";

const deepl = require("deepl-node");
const fs = require("fs");
const PO = require("pofile");
const GoogleTranslate = require("@google-cloud/translate").v3.TranslationServiceClient;

let keys = fs.existsSync("./keys.json") ? JSON.parse(fs.readFileSync("./keys.json")) : null;

const deepl_translator = keys ? new deepl.Translator(keys.deepl_api_key) : null;
const googleTranslate = keys
    ? new GoogleTranslate({
          projectId: keys.google_credentials.project_id,
          credentials: keys.google_credentials,
      })
    : null;

let limit = 1;

async function main() {
    //const countries = JSON.parse(await fs.promises.readFile("./build/countries.json", "utf-8"));
    //const ui_keys = JSON.parse(await fs.promises.readFile("./build/ogs-ui-keys.json", "utf-8"));
    if (!fs.existsSync("./autotranslations.json")) {
        await fs.promises.writeFile("./autotranslations.json", "{}");
    }

    const autotranslations = JSON.parse(
        await fs.promises.readFile("./autotranslations.json", "utf-8"),
    );
    const languages = JSON.parse(await fs.promises.readFile("./languages.json", "utf-8"));
    let translations_missing = {};
    let vandalized_languages = {};
    let autotranslations_needed = {};

    for (let lang in languages) {
        let conv = lang.replace(/([a-z]+)-([a-zA-Z]+)/, (_, a, b) => `${a}_${b.toUpperCase()}`);
        const po = await asyncLoadPoFile(`./locale/${conv}.po`);

        let missing = 0;
        let missing_strings = [];
        let errors = 0;
        let vandalizations = 0;
        for (let item of po.items) {
            if (item.obsolete) {
                continue;
            }

            for (let str of [item.msgid, item.msgid_plural]) {
                if (!str) {
                    continue;
                }

                let label_replacements = str.match(/([{][{][a-zA-Z_]+[}][}])/g);
                let num_percent_s = str.match(/%[sd]/g) ? str.match(/%[sd]/g).length : 0;

                for (let msg of item.msgstr) {
                    let auto_translated = "";
                    if (msg.trim() === "") {
                        missing++;
                        missing_strings.push(str);
                        if (!(lang in autotranslations_needed)) {
                            autotranslations_needed[lang] = {};
                        }
                        autotranslations_needed[lang][str] = str;

                        if (!(lang in autotranslations) || !(str in autotranslations[lang])) {
                            continue;
                        } else {
                            msg = autotranslations[lang][str];
                            auto_translated = "[autotranslated] ";
                            //console.log("Using autotranslation", str, " -> ", msg);
                        }
                    }

                    if (looks_vandalized(lang, msg)) {
                        console.log(
                            `${auto_translated}${conv}.po: vandalization: ${str} -> ${msg}`,
                        );
                        vandalized_languages[lang] = ++vandalizations;
                    }

                    let m = msg.match(/%[sd]/g);
                    if (num_percent_s != (m ? m.length : 0)) {
                        console.log(`${auto_translated}${conv}.po: %s mismatch: ${str} -> ${msg}`);
                        errors++;
                    }

                    if (label_replacements) {
                        for (let replacement of label_replacements) {
                            if (replacement === "{{num}}" && msg.indexOf("{{") < 0) {
                                // {{num}} point often goes to "1" in the case where num == 1, this is a fine translation
                                // so allow this, but if we see any {{ in the string then it's probably likely that the
                                // translator actually translated "num", which is a problem.
                                continue;
                            }
                            if (msg.indexOf(replacement) < 0) {
                                console.log(
                                    `${auto_translated}${conv}.po: label replacement for ${replacement} missing: ${str} -> ${msg}`,
                                );
                                errors++;
                            }
                        }
                    }
                }
            }
        }

        if (vandalizations > 0) {
            console.log(`*** VANDALIZED *** ${vandalizations} vandalizations`);
        } else if (errors > 0) {
            console.log(`** BAD ** ${lang}: ${missing} missing ${errors} errors`);
            //exit_code = 1;
        } else if (missing > 0) {
            console.log(`MISSING ${lang}: ${missing} missing`);
            /*
            if (missing < 10) {
                for (let str of missing_strings) {
                    console.log(`    ${str}`);
                }
            }
            */
        } else {
            console.log(`GOOD ${lang}`);
        }

        translations_missing[lang] = missing;

        if (vandalizations === 0) {
            let data = await fs.promises.readFile(`./locale/${lang}.js`, "utf8");
            if (/window.ogs_missing_translation_count = [0-9]+;/.test(data)) {
                //console.log("Replace missing")
                data = data.replace(
                    /window.ogs_missing_translation_count = [0-9]+;/,
                    `window.ogs_missing_translation_count = ${missing};`,
                );
            } else {
                //console.log("Add missing")
                data += `window.ogs_missing_translation_count = ${missing};`;
            }
            await fs.promises.writeFile(`./locale/${lang}.js`, data);
        }
    }

    await fs.promises.writeFile("./locale/translations_missing.json", JSON.stringify(translations_missing, null, 4));

    if (deepl_translator && googleTranslate) {
        if (Object.keys(vandalized_languages).length > 0) {
            console.error(
                `Critical error: ${
                    Object.keys(vandalized_languages).length
                } languages have been vandalized`,
            );
            console.error(JSON.stringify(vandalized_languages, undefined, 4));
            process.exit(1);
        }

        let num_placeholders = 0;
        const placeholder_a_to_n = { "%s": "8888888", "%d": "9999999" };
        const placeholder_n_to_a = { 8888888: "%s", 9999999: "%d" };

        for (let lang in autotranslations_needed) {
            for (let str in autotranslations_needed[lang]) {
                if (lang in autotranslations && str in autotranslations[lang]) {
                    delete autotranslations_needed[lang][str];
                } else {
                    let label_replacements = str.match(/([{][{][a-zA-Z_]+[}][}])/g);
                    if (label_replacements) {
                        for (let replacement of label_replacements) {
                            if (!(replacement in placeholder_a_to_n)) {
                                let zeropadded = ("987654321000" + num_placeholders).slice(-8);
                                let k = `${zeropadded}`;
                                placeholder_a_to_n[replacement] = k;
                                placeholder_n_to_a[k] = replacement;
                                num_placeholders++;
                            }
                        }
                    }
                }
            }
            if (Object.keys(autotranslations_needed[lang]).length === 0) {
                delete autotranslations_needed[lang];
            }
        }

        const googleSupportedLanguages = {};
        const googleTargetLanguages = await googleTranslate.getSupportedLanguages({
            parent: "projects/" + keys.google_credentials.project_id + "/locations/global",
            displayLanguageCode: "en",
        });
        for (let lang of googleTargetLanguages[0].languages) {
            googleSupportedLanguages[lang.languageCode] = lang.displayName;
        }

        const deeplTargetLanguages = await deepl_translator.getTargetLanguages();
        const deeplSupportedLanguages = {};
        for (let e of deeplTargetLanguages) {
            deeplSupportedLanguages[e.code.toLowerCase()] = true;
        }

        for (let lang in autotranslations_needed) {
            if (lang in deeplSupportedLanguages || lang in googleSupportedLanguages) {
                if (!(lang in autotranslations)) {
                    autotranslations[lang] = {};
                }

                console.log(`Missing translations for ${lang}:`);

                const orig_strs = Object.keys(autotranslations_needed[lang]);
                const strs = orig_strs.map((s) => replace_placeholders(s, placeholder_a_to_n));

                let results = [];

                if (lang in deeplSupportedLanguages) {
                    console.log(`Deepl translating ${strs.length} strings`);
                    results = (await deepl_translator.translateText(strs, "en", lang)).map(
                        (r) => r.text,
                    );
                }
                else if (lang in googleSupportedLanguages) {
                    const todo = strs.map((s) => s);

                    while (todo.length > 0) {
                        const batch_size = Math.min(100, todo.length);
                        const batch = todo.splice(0, batch_size);
                        const batch_results = await googleTranslate.translateText({
                            parent:
                                "projects/" +
                                keys.google_credentials.project_id +
                                "/locations/global",
                            contents: batch,
                            mimeType: "text/plain",
                            sourceLanguageCode: "en",
                            targetLanguageCode: lang,
                        });

                        for (let i = 0; i < batch.length; i++) {
                            results.push(batch_results[0].translations[i].translatedText);
                        }
                        console.log(`Google Translated ${batch.length} strings`);
                    }
                }

                for (let i = 0; i < orig_strs.length; ++i) {
                    autotranslations[lang][orig_strs[i]] = replace_placeholders(
                        results[i],
                        placeholder_n_to_a,
                    );
                }
            } else {
                console.error("Failed to find a translator for " + lang);
            }
        }

        /*
    await autotranslate(lang, str);
    */

        await fs.promises.writeFile(
            "./autotranslations.json",
            JSON.stringify(autotranslations, undefined, 4),
        );
    }
    else {
        console.log("No autotranslation support on this system, skipping autotranslations");
    }
}

function replace_placeholders(str, dict) {
    for (let k in dict) {
        while (str.indexOf(k) >= 0) {
            str = str.replace(k, dict[k]);
        }
    }
    return str;
}

async function asyncLoadPoFile(filename) {
    return new Promise((resolve, reject) => {
        PO.load(filename, (err, po) => {
            if (err) {
                reject(err);
            } else {
                resolve(po);
            }
        });
    });
}

function encode(obj) {
    return btoa(unescape(encodeURIComponent(JSON.stringify(obj)))).replace(/.{76}(?=.)/g, "$&");
}

function decode(str) {
    return JSON.parse(decodeURIComponent(escape(atob(str))));
}

let bad_symbols = decode(
    "WyLljZAiLCLikrYiLCLimK0iLCLinK8iLCLimK4iLCLinKEiLCLljY0iLCLgv5UiLCLgv5YiLCLi" +
        "nJkiLCLgv5ciLCLgv5giLCLqlqYiLCLwn4+0Iiwi4ZuL4ZuLIl0=",
);

let profanity_regex = {};

let profanity_dictionary = decode(
    "eyJlbiI6WyJuaWdnZXIiLCJjdWNrIiwiZnVjayIsInNoaXQiLCJuYXppIiwiMTQ4OCIsImhpdGxl" +
        "ciIsInN0cmFpZ2h0IHByaWRlIiwiZ2F5IHByaWRlIiwiZ2F5IiwiZ2F5cyJdfQ==",
);

for (let lang in profanity_dictionary) {
    profanity_regex[lang] = new RegExp(
        "(" + profanity_dictionary[lang].map((s) => "\\b" + s + "\\b").join("|") + ")",
        "gui",
    );
}

function looks_vandalized(lang, msg) {
    if (detect_profanity("en", msg)) {
        console.error("en Profanity detected");
        return true;
    }
    if (detect_profanity(lang, msg)) {
        console.error(lang, " Profanity detected");
        return true;
    }

    let lower = msg.toLowerCase();

    for (let entry of bad_symbols) {
        if (lower.indexOf(entry) >= 0) {
            return true;
        }
    }

    return false;
}

function detect_profanity(lang, msg) {
    if (lang in profanity_regex) {
        return profanity_regex[lang].test(msg);
    }

    return false;
}

main()
    .then(() => console.log("Done"))
    .catch((err) => console.error(err));
<|endoftext|>


<commit_before>import spinner from './spinners/spinner';
import heart from './spinners/heart';

const mutations = {
  LOAD: 'LOAD',
  END: 'END'
};

const spinners = {
  spinner,
  heart
};

// Base Utils
const uniq = array => {
  return array.filter((el, index, arr) => index == arr.indexOf(el));
};

function createComponent({ componentName, moduleName, className }) {
  return {
    template: `

          <slot name='spinner'>
            <${componentName}-spinner :width="width || '1em'" :height="height || '1em'" />
          </slot>
          {{ message }}

        <slot v-if='!status'></slot>

    `,
    props: ['when', 'loader', 'message', 'height', 'width'],
    computed: {
      isLoading() {
        const store = this.$store;
        if (!store) {
          throw new Error('Vuex not initialized.');
        }
        return store.getters[`${moduleName}/isLoading`];
      },
      anyLoading() {
        const store = this.$store;
        if (!store) {
          throw new Error('Vuex not initialized.');
        }
        return store.getters[`${moduleName}/anyLoading`];
      },
      status() {
        if (this.when) {
          return this.when;
        }
        if (this.loader) {
          return this.isLoading(this.loader);
        }
        return this.anyLoading;
      }
    }
  };
}

// Vuex store to collect loadings
const createStore = function(moduleName) {
  return function(store) {
    store.registerModule(moduleName, {
      namespaced: true,
      state: {
        activeLoaders: []
      },
      getters: {
        isLoading: state => loaderMessage =>
          state.activeLoaders.indexOf(loaderMessage) > -1,
        anyLoading: state => state.activeLoaders.length > 0
      },
      actions: {
        load: ({ commit }, loaderMessage) =>
          commit(mutations.LOAD, loaderMessage),
        end: ({ commit }, loaderMessage) => commit(mutations.END, loaderMessage)
      },
      mutations: {
        [mutations.LOAD](state, loaderMessage) {
          state.activeLoaders.push(loaderMessage);
          state.activeLoaders = uniq(state.activeLoaders);
        },
        [mutations.END](state, loaderMessage) {
          state.activeLoaders = uniq(state.activeLoaders).filter(
            p => p !== loaderMessage
          );
        }
      }
    });
  };
};

// Vue plugin
const createInstaller = function({ moduleName, componentName, className }) {
  return function(Vue) {
    Vue.prototype.$startLoading = function(loaderMessage) {
      this.$store.dispatch(`${moduleName}/load`, loaderMessage, { root: true });
    };
    Vue.prototype.$endLoading = function(loaderMessage) {
      this.$store.dispatch(`${moduleName}/end`, loaderMessage, { root: true });
    };
    Vue.prototype.$isLoading = function(loaderMessage) {
      return this.$store.getters[`${moduleName}/isLoading`](loaderMessage);
    };
    Vue.prototype.$anyLoading = function() {
      return this.$store.getters[`${moduleName}/anyLoading`];
    };

    Vue.component(
      componentName,
      createComponent({ componentName, moduleName, className })
    );
    Object.keys(spinners).forEach(spinner => {
      Vue.component(`${componentName}-${spinner}`, spinners[spinner]);
    });
  };
};

function createVuexLoader({
  moduleName = 'loading',
  componentName = 'v-loading',
  className = 'v-loading'
}) {
  return {
    install: createInstaller({ moduleName, componentName, className }),
    Store: createStore(moduleName)
  };
}

function createActionHelpers({ moduleName }) {
  const start = function(dispatcher, loaderMessage) {
    dispatcher(`${moduleName}/loader`, loaderMessage, { root: true });
  };
  const end = function(dispatcher, loaderMessage) {
    dispatcher(`${moduleName}/end`, loaderMessage, { root: true });
  };
  return {
    // start and stop helpers for async processes
    startLoading(dispatcher, loaderMessage, callback) {
      start(loaderMessage, loaderMessage);
      return callback()
        .then(response => {
          end(loaderMessage, loaderMessage);
          return response;
        })
        .catch(response => {
          end(loaderMessage, loaderMessage);
          return response;
        });
    },
    endLoading(dispatcher, loaderMessage) {
      end(loaderMessage, loaderMessage);
    }
  };
}

export default {
  createVuexLoader,
  createActionHelpers
};
<commit_msg>action name fix<commit_after>import spinner from './spinners/spinner';
import heart from './spinners/heart';

const mutations = {
  LOAD: 'LOAD',
  END: 'END'
};

const spinners = {
  spinner,
  heart
};

// Base Utils
const uniq = array => {
  return array.filter((el, index, arr) => index == arr.indexOf(el));
};

function createComponent({ componentName, moduleName, className }) {
  return {
    template: `

          <slot name='spinner'>
            <${componentName}-spinner :width="width || '1em'" :height="height || '1em'" />
          </slot>
          {{ message }}

        <slot v-if='!status'></slot>

    `,
    props: ['when', 'loader', 'message', 'height', 'width'],
    computed: {
      isLoading() {
        const store = this.$store;
        if (!store) {
          throw new Error('Vuex not initialized.');
        }
        return store.getters[`${moduleName}/isLoading`];
      },
      anyLoading() {
        const store = this.$store;
        if (!store) {
          throw new Error('Vuex not initialized.');
        }
        return store.getters[`${moduleName}/anyLoading`];
      },
      status() {
        if (this.when) {
          return this.when;
        }
        if (this.loader) {
          return this.isLoading(this.loader);
        }
        return this.anyLoading;
      }
    }
  };
}

// Vuex store to collect loadings
const createStore = function(moduleName) {
  return function(store) {
    store.registerModule(moduleName, {
      namespaced: true,
      state: {
        activeLoaders: []
      },
      getters: {
        isLoading: state => loaderMessage =>
          state.activeLoaders.indexOf(loaderMessage) > -1,
        anyLoading: state => state.activeLoaders.length > 0
      },
      actions: {
        load: ({ commit }, loaderMessage) =>
          commit(mutations.LOAD, loaderMessage),
        end: ({ commit }, loaderMessage) => commit(mutations.END, loaderMessage)
      },
      mutations: {
        [mutations.LOAD](state, loaderMessage) {
          state.activeLoaders.push(loaderMessage);
          state.activeLoaders = uniq(state.activeLoaders);
        },
        [mutations.END](state, loaderMessage) {
          state.activeLoaders = uniq(state.activeLoaders).filter(
            p => p !== loaderMessage
          );
        }
      }
    });
  };
};

// Vue plugin
const createInstaller = function({ moduleName, componentName, className }) {
  return function(Vue) {
    Vue.prototype.$startLoading = function(loaderMessage) {
      this.$store.dispatch(`${moduleName}/load`, loaderMessage, { root: true });
    };
    Vue.prototype.$endLoading = function(loaderMessage) {
      this.$store.dispatch(`${moduleName}/end`, loaderMessage, { root: true });
    };
    Vue.prototype.$isLoading = function(loaderMessage) {
      return this.$store.getters[`${moduleName}/isLoading`](loaderMessage);
    };
    Vue.prototype.$anyLoading = function() {
      return this.$store.getters[`${moduleName}/anyLoading`];
    };

    Vue.component(
      componentName,
      createComponent({ componentName, moduleName, className })
    );
    Object.keys(spinners).forEach(spinner => {
      Vue.component(`${componentName}-${spinner}`, spinners[spinner]);
    });
  };
};

function createVuexLoader({
  moduleName = 'loading',
  componentName = 'v-loading',
  className = 'v-loading'
}) {
  return {
    install: createInstaller({ moduleName, componentName, className }),
    Store: createStore(moduleName)
  };
}

function createActionHelpers({ moduleName }) {
  const start = function(dispatcher, loaderMessage) {
    dispatcher(`${moduleName}/load`, loaderMessage, { root: true });
  };
  const end = function(dispatcher, loaderMessage) {
    dispatcher(`${moduleName}/end`, loaderMessage, { root: true });
  };
  return {
    // start and stop helpers for async processes
    startLoading(dispatcher, loaderMessage, callback) {
      start(loaderMessage, loaderMessage);
      return callback()
        .then(response => {
          end(loaderMessage, loaderMessage);
          return response;
        })
        .catch(response => {
          end(loaderMessage, loaderMessage);
          return response;
        });
    },
    endLoading(dispatcher, loaderMessage) {
      end(loaderMessage, loaderMessage);
    }
  };
}

export default {
  createVuexLoader,
  createActionHelpers
};
<|endoftext|>