<commit_before>'use strict';

const _ = require('lodash');
const util = require('util');
const { DataEntity } = require('@terascope/job-components');

function newProcessor(context, opConfig) {
    function formattedDate(record) {
        const offsets = {
            daily: 10,
            monthly: 7,
            yearly: 4
        };

        const end = offsets[opConfig.timeseries] || 10;
        let date;
        try {
            date = new Date(record[opConfig.date_field]).toISOString().slice(0, end);
        } catch (err) {
            throw new Error(`opConfig date field: ${opConfig.date_field} either does not exists or is not a valid date on the records processed`);
        }

        return date.replace(/-/gi, '.');
    }

    function indexName(record) {
        if (opConfig.timeseries) {
            const index = formattedDate(record);
            const prefix = opConfig.index_prefix;
            const indexPrefix = prefix.charAt(prefix.length - 1) === '-' ? prefix : `${prefix}-`;

            return indexPrefix + index;
        }

        return opConfig.index;
    }

    /*
     * Additional configuration fields. Should validate once a schema is available.
     * update - boolean. set to true if the ES request should be an update
     * update_fields - array or strings. Will pull the specified fields from the data record
     *    and create an upsert request
     * id_field - string. Name of the field that contains the id to use for the inserted record.
     * preserve_id - boolean. if incoming data is from ES the existing ID
     * of the record will be preserved.
     */

    return (data) => {
        let fromElastic = false;
        let dataArray = data;
        const fullResponseData = _.get(dataArray, 'hits.hits');

        if (fullResponseData) {
            fromElastic = true;
            dataArray = fullResponseData;
        }
        const formatted = [];

        function generateRequest(start) {
            let record;
            if (fromElastic) {
                record = dataArray[start]._source;
            } else {
                record = dataArray[start];
            }
            const indexSpec = {};

            const meta = {
                _index: indexName(record),
                _type: opConfig.type
            };

            if (opConfig.preserve_id) meta._id = DataEntity.getMetadata(record, '_key');
            if (fromElastic) meta._id = data.hits.hits[start]._id;
            if (opConfig.id_field) meta._id = record[opConfig.id_field];

            if (opConfig.update || opConfig.upsert) {
                indexSpec.update = meta;

                if (opConfig.update_retry_on_conflict > 0) {
                    meta._retry_on_conflict = opConfig.update_retry_on_conflict;
                }
            } else if (opConfig.delete) {
                indexSpec.delete = meta;
            } else if (opConfig.create) {
                indexSpec.create = meta;
            } else {
                indexSpec.index = meta;
            }

            formatted.push(indexSpec);

            if (opConfig.update || opConfig.upsert) {
                const update = {};

                if (opConfig.upsert) {
                    // The upsert field is what is inserted if the key doesn't already exist
                    update.upsert = record;
                }

                // This will merge this record with the existing record.
                if (opConfig.update_fields.length > 0) {
                    update.doc = {};
                    opConfig.update_fields.forEach((field) => {
                        update.doc[field] = record[field];
                    });
                } else if (opConfig.script_file || opConfig.script) {
                    if (opConfig.script_file) {
                        update.script = {
                            file: opConfig.script_file
                        };
                    }

                    if (opConfig.script) {
                        update.script = {
                            inline: opConfig.script
                        };
                    }

                    update.script.params = {};
                    _.forOwn(opConfig.script_params, (field, key) => {
                        if (record[field]) {
                            update.script.params[key] = record[field];
                        }
                    });
                } else {
                    update.doc = record;
                }

                formatted.push(update);
            } else if (opConfig.delete === false) {
                formatted.push(record);
            }
        }

        for (let i = 0; i < dataArray.length; i += 1) {
            generateRequest(i);
        }

        return formatted;
    };
}

function schema() {
    return {
        index: {
            doc: 'Index to where the data will be sent to, if you wish the index to be based on a timeseries, '
              + 'use the timeseries option instead',
            default: '',
            format(val) {
                if (typeof val !== 'string') {
                    throw new Error('index must be of type string');
                }

                if (val.match(/[A-Z]/)) {
                    throw new Error('index must be lowercase');
                }
            }
        },
        type: {
            doc: 'Set the type of the data for elasticsearch. If incoming data is from elasticsearch'
            + ' it will default to the type on the metadata if this field is not set. This field must be set'
            + 'for all other incoming data',
            default: '',
            format: 'optional_String'
        },
        preserve_id: {
            doc: 'If incoming data if from elasticsearch, set this to true if you wish to keep the previous id'
            + ' else elasticsearch will generate one for you (upload performance is faster if you let it auto-generate)',
            default: false,
            format: Boolean
        },
        id_field: {
            doc: 'If you wish to set the id based off another field in the doc, set the name of the field here',
            default: '',
            format: 'optional_String'
        },
        timeseries: {
            doc: 'Set to either daily, monthly or yearly if you want the index to be based off it, must be '
            + 'used in tandem with index_prefix and date_field',
            default: '',
            format(value) {
                // This will generate logstash style timeseries names
                if (value && (!_.includes(['daily', 'monthly', 'yearly'], value))) {
                    throw new Error("timeseries must be one of 'daily', 'monthly', 'yearly'");
                }
            }
        },
        index_prefix: {
            doc: 'Used with timeseries, adds a prefix to the date ie (index_prefix: "events-" ,timeseries: "daily => '
            + 'events-2015.08.20',
            default: '',
            format(val) {
                if (val) {
                    if (typeof val !== 'string') {
                        throw new Error('index_prefix must be of type string');
                    }
                    if (val.match(/[A-Z]/)) {
                        throw new Error('index_prefix must be lowercase');
                    }
                }
            }
        },
        date_field: {
            doc: 'Used with timeseries, specify what field of the data should be used to calculate the timeseries',
            default: '',
            format: 'optional_String'
        },
        delete: {
            doc: 'Use the id_field from the incoming records to bulk delete documents.',
            default: false,
            format: Boolean
        },
        update: {
            doc: 'Specify if the data should update the records or, if not it will index them',
            default: false,
            format: Boolean
        },
        update_retry_on_conflict: {
            doc: 'If there is a version conflict from an update how often should it be retried.',
            default: 0,
            format(val) {
                if (isNaN(val)) {
                    throw new Error('update_retry_on_conflict for elasticsearch_bulk must be a number');
                } else if (val < 0) {
                    throw new Error('update_retry_on_conflict for elasticsearch_bulk must be greater than or equal to zero');
                }
            }
        },
        update_fields: {
            doc: 'if you are updating the documents, you can specify fields to update here (it should be an array '
            + 'containing all the field names you want updated), it defaults to sending the entire document',
            default: [],
            format: Array
        },
        upsert: {
            doc: 'Specify if the incoming records should be used to perform an upsert. If update_fields is also specified then existing records will be updated with those fields otherwise the full incoming record will be inserted.',
            default: false,
            format: Boolean
        },
        create: {
            doc: 'Specify if the incoming records should be used to perform an create event ("put-if-absent" behavior).',
            default: false,
            format: Boolean
        },
        script_file: {
            doc: 'Name of the script file to run as part of an update request.',
            default: '',
            format: 'optional_String'
        },
        script: {
            doc: 'Inline script to include in each indexing request. Only very simple painless scripts are currently supported.',
            default: '',
            format: 'optional_String'
        },
        script_params: {
            doc: 'key -> value parameter mappings. The value will be extracted from the incoming data and passed to the script as param based on the key.',
            default: {},
            format: Object
        }
    };
}

function selfValidation(op) {
    if (op.timeseries || op.index_prefix || op.date_field) {
        if (!(op.timeseries && op.index_prefix && op.date_field)) {
            throw new Error('elasticsearch_index_selector is mis-configured, if any of the following configurations are set: timeseries, index_prefix or date_field, they must all be used together, please set the missing parameters');
        }
    }
}

function crossValidation(job) {
    const opConfig = job.operations.find(op => op._op === 'elasticsearch_index_selector');
    const preserveId = job.operations.find(op => op.preserve_id === true);

    if (!opConfig.type && !preserveId) {
        throw new Error('type must be specified in elasticsearch index selector config if data is not a full response from elasticsearch');
    }
}

const depMsg = 'This native processors in teraslice are being deprecated, please use the elasticsearch-assets project with the assets api to use this module';
const code = 'esReader';

module.exports = {
    newProcessor: util.deprecate(newProcessor, depMsg, code),
    schema: util.deprecate(schema, depMsg, code),
    crossValidation: util.deprecate(crossValidation, depMsg, code),
    selfValidation: util.deprecate(selfValidation, depMsg, code)
};
<commit_msg>index selector should validate the length of the index<commit_after>'use strict';

const _ = require('lodash');
const util = require('util');
const { DataEntity } = require('@terascope/job-components');

function newProcessor(context, opConfig) {
    function formattedDate(record) {
        const offsets = {
            daily: 10,
            monthly: 7,
            yearly: 4
        };

        const end = offsets[opConfig.timeseries] || 10;
        let date;
        try {
            date = new Date(record[opConfig.date_field]).toISOString().slice(0, end);
        } catch (err) {
            throw new Error(`opConfig date field: ${opConfig.date_field} either does not exists or is not a valid date on the records processed`);
        }

        return date.replace(/-/gi, '.');
    }

    function indexName(record) {
        if (opConfig.timeseries) {
            const index = formattedDate(record);
            const prefix = opConfig.index_prefix;
            const indexPrefix = prefix.charAt(prefix.length - 1) === '-' ? prefix : `${prefix}-`;

            return indexPrefix + index;
        }

        return opConfig.index;
    }

    /*
     * Additional configuration fields. Should validate once a schema is available.
     * update - boolean. set to true if the ES request should be an update
     * update_fields - array or strings. Will pull the specified fields from the data record
     *    and create an upsert request
     * id_field - string. Name of the field that contains the id to use for the inserted record.
     * preserve_id - boolean. if incoming data is from ES the existing ID
     * of the record will be preserved.
     */

    return (data) => {
        let fromElastic = false;
        let dataArray = data;
        const fullResponseData = _.get(dataArray, 'hits.hits');

        if (fullResponseData) {
            fromElastic = true;
            dataArray = fullResponseData;
        }
        const formatted = [];

        function generateRequest(start) {
            let record;
            if (fromElastic) {
                record = dataArray[start]._source;
            } else {
                record = dataArray[start];
            }
            const indexSpec = {};

            const meta = {
                _index: indexName(record),
                _type: opConfig.type
            };

            if (opConfig.preserve_id) meta._id = DataEntity.getMetadata(record, '_key');
            if (fromElastic) meta._id = data.hits.hits[start]._id;
            if (opConfig.id_field) meta._id = record[opConfig.id_field];

            if (opConfig.update || opConfig.upsert) {
                indexSpec.update = meta;

                if (opConfig.update_retry_on_conflict > 0) {
                    meta._retry_on_conflict = opConfig.update_retry_on_conflict;
                }
            } else if (opConfig.delete) {
                indexSpec.delete = meta;
            } else if (opConfig.create) {
                indexSpec.create = meta;
            } else {
                indexSpec.index = meta;
            }

            formatted.push(indexSpec);

            if (opConfig.update || opConfig.upsert) {
                const update = {};

                if (opConfig.upsert) {
                    // The upsert field is what is inserted if the key doesn't already exist
                    update.upsert = record;
                }

                // This will merge this record with the existing record.
                if (opConfig.update_fields.length > 0) {
                    update.doc = {};
                    opConfig.update_fields.forEach((field) => {
                        update.doc[field] = record[field];
                    });
                } else if (opConfig.script_file || opConfig.script) {
                    if (opConfig.script_file) {
                        update.script = {
                            file: opConfig.script_file
                        };
                    }

                    if (opConfig.script) {
                        update.script = {
                            inline: opConfig.script
                        };
                    }

                    update.script.params = {};
                    _.forOwn(opConfig.script_params, (field, key) => {
                        if (record[field]) {
                            update.script.params[key] = record[field];
                        }
                    });
                } else {
                    update.doc = record;
                }

                formatted.push(update);
            } else if (opConfig.delete === false) {
                formatted.push(record);
            }
        }

        for (let i = 0; i < dataArray.length; i += 1) {
            generateRequest(i);
        }

        return formatted;
    };
}

function schema() {
    return {
        index: {
            doc: 'Index to where the data will be sent to, if you wish the index to be based on a timeseries, '
              + 'use the timeseries option instead',
            default: '',
            format(val) {
                if (typeof val !== 'string') {
                    throw new Error('index must be of type string');
                }

                if (val.match(/[A-Z]/)) {
                    throw new Error('index must be lowercase');
                }

                if (val.length === 0) {
                    throw new Error('index must not be an empty string');
                }
            }
        },
        type: {
            doc: 'Set the type of the data for elasticsearch. If incoming data is from elasticsearch'
            + ' it will default to the type on the metadata if this field is not set. This field must be set'
            + 'for all other incoming data',
            default: '',
            format: 'optional_String'
        },
        preserve_id: {
            doc: 'If incoming data if from elasticsearch, set this to true if you wish to keep the previous id'
            + ' else elasticsearch will generate one for you (upload performance is faster if you let it auto-generate)',
            default: false,
            format: Boolean
        },
        id_field: {
            doc: 'If you wish to set the id based off another field in the doc, set the name of the field here',
            default: '',
            format: 'optional_String'
        },
        timeseries: {
            doc: 'Set to either daily, monthly or yearly if you want the index to be based off it, must be '
            + 'used in tandem with index_prefix and date_field',
            default: '',
            format(value) {
                // This will generate logstash style timeseries names
                if (value && (!_.includes(['daily', 'monthly', 'yearly'], value))) {
                    throw new Error("timeseries must be one of 'daily', 'monthly', 'yearly'");
                }
            }
        },
        index_prefix: {
            doc: 'Used with timeseries, adds a prefix to the date ie (index_prefix: "events-" ,timeseries: "daily => '
            + 'events-2015.08.20',
            default: '',
            format(val) {
                if (val) {
                    if (typeof val !== 'string') {
                        throw new Error('index_prefix must be of type string');
                    }
                    if (val.match(/[A-Z]/)) {
                        throw new Error('index_prefix must be lowercase');
                    }
                }
            }
        },
        date_field: {
            doc: 'Used with timeseries, specify what field of the data should be used to calculate the timeseries',
            default: '',
            format: 'optional_String'
        },
        delete: {
            doc: 'Use the id_field from the incoming records to bulk delete documents.',
            default: false,
            format: Boolean
        },
        update: {
            doc: 'Specify if the data should update the records or, if not it will index them',
            default: false,
            format: Boolean
        },
        update_retry_on_conflict: {
            doc: 'If there is a version conflict from an update how often should it be retried.',
            default: 0,
            format(val) {
                if (isNaN(val)) {
                    throw new Error('update_retry_on_conflict for elasticsearch_bulk must be a number');
                } else if (val < 0) {
                    throw new Error('update_retry_on_conflict for elasticsearch_bulk must be greater than or equal to zero');
                }
            }
        },
        update_fields: {
            doc: 'if you are updating the documents, you can specify fields to update here (it should be an array '
            + 'containing all the field names you want updated), it defaults to sending the entire document',
            default: [],
            format: Array
        },
        upsert: {
            doc: 'Specify if the incoming records should be used to perform an upsert. If update_fields is also specified then existing records will be updated with those fields otherwise the full incoming record will be inserted.',
            default: false,
            format: Boolean
        },
        create: {
            doc: 'Specify if the incoming records should be used to perform an create event ("put-if-absent" behavior).',
            default: false,
            format: Boolean
        },
        script_file: {
            doc: 'Name of the script file to run as part of an update request.',
            default: '',
            format: 'optional_String'
        },
        script: {
            doc: 'Inline script to include in each indexing request. Only very simple painless scripts are currently supported.',
            default: '',
            format: 'optional_String'
        },
        script_params: {
            doc: 'key -> value parameter mappings. The value will be extracted from the incoming data and passed to the script as param based on the key.',
            default: {},
            format: Object
        }
    };
}

function selfValidation(op) {
    if (op.timeseries || op.index_prefix || op.date_field) {
        if (!(op.timeseries && op.index_prefix && op.date_field)) {
            throw new Error('elasticsearch_index_selector is mis-configured, if any of the following configurations are set: timeseries, index_prefix or date_field, they must all be used together, please set the missing parameters');
        }
    }
}

function crossValidation(job) {
    const opConfig = job.operations.find(op => op._op === 'elasticsearch_index_selector');
    const preserveId = job.operations.find(op => op.preserve_id === true);

    if (!opConfig.type && !preserveId) {
        throw new Error('type must be specified in elasticsearch index selector config if data is not a full response from elasticsearch');
    }
}

const depMsg = 'This native processors in teraslice are being deprecated, please use the elasticsearch-assets project with the assets api to use this module';
const code = 'esReader';

module.exports = {
    newProcessor: util.deprecate(newProcessor, depMsg, code),
    schema: util.deprecate(schema, depMsg, code),
    crossValidation: util.deprecate(crossValidation, depMsg, code),
    selfValidation: util.deprecate(selfValidation, depMsg, code)
};
<|endoftext|>


<commit_before>/* eslint-disable no-plusplus,consistent-return */
const Promise = require('bluebird');
const https = require('https');
const _ = require('lodash');
const Discord = require('discord.js');

const database = require('../../../database');
const constants = require('../../../config/constants');

const alerts = {};

function modifyAlerts(result) {
  _.forEach(result, (alert) => {
    if (alert.twitchChannel in alerts) {
      const channelAlerts = alerts[alert.twitchChannel].alert;
      _.forEach(channelAlerts, (channelAlert) => {
        if (channelAlert.userId === alert.userId) {
          return true;
        }
      });
      alerts[alert.twitchChannel].alert.push({ userId: alert.userId, channelId: alert.channelId });
    } else {
      alerts[alert.twitchChannel] = {
        alert: [{ userId: alert.userId, channelId: alert.channelId }], online: true };
    }
  });
}

function findAlert(alert) {
  if (alert.twitchChannel in alerts) {
    for (let i = 0; i < alerts[alert.twitchChannel].alert.length; i++) {
      const a = alerts[alert.twitchChannel].alert[i];
      if (a.userId === alert.userId && a.channelId === alert.channelId) {
        return i;
      }
    }
  }
  return -1;
}

function getAlerts() {
  if (Object.keys(alerts).length !== 0) return;
  return new Promise((resolve, reject) => {
    database.connection.query('SELECT userId, twitchChannel, channelId FROM alerts', (err, result) => {
      if (err) return reject(err);
      if (result.length === 0) return (resolve('No alerts.'));
      modifyAlerts(result);
      resolve(alerts);
    });
  });
}

function channelExists(channel) {
  const options = {
    host: 'api.twitch.tv',
    path: `/kraken/channels/${channel}`,
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/vnd.twitchtv.v3+json',
      'Client-ID': `${constants.TWITCH_ID}`,
    },
  };
  return new Promise((resolve, reject) => {
    let channelStatus = '';
    const req = https.request(options, (res) => {
      res.on('data', (data) => {
        channelStatus += data;
      });
    });
    req.on('close', () => {
      const channelResult = JSON.parse(channelStatus);
      resolve(channelResult.status);
    });
    req.on('error', (err) => {
      reject(err);
    });
    req.end();
  });
}

function addAlert(parameters, message) {
  if (parameters.length === 0) return;
  const status = channelExists(parameters[0]);
  if (status instanceof Promise) {
    return new Promise((resolve, reject) => {
      status.then((res) => {
        if (res) {
          if (res === 404) return resolve(`Channel ${parameters[0]} does not exist.`);
          const alertExists = modifyAlerts([{
            userId: message.author.id,
            twitchChannel: parameters[0],
            channelId: message.channel.id,
          }]);
          if (alertExists) return resolve(`You already have a alert for channel ${parameters[0]}.`);
          database.connection.query(
            'INSERT INTO alerts (userId, twitchChannel, channelId) VALUES(?, ?, ?)',
          [message.author.id, parameters[0], message.channel.id], (err) => {
            if (err) return reject(err);
            resolve('Alert added. :ok_hand:');
          }
        );
        } else {
          return resolve(`Cant add channel ${parameters[0]}.`);
        }
      })
      .catch((err) => {
        reject(err);
      });
    });
  }
}

function removeAlert(parameters, message) {
  if (parameters.length === 0) return;
  const index = findAlert(
    { channelId: message.channel.id, twitchChannel: parameters[0], userId: message.author.id });
  return new Promise((resolve, reject) => {
    if (index === -1) return (resolve(`You don't have an alert to channel ${parameters[0]}.`));
    database.connection.query(
      'DELETE FROM alerts WHERE userId = ? AND twitchChannel = ? AND channelId = ?',
      [message.author.id, parameters[0], message.channel.id], (err) => {
        if (err) return reject(err);
        alerts[parameters[0]].alert.splice(index, 1);
        if (alerts[parameters[0]].alert.length === 0) {
          delete alerts[parameters[0]];
        }
        resolve('Removed alert.');
      }
    );
  });
}

function listAlerts(parameters, message) {
  return new Promise((resolve) => {
    let alertResult = '';
    for (const key in alerts) {
      const obj = alerts[key];
      for (let i = 0; i < obj.alert.length; i++) {
        if (obj.alert[i].userId === message.author.id &&
          obj.alert[i].channelId === message.channel.id) {
          alertResult += `${key}, `;
          break;
        }
      }
    }
    if (alertResult.length === 0) return (resolve('You have no alerts in this channel. !addalert <twitchchannel> to add alerts.'));
    resolve(alertResult.substring(0, alertResult.length - 2));
  });
}

function checkAlert(channel) {
  if (channel.length === 0) return;
  const options = {
    host: 'api.twitch.tv',
    path: `/kraken/streams/${channel}`,
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/vnd.twitchtv.v3+json',
      'Client-ID': `${constants.TWITCH_ID}`,
    },
  };
  return new Promise((resolve, reject) => {
    let streamStatus = '';
    const req = https.request(options, (res) => {
      res.on('data', (data) => {
        streamStatus += data;
      });
    });
    req.on('close', () => {
      let statusResult;
      try {
        statusResult = JSON.parse(streamStatus);
      } catch (error) {
        return reject(error);
      }
      if (statusResult.stream) {
        if (alerts[channel].online === false) {
          const announcements = {};
          _.forEach(alerts[channel].alert, (alert) => {
            const { channelId, userId } = alert;
            if (channelId in announcements) {
              announcements[channelId].message.push({ userId });
            } else {
              announcements[channelId] = { message: [{ userId }] };
            }
          });
          const embed = new Discord.RichEmbed();
          const { stream } = statusResult;
          embed.setAuthor(stream.channel.display_name, stream.channel.logo);
          embed.addField('Title', stream.channel.status);
          embed.addField('Game', stream.game);
          embed.addField('Viewers', stream.viewers);
          embed.setURL(stream.channel.url);
          embed.addField('Url', stream.channel.url);
          const { preview } = stream;
          if (!_.isNil(preview) && !_.isNil(preview.large)) {
            embed.setThumbnail(preview.large);
          }
          if (!_.isNil(stream.profile_banner_background_color)) {
            embed.setColor(stream.profile_banner_background_color);
          }
          alerts[channel].online = true;
          resolve({ channel: announcements, message: embed, channelName: stream.channel.display_name });
        }
      } else {
        alerts[channel].online = false;
      }
    });
    req.on('error', (err) => {
      reject(err);
    });
    req.end();
  });
}

module.exports = {
  checkalert: checkAlert,
  getalerts: getAlerts,
  addalert: addAlert,
  alerts: listAlerts,
  removealert: removeAlert,
};
<commit_msg>Twitch alert style changes<commit_after>/* eslint-disable no-plusplus,consistent-return */
const Promise = require('bluebird');
const https = require('https');
const _ = require('lodash');
const Discord = require('discord.js');

const database = require('../../../database');
const constants = require('../../../config/constants');

const alerts = {};

function modifyAlerts(result) {
  _.forEach(result, (alert) => {
    if (alert.twitchChannel in alerts) {
      const channelAlerts = alerts[alert.twitchChannel].alert;
      _.forEach(channelAlerts, (channelAlert) => {
        if (channelAlert.userId === alert.userId) {
          return true;
        }
      });
      alerts[alert.twitchChannel].alert.push({ userId: alert.userId, channelId: alert.channelId });
    } else {
      alerts[alert.twitchChannel] = {
        alert: [{ userId: alert.userId, channelId: alert.channelId }], online: true };
    }
  });
}

function findAlert(alert) {
  if (alert.twitchChannel in alerts) {
    for (let i = 0; i < alerts[alert.twitchChannel].alert.length; i++) {
      const a = alerts[alert.twitchChannel].alert[i];
      if (a.userId === alert.userId && a.channelId === alert.channelId) {
        return i;
      }
    }
  }
  return -1;
}

function getAlerts() {
  if (Object.keys(alerts).length !== 0) return;
  return new Promise((resolve, reject) => {
    database.connection.query('SELECT userId, twitchChannel, channelId FROM alerts', (err, result) => {
      if (err) return reject(err);
      if (result.length === 0) return (resolve('No alerts.'));
      modifyAlerts(result);
      resolve(alerts);
    });
  });
}

function channelExists(channel) {
  const options = {
    host: 'api.twitch.tv',
    path: `/kraken/channels/${channel}`,
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/vnd.twitchtv.v3+json',
      'Client-ID': `${constants.TWITCH_ID}`,
    },
  };
  return new Promise((resolve, reject) => {
    let channelStatus = '';
    const req = https.request(options, (res) => {
      res.on('data', (data) => {
        channelStatus += data;
      });
    });
    req.on('close', () => {
      const channelResult = JSON.parse(channelStatus);
      resolve(channelResult.status);
    });
    req.on('error', (err) => {
      reject(err);
    });
    req.end();
  });
}

function addAlert(parameters, message) {
  if (parameters.length === 0) return;
  const status = channelExists(parameters[0]);
  if (status instanceof Promise) {
    return new Promise((resolve, reject) => {
      status.then((res) => {
        if (res) {
          if (res === 404) return resolve(`Channel ${parameters[0]} does not exist.`);
          const alertExists = modifyAlerts([{
            userId: message.author.id,
            twitchChannel: parameters[0],
            channelId: message.channel.id,
          }]);
          if (alertExists) return resolve(`You already have a alert for channel ${parameters[0]}.`);
          database.connection.query(
            'INSERT INTO alerts (userId, twitchChannel, channelId) VALUES(?, ?, ?)',
          [message.author.id, parameters[0], message.channel.id], (err) => {
            if (err) return reject(err);
            resolve('Alert added. :ok_hand:');
          }
        );
        } else {
          return resolve(`Cant add channel ${parameters[0]}.`);
        }
      })
      .catch((err) => {
        reject(err);
      });
    });
  }
}

function removeAlert(parameters, message) {
  if (parameters.length === 0) return;
  const index = findAlert(
    { channelId: message.channel.id, twitchChannel: parameters[0], userId: message.author.id });
  return new Promise((resolve, reject) => {
    if (index === -1) return (resolve(`You don't have an alert to channel ${parameters[0]}.`));
    database.connection.query(
      'DELETE FROM alerts WHERE userId = ? AND twitchChannel = ? AND channelId = ?',
      [message.author.id, parameters[0], message.channel.id], (err) => {
        if (err) return reject(err);
        alerts[parameters[0]].alert.splice(index, 1);
        if (alerts[parameters[0]].alert.length === 0) {
          delete alerts[parameters[0]];
        }
        resolve('Removed alert.');
      }
    );
  });
}

function listAlerts(parameters, message) {
  return new Promise((resolve) => {
    let alertResult = '';
    for (const key in alerts) {
      const obj = alerts[key];
      for (let i = 0; i < obj.alert.length; i++) {
        if (obj.alert[i].userId === message.author.id &&
          obj.alert[i].channelId === message.channel.id) {
          alertResult += `${key}, `;
          break;
        }
      }
    }
    if (alertResult.length === 0) return (resolve('You have no alerts in this channel. !addalert <twitchchannel> to add alerts.'));
    resolve(alertResult.substring(0, alertResult.length - 2));
  });
}

function checkAlert(channel) {
  if (channel.length === 0) return;
  const options = {
    host: 'api.twitch.tv',
    path: `/kraken/streams/${channel}`,
    headers: {
      'Content-Type': 'application/json',
      Accept: 'application/vnd.twitchtv.v3+json',
      'Client-ID': `${constants.TWITCH_ID}`,
    },
  };
  return new Promise((resolve, reject) => {
    let streamStatus = '';
    const req = https.request(options, (res) => {
      res.on('data', (data) => {
        streamStatus += data;
      });
    });
    req.on('close', () => {
      let statusResult;
      try {
        statusResult = JSON.parse(streamStatus);
      } catch (error) {
        return reject(error);
      }
      if (statusResult.stream) {
        if (alerts[channel].online === false) {
          const announcements = {};
          _.forEach(alerts[channel].alert, (alert) => {
            const { channelId, userId } = alert;
            if (channelId in announcements) {
              announcements[channelId].message.push({ userId });
            } else {
              announcements[channelId] = { message: [{ userId }] };
            }
          });
          const embed = new Discord.RichEmbed();
          const { stream } = statusResult;
          embed.setAuthor(stream.channel.display_name, stream.channel.logo);
          embed.addField('Title', stream.channel.status);
          embed.addField('Game', stream.game, true);
          embed.addField('Viewers', stream.viewers, true);
          embed.setURL(stream.channel.url);
          embed.addField('Url', stream.channel.url);
          const { preview } = stream;
          if (!_.isNil(preview) && !_.isNil(preview.medium)) {
            embed.setImage(preview.medium);
          }
          if (!_.isNil(stream.profile_banner_background_color)) {
            embed.setColor(stream.profile_banner_background_color);
          }
          embed.setThumbnail(stream.channel.logo);
          alerts[channel].online = true;
          resolve({ channel: announcements, message: embed, channelName: stream.channel.display_name });
        }
      } else {
        alerts[channel].online = false;
      }
    });
    req.on('error', (err) => {
      reject(err);
    });
    req.end();
  });
}

module.exports = {
  checkalert: checkAlert,
  getalerts: getAlerts,
  addalert: addAlert,
  alerts: listAlerts,
  removealert: removeAlert,
};
<|endoftext|>


<commit_before>(function(global) {
    'use strict';

    HTMLElement.prototype.on = function() {
        this.addEventListener.apply(this, arguments);
    };

    function $(selector) {
        return document.querySelector(selector);
    }

    function $$(selector) {
        return document.querySelectorAll(selector);
    }

    function stdout(msg) {
        $('#stdout').textContent += msg;
    }
    var timeOutNum = 1000,
        timeOutValue = 0,
        running = true;

    var ram = [],
        maxRams = 90,
        ramPointer = 0,
        ramStack = new global.HighlightStack();

    function updatePointer(num) {
        ramPointer = num;
        ramStack.addItem($('#cell-' + num));
    }

    function incrementPointer() {
        var ptr = ramPointer + 1;
        if (ptr >= maxRams)
            ptr = 0;
        updatePointer(ptr);
    }

    function decrementPointer() {
        var ptr = ramPointer - 1;
        if (ptr < 0)
            ptr = maxRams - 1;
        updatePointer(ptr);
    }

    function getRam() {
        return ram[ramPointer] || 0;
    }

    function setRam(value) {
        ram[ramPointer] = value;
    }

    function resetRam() {
        ramPointer = 0;
        ram = [];
        ramStack.clearStack();
    }

    var program = [],
        pc = 0;

    function resetProgram() {
        pc = 0;
        resetRam();
    }

    function runProgram() {
        window.clearTimeout(timeOutValue);
        if (true)
            return;

        // foo

        if (running)
            timeOutValue = window.setTimeout(runProgram, timeOutNum);
    }
    var isComment = /[^<>+\-[\].,]/;

    function tokenizeProgram(text) {
        var prog = [],
            loopStack = [],
            currentComment = '';
        var tokens = text.split('').forEach(function(token) {
            if (isComment.test(token)) {
                currentComment += token;
                return;
            }
            if (currentComment) {
                prog.push(['comment', currentComment]);
                currentComment = '';
                pc++;
            }
            if (token == '[') {
                loopStack.push(prog.length);
                prog.push(['opcode', token, -1]);
            } else if (token == ']') {
                if (!loopStack.length)
                    throw new Error("Unbalanced []s!");
                var match = loopStack.pop();
                prog[match][2] = prog.length;
                prog.push(['opcode', token, match]);
            } else {
                prog.push(['opcode', token]);
            }
        });
        if (loopStack.length > 0)
            throw new Error("Unbalanced []s!");
        return prog;
    }
    // debug
    global.tokenizeProgram = tokenizeProgram;

    (function(id) {
        var i, j;
        var tab = document.getElementById(id);
        var thead = tab.querySelector('thead');
        var tbody = tab.querySelector('tbody');
        var tr, td;
        for (i = 0; i < 3; i++) {
            tr = document.createElement('tr');
            tr.id = "row-" + i;
            for (j = 0; j < 30; j++) {
                td = document.createElement('td');
                td.textContent = 0;
                td.id = "cell-" + (i * 30 + j);
                tr.appendChild(td);
            }
            tbody.appendChild(tr);
        }
    })('rams');

    var classes = {
        '<': 'lt',
        '>': 'gt',
        '+': 'plus',
        '-': 'dash',
        '[': 'lbrk',
        ']': 'rbrk',
        '.': 'dot',
        ',': 'coma',
    };

    $('#program-compile').on('click', function() {
        var tarea, srccode, progEl, tokens, span;
        tarea = $('#program-input');
        srccode = tarea.value;
        tarea.value = '';
        progEl = $('#program');
        tokens = tokenizeProgram(srccode);
        tokens.forEach(function(token) {
            span = document.createElement('span');
            span.classList.add(token[0]);
            if ('opcode' == token[0]) {
                var tokenClass = classes[token[1]];
                if (tokenClass)
                    span.classList.add(token[0] + '-' + tokenClass);
            }
            span.textContent = token[1];
            progEl.appendChild(span);
        });
        program = tokens.filter(function(token) {
            return token[1] == 'opcode';
        });
        resetProgram();
    });

    $('#exec-speed').on('change', function() {
        timeOutNum = this.value;
    });
    $('#program-step').on('click', function() {
        runProgram();
    });
    $('#program-pause').on('click', function() {
        running = !running;
        if (!running) {
            window.clearTimeout(timeOutValue);
        } else {
            runProgram();
        }
    });
    $('#program-restart').on('click', function() {
        resetProgram();
    });

})(this);
<commit_msg>Fix incorrect comment handling<commit_after>(function(global) {
    'use strict';

    HTMLElement.prototype.on = function() {
        this.addEventListener.apply(this, arguments);
    };

    function $(selector) {
        return document.querySelector(selector);
    }

    function $$(selector) {
        return document.querySelectorAll(selector);
    }

    function stdout(msg) {
        $('#stdout').textContent += msg;
    }
    var timeOutNum = 1000,
        timeOutValue = 0,
        running = true;

    var ram = [],
        maxRams = 90,
        ramPointer = 0,
        ramStack = new global.HighlightStack();

    function updatePointer(num) {
        ramPointer = num;
        ramStack.addItem($('#cell-' + num));
    }

    function incrementPointer() {
        var ptr = ramPointer + 1;
        if (ptr >= maxRams)
            ptr = 0;
        updatePointer(ptr);
    }

    function decrementPointer() {
        var ptr = ramPointer - 1;
        if (ptr < 0)
            ptr = maxRams - 1;
        updatePointer(ptr);
    }

    function getRam() {
        return ram[ramPointer] || 0;
    }

    function setRam(value) {
        ram[ramPointer] = value;
    }

    function resetRam() {
        ramPointer = 0;
        ram = [];
        ramStack.clearStack();
    }

    var program = [],
        pc = 0;

    function resetProgram() {
        pc = 0;
        resetRam();
    }

    function runProgram() {
        window.clearTimeout(timeOutValue);
        if (true)
            return;

        // foo

        if (running)
            timeOutValue = window.setTimeout(runProgram, timeOutNum);
    }
    var isComment = /[^<>+\-[\].,]/;

    function tokenizeProgram(text) {
        var prog = [],
            loopStack = [],
            currentComment = '';
        var tokens = text.split('').forEach(function(token) {
            if (isComment.test(token)) {
                currentComment += token;
                return;
            }
            if (currentComment) {
                prog.push(['comment', currentComment]);
                currentComment = '';
            }
            if (token == '[') {
                loopStack.push(prog.length);
                prog.push(['opcode', token, -1]);
            } else if (token == ']') {
                if (!loopStack.length)
                    throw new Error("Unbalanced []s!");
                var match = loopStack.pop();
                prog[match][2] = prog.length;
                prog.push(['opcode', token, match]);
            } else {
                prog.push(['opcode', token]);
            }
        });
        if (currentComment) {
            prog.push(['comment', currentComment]);
        }
        if (loopStack.length > 0)
            throw new Error("Unbalanced []s!");
        return prog;
    }
    // debug
    global.tokenizeProgram = tokenizeProgram;

    (function(id) {
        var i, j;
        var tab = document.getElementById(id);
        var thead = tab.querySelector('thead');
        var tbody = tab.querySelector('tbody');
        var tr, td;
        for (i = 0; i < 3; i++) {
            tr = document.createElement('tr');
            tr.id = "row-" + i;
            for (j = 0; j < 30; j++) {
                td = document.createElement('td');
                td.textContent = 0;
                td.id = "cell-" + (i * 30 + j);
                tr.appendChild(td);
            }
            tbody.appendChild(tr);
        }
    })('rams');

    var classes = {
        '<': 'lt',
        '>': 'gt',
        '+': 'plus',
        '-': 'dash',
        '[': 'lbrk',
        ']': 'rbrk',
        '.': 'dot',
        ',': 'coma',
    };

    $('#program-compile').on('click', function() {
        var tarea, srccode, progEl, tokens, span;
        tarea = $('#program-input');
        srccode = tarea.value;
        tarea.value = '';
        progEl = $('#program');
        tokens = tokenizeProgram(srccode);
        tokens.forEach(function(token) {
            span = document.createElement('span');
            span.classList.add(token[0]);
            if ('opcode' == token[0]) {
                var tokenClass = classes[token[1]];
                if (tokenClass)
                    span.classList.add(token[0] + '-' + tokenClass);
            }
            span.textContent = token[1];
            progEl.appendChild(span);
        });
        program = tokens.filter(function(token) {
            return token[1] == 'opcode';
        });
        resetProgram();
    });

    $('#exec-speed').on('change', function() {
        timeOutNum = this.value;
    });
    $('#program-step').on('click', function() {
        runProgram();
    });
    $('#program-pause').on('click', function() {
        running = !running;
        if (!running) {
            window.clearTimeout(timeOutValue);
        } else {
            runProgram();
        }
    });
    $('#program-restart').on('click', function() {
        resetProgram();
    });

})(this);
<|endoftext|>


<commit_before>import { arraySet, objectSet, isArray } from './functionalHelpers';
import { curry, compose, identity, kestrel, when } from './combinators';
import { Maybe } from './containers/monads/maybe_monad';

/**
 * @description:
 * @type: {function}
 * @param: {number} idx
 * @param: {function} f
 * @param: {Array} xs
 * @return: {Array}
 */
var arrayLens = curry(function _arrayLens(idx, f, xs) {
    return map(function (val) {
        return arraySet(idx, val, xs)
    }, f(xs[idx]));
});

/**
 * @description:
 * @type: {function}
 * @param: {string} prop
 * @param: {function} f
 * @param: {Object} xs
 * @return: {Object}
 */
var objectLens = curry(function _objectLens(prop, f, xs) {
    return map(function _map(rep) {
        return objectSet(prop, rep, xs);
    }, f(xs[prop]));
});

/**
 * @description:
 * @type: {function}
 * @param: {function} lens
 * @param: {Object} target
 * @return:
 */
var view = curry(function _view(lens, target) {
    return lens(kestrel)(target).value;
});

/**
 * @description:
 * @type: {function}
 * @param: {function} lens
 * @param: {function} mapFn
 * @param: {Object} target
 * @return:
 */
var over = curry(function _over(lens, mapFn, target) {
    return lens(function _lens(y) {
        return identity(mapFn(y));
    })(target).value;
});

/**
 * @description:
 * @type: {function}
 * @param: {function} lens
 * @param: {*} val
 * @param: {*} target
 * @return:
 */
var put = curry(function _put(lens, val, target) {
    return over(lens, kestrel(val), target);
});

/**
 * @description:
 * @param: {Array} paths
 * @return: {*}
 */
function makeLenses(...paths) {
    return paths.reduce(function _pathReduce(cur, next) {
        var ol = objectLens(next);
        return put(ol, ol, cur);
    }, { num: arrayLens });
}

/**
 * @description:
 * @param: {Array} path
 * @return: {*}
 */
function lensPath(...path) {
    return compose(...path.map(function _pathMap(p) {
        return 'string' === typeof p ? objectLens(p) : arrayLens(p);
    }));
}

var maybePath = curry(function _maybePath(path, obj) {
    path = when(not(isArray), split('.'), path);
    var val = obj,
        idx = 0;
    while (idx < path.length) {
        if (null == val) return Maybe.Nothing();
        val = val[path[idx]];
        ++idx;
    }
    return Maybe(val);
});

var split = curry(function _split(delimiter, string) {
    return string.split(delimiter);
});

export { arrayLens, objectLens, view, over, put, makeLenses, lensPath };<commit_msg>develop: added comments to lens functions without; added a couple of new functions to lenses; fixed some of the original lens functions that expect to operate on top of a functor<commit_after>import { arraySet, objectSet, isArray } from './functionalHelpers';
import { curry, compose, kestrel, when } from './combinators';
import { Maybe } from './containers/monads/maybe_monad';
import { Identity } from './containers/monads/identity_monad';
import { Constant } from './containers/monads/constant_monad';
import { mapWith } from './pointlessContainers';

/**
 * @description:
 * @type: {function}
 * @param: {number} idx
 * @param: {function} f
 * @param: {Array} xs
 * @return: {Array}
 */
var arrayLens = curry(function _arrayLens(idx, f, xs) {
    return mapWith(function (val) {
        return arraySet(idx, val, xs)
    }, f(xs[idx]));
});

/**
 * @description:
 * @type: {function}
 * @param: {string} prop
 * @param: {function} f
 * @param: {Object} xs
 * @return: {Object}
 */
var objectLens = curry(function _objectLens(prop, f, xs) {
    return mapWith(function _map(rep) {
        return objectSet(prop, rep, xs);
    }, f(xs[prop]));
});

/**
 * @description:
 * @type: {function}
 * @param: {function} lens
 * @param: {Object} target
 * @return:
 */
var view = curry(function _view(lens, target) {
    return lens(Constant)(target).value;
});

/**
 * @description:
 * @type: {function}
 * @param: {function} lens
 * @param: {function} mapFn
 * @param: {Object} target
 * @return: {*}
 */
var over = curry(function _over(lens, mapFn, target) {
    return lens(function _lens(y) {
        return Identity(mapFn(y));
    })(target).value;
});

/**
 * @description:
 * @type: {function}
 * @param: {function} lens
 * @param: {*} val
 * @param: {*} target
 * @return: {*}
 */
var put = curry(function _put(lens, val, target) {
    return over(lens, kestrel(val), target);
});

/**
 * @type: {*}
 * @description:
 * @param: {function} lens
 * @param: {*} val
 * @param: {Object} targetData
 * @return: {*}
 */
var set = curry((lens, val, targetData) => over(lens, kestrel(val), targetData));

/**
 * @description:
 * @param: {Array} paths
 * @return: {*}
 */
function makeLenses(...paths) {
    return paths.reduce(function _pathReduce(cur, next) {
        var ol = objectLens(next);
        return put(ol, ol, cur);
    }, { num: arrayLens });
}

/**
 * @description:
 * @param: {Array} path
 * @return: {*}
 */
function lensPath(...path) {
    return compose(...path.map(function _pathMap(p) {
        return 'string' === typeof p ? objectLens(p) : arrayLens(p);
    }));
}

/**
 * @type: {*}
 * @description:
 * @param: {function} getter
 * @param: {function} setter
 * @param: {String} key
 * @param: {function} f
 * @param: {Array} xs
 * @return: {*}
 */
var lens = curry(function _lens(getter, setter, key, f, xs) {
    return mapWith(replace => setter(key, replace, xs), f(getter(key, xs)));
});

/**
 * @type" {*}
 * @description:
 * @param: {Array|String} path
 * @param: {Object} obj
 * @return: {*}
 */
var maybePath = curry(function _maybePath(path, obj) {
    path = when(not(isArray), split('.'), path);
    var val = obj,
        idx = 0;
    while (idx < path.length) {
        if (null == val) return Maybe.Nothing();
        val = val[path[idx]];
        ++idx;
    }
    return Maybe(val);
});

/**
 * @type {*}
 * @description:
 * @param: {String} delimiter
 * @param: {String} string
 * @return: {Array}
 */
var split = curry(function _split(delimiter, string) {
    return string.split(delimiter);
});

export { arrayLens, objectLens, view, over, put, set, lens, maybePath, makeLenses, lensPath };<|endoftext|>


<commit_before>
/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over.
----------------------------------------------------------------------------------------------------------------------*/
// TODO: very useful to have a handler that gets called upon cellOut OR when dragging stops (for cleanup)

var DragListener = Class.extend({

	coordMap: null,
	options: null,

	isListening: false,
	isDragging: false,
	isScrolling: false,

	// the cell the mouse was over when listening started
	origCell: null,

	// the cell the mouse is over
	cell: null,

	// coordinates of the initial mousedown
	mouseX0: null,
	mouseY0: null,

	// handler attached to the document, bound to the DragListener's `this`
	mousemoveProxy: null,
	mouseupProxy: null,

	scrollEl: null,
	scrollBounds: null, // { top, bottom, left, right }
	scrollTopVel: null, // pixels per second
	scrollLeftVel: null, // pixels per second
	scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
	scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled

	scrollSensitivity: 30, // pixels from edge for scrolling to start
	scrollSpeed: 200, // pixels per second, at maximum speed
	scrollIntervalMs: 50, // millisecond wait between scroll increment

	constructor: function(coordMap, options) {
		this.coordMap = coordMap;
		this.options = options || {};
	},

	// Call this when the user does a mousedown. Will probably lead to startListening
	mousedown: function(ev) {
		if (isPrimaryMouseButton(ev)) {

			if (!isTouchEvent(ev)) {
				ev.preventDefault(); // prevents native selection in most browsers but we still want to scroll on touch devices
			}

			this.startListening(ev);

			// start the drag immediately if there is no minimum distance for a drag start
			if (!this.options.distance) {
				this.startDrag(ev);
			}
		}
	},

	// Call this to start tracking mouse movements
	startListening: function(ev) {
		var scrollParent;
		var cell;

		if (!this.isListening) {
			this.isScrolling = false;
			// grab scroll container and attach handler
			if (ev && this.options.scroll) {
				scrollParent = getScrollParent($(ev.target));
				if (!scrollParent.is(window) && !scrollParent.is(document)) {
					this.scrollEl = scrollParent;

					// scope to `this`, and use `debounce` to make sure rapid calls don't happen
					this.scrollHandlerProxy = debounce($.proxy(this, 'scrollHandler'), 100);
					this.scrollEl.on('scroll', this.scrollHandlerProxy);
				}
			}

			this.computeCoords(); // relies on `scrollEl`

			// get info on the initial cell and its coordinates
			if (ev) {
				cell = this.getCell(ev);
				this.origCell = cell;

				this.mouseX0 = pointerEventToXY(ev).x;
				this.mouseY0 = pointerEventToXY(ev).y;
			}

			$(document)
				.on(getMouseMoveEvent(), this.mousemoveProxy = $.proxy(this, 'mousemove'))
				.on(getMouseUpEvent(), this.mouseupProxy = $.proxy(this, 'mouseup'))
				.on('selectstart', this.preventDefault); // prevents native selection in IE<=8

			this.isListening = true;
			this.trigger('listenStart', ev);
		}
	},

	// Recomputes the drag-critical positions of elements
	computeCoords: function() {
		this.coordMap.build();
		this.computeScrollBounds();
	},

	// Called when the user moves the mouse
	mousemove: function(ev) {
		var minDistance;
		var distanceSq; // current distance from mouseX0/mouseY0, squared

		if (!this.isDragging) { // if not already dragging...
			// then start the drag if the minimum distance criteria is met
			minDistance = this.options.distance || 1;
			distanceSq = Math.pow(pointerEventToXY(ev).x - this.mouseX0, 2) + Math.pow(pointerEventToXY(ev).y - this.mouseY0, 2);
			if (distanceSq >= minDistance * minDistance) { // use Pythagorean theorem
				this.startDrag(ev);
			}
		}

		if (this.isDragging) {
			this.drag(ev); // report a drag, even if this mousemove initiated the drag
		}
	},

	// Call this to initiate a legitimate drag.
	// This function is called internally from this class, but can also be called explicitly from outside
	startDrag: function(ev) {
		var cell;
		this.isScrolling = true;

		if (!this.isListening) { // startDrag must have manually initiated
			this.startListening();
		}

		if (!this.isDragging) {
			this.isDragging = true;
			this.trigger('dragStart', ev);

			// report the initial cell the mouse is over
			// especially important if no min-distance and drag starts immediately
			cell = this.getCell(ev); // this might be different from this.origCell if the min-distance is large
			if (cell) {
				this.cellOver(cell);
			}
		}
	},

	// Called while the mouse is being moved and when we know a legitimate drag is taking place
	drag: function(ev) {
		var cell;

		if (this.isDragging) {
			cell = this.getCell(ev);

			if (!isCellsEqual(cell, this.cell)) { // a different cell than before?
				if (this.cell) {
					this.cellOut();
				}
				if (cell && (!isTouchEvent(ev) || dragOnTouchDevices)) {
					this.cellOver(cell);
				}
			}

			this.dragScroll(ev); // will possibly cause scrolling
		}
	},

	// Called when a the mouse has just moved over a new cell
	cellOver: function(cell) {
		this.cell = cell;
		this.trigger('cellOver', cell, isCellsEqual(cell, this.origCell));
	},

	// Called when the mouse has just moved out of a cell
	cellOut: function() {
		if (this.cell) {
			this.trigger('cellOut', this.cell);
			this.cell = null;
		}
	},

	// Called when the user does a mouseup
	mouseup: function(ev) {
		this.stopDrag(ev);
		this.stopListening(ev);
	},

	// Called when the drag is over. Will not cause listening to stop however.
	// A concluding 'cellOut' event will NOT be triggered.
	stopDrag: function(ev) {
		if (this.isDragging) {
			this.stopScrolling();
			this.trigger('dragStop', ev);
			this.isDragging = false;
		}
	},

	// Call this to stop listening to the user's mouse events
	stopListening: function(ev) {
		if (this.isListening) {

			// remove the scroll handler if there is a scrollEl
			if (this.scrollEl) {
				this.scrollEl.off('scroll', this.scrollHandlerProxy);
				this.scrollHandlerProxy = null;
			}

			$(document)
				.off(getMouseMoveEvent(), this.mousemoveProxy)
				.off(getMouseUpEvent(), this.mouseupProxy)
				.off('selectstart', this.preventDefault);

			this.mousemoveProxy = null;
			this.mouseupProxy = null;

			this.isListening = false;
			this.trigger('listenStop', ev);

			this.origCell = this.cell = null;
			this.coordMap.clear();
		}
	},

	// Gets the cell underneath the coordinates for the given mouse event
	getCell: function(ev) {
		return this.coordMap.getCell(pointerEventToXY(ev).x, pointerEventToXY(ev).y);
	},

	// Triggers a callback. Calls a function in the option hash of the same name.
	// Arguments beyond the first `name` are forwarded on.
	trigger: function(name) {
		if (this.options[name]) {
			this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
		}
	},

	// Stops a given mouse event from doing it's native browser action. In our case, text selection.
	preventDefault: function(ev) {
		ev.preventDefault();
	},

	/* Scrolling
	------------------------------------------------------------------------------------------------------------------*/

	// Computes and stores the bounding rectangle of scrollEl
	computeScrollBounds: function() {
		var el = this.scrollEl;
		var offset;

		if (el) {
			offset = el.offset();
			this.scrollBounds = {
				top: offset.top,
				left: offset.left,
				bottom: offset.top + el.outerHeight(),
				right: offset.left + el.outerWidth()
			};
		}
	},

	// Called when the dragging is in progress and scrolling should be updated
	dragScroll: function(ev) {
		var sensitivity = this.scrollSensitivity;
		var bounds = this.scrollBounds;
		var topCloseness, bottomCloseness;
		var leftCloseness, rightCloseness;
		var topVel = 0;
		var leftVel = 0;

		if (bounds) { // only scroll if scrollEl exists

			// compute closeness to edges. valid range is from 0.0 - 1.0
			topCloseness = (sensitivity - (pointerEventToXY(ev).y - bounds.top)) / sensitivity;
			bottomCloseness = (sensitivity - (bounds.bottom - pointerEventToXY(ev).y)) / sensitivity;
			leftCloseness = (sensitivity - (pointerEventToXY(ev).x - bounds.left)) / sensitivity;
			rightCloseness = (sensitivity - (bounds.right - pointerEventToXY(ev).x)) / sensitivity;

			// translate vertical closeness into velocity.
			// mouse must be completely in bounds for velocity to happen.
			if (topCloseness >= 0 && topCloseness <= 1) {
				topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
			}
			else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
				topVel = bottomCloseness * this.scrollSpeed;
			}

			// translate horizontal closeness into velocity
			if (leftCloseness >= 0 && leftCloseness <= 1) {
				leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
			}
			else if (rightCloseness >= 0 && rightCloseness <= 1) {
				leftVel = rightCloseness * this.scrollSpeed;
			}
		}

		this.setScrollVel(topVel, leftVel);
	},

	// Sets the speed-of-scrolling for the scrollEl
	setScrollVel: function(topVel, leftVel) {

		this.scrollTopVel = topVel;
		this.scrollLeftVel = leftVel;

		this.constrainScrollVel(); // massages into realistic values

		// if there is non-zero velocity, and an animation loop hasn't already started, then START
		if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
			this.scrollIntervalId = setInterval(
				$.proxy(this, 'scrollIntervalFunc'), // scope to `this`
				this.scrollIntervalMs
			);
		}
	},

	// Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
	constrainScrollVel: function() {
		var el = this.scrollEl;

		if (this.scrollTopVel < 0) { // scrolling up?
			if (el.scrollTop() <= 0) { // already scrolled all the way up?
				this.scrollTopVel = 0;
			}
		}
		else if (this.scrollTopVel > 0) { // scrolling down?
			if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
				this.scrollTopVel = 0;
			}
		}

		if (this.scrollLeftVel < 0) { // scrolling left?
			if (el.scrollLeft() <= 0) { // already scrolled all the left?
				this.scrollLeftVel = 0;
			}
		}
		else if (this.scrollLeftVel > 0) { // scrolling right?
			if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
				this.scrollLeftVel = 0;
			}
		}
	},

	// This function gets called during every iteration of the scrolling animation loop
	scrollIntervalFunc: function() {
		var el = this.scrollEl;
		var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by

		// change the value of scrollEl's scroll
		if (this.scrollTopVel) {
			el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
		}
		if (this.scrollLeftVel) {
			el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
		}

		this.constrainScrollVel(); // since the scroll values changed, recompute the velocities

		// if scrolled all the way, which causes the vels to be zero, stop the animation loop
		if (!this.scrollTopVel && !this.scrollLeftVel) {
			this.stopScrolling();
		}
	},

	// Kills any existing scrolling animation loop
	stopScrolling: function() {
		if (this.scrollIntervalId) {
			clearInterval(this.scrollIntervalId);
			this.scrollIntervalId = null;

			// when all done with scrolling, recompute positions since they probably changed
			this.computeCoords();
		}
	},

	// Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
	scrollHandler: function() {
		this.isScrolling = true;
		// recompute all coordinates, but *only* if this is *not* part of our scrolling animation
		if (!this.scrollIntervalId) {
			this.computeCoords();
		}
	}

});

// Returns `true` if the cells are identically equal. `false` otherwise.
// They must have the same row, col, and be from the same grid.
// Two null values will be considered equal, as two "out of the grid" states are the same.
function isCellsEqual(cell1, cell2) {

	if (!cell1 && !cell2) {
		return true;
	}

	if (cell1 && cell2) {
		return cell1.grid === cell2.grid &&
			cell1.row === cell2.row &&
			cell1.col === cell2.col;
	}

	return false;
}
<commit_msg>fix scrolling bug on touch devices<commit_after>
/* Tracks mouse movements over a CoordMap and raises events about which cell the mouse is over.
----------------------------------------------------------------------------------------------------------------------*/
// TODO: very useful to have a handler that gets called upon cellOut OR when dragging stops (for cleanup)

var DragListener = Class.extend({

	coordMap: null,
	options: null,

	isListening: false,
	isDragging: false,
	isScrolling: false,

	// the cell the mouse was over when listening started
	origCell: null,

	// the cell the mouse is over
	cell: null,

	// coordinates of the initial mousedown
	mouseX0: null,
	mouseY0: null,

	// handler attached to the document, bound to the DragListener's `this`
	mousemoveProxy: null,
	mouseupProxy: null,

	scrollEl: null,
	scrollBounds: null, // { top, bottom, left, right }
	scrollTopVel: null, // pixels per second
	scrollLeftVel: null, // pixels per second
	scrollIntervalId: null, // ID of setTimeout for scrolling animation loop
	scrollHandlerProxy: null, // this-scoped function for handling when scrollEl is scrolled

	scrollSensitivity: 30, // pixels from edge for scrolling to start
	scrollSpeed: 200, // pixels per second, at maximum speed
	scrollIntervalMs: 50, // millisecond wait between scroll increment

	constructor: function(coordMap, options) {
		this.coordMap = coordMap;
		this.options = options || {};
	},

	// Call this when the user does a mousedown. Will probably lead to startListening
	mousedown: function(ev) {
		if (isPrimaryMouseButton(ev)) {

			if (!isTouchEvent(ev)) {
				ev.preventDefault(); // prevents native selection in most browsers but we still want to scroll on touch devices
			}

			this.startListening(ev);

			// start the drag immediately if there is no minimum distance for a drag start
			if (!this.options.distance) {
				this.startDrag(ev);
			}
		}
	},

	// Call this to start tracking mouse movements
	startListening: function(ev) {
		var scrollParent;
		var cell;

		if (!this.isListening) {
			this.isScrolling = false;
			// grab scroll container and attach handler
			if (ev && this.options.scroll) {
				scrollParent = getScrollParent($(ev.target));
				if (!scrollParent.is(window) && !scrollParent.is(document)) {
					this.scrollEl = scrollParent;

					// scope to `this`, and use `debounce` to make sure rapid calls don't happen
					this.scrollHandlerProxy = debounce($.proxy(this, 'scrollHandler'), 100);
					this.scrollEl.on('scroll', this.scrollHandlerProxy);
				}
			}

			this.computeCoords(); // relies on `scrollEl`

			// get info on the initial cell and its coordinates
			if (ev) {
				cell = this.getCell(ev);
				this.origCell = cell;

				this.mouseX0 = pointerEventToXY(ev).x;
				this.mouseY0 = pointerEventToXY(ev).y;
			}

			$(document)
				.on(getMouseMoveEvent(), this.mousemoveProxy = $.proxy(this, 'mousemove'))
				.on(getMouseUpEvent(), this.mouseupProxy = $.proxy(this, 'mouseup'))
				.on('selectstart', this.preventDefault); // prevents native selection in IE<=8

			this.isListening = true;
			this.trigger('listenStart', ev);
		}
	},

	// Recomputes the drag-critical positions of elements
	computeCoords: function() {
		this.coordMap.build();
		this.computeScrollBounds();
	},

	// Called when the user moves the mouse
	mousemove: function(ev) {
		var minDistance;
		var distanceSq; // current distance from mouseX0/mouseY0, squared

		if (!this.isDragging) { // if not already dragging...
			// then start the drag if the minimum distance criteria is met
			minDistance = this.options.distance || 1;
			distanceSq = Math.pow(pointerEventToXY(ev).x - this.mouseX0, 2) + Math.pow(pointerEventToXY(ev).y - this.mouseY0, 2);
			if (distanceSq >= minDistance * minDistance) { // use Pythagorean theorem
				this.startDrag(ev);
			}
		}

		if (this.isDragging) {
			this.drag(ev); // report a drag, even if this mousemove initiated the drag
		}
	},

	// Call this to initiate a legitimate drag.
	// This function is called internally from this class, but can also be called explicitly from outside
	startDrag: function(ev) {
		var cell;

		if (!this.isListening) { // startDrag must have manually initiated
			this.startListening();
		}

		if (!this.isDragging) {
			this.isDragging = true;
			this.trigger('dragStart', ev);

			// report the initial cell the mouse is over
			// especially important if no min-distance and drag starts immediately
			cell = this.getCell(ev); // this might be different from this.origCell if the min-distance is large
			if (cell) {
				this.cellOver(cell);
			}
		}
	},

	// Called while the mouse is being moved and when we know a legitimate drag is taking place
	drag: function(ev) {
		var cell;
		this.isScrolling = true;
		if (this.isDragging) {
			cell = this.getCell(ev);

			if (!isCellsEqual(cell, this.cell)) { // a different cell than before?
				if (this.cell) {
					this.cellOut();
				}
				if (cell && (!isTouchEvent(ev) || dragOnTouchDevices)) {
					this.cellOver(cell);
				}
			}

			this.dragScroll(ev); // will possibly cause scrolling
		}
	},

	// Called when a the mouse has just moved over a new cell
	cellOver: function(cell) {
		this.cell = cell;
		this.trigger('cellOver', cell, isCellsEqual(cell, this.origCell));
	},

	// Called when the mouse has just moved out of a cell
	cellOut: function() {
		if (this.cell) {
			this.trigger('cellOut', this.cell);
			this.cell = null;
		}
	},

	// Called when the user does a mouseup
	mouseup: function(ev) {
		this.stopDrag(ev);
		this.stopListening(ev);
	},

	// Called when the drag is over. Will not cause listening to stop however.
	// A concluding 'cellOut' event will NOT be triggered.
	stopDrag: function(ev) {
		if (this.isDragging) {
			this.stopScrolling();
			this.trigger('dragStop', ev);
			this.isDragging = false;
		}
	},

	// Call this to stop listening to the user's mouse events
	stopListening: function(ev) {
		if (this.isListening) {

			// remove the scroll handler if there is a scrollEl
			if (this.scrollEl) {
				this.scrollEl.off('scroll', this.scrollHandlerProxy);
				this.scrollHandlerProxy = null;
			}

			$(document)
				.off(getMouseMoveEvent(), this.mousemoveProxy)
				.off(getMouseUpEvent(), this.mouseupProxy)
				.off('selectstart', this.preventDefault);

			this.mousemoveProxy = null;
			this.mouseupProxy = null;

			this.isListening = false;
			this.trigger('listenStop', ev);

			this.origCell = this.cell = null;
			this.coordMap.clear();
		}
	},

	// Gets the cell underneath the coordinates for the given mouse event
	getCell: function(ev) {
		return this.coordMap.getCell(pointerEventToXY(ev).x, pointerEventToXY(ev).y);
	},

	// Triggers a callback. Calls a function in the option hash of the same name.
	// Arguments beyond the first `name` are forwarded on.
	trigger: function(name) {
		if (this.options[name]) {
			this.options[name].apply(this, Array.prototype.slice.call(arguments, 1));
		}
	},

	// Stops a given mouse event from doing it's native browser action. In our case, text selection.
	preventDefault: function(ev) {
		ev.preventDefault();
	},

	/* Scrolling
	------------------------------------------------------------------------------------------------------------------*/

	// Computes and stores the bounding rectangle of scrollEl
	computeScrollBounds: function() {
		var el = this.scrollEl;
		var offset;

		if (el) {
			offset = el.offset();
			this.scrollBounds = {
				top: offset.top,
				left: offset.left,
				bottom: offset.top + el.outerHeight(),
				right: offset.left + el.outerWidth()
			};
		}
	},

	// Called when the dragging is in progress and scrolling should be updated
	dragScroll: function(ev) {
		var sensitivity = this.scrollSensitivity;
		var bounds = this.scrollBounds;
		var topCloseness, bottomCloseness;
		var leftCloseness, rightCloseness;
		var topVel = 0;
		var leftVel = 0;

		if (bounds) { // only scroll if scrollEl exists

			// compute closeness to edges. valid range is from 0.0 - 1.0
			topCloseness = (sensitivity - (pointerEventToXY(ev).y - bounds.top)) / sensitivity;
			bottomCloseness = (sensitivity - (bounds.bottom - pointerEventToXY(ev).y)) / sensitivity;
			leftCloseness = (sensitivity - (pointerEventToXY(ev).x - bounds.left)) / sensitivity;
			rightCloseness = (sensitivity - (bounds.right - pointerEventToXY(ev).x)) / sensitivity;

			// translate vertical closeness into velocity.
			// mouse must be completely in bounds for velocity to happen.
			if (topCloseness >= 0 && topCloseness <= 1) {
				topVel = topCloseness * this.scrollSpeed * -1; // negative. for scrolling up
			}
			else if (bottomCloseness >= 0 && bottomCloseness <= 1) {
				topVel = bottomCloseness * this.scrollSpeed;
			}

			// translate horizontal closeness into velocity
			if (leftCloseness >= 0 && leftCloseness <= 1) {
				leftVel = leftCloseness * this.scrollSpeed * -1; // negative. for scrolling left
			}
			else if (rightCloseness >= 0 && rightCloseness <= 1) {
				leftVel = rightCloseness * this.scrollSpeed;
			}
		}

		this.setScrollVel(topVel, leftVel);
	},

	// Sets the speed-of-scrolling for the scrollEl
	setScrollVel: function(topVel, leftVel) {

		this.scrollTopVel = topVel;
		this.scrollLeftVel = leftVel;

		this.constrainScrollVel(); // massages into realistic values

		// if there is non-zero velocity, and an animation loop hasn't already started, then START
		if ((this.scrollTopVel || this.scrollLeftVel) && !this.scrollIntervalId) {
			this.scrollIntervalId = setInterval(
				$.proxy(this, 'scrollIntervalFunc'), // scope to `this`
				this.scrollIntervalMs
			);
		}
	},

	// Forces scrollTopVel and scrollLeftVel to be zero if scrolling has already gone all the way
	constrainScrollVel: function() {
		var el = this.scrollEl;

		if (this.scrollTopVel < 0) { // scrolling up?
			if (el.scrollTop() <= 0) { // already scrolled all the way up?
				this.scrollTopVel = 0;
			}
		}
		else if (this.scrollTopVel > 0) { // scrolling down?
			if (el.scrollTop() + el[0].clientHeight >= el[0].scrollHeight) { // already scrolled all the way down?
				this.scrollTopVel = 0;
			}
		}

		if (this.scrollLeftVel < 0) { // scrolling left?
			if (el.scrollLeft() <= 0) { // already scrolled all the left?
				this.scrollLeftVel = 0;
			}
		}
		else if (this.scrollLeftVel > 0) { // scrolling right?
			if (el.scrollLeft() + el[0].clientWidth >= el[0].scrollWidth) { // already scrolled all the way right?
				this.scrollLeftVel = 0;
			}
		}
	},

	// This function gets called during every iteration of the scrolling animation loop
	scrollIntervalFunc: function() {
		var el = this.scrollEl;
		var frac = this.scrollIntervalMs / 1000; // considering animation frequency, what the vel should be mult'd by

		// change the value of scrollEl's scroll
		if (this.scrollTopVel) {
			el.scrollTop(el.scrollTop() + this.scrollTopVel * frac);
		}
		if (this.scrollLeftVel) {
			el.scrollLeft(el.scrollLeft() + this.scrollLeftVel * frac);
		}

		this.constrainScrollVel(); // since the scroll values changed, recompute the velocities

		// if scrolled all the way, which causes the vels to be zero, stop the animation loop
		if (!this.scrollTopVel && !this.scrollLeftVel) {
			this.stopScrolling();
		}
	},

	// Kills any existing scrolling animation loop
	stopScrolling: function() {
		if (this.scrollIntervalId) {
			clearInterval(this.scrollIntervalId);
			this.scrollIntervalId = null;

			// when all done with scrolling, recompute positions since they probably changed
			this.computeCoords();
		}
	},

	// Get called when the scrollEl is scrolled (NOTE: this is delayed via debounce)
	scrollHandler: function() {
		this.isScrolling = true;
		// recompute all coordinates, but *only* if this is *not* part of our scrolling animation
		if (!this.scrollIntervalId) {
			this.computeCoords();
		}
	}

});

// Returns `true` if the cells are identically equal. `false` otherwise.
// They must have the same row, col, and be from the same grid.
// Two null values will be considered equal, as two "out of the grid" states are the same.
function isCellsEqual(cell1, cell2) {

	if (!cell1 && !cell2) {
		return true;
	}

	if (cell1 && cell2) {
		return cell1.grid === cell2.grid &&
			cell1.row === cell2.row &&
			cell1.col === cell2.col;
	}

	return false;
}
<|endoftext|>


<commit_before>/* @flow */
'use strict';
const _ = require('lodash');
const transactionParser = require('ripple-lib-transactionparser');
const toTimestamp = require('../../../core/utils').toTimestamp;
const utils = require('../utils');
const BigNumber = require('bignumber.js');

function adjustQualityForXRP(quality: string, takerGetsCurrency: string,
    takerPaysCurrency: string) {
  const shift = (takerGetsCurrency === 'XRP' ? 6 : 0)
              - (takerPaysCurrency === 'XRP' ? 6 : 0);
  return shift === 0 ? quality :
    (new BigNumber(quality)).shift(shift).toString();
}

function parseTimestamp(tx: {date: string}): string | void {
  return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined;
}

function removeUndefined(obj: Object): Object {
  return _.omit(obj, _.isUndefined);
}

function removeEmptyCounterparty(amount) {
  if (amount.counterparty === '') {
    delete amount.counterparty;
  }
}

function removeEmptyCounterpartyInBalanceChanges(balanceChanges) {
  _.forEach(balanceChanges, (changes) => {
    _.forEach(changes, removeEmptyCounterparty);
  });
}

function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
  _.forEach(orderbookChanges, (changes) => {
    _.forEach(changes, (change) => {
      _.forEach(change, removeEmptyCounterparty);
    });
  });
}

function parseOutcome(tx: Object): ?Object {
  if (!tx.validated) {
    return undefined;
  }

  const balanceChanges = transactionParser.parseBalanceChanges(tx.meta);
  const orderbookChanges = transactionParser.parseOrderBookChanges(tx.meta);
  removeEmptyCounterpartyInBalanceChanges(balanceChanges);
  removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);

  return {
    result: tx.meta.TransactionResult,
    timestamp: parseTimestamp(tx),
    fee: utils.common.dropsToXrp(tx.Fee),
    balanceChanges: balanceChanges,
    orderbookChanges: orderbookChanges,
    ledgerVersion: tx.ledger_index,
    indexInLedger: tx.meta.TransactionIndex,
    sequence: tx.Sequence
  };
}

module.exports = {
  parseOutcome,
  removeUndefined,
  adjustQualityForXRP,
  dropsToXrp: utils.common.dropsToXrp,
  constants: utils.common.constants,
  core: utils.common.core
};
<commit_msg>Refactor to make quality adjustment for XRP more clear<commit_after>/* @flow */
'use strict';
const _ = require('lodash');
const transactionParser = require('ripple-lib-transactionparser');
const toTimestamp = require('../../../core/utils').toTimestamp;
const utils = require('../utils');
const BigNumber = require('bignumber.js');

function adjustQualityForXRP(quality: string, takerGetsCurrency: string,
    takerPaysCurrency: string) {
  // quality = takerPays.value/takerGets.value
  // using drops (1e-6 XRP) for XRP values
  const numeratorShift = (takerPaysCurrency === 'XRP' ? -6 : 0);
  const denominatorShift = (takerGetsCurrency === 'XRP' ? -6 : 0);
  const shift = numeratorShift - denominatorShift;
  return shift === 0 ? quality :
    (new BigNumber(quality)).shift(shift).toString();
}

function parseTimestamp(tx: {date: string}): string | void {
  return tx.date ? (new Date(toTimestamp(tx.date))).toISOString() : undefined;
}

function removeUndefined(obj: Object): Object {
  return _.omit(obj, _.isUndefined);
}

function removeEmptyCounterparty(amount) {
  if (amount.counterparty === '') {
    delete amount.counterparty;
  }
}

function removeEmptyCounterpartyInBalanceChanges(balanceChanges) {
  _.forEach(balanceChanges, (changes) => {
    _.forEach(changes, removeEmptyCounterparty);
  });
}

function removeEmptyCounterpartyInOrderbookChanges(orderbookChanges) {
  _.forEach(orderbookChanges, (changes) => {
    _.forEach(changes, (change) => {
      _.forEach(change, removeEmptyCounterparty);
    });
  });
}

function parseOutcome(tx: Object): ?Object {
  if (!tx.validated) {
    return undefined;
  }

  const balanceChanges = transactionParser.parseBalanceChanges(tx.meta);
  const orderbookChanges = transactionParser.parseOrderBookChanges(tx.meta);
  removeEmptyCounterpartyInBalanceChanges(balanceChanges);
  removeEmptyCounterpartyInOrderbookChanges(orderbookChanges);

  return {
    result: tx.meta.TransactionResult,
    timestamp: parseTimestamp(tx),
    fee: utils.common.dropsToXrp(tx.Fee),
    balanceChanges: balanceChanges,
    orderbookChanges: orderbookChanges,
    ledgerVersion: tx.ledger_index,
    indexInLedger: tx.meta.TransactionIndex,
    sequence: tx.Sequence
  };
}

module.exports = {
  parseOutcome,
  removeUndefined,
  adjustQualityForXRP,
  dropsToXrp: utils.common.dropsToXrp,
  constants: utils.common.constants,
  core: utils.common.core
};
<|endoftext|>


<commit_before>'use strict'
var ethJSABI = require('ethereumjs-abi')
var $ = require('jquery')

module.exports = {
  encodeParams: function (funABI, args) {
    var types = []
    if (funABI.inputs && funABI.inputs.length) {
      for (var i = 0; i < funABI.inputs.length; i++) {
        types.push(funABI.inputs[i].type)
      }
    }

    // NOTE: the caller will concatenate the bytecode and this
    //       it could be done here too for consistency
    return ethJSABI.rawEncode(types, args)
  },

  encodeFunctionId: function (funABI) {
    var types = []
    if (funABI.inputs && funABI.inputs.length) {
      for (var i = 0; i < funABI.inputs.length; i++) {
        types.push(funABI.inputs[i].type)
      }
    }

    return ethJSABI.methodID(funABI.name, types)
  },

  sortAbiFunction: function (contract) {
    var abi = JSON.parse(contract.interface).sort(function (a, b) {
      if (a.name > b.name) {
        return -1
      } else {
        return 1
      }
    }).sort(function (a, b) {
      if (a.constant === true) {
        return -1
      } else {
        return 1
      }
    })
    return abi
  },

  getConstructorInterface: function (abi) {
    var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] }
    if (typeof abi === 'string') {
      try {
        abi = JSON.parse(abi)
      } catch (e) {
        console.log('exception retrieving ctor abi ' + abi)
        return funABI
      }
    }

    for (var i = 0; i < abi.length; i++) {
      if (abi[i].type === 'constructor') {
        funABI.inputs = abi[i].inputs || []
        break
      }
    }

    return funABI
  },

  getFunction: function (abi, fnName) {
    for (var i = 0; i < abi.length; i++) {
      if (abi[i].name === fnName) {
        return abi[i]
      }
    }
    return null
  },

  getFallbackInterface: function (abi) {
    for (var i = 0; i < abi.length; i++) {
      if (abi[i].type === 'fallback') {
        return abi[i]
      }
    }
  },

  getContractByName: function (contractName, contracts) {
    for (var c in contracts) {
      if (contracts[c].name === contractName) {
        return contracts[c]
      }
    }
    return null
  },

  inputParametersDeclarationToString: function (abiinputs) {
    var inputs = ''
    if (abiinputs) {
      $.each(abiinputs, function (i, inp) {
        if (inputs !== '') {
          inputs += ', '
        }
        inputs += inp.type + ' ' + inp.name
      })
    }
    return inputs
  }
}
<commit_msg>fix encoding params<commit_after>'use strict'
var ethJSABI = require('ethereumjs-abi')
var $ = require('jquery')

module.exports = {
  encodeParams: function (funABI, args) {
    var types = []
    if (funABI.inputs && funABI.inputs.length) {
      for (var i = 0; i < funABI.inputs.length; i++) {
        types.push(funABI.inputs[i].type)
        if (args.length < types.length) {
          args.push('')
        }
      }
    }

    // NOTE: the caller will concatenate the bytecode and this
    //       it could be done here too for consistency
    return ethJSABI.rawEncode(types, args)
  },

  encodeFunctionId: function (funABI) {
    var types = []
    if (funABI.inputs && funABI.inputs.length) {
      for (var i = 0; i < funABI.inputs.length; i++) {
        types.push(funABI.inputs[i].type)
      }
    }

    return ethJSABI.methodID(funABI.name, types)
  },

  sortAbiFunction: function (contract) {
    var abi = JSON.parse(contract.interface).sort(function (a, b) {
      if (a.name > b.name) {
        return -1
      } else {
        return 1
      }
    }).sort(function (a, b) {
      if (a.constant === true) {
        return -1
      } else {
        return 1
      }
    })
    return abi
  },

  getConstructorInterface: function (abi) {
    var funABI = { 'name': '', 'inputs': [], 'type': 'constructor', 'outputs': [] }
    if (typeof abi === 'string') {
      try {
        abi = JSON.parse(abi)
      } catch (e) {
        console.log('exception retrieving ctor abi ' + abi)
        return funABI
      }
    }

    for (var i = 0; i < abi.length; i++) {
      if (abi[i].type === 'constructor') {
        funABI.inputs = abi[i].inputs || []
        break
      }
    }

    return funABI
  },

  getFunction: function (abi, fnName) {
    for (var i = 0; i < abi.length; i++) {
      if (abi[i].name === fnName) {
        return abi[i]
      }
    }
    return null
  },

  getFallbackInterface: function (abi) {
    for (var i = 0; i < abi.length; i++) {
      if (abi[i].type === 'fallback') {
        return abi[i]
      }
    }
  },

  getContractByName: function (contractName, contracts) {
    for (var c in contracts) {
      if (contracts[c].name === contractName) {
        return contracts[c]
      }
    }
    return null
  },

  inputParametersDeclarationToString: function (abiinputs) {
    var inputs = ''
    if (abiinputs) {
      $.each(abiinputs, function (i, inp) {
        if (inputs !== '') {
          inputs += ', '
        }
        inputs += inp.type + ' ' + inp.name
      })
    }
    return inputs
  }
}
<|endoftext|>


<commit_before>'use strict'
var yo = require('yo-yo')
var remix = require('ethereum-remix')
var EventManager = remix.lib.EventManager
var helper = require('../../lib/helper')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var executionContext = require('../../execution-context')

/**
  * This just export a function that register to `newTransaction` and forward them to the logger.
  * Emit debugRequested
  *
  */
class TxLogger {
  constructor (opts = {}) {
    this.event = new EventManager()
    this.opts = opts
    opts.api.editorpanel.registerLogType('knownTransaction', (data) => {
      return renderKnownTransaction(this, data)
    })
    opts.api.editorpanel.registerLogType('unknownTransaction', (data) => {
      return renderUnknownTransaction(this, data)
    })
    opts.events.txListener.register('newTransaction', (tx) => {
      log(this, tx, opts.api)
    })
  }
}

function log (self, tx, api) {
  var resolvedTransaction = api.resolvedTransaction(tx.hash)
  if (resolvedTransaction) {
    api.parseLogs(tx, resolvedTransaction.contractName, api.compiledContracts(), (error, logs) => {
      if (!error) {
        api.editorpanel.log({type: 'knownTransaction', value: { tx: tx, resolvedData: resolvedTransaction, logs: logs }})
      }
    })
  } else {
    // contract unknown - just displaying raw tx.
    api.editorpanel.log({ type: 'unknownTransaction', value: { tx: tx } })
  }
}

function renderKnownTransaction (self, data) {
  var to = data.tx.to
  if (to) to = helper.shortenAddress(data.tx.to)
  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  function detail () {
    // @TODO here should open a modal containing some info (e.g input params, logs, ...)
  }
  return yo`${context(self, data.tx)}: from:${helper.shortenAddress(data.tx.from)}, to:${to}, ${data.resolvedData.contractName}.${data.resolvedData.fn}, value:${value(data.tx.value)} wei, data:${helper.shortenHexData(data.tx.input)}, ${data.logs.length} logs, hash:${helper.shortenHexData((data.tx.hash))},<button onclick=${detail}>Details</button> <button onclick=${debug}>Debug</button>`
}

function renderUnknownTransaction (self, data) {
  var to = data.tx.to
  if (to) to = helper.shortenAddress(data.tx.to)
  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  function detail () {
    // @TODO here should open a modal containing some info (e.g input params, logs, ...)
  }
  return yo`${context(self, data.tx)}: from:${helper.shortenAddress(data.tx.from)}, to:${to}, value:${value(data.tx.value)} wei, data:${helper.shortenHexData((data.tx.input))}, hash:${helper.shortenHexData((data.tx.hash))}, <button onclick=${detail}>Details</button> <button onclick=${debug}>Debug</button>`
}

function context (self, tx) {
  if (executionContext.getProvider() === 'vm') {
    return yo`<span>(vm)</span>`
  } else {
    return yo`<span>block:${tx.blockNumber}, txIndex:${tx.transactionIndex}`
  }
}

function value (v) {
  try {
    if (v.indexOf && v.indexOf('0x') === 0) {
      return (new BN(v.replace('0x', ''), 16)).toString(10)
    } else {
      return v.toString(10)
    }
  } catch (e) {
    console.log(e)
    return v
  }
}

module.exports = TxLogger
<commit_msg>log if block contains no tx<commit_after>'use strict'
var yo = require('yo-yo')
var remix = require('ethereum-remix')
var EventManager = remix.lib.EventManager
var helper = require('../../lib/helper')
var ethJSUtil = require('ethereumjs-util')
var BN = ethJSUtil.BN
var executionContext = require('../../execution-context')

/**
  * This just export a function that register to `newTransaction` and forward them to the logger.
  * Emit debugRequested
  *
  */
class TxLogger {
  constructor (opts = {}) {
    this.event = new EventManager()
    this.opts = opts
    opts.api.editorpanel.registerLogType('knownTransaction', (data) => {
      return renderKnownTransaction(this, data)
    })
    opts.api.editorpanel.registerLogType('unknownTransaction', (data) => {
      return renderUnknownTransaction(this, data)
    })
    opts.api.editorpanel.registerLogType('emptyBlock', (data) => {
      return renderEmptyBlock(this, data)
    })

    opts.events.txListener.register('newBlock', (block) => {
      if (!block.transactions.length) {
        opts.api.editorpanel.log({type: 'emptyBlock', value: { block: block }})
      }
    })

    opts.events.txListener.register('newTransaction', (tx) => {
      log(this, tx, opts.api)
    })
  }
}

function log (self, tx, api) {
  var resolvedTransaction = api.resolvedTransaction(tx.hash)
  if (resolvedTransaction) {
    api.parseLogs(tx, resolvedTransaction.contractName, api.compiledContracts(), (error, logs) => {
      if (!error) {
        api.editorpanel.log({type: 'knownTransaction', value: { tx: tx, resolvedData: resolvedTransaction, logs: logs }})
      }
    })
  } else {
    // contract unknown - just displaying raw tx.
    api.editorpanel.log({ type: 'unknownTransaction', value: { tx: tx } })
  }
}

function renderKnownTransaction (self, data) {
  var to = data.tx.to
  if (to) to = helper.shortenAddress(data.tx.to)
  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  function detail () {
    // @TODO here should open a modal containing some info (e.g input params, logs, ...)
  }
  return yo`${context(self, data.tx)}: from:${helper.shortenAddress(data.tx.from)}, to:${to}, ${data.resolvedData.contractName}.${data.resolvedData.fn}, value:${value(data.tx.value)} wei, data:${helper.shortenHexData(data.tx.input)}, ${data.logs.length} logs, hash:${helper.shortenHexData((data.tx.hash))},<button onclick=${detail}>Details</button> <button onclick=${debug}>Debug</button>`
}

function renderUnknownTransaction (self, data) {
  var to = data.tx.to
  if (to) to = helper.shortenAddress(data.tx.to)
  function debug () {
    self.event.trigger('debugRequested', [data.tx.hash])
  }
  function detail () {
    // @TODO here should open a modal containing some info (e.g input params, logs, ...)
  }
  return yo`${context(self, data.tx)}: from:${helper.shortenAddress(data.tx.from)}, to:${to}, value:${value(data.tx.value)} wei, data:${helper.shortenHexData((data.tx.input))}, hash:${helper.shortenHexData((data.tx.hash))}, <button onclick=${detail}>Details</button> <button onclick=${debug}>Debug</button>`
}

function renderEmptyBlock (self, data) {
  return yo`<span>block ${data.block.number} - O transactions</span>`
}

function context (self, tx) {
  if (executionContext.getProvider() === 'vm') {
    return yo`<span>(vm)</span>`
  } else {
    return yo`<span>block:${tx.blockNumber}, txIndex:${tx.transactionIndex}</span>`
  }
}

function value (v) {
  try {
    if (v.indexOf && v.indexOf('0x') === 0) {
      return (new BN(v.replace('0x', ''), 16)).toString(10)
    } else {
      return v.toString(10)
    }
  } catch (e) {
    console.log(e)
    return v
  }
}

module.exports = TxLogger
<|endoftext|>


<commit_before>(function (){
    angular.module('sv').factory('GithubAPI', GithubAPI);
    GithubAPI.$inject = ['$http', '$q'];
    function GithubAPI($http, $q){
        var baseURL = 'https://api.github.com';
        var getRepos = function(username)
        {
            var deferred = $q.defer();
            $http.get(baseURL+'/users/'+username+'/repos').
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
        };

        var getBranches = function(username, reponame)
        {
            var deferred = $q.defer();
            $http.get(baseURL+'/repos/'+username+'/'+reponame+'/branches').
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
        };

        var getCommits = function(username, reponame, branchname)
        {
            var deferred = $q.defer();
            $http.get(baseURL+'/repos/'+username+'/'+reponame+'/commits?author='+username+'&sha='+branchname).
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
        };

		var getCommit = function(username, reponame, commitId){
			var deferred = $q.defer();
            $http.get(baseURL+'/repos/'+username+'/'+reponame+'/commits/'+commitId).
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
		};

        return {
            getRepos: getRepos,
            getBranches: getBranches,
            getCommits: getCommits,
            getCommit: getCommit
        };
    }
})();<commit_msg>testing 123<commit_after>(function (){
    angular.module('sv').factory('GithubAPI', GithubAPI);
    GithubAPI.$inject = ['$http', '$q'];
    function GithubAPI($http, $q){
        var baseURL = 'https://api.github.com';
        var getRepos = function(username)
        {
            var deferred = $q.defer();
            $http.get(baseURL+'/users/'+username+'/repos').
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
        };

        var getBranches = function(username, reponame)
        {
            var deferred = $q.defer();
            $http.get(baseURL+'/repos/'+username+'/'+reponame+'/branches').
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
        };

        var getCommits = function(username, reponame, branchname)
        {
            var deferred = $q.defer();
            $http.get(baseURL+'/repos/'+username+'/'+reponame+'/commits?author='+username+'&sha='+branchname).
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
        };

		var getCommit = function(username, reponame, commitId){
			var deferred = $q.defer();
            $http.get(baseURL+'/repos/'+username+'/'+reponame+'/commits/'+commitId).
                success(function (data, status, headers, config) {
                    deferred.resolve(data);
                }).
                error(function (data, status, headers, config) {
                    deferred.resolve({'error': 'Could not make request'});
                });
            return deferred.promise;
		};

        return {
            getRepos: getRepos,
            getBranches: getBranches,
            getCommits: getCommits,
            getCommit: getCommit
        };
    }
})();<|endoftext|>