<commit_before>var stream = require('stream'),
    fs = require('fs'),
    util = require('util'),
    events = require('events');

var Parser = function(fileName, options) {
  var self = this;
  this.fileName   = fileName;
  this.header     = this.getHeader();
  this.fieldsCnt  = this.header.fields.length;
  this.parseTypes = true;
  this.recAsArray = false;

  if (options) {
    if ( options.parseTypes != undefined )
      this.parseTypes = options.parseTypes;
    if ( options.recAsArray != undefined )
      this.recAsArray = options.recAsArray;
  }

  var hNumRecs  = this.header.numberOfRecords
    , hRecLen   = this.header.recordLength
    , hDataSize = hNumRecs * hRecLen;

  var seqNumber  = 0
    , skipBytes  = this.header.headerLength
    , byteReades = 0
    , buffer     = new Buffer(0);

  this.stream = new stream.Transform({ 'objectMode': true });
  this.stream._transform = function( chunk, encoding, done ) {
    var buffPos = 0
      , buffLen = 0
      , rec;
    buffer  = Buffer.concat([ buffer, chunk ]);
    buffLen = buffer.length;

    if ( skipBytes ) {
      if( skipBytes >= buffLen ) {
        skipBytes = skipBytes - buffLen;
        done();
        return;
      }
      buffPos   = skipBytes;
      skipBytes = 0;
    }

    while ( byteReades<hDataSize && (buffPos+hRecLen)<=buffLen ) {
      if( self.recAsArray ) {
        rec = self.parseRecordToArray(
                ++seqNumber,
                buffer.slice( buffPos, buffPos+hRecLen )
        );
      }
      else {
        rec = self.parseRecordToObject(
                ++seqNumber,
                buffer.slice( buffPos, buffPos+hRecLen )
        );
      }
      buffPos += hRecLen;
      byteReades += hRecLen;
      this.push( rec );
    }

    buffer = buffer.slice( buffPos, buffLen );
    done();
  };

  fs.createReadStream(this.fileName).pipe(this.stream);
};

util.inherits(Parser, events.EventEmitter);

Parser.prototype.parseRecordToObject = function(sequenceNumber, buffer) {
  var record = {
    '@sequenceNumber': sequenceNumber,
    '@deleted'       : buffer[0] !== 32
  };
  for ( var i=0, pos=1, fld; i < this.fieldsCnt; i++ ) {
    fld = this.header.fields[i];
    record[fld.name] = this.parseField( fld, buffer.slice(pos, pos+fld.length) );
    pos += fld.length;
  }
  return record;
};

Parser.prototype.parseRecordToArray = function(sequenceNumber, buffer) {
  var record = new Array( this.fieldsCnt+2 );
  record[0] = sequenceNumber;
  record[1] = buffer[0] !== 32;
  for ( var i=0, pos=1, fld; i < this.fieldsCnt; i++ ) {
    fld = this.header.fields[i];
    record[i+2] = this.parseField( fld, buffer.slice(pos, pos+fld.length) );
    pos += fld.length;
  }
  return record;
};

Parser.prototype.parseField = function(field, buffer) {
  var st  = 0
    , end = buffer.length;
  while( end>st && buffer[end-1]===32 ) end--;
  while( st<end && buffer[st   ]===32 ) st++;

  if( field.raw ) {
        return buffer.slice( st, end );
  }

  var data = buffer.toString( 'utf-8', st, end );
  if ( this.parseTypes ) {
    if ( field.type==='N' || field.type==='F' ) {
      data = Number( data );
    }
  }

  return data;
};

Parser.prototype.getHeader = function() {
  var fd = fs.openSync( this.fileName, 'r' )
    , buff = new Buffer( 32 )
    , header;
  fs.readSync( fd, buff, 0, 32, 0 );
  header = this.parseBaseHeader( buff );
  buff = new Buffer( header.headerLength );
  fs.readSync( fd, buff, 0, header.headerLength, 0 );
  this.parseFieldsHeader( header, buff );
  fs.closeSync( fd );
  return header;
};

Parser.prototype.parseBaseHeader = function(data) {
  var header = {
    'version'        : data.readUInt8  (  0, true ),
    'dateUpdated'    : this.parseHeaderDate( data.slice(1, 4) ),
    'numberOfRecords': data.readInt32LE(  4, true ),
    'headerLength'   : data.readInt16LE(  8, true ),
    'recordLength'   : data.readInt16LE( 10, true ),
    'fields'         : []
  };
  return header;
};

Parser.prototype.parseFieldsHeader = function(header, data) {
  var fieldData = [];
  for (var i = 32; i <= header.headerLength-32; i += 32) {
    fieldData.push( data.slice(i, i + 32) );
  }

  header.fields = fieldData.map(this.parseFieldSubRecord);
};

Parser.prototype.parseHeaderDate = function(buffer) {
  var day   = buffer.readUInt8( 0, true ) + 1900
    , month = buffer.readUInt8( 1, true ) - 1
    , year  = buffer.readUInt8( 2, true );
  return new Date( year, month, day );
};

Parser.prototype.parseFieldSubRecord = function(buffer) {
  var field = {
    'name'         : buffer.toString( 'utf-8',  0, 11 ).replace( /\x00+$/, '' ),
    'type'         : buffer.toString( 'utf-8', 11, 12 ),
    'displacement' : buffer.readInt32LE( 12, true ),
    'length'       : buffer.readUInt8( 16, true ),
    'decimalPlaces': buffer.readUInt8( 17, true ),
    'indexFlag'    : buffer.readUInt8( 31, true )
  };
  return field;
};

module.exports = Parser;
<commit_msg>implement _flush method<commit_after>var stream = require('stream'),
    fs = require('fs'),
    util = require('util'),
    events = require('events');

var Parser = function(fileName, options) {
  var self = this;
  this.fileName   = fileName;
  this.header     = this.getHeader();
  this.fieldsCnt  = this.header.fields.length;
  this.parseTypes = true;
  this.recAsArray = false;

  if (options) {
    if ( options.parseTypes != undefined )
      this.parseTypes = options.parseTypes;
    if ( options.recAsArray != undefined )
      this.recAsArray = options.recAsArray;
  }

  var hNumRecs  = this.header.numberOfRecords
    , hRecLen   = this.header.recordLength
    , hDataSize = hNumRecs * hRecLen;

  var seqNumber  = 0
    , skipBytes  = this.header.headerLength
    , byteReaded = 0
    , buffer     = new Buffer(0);

  function pushRecords( stream, buffer, buff_offset ) {
    var buffPos = buff_offset
      , buffLen = buffer.length
      , rec;

    while ( byteReaded<hDataSize && (buffPos+hRecLen)<=buffLen ) {
      if( self.recAsArray ) {
        rec = self.parseRecordToArray(
                ++seqNumber,
                buffer.slice( buffPos, buffPos+hRecLen )
        );
      }
      else {
        rec = self.parseRecordToObject(
                ++seqNumber,
                buffer.slice( buffPos, buffPos+hRecLen )
        );
      }
      buffPos += hRecLen;
      byteReaded += hRecLen;
      stream.push( rec );
    }
    return buffPos;
  }

  this.stream = new stream.Transform({ 'objectMode': true });
  this.stream._transform = function( chunk, encoding, done ) {
    var buffPos = 0
      , buffLen = 0
      , rec;
    buffer  = Buffer.concat([ buffer, chunk ]);
    buffLen = buffer.length;

    if ( skipBytes ) {
      if( skipBytes >= buffLen ) {
        skipBytes = skipBytes - buffLen;
        done();
        return;
      }
      buffPos   = skipBytes;
      skipBytes = 0;
    }

    buffPos = pushRecords( this, buffer, buffPos );
    buffer = buffer.slice( buffPos, buffLen );
    done();
  };
  this.stream._flush = function( done ) {
    if( buffer.length > 0 ) {
      pushRecords( this, buffer, 0 );
    }
    done();
  };

  fs.createReadStream(this.fileName).pipe(this.stream);
};

util.inherits(Parser, events.EventEmitter);

Parser.prototype.parseRecordToObject = function(sequenceNumber, buffer) {
  var record = {
    '@sequenceNumber': sequenceNumber,
    '@deleted'       : buffer[0] !== 32
  };
  for ( var i=0, pos=1, fld; i < this.fieldsCnt; i++ ) {
    fld = this.header.fields[i];
    record[fld.name] = this.parseField( fld, buffer.slice(pos, pos+fld.length) );
    pos += fld.length;
  }
  return record;
};

Parser.prototype.parseRecordToArray = function(sequenceNumber, buffer) {
  var record = new Array( this.fieldsCnt+2 );
  record[0] = sequenceNumber;
  record[1] = buffer[0] !== 32;
  for ( var i=0, pos=1, fld; i < this.fieldsCnt; i++ ) {
    fld = this.header.fields[i];
    record[i+2] = this.parseField( fld, buffer.slice(pos, pos+fld.length) );
    pos += fld.length;
  }
  return record;
};

Parser.prototype.parseField = function(field, buffer) {
  var st  = 0
    , end = buffer.length;
  while( end>st && buffer[end-1]===32 ) end--;
  while( st<end && buffer[st   ]===32 ) st++;

  if( field.raw ) {
        return buffer.slice( st, end );
  }

  var data = buffer.toString( 'utf-8', st, end );
  if ( this.parseTypes ) {
    if ( field.type==='N' || field.type==='F' ) {
      data = Number( data );
    }
  }

  return data;
};

Parser.prototype.getHeader = function() {
  var fd = fs.openSync( this.fileName, 'r' )
    , buff = new Buffer( 32 )
    , header;
  fs.readSync( fd, buff, 0, 32, 0 );
  header = this.parseBaseHeader( buff );
  buff = new Buffer( header.headerLength );
  fs.readSync( fd, buff, 0, header.headerLength, 0 );
  this.parseFieldsHeader( header, buff );
  fs.closeSync( fd );
  return header;
};

Parser.prototype.parseBaseHeader = function(data) {
  var header = {
    'version'        : data.readUInt8  (  0, true ),
    'dateUpdated'    : this.parseHeaderDate( data.slice(1, 4) ),
    'numberOfRecords': data.readInt32LE(  4, true ),
    'headerLength'   : data.readInt16LE(  8, true ),
    'recordLength'   : data.readInt16LE( 10, true ),
    'fields'         : []
  };
  return header;
};

Parser.prototype.parseFieldsHeader = function(header, data) {
  var fieldData = [];
  for (var i = 32; i <= header.headerLength-32; i += 32) {
    fieldData.push( data.slice(i, i + 32) );
  }

  header.fields = fieldData.map(this.parseFieldSubRecord);
};

Parser.prototype.parseHeaderDate = function(buffer) {
  var day   = buffer.readUInt8( 0, true ) + 1900
    , month = buffer.readUInt8( 1, true ) - 1
    , year  = buffer.readUInt8( 2, true );
  return new Date( year, month, day );
};

Parser.prototype.parseFieldSubRecord = function(buffer) {
  var field = {
    'name'         : buffer.toString( 'utf-8',  0, 11 ).replace( /\x00+$/, '' ),
    'type'         : buffer.toString( 'utf-8', 11, 12 ),
    'displacement' : buffer.readInt32LE( 12, true ),
    'length'       : buffer.readUInt8( 16, true ),
    'decimalPlaces': buffer.readUInt8( 17, true ),
    'indexFlag'    : buffer.readUInt8( 31, true )
  };
  return field;
};

module.exports = Parser;
<|endoftext|>


<commit_before>// ==UserScript==
// @name        Animmex video width fix
// @description Sets the player size to 100% on animmex
// @namespace   hansen-i-nate24215@gmail.com
// @include     https://amx.*.xyz/*
// @include     http://amx.*.xyz/*
// @include     https://www.animmex.net/*
// @include     http://www.animmex.net/*
// @version     1.0
// @grant       GM_addStyle
// @license     MIT License (Expat); opensource.org/licenses/MIT
// ==/UserScript==
'use strict';

// only has effect under amx.*.xyz
GM_addStyle(`
.video-js {
    width: 100% !important;
    height: 100% !important;
}
`);

// we can't use a CSS selector here because it might bugger up!
const voteBox = document.getElementsByClassName('vote-box');
if (voteBox.length > 0) { // will only be true under animmex.net
  voteBox[0].parentElement.style.width = '100%';
}
<commit_msg>animmex fix now blocks popups + uses @grant none<commit_after>// ==UserScript==
// @name        Animmex video width fix
// @description Sets the player size to 100% on animmex
// @namespace   hansen-i-nate24215@gmail.com
// @include     https://amx.*.xyz/*
// @include     http://amx.*.xyz/*
// @include     https://www.animmex.net/*
// @include     http://www.animmex.net/*
// @version     1.1
// @grant       none
// @license     MIT License (Expat); opensource.org/licenses/MIT
// ==/UserScript==
'use strict';

function addStyle(css) {
  const head = document.head || document.getElementsByTagName('head')[0];
  if (!head) return;

  const style = document.createElement('style');
  style.type = 'text/css';

  if (style.textContent) {
    style.textContent = css;
  } else if (style.innerHTML) {
    style.innerHTML = css;
  }/* else if (style.innerText) {
    style.innerText = css;
  }*/ else {
    return;
  }

  head.appendChild(style);
};

// only has effect under amx.*.xyz
addStyle(`
.video-js {
    width: 100% !important;
    height: 100% !important;
}
`);

// we can't use a CSS selector here because it might bugger up!
const voteBox = document.getElementsByClassName('vote-box');
if (voteBox.length > 0) { // will only be true under animmex.net
  voteBox[0].parentElement.style.width = '100%';
}

// we want @grant none so we can assign this to `window` and block ze popups
window.open = function(){};
<|endoftext|>


<commit_before>/*!
 * helper-codelinks <https://github.com/helpers/helper-codelinks>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var api = require('api-toc');

/**
 * Generate a clickable list of API "code-links" for the methods in directory.
 * Each link takes you to the beginning line of code for the corresponding method.
 *
 * Usage:
 *
 * ```js
 * {%= apitoc('lib/') %}
 * ```
 */

module.exports = function(dir, append) {
  if (typeof dir !== 'string') {
    throw new TypeError('helper-codelinks expects the first argument to be a string.');
  }
  var res = api(dir, append);
  res += '\n\n';
  res += '_(Code links generated by Verb\'s [api-toc] helper)_';
  res += '\n\n';
  res += '[api-toc]: https://github.com/jonschlinkert/api-toc'
  return res;
};
<commit_msg>return a function, add docs<commit_after>/*!
 * helper-codelinks <https://github.com/helpers/helper-codelinks>
 *
 * Copyright (c) 2015, Jon Schlinkert.
 * Licensed under the MIT License.
 */

'use strict';

var api = require('api-toc');

/**
 * Generate a clickable list of API "code-links" for the methods
 * in the given `directory`.
 *
 * Each link takes you to the beginning line of code for the
 * corresponding method.
 *
 * **Example**
 *
 * ```js
 * {%%= codelinks('lib/') %}
 * ```
 * @param  {Object} `app` Pass an instance of [Template][template] or any application based on Template, like [verb], [assemble] or [generate].
 * @param  {String} `dir` The directory to scan for API methods to link to
 * @param  {String} `append`
 * @return {String}
 */

module.exports = function codelinks(app, options) {
  options = options || {};

  // if an instance of `Template` is passed, generate reflinks for libs referenced in the docs
  if (app && 'helpers' in app) {
    app.set('reflinks', ['api-toc', 'verb', 'template']);
  }

  return function (dir, append) {
    if (typeof dir !== 'string') {
      throw new TypeError('helper-codelinks expects the first argument to be a string.');
    }

    var res = api(dir, append);
    res += '\n\n';
    res += '_(Code links generated by [verb] using the [api-toc] helper)_';
    return res;
  }
};
<|endoftext|>


<commit_before>/* eslint-env node */
'use strict'

const path = require('path')
const merge = require('merge')
const mergeTrees = require('broccoli-merge-trees')
const postcssFilter = require('broccoli-postcss')
const PostcssCompiler = require('broccoli-postcss-single')

function PostcssPlugin (addon) {
  this.name = 'ember-cli-postcss'
  this.addon = addon
}

PostcssPlugin.prototype.toTree = function (tree, inputPath, outputPath, inputOptions) {
  let inputTrees = [tree]
  let defaultOptions = {
    enabled: true
  }
  let options = merge.recursive(defaultOptions, this.addon.options.compile, inputOptions)

  if (!options.enabled) {
    return tree
  }

  if (options.includePaths) {
    inputTrees = inputTrees.concat(options.includePaths)
  }

  let plugins = options.plugins // eslint-disable-line
  let map = options.map // eslint-disable-line
  let ext = options.extension || 'css'
  let paths = options.outputPaths
  let trees = Object.keys(paths).map(function (file) {
    let input = path.join(inputPath, `${file}.${ext}`)
    let output = paths[file]
    return new PostcssCompiler(inputTrees, input, output, plugins, map)
  })

  return mergeTrees(trees)
}

module.exports = {
  name: 'ember-cli-postcss',

  included: function included (app) {
    this._super.included.apply(this, arguments)

    let env = process.env.EMBER_ENV

    // Initialize options if none were passed
    this.options = merge.recursive({}, {
      compile: {
        enabled: true,
        map: env !== 'development' ? false : {},
        plugins: [],
        inputFile: 'app.css',
        outputFile: `${this.project.name()}.css`
      },
      filter: {
        enabled: false,
        map: env !== 'development' ? false : {},
        plugins: []
      }
    }, this._getAddonOptions(app).postcssOptions)
  },

  _getAddonOptions (app) {
    return (this.parent && this.parent.options) || (app && app.options) || {}
  },

  postprocessTree (type, tree) {
    if (this.options.filter.enabled && (type === 'all' || type === 'styles')) {
      tree = postcssFilter(tree, this.options.filter)
    }
    return tree
  },

  setupPreprocessorRegistry (type, registry) {
    let addon = this
    registry.add('css', new PostcssPlugin(addon))
  }
}
<commit_msg>fix include/exclude from removing CSS files<commit_after>/* eslint-env node */
'use strict'

const path = require('path')
const merge = require('merge')
const mergeTrees = require('broccoli-merge-trees')
const postcssFilter = require('broccoli-postcss')
const PostcssCompiler = require('broccoli-postcss-single')

function PostcssPlugin (addon) {
  this.name = 'ember-cli-postcss'
  this.addon = addon
}

PostcssPlugin.prototype.toTree = function (tree, inputPath, outputPath, inputOptions) {
  let inputTrees = [tree]
  let defaultOptions = {
    enabled: true
  }
  let options = merge.recursive(defaultOptions, this.addon.options.compile, inputOptions)

  if (!options.enabled) {
    return tree
  }

  if (options.includePaths) {
    inputTrees = inputTrees.concat(options.includePaths)
  }

  let plugins = options.plugins // eslint-disable-line
  let map = options.map // eslint-disable-line
  let ext = options.extension || 'css'
  let paths = options.outputPaths
  let trees = Object.keys(paths).map(function (file) {
    let input = path.join(inputPath, `${file}.${ext}`)
    let output = paths[file]
    return new PostcssCompiler(inputTrees, input, output, plugins, map)
  })

  return mergeTrees(trees)
}

module.exports = {
  name: 'ember-cli-postcss',

  included: function included (app) {
    this._super.included.apply(this, arguments)

    let env = process.env.EMBER_ENV

    // Initialize options if none were passed
    this.options = merge.recursive({}, {
      compile: {
        enabled: true,
        map: env !== 'development' ? false : {},
        plugins: [],
        inputFile: 'app.css',
        outputFile: `${this.project.name()}.css`
      },
      filter: {
        enabled: false,
        map: env !== 'development' ? false : {},
        plugins: []
      }
    }, this._getAddonOptions(app).postcssOptions)
  },

  _getAddonOptions (app) {
    return (this.parent && this.parent.options) || (app && app.options) || {}
  },

  postprocessTree (type, tree) {
    if (this.options.filter.enabled && (type === 'all' || type === 'styles')) {
      tree = mergeTrees([tree, postcssFilter(tree, this.options.filter)], { overwrite: true })
    }
    return tree
  },

  setupPreprocessorRegistry (type, registry) {
    let addon = this
    registry.add('css', new PostcssPlugin(addon))
  }
}
<|endoftext|>


<commit_before>const scrapeIt = require("scrape-it");
const Datastore = require('nedb')
const news = new Datastore({filename: 'news.db', autoload: true});
const universities = new Datastore({filename: 'universities.db', autoload: true});
const moment = require('moment');
const exec = require('child_process').exec;
const CronJob = require('cron').CronJob;
const TelegramBot = require('node-telegram-bot-api');
const rssParser = require('rss-parser');

const appUrl = `https://${process.env.APP_NAME}.herokuapp.com:443`;
const options = {
  webHook: { port: process.env.PORT },
};
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN, options);

bot.setWebHook(`${appUrl}/bot${process.env.TELEGRAM_TOKEN}`);

// Make unique.
news.ensureIndex({
    fieldName: 'url',
    unique: true
}, function(err) {});
// Make unique.
universities.ensureIndex({
    fieldName: 'university',
    unique: true
}, function(err) {});

moment.locale("tr");

// telegram section
bot.onText(/(.+)$/, function (msg, match) {
  const chatId = msg.chat.id;
  let replyOptions = {
      reply_markup: {
          inline_keyboard: inlineKeyboardItems().then((entries) => {
                              return entries.map((entry) => {
                                return [{ text: entry.university,  callback_data: entry.university,  }]
                              })
                            }).catch((err) => {
                              console.log('ERROR WHEN GET InlineKeyboardItems', err);
                            })
      },
  };

  bot.sendMessage(chatId, "Did you mean?", replyOptions)
      .then(() => {
          bot.once("callback_query", answer => {
            console.log(answer.message.chat.username, answer.data, answer.message.chat.id);
                if (answer.data !== 'nope') {
                  bot.sendMessage(chatId, `You choose ${answer.data}`);
                  addParticipant({
                    username: answer.message.chat.username,
                    telegramId: answer.message.chat.id,
                    university: answer.data.trim()
                  });
                  bot.sendMessage(chatId, `You are participant ${answer.data} now.`);
                } else {
                  bot.sendMessage(chatId, "Maybe you can add your university : https://github.com/cagataycali/university-news-notifier")
                }
              });
          });
});

// telegram section

// Extract sites from sites/*.json ..
exec('cd sites && ls *.json', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  // Data ..
  // const scrape = require(`${process.cwd()}/sites/${stdout.trim()}`);
  stdout.split('\n').forEach((item) => {
    item = item.trim();
    scrapeAndSave(item);
    if (item.split('.json')[0].trim().length > 0) {
      let content = require(`${process.cwd()}/sites/${item}`);
      let data = {
        university: content.university,
        faculity: content.faculity,
        directory: item,
        participants: []
      }
      universities.insert(data, function (err, newDoc) {
        if (err) {
          console.log(err.errorType === 'uniqueViolated' ? 'Bu üniversite mevcut..': 'Bir hata oluştu' + err);
        } else {
          console.log(`Bu üniversite eklendi ${item.trim().split('.json')[0]}`);
        }
      });
    }
  })
});

/*
  Get inline keyboard items(universities).
  Example usage: let inlineKeyboardItems = inlineKeyboardItems();
*/
function inlineKeyboardItems() {
  new Promise((resolve, reject) => {
    universities.find({ university: { $gt: "" } }, (err, docs) => {
      err ? reject(err.errorType) : resolve(docs);
    })
  });
}

/*
  Add participants in university feeds.
  Example usage: let numRowsUpdated = await addParticipant({
    username: 'cagataycali',
    telegramId: 149632499,
    university: 'Pamukkale Üniversitesi'
  });
*/
function addParticipant(obj) {
  return new Promise((resolve, reject) => {
    universities.update({ university: obj.university }, { $push: { participants: obj } }, {}, (err, numRowsUpdated) => {
      err ? reject(err.errorType) : resolve(numRowsUpdated);
    });
  });
}

/*
  Get participants in university.
  Example: university: 'Pamukkale Üniversitesi'
  Usage : let participants = await getParticipants('Pamukkale Üniversitesi');
*/
function getParticipants(university) {
  return new Promise((resolve, reject) => {
    universities.findOne({ university },  (err, docs) => {
      err ? reject(err.errorType) : resolve(docs.participants);
    })
  });
}

/*
  Scrape with directory's params.
  Save database.
  Send notification with telegram university listener participants.
  Example,
  file: pamukkale.json
*/
function scrapeAndSave(file) {
  return new Promise((resolve, reject) => {
    let data = require(`${process.cwd()}/sites/${file}`);
    const baseURI = data.home;

    if (data.rss) {
      rssParser.parseURL(data.rss, function(err, parsed) {
        console.log(parsed.feed.title);
        parsed.feed.entries.forEach(function(entry) {
          news.insert({
            "title": entry.title,
            "url": entry.link,
            "publishedAt": entry.pubDate,
            "scrapedAt": moment().format()
          }, function (err, newDoc) {
            if (!err) {
              console.log('New entry!');
              getParticipants(data.university)
                .then((participants) => {
                  participants.forEach((participant) => {
                    console.log(participant.username, participant.telegramId, 'need notify.', entry.title, entry.link, entry.pubDate);
                    bot.sendMessage(participant.telegramId, `${entry.title} ${entry.pubDate} \n ${entry.link}`);
                  })
                })
                .catch((err) => {
                  console.log('ERROR WHEN GET Participants', err);
                })
            }
           });
        })
      })
    } else {
      const scrape = data.scrape;
      // Scrape it.
      scrapeIt(data.url, scrape).then(page => {
          page = page.news.filter((piece) => {
            if (piece.url && piece.title) {
              if (!piece.url.includes(baseURI)) {
                piece.url = `${baseURI}${piece.url}`;
                return piece;
              } else {
                return piece;
              }
            }
          });
          page.forEach((item) => {
            item.scrapedAt = moment().format()
            news.insert(item, function (err, newDoc) {
              if (err) {
                // console.log(err.errorType === 'uniqueViolated' ? 'Veritabanında mevcut..': 'Başka bir hata mevcut' + err);
              } else {
                console.log('New entry!');
                getParticipants(data.university)
                  .then((participants) => {
                    participants.forEach((participant) => {
                      // send telegram notification
                      console.log(participant.username, participant.telegramId, 'need notify.', item.title, item.url, item.publishedAt);
                      bot.sendMessage(participant.telegramId, `${item.title} ${item.publishedAt} \n ${item.url}`);
                    })
                  })
                  .catch((err) => {
                    console.log('ERROR WHEN GET Participants');
                  })
              }
             });
          });
      });
    }
  });
}

new CronJob('00 * * * * *', function() {
  console.log('Cron..');
  exec('cd sites && ls *.json', (error, stdout, stderr) => {
    if (error) {
      console.error(`exec error: ${error}`);
      return;
    }
    // Data ..
    stdout.split('\n').forEach((item) => {
      item = item.trim();
      scrapeAndSave(item)
    })
  });
}, null, true, 'America/Los_Angeles');
<commit_msg>Return inline keyboard items promise<commit_after>const scrapeIt = require("scrape-it");
const Datastore = require('nedb')
const news = new Datastore({filename: 'news.db', autoload: true});
const universities = new Datastore({filename: 'universities.db', autoload: true});
const moment = require('moment');
const exec = require('child_process').exec;
const CronJob = require('cron').CronJob;
const TelegramBot = require('node-telegram-bot-api');
const rssParser = require('rss-parser');

const appUrl = `https://${process.env.APP_NAME}.herokuapp.com:443`;
const options = {
  webHook: { port: process.env.PORT },
};
const bot = new TelegramBot(process.env.TELEGRAM_TOKEN, options);

bot.setWebHook(`${appUrl}/bot${process.env.TELEGRAM_TOKEN}`);

// Make unique.
news.ensureIndex({
    fieldName: 'url',
    unique: true
}, function(err) {});
// Make unique.
universities.ensureIndex({
    fieldName: 'university',
    unique: true
}, function(err) {});

moment.locale("tr");

// telegram section
bot.onText(/(.+)$/, function (msg, match) {
  const chatId = msg.chat.id;
  let replyOptions = {
      reply_markup: {
          inline_keyboard: [inlineKeyboardItems().then((entries) => {
                              return entries.map((entry) => {
                                return [{ text: entry.university,  callback_data: entry.university,  }]
                              })
                            }).catch((err) => {
                              console.log('ERROR WHEN GET InlineKeyboardItems', err);
                            })]
      },
  };

  bot.sendMessage(chatId, "Did you mean?", replyOptions)
      .then(() => {
          bot.once("callback_query", answer => {
            console.log(answer.message.chat.username, answer.data, answer.message.chat.id);
                if (answer.data !== 'nope') {
                  bot.sendMessage(chatId, `You choose ${answer.data}`);
                  addParticipant({
                    username: answer.message.chat.username,
                    telegramId: answer.message.chat.id,
                    university: answer.data.trim()
                  });
                  bot.sendMessage(chatId, `You are participant ${answer.data} now.`);
                } else {
                  bot.sendMessage(chatId, "Maybe you can add your university : https://github.com/cagataycali/university-news-notifier")
                }
              });
          });
});

// telegram section

// Extract sites from sites/*.json ..
exec('cd sites && ls *.json', (error, stdout, stderr) => {
  if (error) {
    console.error(`exec error: ${error}`);
    return;
  }
  // Data ..
  // const scrape = require(`${process.cwd()}/sites/${stdout.trim()}`);
  stdout.split('\n').forEach((item) => {
    item = item.trim();
    scrapeAndSave(item);
    if (item.split('.json')[0].trim().length > 0) {
      let content = require(`${process.cwd()}/sites/${item}`);
      let data = {
        university: content.university,
        faculity: content.faculity,
        directory: item,
        participants: []
      }
      universities.insert(data, function (err, newDoc) {
        if (err) {
          console.log(err.errorType === 'uniqueViolated' ? 'Bu üniversite mevcut..': 'Bir hata oluştu' + err);
        } else {
          console.log(`Bu üniversite eklendi ${item.trim().split('.json')[0]}`);
        }
      });
    }
  })
});

/*
  Get inline keyboard items(universities).
  Example usage: let inlineKeyboardItems = inlineKeyboardItems();
*/
function inlineKeyboardItems() {
  return new Promise((resolve, reject) => {
    universities.find({ university: { $gt: "" } }, (err, docs) => {
      err ? reject(err.errorType) : resolve(docs);
    })
  });
}

/*
  Add participants in university feeds.
  Example usage: let numRowsUpdated = await addParticipant({
    username: 'cagataycali',
    telegramId: 149632499,
    university: 'Pamukkale Üniversitesi'
  });
*/
function addParticipant(obj) {
  return new Promise((resolve, reject) => {
    universities.update({ university: obj.university }, { $push: { participants: obj } }, {}, (err, numRowsUpdated) => {
      err ? reject(err.errorType) : resolve(numRowsUpdated);
    });
  });
}

/*
  Get participants in university.
  Example: university: 'Pamukkale Üniversitesi'
  Usage : let participants = await getParticipants('Pamukkale Üniversitesi');
*/
function getParticipants(university) {
  return new Promise((resolve, reject) => {
    universities.findOne({ university },  (err, docs) => {
      err ? reject(err.errorType) : resolve(docs.participants);
    })
  });
}

/*
  Scrape with directory's params.
  Save database.
  Send notification with telegram university listener participants.
  Example,
  file: pamukkale.json
*/
function scrapeAndSave(file) {
  return new Promise((resolve, reject) => {
    let data = require(`${process.cwd()}/sites/${file}`);
    const baseURI = data.home;

    if (data.rss) {
      rssParser.parseURL(data.rss, function(err, parsed) {
        console.log(parsed.feed.title);
        parsed.feed.entries.forEach(function(entry) {
          news.insert({
            "title": entry.title,
            "url": entry.link,
            "publishedAt": entry.pubDate,
            "scrapedAt": moment().format()
          }, function (err, newDoc) {
            if (!err) {
              console.log('New entry!');
              getParticipants(data.university)
                .then((participants) => {
                  participants.forEach((participant) => {
                    console.log(participant.username, participant.telegramId, 'need notify.', entry.title, entry.link, entry.pubDate);
                    bot.sendMessage(participant.telegramId, `${entry.title} ${entry.pubDate} \n ${entry.link}`);
                  })
                })
                .catch((err) => {
                  console.log('ERROR WHEN GET Participants', err);
                })
            }
           });
        })
      })
    } else {
      const scrape = data.scrape;
      // Scrape it.
      scrapeIt(data.url, scrape).then(page => {
          page = page.news.filter((piece) => {
            if (piece.url && piece.title) {
              if (!piece.url.includes(baseURI)) {
                piece.url = `${baseURI}${piece.url}`;
                return piece;
              } else {
                return piece;
              }
            }
          });
          page.forEach((item) => {
            item.scrapedAt = moment().format()
            news.insert(item, function (err, newDoc) {
              if (err) {
                // console.log(err.errorType === 'uniqueViolated' ? 'Veritabanında mevcut..': 'Başka bir hata mevcut' + err);
              } else {
                console.log('New entry!');
                getParticipants(data.university)
                  .then((participants) => {
                    participants.forEach((participant) => {
                      // send telegram notification
                      console.log(participant.username, participant.telegramId, 'need notify.', item.title, item.url, item.publishedAt);
                      bot.sendMessage(participant.telegramId, `${item.title} ${item.publishedAt} \n ${item.url}`);
                    })
                  })
                  .catch((err) => {
                    console.log('ERROR WHEN GET Participants');
                  })
              }
             });
          });
      });
    }
  });
}

new CronJob('00 * * * * *', function() {
  console.log('Cron..');
  exec('cd sites && ls *.json', (error, stdout, stderr) => {
    if (error) {
      console.error(`exec error: ${error}`);
      return;
    }
    // Data ..
    stdout.split('\n').forEach((item) => {
      item = item.trim();
      scrapeAndSave(item)
    })
  });
}, null, true, 'America/Los_Angeles');
<|endoftext|>


<commit_before>/*
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import PlatformPeg from "../PlatformPeg";
import {MatrixClientPeg} from "../MatrixClientPeg";
import SettingsStore from '../settings/SettingsStore';
import {SettingLevel} from "../settings/SettingsStore";
import {sleep} from "../utils/promise";

/*
 * Event indexing class that wraps the platform specific event indexing.
 */
export default class EventIndex {
    constructor() {
        this.crawlerCheckpoints = [];
        // The time in ms that the crawler will wait loop iterations if there
        // have not been any checkpoints to consume in the last iteration.
        this._crawlerIdleTime = 5000;
        // The maximum number of events our crawler should fetch in a single
        // crawl.
        this._eventsPerCrawl = 100;
        this._crawler = null;
        this.liveEventsForIndex = new Set();
    }

    async init() {
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        await indexManager.initEventIndex();
        console.log("EventIndex: Successfully initialized the event index");

        this.crawlerCheckpoints = await indexManager.loadCheckpoints();
        console.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints);

        this.registerListeners();
    }

    registerListeners() {
        const client = MatrixClientPeg.get();

        client.on('sync', this.onSync);
        client.on('Room.timeline', this.onRoomTimeline);
        client.on('Event.decrypted', this.onEventDecrypted);
        client.on('Room.timelineReset', this.onTimelineReset);
    }

    removeListeners() {
        const client = MatrixClientPeg.get();
        if (client === null) return;

        client.removeListener('sync', this.onSync);
        client.removeListener('Room.timeline', this.onRoomTimeline);
        client.removeListener('Event.decrypted', this.onEventDecrypted);
        client.removeListener('Room.timelineReset', this.onTimelineReset);
    }

    onSync = async (state, prevState, data) => {
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        if (prevState === "PREPARED" && state === "SYNCING") {
            const addInitialCheckpoints = async () => {
                const client = MatrixClientPeg.get();
                const rooms = client.getRooms();

                const isRoomEncrypted = (room) => {
                    return client.isRoomEncrypted(room.roomId);
                };

                // We only care to crawl the encrypted rooms, non-encrypted.
                // rooms can use the search provided by the homeserver.
                const encryptedRooms = rooms.filter(isRoomEncrypted);

                console.log("EventIndex: Adding initial crawler checkpoints");

                // Gather the prev_batch tokens and create checkpoints for
                // our message crawler.
                await Promise.all(encryptedRooms.map(async (room) => {
                    const timeline = room.getLiveTimeline();
                    const token = timeline.getPaginationToken("b");

                    console.log("EventIndex: Got token for indexer",
                                room.roomId, token);

                    const backCheckpoint = {
                        roomId: room.roomId,
                        token: token,
                        direction: "b",
                    };

                    const forwardCheckpoint = {
                        roomId: room.roomId,
                        token: token,
                        direction: "f",
                    };

                    await indexManager.addCrawlerCheckpoint(backCheckpoint);
                    await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
                    this.crawlerCheckpoints.push(backCheckpoint);
                    this.crawlerCheckpoints.push(forwardCheckpoint);
                }));
            };

            // If our indexer is empty we're most likely running Riot the
            // first time with indexing support or running it with an
            // initial sync. Add checkpoints to crawl our encrypted rooms.
            const eventIndexWasEmpty = await indexManager.isEventIndexEmpty();
            if (eventIndexWasEmpty) await addInitialCheckpoints();

            // Start our crawler.
            if (SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling')) {
                this.startCrawler();
            }
            return;
        }

        if (prevState === "SYNCING" && state === "SYNCING") {
            // A sync was done, presumably we queued up some live events,
            // commit them now.
            console.log("EventIndex: Committing events");
            await indexManager.commitLiveEvents();
            return;
        }
    }

    onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => {
        // We only index encrypted rooms locally.
        if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;

        // If it isn't a live event or if it's redacted there's nothing to
        // do.
        if (toStartOfTimeline || !data || !data.liveEvent
            || ev.isRedacted()) {
            return;
        }

        // If the event is not yet decrypted mark it for the
        // Event.decrypted callback.
        if (ev.isBeingDecrypted()) {
            const eventId = ev.getId();
            this.liveEventsForIndex.add(eventId);
        } else {
            // If the event is decrypted or is unencrypted add it to the
            // index now.
            await this.addLiveEventToIndex(ev);
        }
    }

    onEventDecrypted = async (ev, err) => {
        const eventId = ev.getId();

        // If the event isn't in our live event set, ignore it.
        if (!this.liveEventsForIndex.delete(eventId)) return;
        if (err) return;
        await this.addLiveEventToIndex(ev);
    }

    async addLiveEventToIndex(ev) {
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        if (["m.room.message", "m.room.name", "m.room.topic"]
            .indexOf(ev.getType()) == -1) {
            return;
        }

        const e = ev.toJSON().decrypted;
        const profile = {
            displayname: ev.sender.rawDisplayName,
            avatar_url: ev.sender.getMxcAvatarUrl(),
        };

        indexManager.addEventToIndex(e, profile);
    }

    async crawlerFunc() {
        let cancelled = false;

        console.log("EventIndex: Started crawler function");

        const client = MatrixClientPeg.get();
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        this._crawler = {};

        this._crawler.cancel = () => {
            cancelled = true;
        };

        let idle = false;

        while (!cancelled) {
            // This is a low priority task and we don't want to spam our
            // homeserver with /messages requests so we set a hefty timeout
            // here.
            let sleepTime = SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime');

            // Don't let the user configure a lower sleep time than 100 ms.
            sleepTime = Math.max(sleepTime, 100);

            if (idle) {
                sleepTime = this._crawlerIdleTime;
            }

            await sleep(sleepTime);

            console.log("EventIndex: Running the crawler loop.");

            if (cancelled) {
                break;
            }

            const checkpoint = this.crawlerCheckpoints.shift();

            /// There is no checkpoint available currently, one may appear if
            // a sync with limited room timelines happens, so go back to sleep.
            if (checkpoint === undefined) {
                idle = true;
                continue;
            }

            idle = false;

            console.log("EventIndex: crawling using checkpoint", checkpoint);

            // We have a checkpoint, let us fetch some messages, again, very
            // conservatively to not bother our homeserver too much.
            const eventMapper = client.getEventMapper();
            // TODO we need to ensure to use member lazy loading with this
            // request so we get the correct profiles.
            let res;

            try {
                res = await client._createMessagesRequest(
                    checkpoint.roomId, checkpoint.token, this._eventsPerCrawl,
                    checkpoint.direction);
            } catch (e) {
                console.log("EventIndex: Error crawling events:", e);
                this.crawlerCheckpoints.push(checkpoint);
                continue;
            }

            if (res.chunk.length === 0) {
                console.log("EventIndex: Done with the checkpoint", checkpoint);
                // We got to the start/end of our timeline, lets just
                // delete our checkpoint and go back to sleep.
                await indexManager.removeCrawlerCheckpoint(checkpoint);
                continue;
            }

            // Convert the plain JSON events into Matrix events so they get
            // decrypted if necessary.
            const matrixEvents = res.chunk.map(eventMapper);
            let stateEvents = [];
            if (res.state !== undefined) {
                stateEvents = res.state.map(eventMapper);
            }

            const profiles = {};

            stateEvents.forEach(ev => {
                if (ev.event.content &&
                    ev.event.content.membership === "join") {
                    profiles[ev.event.sender] = {
                        displayname: ev.event.content.displayname,
                        avatar_url: ev.event.content.avatar_url,
                    };
                }
            });

            const decryptionPromises = [];

            matrixEvents.forEach(ev => {
                if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
                    // TODO the decryption promise is a private property, this
                    // should either be made public or we should convert the
                    // event that gets fired when decryption is done into a
                    // promise using the once event emitter method:
                    // https://nodejs.org/api/events.html#events_events_once_emitter_name
                    decryptionPromises.push(ev._decryptionPromise);
                }
            });

            // Let us wait for all the events to get decrypted.
            await Promise.all(decryptionPromises);

            // We filter out events for which decryption failed, are redacted
            // or aren't of a type that we know how to index.
            const isValidEvent = (value) => {
                return ([
                        "m.room.message",
                        "m.room.name",
                        "m.room.topic",
                    ].indexOf(value.getType()) >= 0
                        && !value.isRedacted() && !value.isDecryptionFailure()
                );
                // TODO do we need to check if the event has all the valid
                // attributes?
            };

            // TODO if there are no events at this point we're missing a lot
            // decryption keys, do we want to retry this checkpoint at a later
            // stage?
            const filteredEvents = matrixEvents.filter(isValidEvent);

            // Let us convert the events back into a format that EventIndex can
            // consume.
            const events = filteredEvents.map((ev) => {
                const jsonEvent = ev.toJSON();

                let e;
                if (ev.isEncrypted()) e = jsonEvent.decrypted;
                else e = jsonEvent;

                let profile = {};
                if (e.sender in profiles) profile = profiles[e.sender];
                const object = {
                    event: e,
                    profile: profile,
                };
                return object;
            });

            // Create a new checkpoint so we can continue crawling the room for
            // messages.
            const newCheckpoint = {
                roomId: checkpoint.roomId,
                token: res.end,
                fullCrawl: checkpoint.fullCrawl,
                direction: checkpoint.direction,
            };

            console.log(
                "EventIndex: Crawled room",
                client.getRoom(checkpoint.roomId).name,
                "and fetched", events.length, "events.",
            );

            try {
                const eventsAlreadyAdded = await indexManager.addHistoricEvents(
                    events, newCheckpoint, checkpoint);
                // If all events were already indexed we assume that we catched
                // up with our index and don't need to crawl the room further.
                // Let us delete the checkpoint in that case, otherwise push
                // the new checkpoint to be used by the crawler.
                if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
                    console.log("EventIndex: Checkpoint had already all events",
                                "added, stopping the crawl", checkpoint);
                    await indexManager.removeCrawlerCheckpoint(newCheckpoint);
                } else {
                    this.crawlerCheckpoints.push(newCheckpoint);
                }
            } catch (e) {
                console.log("EventIndex: Error durring a crawl", e);
                // An error occurred, put the checkpoint back so we
                // can retry.
                this.crawlerCheckpoints.push(checkpoint);
            }
        }

        this._crawler = null;

        console.log("EventIndex: Stopping crawler function");
    }

    onTimelineReset = async (room, timelineSet, resetAllTimelines) => {
        if (room === null) return;

        const indexManager = PlatformPeg.get().getEventIndexingManager();
        if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;

        const timeline = room.getLiveTimeline();
        const token = timeline.getPaginationToken("b");

        const backwardsCheckpoint = {
            roomId: room.roomId,
            token: token,
            fullCrawl: false,
            direction: "b",
        };

        console.log("EventIndex: Added checkpoint because of a limited timeline",
            backwardsCheckpoint);

        await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);

        this.crawlerCheckpoints.push(backwardsCheckpoint);
    }

    startCrawler() {
        if (this._crawler !== null) return;
        this.crawlerFunc();
    }

    stopCrawler() {
        if (this._crawler === null) return;
        this._crawler.cancel();
    }

    async close() {
        const indexManager = PlatformPeg.get().getEventIndexingManager();
        this.removeListeners();
        this.stopCrawler();
        return indexManager.closeEventIndex();
    }

    async search(searchArgs) {
        const indexManager = PlatformPeg.get().getEventIndexingManager();
        return indexManager.searchEventIndex(searchArgs);
    }

    async indexSize() {
        const indexManager = PlatformPeg.get().getEventIndexingManager();
        return indexManager.indexSize();
    }
}
<commit_msg>EventIndex: Add a method to gather the currently crawled rooms.<commit_after>/*
Copyright 2019 The Matrix.org Foundation C.I.C.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

import PlatformPeg from "../PlatformPeg";
import {MatrixClientPeg} from "../MatrixClientPeg";
import SettingsStore from '../settings/SettingsStore';
import {SettingLevel} from "../settings/SettingsStore";
import {sleep} from "../utils/promise";

/*
 * Event indexing class that wraps the platform specific event indexing.
 */
export default class EventIndex {
    constructor() {
        this.crawlerCheckpoints = [];
        // The time in ms that the crawler will wait loop iterations if there
        // have not been any checkpoints to consume in the last iteration.
        this._crawlerIdleTime = 5000;
        // The maximum number of events our crawler should fetch in a single
        // crawl.
        this._eventsPerCrawl = 100;
        this._crawler = null;
        this._currentCheckpoint = null;
        this.liveEventsForIndex = new Set();
    }

    async init() {
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        await indexManager.initEventIndex();
        console.log("EventIndex: Successfully initialized the event index");

        this.crawlerCheckpoints = await indexManager.loadCheckpoints();
        console.log("EventIndex: Loaded checkpoints", this.crawlerCheckpoints);

        this.registerListeners();
    }

    registerListeners() {
        const client = MatrixClientPeg.get();

        client.on('sync', this.onSync);
        client.on('Room.timeline', this.onRoomTimeline);
        client.on('Event.decrypted', this.onEventDecrypted);
        client.on('Room.timelineReset', this.onTimelineReset);
    }

    removeListeners() {
        const client = MatrixClientPeg.get();
        if (client === null) return;

        client.removeListener('sync', this.onSync);
        client.removeListener('Room.timeline', this.onRoomTimeline);
        client.removeListener('Event.decrypted', this.onEventDecrypted);
        client.removeListener('Room.timelineReset', this.onTimelineReset);
    }

    onSync = async (state, prevState, data) => {
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        if (prevState === "PREPARED" && state === "SYNCING") {
            const addInitialCheckpoints = async () => {
                const client = MatrixClientPeg.get();
                const rooms = client.getRooms();

                const isRoomEncrypted = (room) => {
                    return client.isRoomEncrypted(room.roomId);
                };

                // We only care to crawl the encrypted rooms, non-encrypted.
                // rooms can use the search provided by the homeserver.
                const encryptedRooms = rooms.filter(isRoomEncrypted);

                console.log("EventIndex: Adding initial crawler checkpoints");

                // Gather the prev_batch tokens and create checkpoints for
                // our message crawler.
                await Promise.all(encryptedRooms.map(async (room) => {
                    const timeline = room.getLiveTimeline();
                    const token = timeline.getPaginationToken("b");

                    console.log("EventIndex: Got token for indexer",
                                room.roomId, token);

                    const backCheckpoint = {
                        roomId: room.roomId,
                        token: token,
                        direction: "b",
                    };

                    const forwardCheckpoint = {
                        roomId: room.roomId,
                        token: token,
                        direction: "f",
                    };

                    await indexManager.addCrawlerCheckpoint(backCheckpoint);
                    await indexManager.addCrawlerCheckpoint(forwardCheckpoint);
                    this.crawlerCheckpoints.push(backCheckpoint);
                    this.crawlerCheckpoints.push(forwardCheckpoint);
                }));
            };

            // If our indexer is empty we're most likely running Riot the
            // first time with indexing support or running it with an
            // initial sync. Add checkpoints to crawl our encrypted rooms.
            const eventIndexWasEmpty = await indexManager.isEventIndexEmpty();
            if (eventIndexWasEmpty) await addInitialCheckpoints();

            // Start our crawler.
            if (SettingsStore.getValueAt(SettingLevel.DEVICE, 'enableCrawling')) {
                this.startCrawler();
            }
            return;
        }

        if (prevState === "SYNCING" && state === "SYNCING") {
            // A sync was done, presumably we queued up some live events,
            // commit them now.
            console.log("EventIndex: Committing events");
            await indexManager.commitLiveEvents();
            return;
        }
    }

    onRoomTimeline = async (ev, room, toStartOfTimeline, removed, data) => {
        // We only index encrypted rooms locally.
        if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;

        // If it isn't a live event or if it's redacted there's nothing to
        // do.
        if (toStartOfTimeline || !data || !data.liveEvent
            || ev.isRedacted()) {
            return;
        }

        // If the event is not yet decrypted mark it for the
        // Event.decrypted callback.
        if (ev.isBeingDecrypted()) {
            const eventId = ev.getId();
            this.liveEventsForIndex.add(eventId);
        } else {
            // If the event is decrypted or is unencrypted add it to the
            // index now.
            await this.addLiveEventToIndex(ev);
        }
    }

    onEventDecrypted = async (ev, err) => {
        const eventId = ev.getId();

        // If the event isn't in our live event set, ignore it.
        if (!this.liveEventsForIndex.delete(eventId)) return;
        if (err) return;
        await this.addLiveEventToIndex(ev);
    }

    async addLiveEventToIndex(ev) {
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        if (["m.room.message", "m.room.name", "m.room.topic"]
            .indexOf(ev.getType()) == -1) {
            return;
        }

        const e = ev.toJSON().decrypted;
        const profile = {
            displayname: ev.sender.rawDisplayName,
            avatar_url: ev.sender.getMxcAvatarUrl(),
        };

        indexManager.addEventToIndex(e, profile);
    }

    async crawlerFunc() {
        let cancelled = false;

        console.log("EventIndex: Started crawler function");

        const client = MatrixClientPeg.get();
        const indexManager = PlatformPeg.get().getEventIndexingManager();

        this._crawler = {};

        this._crawler.cancel = () => {
            cancelled = true;
        };

        let idle = false;

        while (!cancelled) {
            // This is a low priority task and we don't want to spam our
            // homeserver with /messages requests so we set a hefty timeout
            // here.
            let sleepTime = SettingsStore.getValueAt(SettingLevel.DEVICE, 'crawlerSleepTime');

            // Don't let the user configure a lower sleep time than 100 ms.
            sleepTime = Math.max(sleepTime, 100);

            if (idle) {
                sleepTime = this._crawlerIdleTime;
            }

            this._currentCheckpoint = null;

            await sleep(sleepTime);

            console.log("EventIndex: Running the crawler loop.");

            if (cancelled) {
                break;
            }

            const checkpoint = this.crawlerCheckpoints.shift();

            /// There is no checkpoint available currently, one may appear if
            // a sync with limited room timelines happens, so go back to sleep.
            if (checkpoint === undefined) {
                idle = true;
                continue;
            }

            this._currentCheckpoint = checkpoint;

            idle = false;

            console.log("EventIndex: crawling using checkpoint", checkpoint);

            // We have a checkpoint, let us fetch some messages, again, very
            // conservatively to not bother our homeserver too much.
            const eventMapper = client.getEventMapper();
            // TODO we need to ensure to use member lazy loading with this
            // request so we get the correct profiles.
            let res;

            try {
                res = await client._createMessagesRequest(
                    checkpoint.roomId, checkpoint.token, this._eventsPerCrawl,
                    checkpoint.direction);
            } catch (e) {
                console.log("EventIndex: Error crawling events:", e);
                this.crawlerCheckpoints.push(checkpoint);
                continue;
            }

            if (res.chunk.length === 0) {
                console.log("EventIndex: Done with the checkpoint", checkpoint);
                // We got to the start/end of our timeline, lets just
                // delete our checkpoint and go back to sleep.
                await indexManager.removeCrawlerCheckpoint(checkpoint);
                continue;
            }

            // Convert the plain JSON events into Matrix events so they get
            // decrypted if necessary.
            const matrixEvents = res.chunk.map(eventMapper);
            let stateEvents = [];
            if (res.state !== undefined) {
                stateEvents = res.state.map(eventMapper);
            }

            const profiles = {};

            stateEvents.forEach(ev => {
                if (ev.event.content &&
                    ev.event.content.membership === "join") {
                    profiles[ev.event.sender] = {
                        displayname: ev.event.content.displayname,
                        avatar_url: ev.event.content.avatar_url,
                    };
                }
            });

            const decryptionPromises = [];

            matrixEvents.forEach(ev => {
                if (ev.isBeingDecrypted() || ev.isDecryptionFailure()) {
                    // TODO the decryption promise is a private property, this
                    // should either be made public or we should convert the
                    // event that gets fired when decryption is done into a
                    // promise using the once event emitter method:
                    // https://nodejs.org/api/events.html#events_events_once_emitter_name
                    decryptionPromises.push(ev._decryptionPromise);
                }
            });

            // Let us wait for all the events to get decrypted.
            await Promise.all(decryptionPromises);

            // We filter out events for which decryption failed, are redacted
            // or aren't of a type that we know how to index.
            const isValidEvent = (value) => {
                return ([
                        "m.room.message",
                        "m.room.name",
                        "m.room.topic",
                    ].indexOf(value.getType()) >= 0
                        && !value.isRedacted() && !value.isDecryptionFailure()
                );
                // TODO do we need to check if the event has all the valid
                // attributes?
            };

            // TODO if there are no events at this point we're missing a lot
            // decryption keys, do we want to retry this checkpoint at a later
            // stage?
            const filteredEvents = matrixEvents.filter(isValidEvent);

            // Let us convert the events back into a format that EventIndex can
            // consume.
            const events = filteredEvents.map((ev) => {
                const jsonEvent = ev.toJSON();

                let e;
                if (ev.isEncrypted()) e = jsonEvent.decrypted;
                else e = jsonEvent;

                let profile = {};
                if (e.sender in profiles) profile = profiles[e.sender];
                const object = {
                    event: e,
                    profile: profile,
                };
                return object;
            });

            // Create a new checkpoint so we can continue crawling the room for
            // messages.
            const newCheckpoint = {
                roomId: checkpoint.roomId,
                token: res.end,
                fullCrawl: checkpoint.fullCrawl,
                direction: checkpoint.direction,
            };

            console.log(
                "EventIndex: Crawled room",
                client.getRoom(checkpoint.roomId).name,
                "and fetched", events.length, "events.",
            );

            try {
                const eventsAlreadyAdded = await indexManager.addHistoricEvents(
                    events, newCheckpoint, checkpoint);
                // If all events were already indexed we assume that we catched
                // up with our index and don't need to crawl the room further.
                // Let us delete the checkpoint in that case, otherwise push
                // the new checkpoint to be used by the crawler.
                if (eventsAlreadyAdded === true && newCheckpoint.fullCrawl !== true) {
                    console.log("EventIndex: Checkpoint had already all events",
                                "added, stopping the crawl", checkpoint);
                    await indexManager.removeCrawlerCheckpoint(newCheckpoint);
                } else {
                    this.crawlerCheckpoints.push(newCheckpoint);
                }
            } catch (e) {
                console.log("EventIndex: Error durring a crawl", e);
                // An error occurred, put the checkpoint back so we
                // can retry.
                this.crawlerCheckpoints.push(checkpoint);
            }
        }

        this._crawler = null;

        console.log("EventIndex: Stopping crawler function");
    }

    onTimelineReset = async (room, timelineSet, resetAllTimelines) => {
        if (room === null) return;

        const indexManager = PlatformPeg.get().getEventIndexingManager();
        if (!MatrixClientPeg.get().isRoomEncrypted(room.roomId)) return;

        const timeline = room.getLiveTimeline();
        const token = timeline.getPaginationToken("b");

        const backwardsCheckpoint = {
            roomId: room.roomId,
            token: token,
            fullCrawl: false,
            direction: "b",
        };

        console.log("EventIndex: Added checkpoint because of a limited timeline",
            backwardsCheckpoint);

        await indexManager.addCrawlerCheckpoint(backwardsCheckpoint);

        this.crawlerCheckpoints.push(backwardsCheckpoint);
    }

    startCrawler() {
        if (this._crawler !== null) return;
        this.crawlerFunc();
    }

    stopCrawler() {
        if (this._crawler === null) return;
        this._crawler.cancel();
    }

    async close() {
        const indexManager = PlatformPeg.get().getEventIndexingManager();
        this.removeListeners();
        this.stopCrawler();
        return indexManager.closeEventIndex();
    }

    async search(searchArgs) {
        const indexManager = PlatformPeg.get().getEventIndexingManager();
        return indexManager.searchEventIndex(searchArgs);
    }

    async indexSize() {
        const indexManager = PlatformPeg.get().getEventIndexingManager();
        return indexManager.indexSize();
    }

    currentlyCrawledRooms() {
        let crawlingRooms = new Set();
        let totalRooms = new Set();

        this.crawlerCheckpoints.forEach((checkpoint, index) => {
            crawlingRooms.add(checkpoint.roomId);
        });

        if (this._currentCheckpoint !== null) {
            crawlingRooms.add(this._currentCheckpoint.roomId);
        }

        const client = MatrixClientPeg.get();
        const rooms = client.getRooms();

        const isRoomEncrypted = (room) => {
            return client.isRoomEncrypted(room.roomId);
        };

        const encryptedRooms = rooms.filter(isRoomEncrypted);
        encryptedRooms.forEach((room, index) => {
            totalRooms.add(room.roomId);
        });

        return {crawlingRooms, totalRooms}
    }
}
<|endoftext|>


<commit_before>/*
Special thanks to https://violentatom.com/2015/07/08/node-js-chokidar-wait-for-file-copy-to-complete-before-modifying/ for the code to detect end of file transfer
* */
// Setup video source folder observer for notifications of new files
var chokidar = require('chokidar');
var logger = require('logger').createLogger(); // logs to STDOUT
var fs = require('fs');
var WAITSECONDS = 1;
var watcher = chokidar.watch(['/Users/ronniekinsley/Downloads/Test'], {
    persistent: true,
    followSymlinks: false,
    usePolling: true,
    depth: undefined,
    interval: 100,
    ignorePermissionErrors: false,
    ignoreInitial: true
});

watcher
    .on('ready', () => { logger.info('Initial scan complete. Ready for changes.'); })
    .on('unlink', (path) => { logger.info('File: ' + path + ', has been REMOVED'); })
    .on('error', (err) => {
        logger.error('Chokidar file watcher failed. ERR: ' + err.message);
    })
    .on('add', (path) => {
        logger.info('File', path, 'has been ADDED');

        fs.stat(path, function (err, stat) {

            if (err){
                logger.error('Error watching file for copy completion. ERR: ' + err.message);
                logger.error('Error file not processed. PATH: ' + path);
            } else {
                logger.info('File copy started...');
                setTimeout(checkFileCopyComplete, WAITSECONDS*1000, path, stat);
            }
        });
    });

// Makes sure that the file added to the directory, but may not have been completely copied yet by the
// Operating System, finishes being copied before it attempts to do anything with the file.
function checkFileCopyComplete(path, prev) {
    fs.stat(path, (err, stat) => {

        if (err) {
            throw err;
        }
        if (stat.mtime.getTime() === prev.mtime.getTime()) {
            logger.info('File copy complete => beginning processing');
            //-------------------------------------
            // CALL A FUNCTION TO PROCESS FILE HERE
            //-------------------------------------
            fs.copyFile('source.txt', 'destination.txt', (err) => {
                if (err) throw err;
            console.log('source.txt was copied to destination.txt');
        });
        }
        else {
            setTimeout(checkFileCopyComplete, WAITSECONDS*1000, path, stat);
        }
    });
}<commit_msg>some more ES6<commit_after>/*
Special thanks to https://violentatom.com/2015/07/08/node-js-chokidar-wait-for-file-copy-to-complete-before-modifying/ for the code to detect end of file transfer
* */
// Setup video source folder observer for notifications of new files
let chokidar = require('chokidar');
let logger = require('logger').createLogger(); // logs to STDOUT
let fs = require('fs');
let WAITSECONDS = 1;
let watcher = chokidar.watch(['/Users/ronniekinsley/Downloads/Test'], {
    persistent: true,
    followSymlinks: false,
    usePolling: true,
    depth: undefined,
    interval: 100,
    ignorePermissionErrors: false,
    ignoreInitial: true
});

watcher
    .on('ready', () => { logger.info('Initial scan complete. Ready for changes.'); })
    .on('unlink', (path) => { logger.info('File: ' + path + ', has been REMOVED'); })
    .on('error', (err) => {
        logger.error('Chokidar file watcher failed. ERR: ' + err.message);
    })
    .on('add', (path) => {
        logger.info('File', path, 'has been ADDED');

        fs.stat(path, function (err, stat) {

            if (err){
                logger.error('Error watching file for copy completion. ERR: ' + err.message);
                logger.error('Error file not processed. PATH: ' + path);
            } else {
                logger.info('File copy started...');
                setTimeout(checkFileCopyComplete, WAITSECONDS*1000, path, stat);
            }
        });
    });

// Makes sure that the file added to the directory, but may not have been completely copied yet by the
// Operating System, finishes being copied before it attempts to do anything with the file.
function checkFileCopyComplete(path, prev) {
    fs.stat(path, (err, stat) => {

        if (err) {
            throw err;
        }
        if (stat.mtime.getTime() === prev.mtime.getTime()) {
            logger.info('File copy complete => beginning processing');
            //-------------------------------------
            // CALL A FUNCTION TO PROCESS FILE HERE
            //-------------------------------------
            fs.copyFile('source.txt', 'destination.txt', (err) => {
                if (err) throw err;
            console.log('source.txt was copied to destination.txt');
        });
        }
        else {
            setTimeout(checkFileCopyComplete, WAITSECONDS*1000, path, stat);
        }
    });
}<|endoftext|>


<commit_before>import Obj from 'snew/obj/class';

export default Obj({
	//// ------------------------------------------------------------
	// Existing templates collection
	list: {},
	init() {
		tempartCompiler.trigger = this.triggerEvent.bind(this);
	},
	triggerEvent: function(componentId, action, parameter) {
		const controllers = snew.search(componentId);
		for( let i = 0; i < controllers.length; i++) {
			let controller = controllers[ i ];
			let found = false;
			if(controller[ action ]) {
				found = true;
				controller[ action ].apply( controller, parameter );
			}
			if( controller.view[ action ] ) {
				found = true;
				controller.view[action].apply( controller.view, parameter );
			}
			if(!found) {
				throw 'Could not matching event(' + action + ') to controller(' + controller._meta.path +')';
			}
		}
	},
	//// ------------------------------------------------------------
	// Layer for comunnicate with tempart
	compile(path, uid, content, currentValues, dirties, prefix) {
		if( this.list[ path ] ){
			return tempartCompiler.compile({
				blocks: this.list[ path ],
				content: content,
				currentValues: currentValues,
				dirties: dirties,
				path: path,
				prefix: prefix
			});
		} else {
			throw `Template ${path} does not exist`;
		}
	},
	//// ------------------------------------------------------------
	// Layer for comunnicate with tempart, to sync values
	syncModel(path, blockId, type, value, currentValues) {
		let blocks = this.list[ path ];
		const parts  = blockId.split( '-' );
		let blocksList = [];
		for( let i = 0; i < parts.length; i++ ){
			for( let blockIndex = 0; blockIndex < blocks.length; blockIndex++ ){
				const block      = blocks[ blockIndex ];
				const blockParts = parts[ i ].split( ':' );
				const blockId    = blockParts[ 0 ];

				if( block.id == blockId ){
					var currentValue  = null
					if( blockParts[ 1 ] ) {
						currentValue = currentValues[ blockId ];
					}
					blocksList.push({block: block, local: blockParts[ 1 ], id: blockParts[ 0 ], currentValues: currentValue}); // @TODO add currentValues
					if( i + 1 < parts.length) {
						blocks = block[ currentValues[ block.id ].type ];
					} else {
						if(block.type !== 'dom') throw 'Something went wrong here!';
						// @TODO add updating of currentValues in block.dom
						for( let orderIndex = 0; orderIndex < block.order.length; orderIndex++ ){
							if( block.order[ orderIndex ] == type ){
								return this.generateRealKey(block.contains[orderIndex].depending[0], blocksList);
							}
						}
					}
					if( blockParts[ 1 ] ){
						currentValues = currentValues[ blockId ].values[ blockParts[ 1 ]];
					}
					break;
				}
			}
		}
		console.error(' Couldnt update your value, seems like no one cares');
	},
	//// ------------------------------------------------------------
	// In template can happen renamings, eg in loops this has to be reversed
	generateRealKey(source, blocks) {
		var key = source.split('.');
		for(var i = 0; i < blocks.length; i++) {
			if(blocks[ i ].local) {
				var blockEntity = blocks[ i ];
				var pos = blockEntity.currentValues.order.indexOf(blocks[i].local);

				if( pos === -1 ) throw 'Generating arrayposition of hash did not work'
				if( blockEntity.block.depending[ blockEntity.block.depending.length - 1 ] === key[ 0 ] ){
					key.shift();
					key.unshift(pos);
					key.unshift(blockEntity.block.depending[ 0 ]);
				}
			}
		}
		return key;
	}
});<commit_msg>[tempart] moved tempart logic for modelsync, to tempart<commit_after>import Obj from 'snew/obj/class';

export default Obj({
	//// ------------------------------------------------------------
	// Existing templates collection
	list: {},
	init() {
		tempartCompiler.trigger = this.triggerEvent.bind(this);
	},
	triggerEvent: function(componentId, action, parameter) {
		const controllers = snew.search(componentId);
		for( let i = 0; i < controllers.length; i++) {
			let controller = controllers[ i ];
			let found = false;
			if(controller[ action ]) {
				found = true;
				controller[ action ].apply( controller, parameter );
			}
			if( controller.view[ action ] ) {
				found = true;
				controller.view[action].apply( controller.view, parameter );
			}
			if(!found) {
				throw 'Could not matching event(' + action + ') to controller(' + controller._meta.path +')';
			}
		}
	},
	//// ------------------------------------------------------------
	// Layer for comunnicate with tempart
	compile(path, uid, content, currentValues, dirties, prefix) {
		if( this.list[ path ] ){
			return tempartCompiler.compile({
				blocks: this.list[ path ],
				content: content,
				currentValues: currentValues,
				dirties: dirties,
				path: path,
				prefix: prefix
			});
		} else {
			throw `Template ${path} does not exist`;
		}
	},
	//// ------------------------------------------------------------
	// Layer for comunnicate with tempart, to sync values
	syncModel(path, blockId, type, value, currentValues) {
		if(!this.list[path]) throw 'No such template found'
		return tempartCompiler.syncModel(this.list[path], blockId, type, value, currentValues);
	}
});<|endoftext|>


<commit_before>/**
 * @class  	Sharer
 * @author  Flavio De Stefano <flavio.destefano@caffeinalab.com>
 */

/**
 * @property config
 */
exports.config = _.extend({
}, Alloy.CFG.T ? Alloy.CFG.T.sharer : {});

var Util = require('T/util');
var globalCallback = null; // Handle all callbacks

function onSocialComplete(e) {
	if (!_.isFunction(globalCallback)) return;

	e.type = 'complete';
	if (e.activityName) e.platform = e.activityName;
	globalCallback(e);
}

function onSocialCancel(e) {
	if (!_.isFunction(globalCallback)) return;

	e.type = 'cancelled';
	if (e.activityName) e.platform = e.activityName;
	globalCallback(e);
}

function parseArgs(args) {
	if (!_.isObject(args)) return {};
	args = _.clone(args);

	if (args.image != null) {
		if (_.isObject(args.image)) {
			if (args.image.resolve) {
				args.imageBlob = args.image;
				args.image = args.imageBlob.resolve();
			} else if (args.image.nativePath) {
				args.imageBlob = args.image;
				args.image = args.imageBlob.nativePath;
			} else {
				delete args.image;
			}
		} else if (_.isString(args.image) && args.image.indexOf('://') !== -1) {
			args.imageUrl = args.image;
		}
	}

	if (args.link != null) {
		args.url = args.link;
		delete args.link;
	}

	if (args.removeIcons === 'ALL') args.removeIcons = 'print,sms,copy,contact,camera,readinglist';

	if (args.text != null && args.url != null) {
		args.fullText = args.text + ' (' + args.url + ')';
	} else if (args.text != null) {
		args.fullText = args.text;
	} else if (args.url != null) {
		args.fullText = args.url;
	}

	if (_.isFunction(args.callback)) {
		globalCallback = args.callback;
		delete args.callback;
	}

	return args;
}

/**
 * @method facebook
 * Share on Facebook
 * @param {Object} args
 */
exports.facebook = function(args) {
	args = parseArgs(args);
	require('T/ga').social('facebook', 'share', args.url);

	// Native iOS dialog
	if (OS_IOS && (dkNappSocial != null && dkNappSocial.isFacebookSupported()) &&
		false === /https?\:\/\/(www\.)?facebook\.com/.test(args.url) // BUG: iOS share dialog doesn't share Facebook links
	) {
		dkNappSocial.facebook({
			text: args.text,
			image: args.image,
			url: args.url
		});
		return true;
	}

	// SDK
	if (FB != null && FB.canPresentShareDialog === true) {
		FB.presentShareDialog({
			url: args.url,
			title: args.title,
			description: args.description
		});
		return true;
	}

	// Android Native intent
	if (OS_ANDROID) {
		try {
			var intent = Ti.Android.createIntent({
				action: Ti.Android.ACTION_SEND,
				packageName: 'com.facebook.katana',
				type: 'text/plain'
			});
			intent.putExtra(Ti.Android.EXTRA_TEXT, args.url);
			Ti.Android.currentActivity.startActivity(intent);

			return true;
		} catch (err) {}
	}

	// SDK Web
	if (FB != null) {
		FB.presentWebShareDialog({
			url: args.url,
			title: args.title,
			description: args.description
		});
		return;
	}

	// Fallback
	Ti.Platform.openURL('http://www.facebook.com/dialog/share' + Util.buildQuery({
		app_id: Ti.App.Properties.getString('ti.facebook.appid'),
		display: 'touch',
		redirect_uri: args.redirect_uri || Ti.App.url,
		href: args.url
	}));
};

/**
 * @method twitter
 * Share on Twitter
 * @param {Object} args
 */
exports.twitter = function(args) {
	args = parseArgs(args);
	require('T/ga').social('twitter', 'share', args.url);

	// iOS Tweetbot App
	if (OS_IOS) {
		var tweetbotNativeURL = 'tweetbot:///post' + Util.buildQuery({
			text: (args.tweetText || args.text) + (args.url ? ' (' + args.url + ')' : '')
		});
		if (Ti.Platform.canOpenURL(tweetbotNativeURL)) {
			Ti.Platform.openURL(tweetbotNativeURL);
			return true;
		}
	}

	// iOS Twitter App
	if (OS_IOS) {
		var twitterNativeURL = 'twitter://post' + Util.buildQuery({
			message: (args.tweetText || args.text) + (args.url ? ' (' + args.url + ')' : '')
		});
		if (Ti.Platform.canOpenURL(twitterNativeURL)) {
			Ti.Platform.openURL(twitterNativeURL);
			return true;
		}
	}

	// Native iOS Dialog
	if (OS_IOS && (dkNappSocial != null && dkNappSocial.isTwitterSupported())) {
		dkNappSocial.twitter({
			text: (args.tweetText || args.text),
			image: args.image,
			url: args.url
		});
		return true;
	}

	// Fallback
	Ti.Platform.openURL('http://www.twitter.com/intent/tweet' + Util.buildQuery({
		 text: (args.tweetText || args.text),
		 url: args.url
	}));
};

/**
 * @method email
 * Share via Mail
 * @param {Object} args
 */
exports.email = exports.mail = function(args) {
	args = parseArgs(args);
	require('T/ga').social('email', 'sent', args.url);

	var $dialog = Ti.UI.createEmailDialog({
		subject: args.subject || args.title,
		messageBody: args.fullText,
		toRecipients: args.mailTo || []
	});

	if (args.imageBlob != null) {
		$dialog.addAttachment(args.imageBlob);
	}

	$dialog.addEventListener('complete', function(e) {
		if (e.result !== this.CANCELLED) {
			onSocialComplete({
				success: (e.result === this.SENT),
				platform: 'email'
			});
		} else {
			onSocialCancel({
				success: false,
				platform: 'email'
			});
		}
	});

	$dialog.open();
};

/**
 * @method googleplus
 * Share on Google Plus
 * @param {Object} args
 */
exports.googleplus = function(args) {
	args = parseArgs(args);
	require('T/ga').social('googleplus', 'share', args.url);

	Ti.Platform.openURL('https://plus.google.com/share' + Util.buildQuery({
		url: args.url
	}));
};

/**
 * @method whatsapp
 * Share via Whatsapp
 * @param {Object} args
 */
exports.whatsapp = function(args) {
	args = parseArgs(args);
	require('T/ga').social('whatsapp', 'share', args.url);

	// Native protocol binding
	if (OS_IOS) {
		Util.openURL('whatsapp://send?text=' + args.fullText, function() {
			require('T/dialog').confirmYes(
				L('app_not_installed', 'App not installed'),
				String.format(L('app_install_question', 'You must install %s to proceed.'), 'Whatsapp'),
			function() {
				Util.openInStore('310633997');
			}, L('install_app', 'Install app'));
		});

		return true;
	}

	// Android Intent using package
	if (OS_ANDROID) {
		try {
			var intent = Ti.Android.createIntent({
				action: Ti.Android.ACTION_SEND,
				type: 'text/plain',
				packageName: 'com.whatsapp'
			});
			intent.putExtra(Ti.Android.EXTRA_TEXT, args.fullText);
			Ti.Android.currentActivity.startActivity(intent, L('share', 'Share'));

			return true;
		} catch (err) {
			require('T/dialog').confirmYes(L('app_not_installed', 'App not installed'), String.format(L('app_install_question', 'Do you want to install %s?'), 'Whatsapp'), function() {
				Util.openInStore('com.whatsapp');
			}, L('install_app', 'Install app'));
		}
	}
};

/**
 * @method message
 * Share via Messages
 * @param {Object} args
 */
exports.message = exports.sms = function(args) {
	args = parseArgs(args);
	require('T/ga').social('message', 'sent', args.url);

	// iOS Native modal
	if (OS_IOS && benCodingSMS !== null) {
		var recipients = null;
		if (args.recipients != null) recipients = _.isArray(args.recipients) ? args.recipients : [ args.recipients ];

		var $dialog = benCodingSMS.createSMSDialog({
			toRecipients: recipients,
			messageBody: args.fullText
		});

		$dialog.addEventListener('completed', function(){
			onSocialComplete({
				success: true,
				platform: 'messages'
			});
		});

		$dialog.addEventListener('cancelled', function(){
			onSocialCancel({
				success: false,
				platform: 'messages'
			});
		});

		$dialog.addEventListener('error', function(){
			onSocialComplete({
				success: false,
				platform: 'messages'
			});
		});

		$dialog.open({
			animated: true
		});

		return true;
	}

	// Android Native
	if (OS_ANDROID) {
		var url = 'sms:';
		if (args.recipients != null) url += _.isArray(args.recipients) ? args.recipients[0] : args.recipients;

		url += Util.buildQuery({
			body: args.fullText
		});

		return Ti.Platform.openURL(url);
	}
};

/**
 * @method activity
 * Share using iOS ActivityPopover or Android Intents
 * @param {Object} args
 */
exports.activity = function(args) {
	args = parseArgs(args);

	// iOS Activity native
	if (OS_IOS && dkNappSocial != null) {
		dkNappSocial[ Util.isIPad() ? 'activityPopover' : 'activityView' ]({
			text: args.text,
			title: args.title,
			image: args.image,
			removeIcons: args.removeIcons,
			view: args.view,
			url: args.url
		}, args.customIcons || []);

		return true;
	}

	// Android intents
	if (OS_ANDROID) {
		var intent = Ti.Android.createIntent({ action: Ti.Android.ACTION_SEND });
		if (args.fullText) intent.putExtra(Ti.Android.EXTRA_TEXT, args.fullText);
		if (args.title) intent.putExtra(Ti.Android.EXTRA_TITLE, args.title);
		if (args.image) intent.putExtraUri(Ti.Android.EXTRA_STREAM, args.image);

		Ti.Android.currentActivity.startActivity(Ti.Android.createIntentChooser(intent, L('share', 'Share')));

		return true;
	}
};

/*
Init
*/

var FB = Util.requireOrNull('T/fb');
if (FB != null) {
	FB.addEventListener('shareCompleted', onSocialComplete);
}

if (OS_IOS) {

	var benCodingSMS = Util.requireOrNull('bencoding.sms');

	var dkNappSocial = Util.requireOrNull('dk.napp.social');
	if (dkNappSocial != null) {
		dkNappSocial.addEventListener('complete', onSocialComplete);
		dkNappSocial.addEventListener('cancelled', onSocialCancel);
	}

}
<commit_msg>Sharer Twitter fix<commit_after>/**
 * @class  	Sharer
 * @author  Flavio De Stefano <flavio.destefano@caffeinalab.com>
 */

/**
 * @property config
 */
exports.config = _.extend({
}, Alloy.CFG.T ? Alloy.CFG.T.sharer : {});

var Util = require('T/util');
var globalCallback = null; // Handle all callbacks

function onSocialComplete(e) {
	if (!_.isFunction(globalCallback)) return;

	e.type = 'complete';
	if (e.activityName) e.platform = e.activityName;
	globalCallback(e);
}

function onSocialCancel(e) {
	if (!_.isFunction(globalCallback)) return;

	e.type = 'cancelled';
	if (e.activityName) e.platform = e.activityName;
	globalCallback(e);
}

function parseArgs(args) {
	if (!_.isObject(args)) return {};
	args = _.clone(args);

	if (args.image != null) {
		if (_.isObject(args.image)) {
			if (args.image.resolve) {
				args.imageBlob = args.image;
				args.image = args.imageBlob.resolve();
			} else if (args.image.nativePath) {
				args.imageBlob = args.image;
				args.image = args.imageBlob.nativePath;
			} else {
				delete args.image;
			}
		} else if (_.isString(args.image) && args.image.indexOf('://') !== -1) {
			args.imageUrl = args.image;
		}
	}

	if (args.link != null) {
		args.url = args.link;
		delete args.link;
	}

	if (args.removeIcons === 'ALL') args.removeIcons = 'print,sms,copy,contact,camera,readinglist';

	if (args.text != null && args.url != null) {
		args.fullText = args.text + ' (' + args.url + ')';
	} else if (args.text != null) {
		args.fullText = args.text;
	} else if (args.url != null) {
		args.fullText = args.url;
	}

	if (_.isFunction(args.callback)) {
		globalCallback = args.callback;
		delete args.callback;
	}

	return args;
}

/**
 * @method facebook
 * Share on Facebook
 * @param {Object} args
 */
exports.facebook = function(args) {
	args = parseArgs(args);
	require('T/ga').social('facebook', 'share', args.url);

	// Native iOS dialog
	if (OS_IOS && (dkNappSocial != null && dkNappSocial.isFacebookSupported()) &&
		false === /https?\:\/\/(www\.)?facebook\.com/.test(args.url) // BUG: iOS share dialog doesn't share Facebook links
	) {
		dkNappSocial.facebook({
			text: args.text,
			image: args.image,
			url: args.url
		});
		return true;
	}

	// SDK
	if (FB != null && FB.canPresentShareDialog === true) {
		FB.presentShareDialog({
			url: args.url,
			title: args.title,
			description: args.description
		});
		return true;
	}

	// Android Native intent
	if (OS_ANDROID) {
		try {
			var intent = Ti.Android.createIntent({
				action: Ti.Android.ACTION_SEND,
				packageName: 'com.facebook.katana',
				type: 'text/plain'
			});
			intent.putExtra(Ti.Android.EXTRA_TEXT, args.url);
			Ti.Android.currentActivity.startActivity(intent);

			return true;
		} catch (err) {}
	}

	// SDK Web
	if (FB != null) {
		FB.presentWebShareDialog({
			url: args.url,
			title: args.title,
			description: args.description
		});
		return;
	}

	// Fallback
	Ti.Platform.openURL('http://www.facebook.com/dialog/share' + Util.buildQuery({
		app_id: Ti.App.Properties.getString('ti.facebook.appid'),
		display: 'touch',
		redirect_uri: args.redirect_uri || Ti.App.url,
		href: args.url
	}));
};

/**
 * @method twitter
 * Share on Twitter
 * @param {Object} args
 */
exports.twitter = function(args) {
	args = parseArgs(args);
	require('T/ga').social('twitter', 'share', args.url);

	var text = (args.tweetText || args.text);
	if (args.url != null) {
		if (text == null) {
			text = args.url;
		} else {
			text = text + ' (' + args.url + ')';
		}
	}

	// iOS Tweetbot App
	if (OS_IOS) {
		var tweetbotNativeURL = 'tweetbot:///post' + Util.buildQuery({
			text: text
		});
		if (Ti.Platform.canOpenURL(tweetbotNativeURL)) {
			Ti.Platform.openURL(tweetbotNativeURL);
			return true;
		}
	}

	// iOS Twitter App
	if (OS_IOS) {
		var twitterNativeURL = 'twitter://post' + Util.buildQuery({
			message: text
		});
		if (Ti.Platform.canOpenURL(twitterNativeURL)) {
			Ti.Platform.openURL(twitterNativeURL);
			return true;
		}
	}

	// Native iOS Dialog
	if (OS_IOS && (dkNappSocial != null && dkNappSocial.isTwitterSupported())) {
		dkNappSocial.twitter({
			text: text,
			image: args.image,
			url: args.url
		});
		return true;
	}

	// Fallback
	Ti.Platform.openURL('http://www.twitter.com/intent/tweet' + Util.buildQuery({
		 text: text,
		 url: args.url
	}));
};

/**
 * @method email
 * Share via Mail
 * @param {Object} args
 */
exports.email = exports.mail = function(args) {
	args = parseArgs(args);
	require('T/ga').social('email', 'sent', args.url);

	var $dialog = Ti.UI.createEmailDialog({
		subject: args.subject || args.title,
		messageBody: args.fullText,
		toRecipients: args.mailTo || []
	});

	if (args.imageBlob != null) {
		$dialog.addAttachment(args.imageBlob);
	}

	$dialog.addEventListener('complete', function(e) {
		if (e.result !== this.CANCELLED) {
			onSocialComplete({
				success: (e.result === this.SENT),
				platform: 'email'
			});
		} else {
			onSocialCancel({
				success: false,
				platform: 'email'
			});
		}
	});

	$dialog.open();
};

/**
 * @method googleplus
 * Share on Google Plus
 * @param {Object} args
 */
exports.googleplus = function(args) {
	args = parseArgs(args);
	require('T/ga').social('googleplus', 'share', args.url);

	Ti.Platform.openURL('https://plus.google.com/share' + Util.buildQuery({
		url: args.url
	}));
};

/**
 * @method whatsapp
 * Share via Whatsapp
 * @param {Object} args
 */
exports.whatsapp = function(args) {
	args = parseArgs(args);
	require('T/ga').social('whatsapp', 'share', args.url);

	// Native protocol binding
	if (OS_IOS) {
		Util.openURL('whatsapp://send?text=' + args.fullText, function() {
			require('T/dialog').confirmYes(
				L('app_not_installed', 'App not installed'),
				String.format(L('app_install_question', 'You must install %s to proceed.'), 'Whatsapp'),
			function() {
				Util.openInStore('310633997');
			}, L('install_app', 'Install app'));
		});

		return true;
	}

	// Android Intent using package
	if (OS_ANDROID) {
		try {
			var intent = Ti.Android.createIntent({
				action: Ti.Android.ACTION_SEND,
				type: 'text/plain',
				packageName: 'com.whatsapp'
			});
			intent.putExtra(Ti.Android.EXTRA_TEXT, args.fullText);
			Ti.Android.currentActivity.startActivity(intent, L('share', 'Share'));

			return true;
		} catch (err) {
			require('T/dialog').confirmYes(L('app_not_installed', 'App not installed'), String.format(L('app_install_question', 'Do you want to install %s?'), 'Whatsapp'), function() {
				Util.openInStore('com.whatsapp');
			}, L('install_app', 'Install app'));
		}
	}
};

/**
 * @method message
 * Share via Messages
 * @param {Object} args
 */
exports.message = exports.sms = function(args) {
	args = parseArgs(args);
	require('T/ga').social('message', 'sent', args.url);

	// iOS Native modal
	if (OS_IOS && benCodingSMS !== null) {
		var recipients = null;
		if (args.recipients != null) recipients = _.isArray(args.recipients) ? args.recipients : [ args.recipients ];

		var $dialog = benCodingSMS.createSMSDialog({
			toRecipients: recipients,
			messageBody: args.fullText
		});

		$dialog.addEventListener('completed', function(){
			onSocialComplete({
				success: true,
				platform: 'messages'
			});
		});

		$dialog.addEventListener('cancelled', function(){
			onSocialCancel({
				success: false,
				platform: 'messages'
			});
		});

		$dialog.addEventListener('error', function(){
			onSocialComplete({
				success: false,
				platform: 'messages'
			});
		});

		$dialog.open({
			animated: true
		});

		return true;
	}

	// Android Native
	if (OS_ANDROID) {
		var url = 'sms:';
		if (args.recipients != null) url += _.isArray(args.recipients) ? args.recipients[0] : args.recipients;

		url += Util.buildQuery({
			body: args.fullText
		});

		return Ti.Platform.openURL(url);
	}
};

/**
 * @method activity
 * Share using iOS ActivityPopover or Android Intents
 * @param {Object} args
 */
exports.activity = function(args) {
	args = parseArgs(args);

	// iOS Activity native
	if (OS_IOS && dkNappSocial != null) {
		dkNappSocial[ Util.isIPad() ? 'activityPopover' : 'activityView' ]({
			text: args.text,
			title: args.title,
			image: args.image,
			removeIcons: args.removeIcons,
			view: args.view,
			url: args.url
		}, args.customIcons || []);

		return true;
	}

	// Android intents
	if (OS_ANDROID) {
		var intent = Ti.Android.createIntent({ action: Ti.Android.ACTION_SEND });
		if (args.fullText) intent.putExtra(Ti.Android.EXTRA_TEXT, args.fullText);
		if (args.title) intent.putExtra(Ti.Android.EXTRA_TITLE, args.title);
		if (args.image) intent.putExtraUri(Ti.Android.EXTRA_STREAM, args.image);

		Ti.Android.currentActivity.startActivity(Ti.Android.createIntentChooser(intent, L('share', 'Share')));

		return true;
	}
};

/*
Init
*/

var FB = Util.requireOrNull('T/fb');
if (FB != null) {
	FB.addEventListener('shareCompleted', onSocialComplete);
}

if (OS_IOS) {

	var benCodingSMS = Util.requireOrNull('bencoding.sms');

	var dkNappSocial = Util.requireOrNull('dk.napp.social');
	if (dkNappSocial != null) {
		dkNappSocial.addEventListener('complete', onSocialComplete);
		dkNappSocial.addEventListener('cancelled', onSocialCancel);
	}

}
<|endoftext|>