<commit_before>var socketIO = require('socket.io'),
    config = require('../config.js'),
    playlistManager = require('./playlistManager.js'),
    mopidyCom = require('./mopidyCom.js');

var io;

function sendNextTracks (socket) {
  playlistManager.getNextTracks(20)
    .then(function (nextTracks) {
      socket.emit('nextTracks', nextTracks);
    })
    .done();
}

function sendTrack (isCurrent, track) {
  var broadcastEvent = (isCurrent ? 'currentTrack' : 'nextTrack');
  io.sockets.emit(broadcastEvent, track);
}

function init(listener) {
  io = socketIO(listener);

  io.on('connection', function (socket) {
      mopidyCom.getCurrentTrack()
        .then(function (track) {
          socket.emit('currentTrack', track);
        })
        .done();

      sendNextTracks(socket);

      setInterval(function () {
        sendNextTracks(socket);
      }, 1 * 1000);

      var votedRecently = false;

      function voteTimeout () {
        votedRecently = true;
        setTimeout(function () {
          votedRecently = false;
        }, config.user.voteDelay);
      }

      socket.on('addTrack', function (track) {
        if (!votedRecently) {
          playlistManager.addTrack(track);
          voteTimeout();
        };
      });

      socket.on('voteUp', function (trackUri) {
        if (!votedRecently) {
          playlistManager.voteUp(trackUri);
          voteTimeout();
        };
      });

  });

};

exports.init = init;
exports.io = io;
exports.sendTrack = sendTrack;
<commit_msg>only send tracklist when something changed<commit_after>var socketIO = require('socket.io'),
    config = require('../config.js'),
    playlistManager = require('./playlistManager.js'),
    mopidyCom = require('./mopidyCom.js');

var io;

/**
 * Send current queue to a specific socket
 * @param  {Socket} socket Socket which will receive the queue
 */
function sendNextTracks (socket) {
  playlistManager.getNextTracks(20)
    .then(function (nextTracks) {
      socket.emit('nextTracks', nextTracks);
    })
    .done();
}

/**
 * Send current queue to all sockets
 */
function broadcastNextTracks () {
  setTimeout(function () {
    playlistManager.getNextTracks(20)
      .then(function (nextTracks) {
        io.sockets.emit('nextTracks', nextTracks);
      })
      .done();
  }, 200);
}

/**
 * Send new next/current track to all sockets
 * @param  {Boolean} isCurrent Whether track is the current track or not
 * @param  {Track}  track     The track to send
 */
function sendTrack (isCurrent, track) {
  var broadcastEvent = (isCurrent ? 'currentTrack' : 'nextTrack');
  io.sockets.emit(broadcastEvent, track);
}

function init(listener) {
  io = socketIO(listener);

  io.on('connection', function (socket) {
      /*
       * Send current status
       */
      // Current track
      mopidyCom.getCurrentTrack()
        .then(function (track) {
          socket.emit('currentTrack', track);
        })
        .done();

      // Current queue
      sendNextTracks(socket);

      /*
       * Setup voting
       */
      var votedRecently = false;

      /**
       * Set votedRecently true and reset to false after timeout
       */
      function voteTimeout () {
        votedRecently = true;
        setTimeout(function () {
          votedRecently = false;
        }, /*config.user.voteDelay*/ 1);
      }

      // New track was received
      socket.on('addTrack', function (track) {
        if (!votedRecently) {
          playlistManager.addTrack(track);
          voteTimeout();
          broadcastNextTracks();
        };
      });

      // Vote for track was received
      socket.on('voteUp', function (trackUri) {
        if (!votedRecently) {
          playlistManager.voteUp(trackUri);
          voteTimeout();
          broadcastNextTracks();
        };
      });

  });

};

exports.init = init;
exports.io = io;
exports.sendTrack = sendTrack;
<|endoftext|>


<commit_before>/**
 *  \details © 2011  Open Ximdex Evolution SL [http://www.ximdex.org]
 *
 *  Ximdex a Semantic Content Management System (CMS)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  See the Affero GNU General Public License for more details.
 *  You should have received a copy of the Affero GNU General Public License
 *  version 3 along with Ximdex (see LICENSE file).
 *
 *  If not, visit http://gnu.org/licenses/agpl-3.0.html.
 *
 *  @author Ximdex DevTeam <dev@ximdex.com>
 *  @version $Revision$
 */

(function(X) {

	var B = X.browser;

	X.ActionTypes = {
		create: [	'addfoldernode',
					'addsectionnode',
					'createlink',
					'newemptynode',
					'copy',
					'createrole',
					'createuser',
					'createxmlcontainer'
				],
		remove: ['deletenode','movenode'],
		reload: ['addximlet', 'linkreport', 'managefolders']
	};

	var idCount = 0;

	B.ActionView = Object.xo_create(B.AbstractView, {

		url: null,
		action: null,
		nodes: null,
		forms: null,
		history: null,

		_init: function(options) {
			B.ActionView._construct(this, options);

			this.id = 'browser-action-view-' + (++idCount);
			this.className = 'browser-action-view';
			this.content = $('

')
				.addClass('browser-action-view-content')
				.attr({id: this.id});
			this.forms = {};
			this.history = [];

			this.openAction(options);
		},

		openAction: function(options) {

			this.url = options.url || null;
			this.action = options.action || null;
			this.nodes = options.nodes || [];
			this.nodes = (!Object.isArray(this.nodes) ? [this.nodes] : this.nodes);
			this.label = options.label;

			if (this.url === null && this.action !== null) {
				this._createUrl();
			}

			this.history.push(this.url);

			this.content.load(this.url, function(data, textStatus, xhr) {
				this.processAction();
			}.bind(this));
		},

		actionDoneCallback: function(result, form) {
    		$form = $(form);
    		var nodeId = result.nodeID || result.parentID || result.idNode;
    		//Refresh node
    		if (nodeId) $(this.container).trigger('nodemodified', nodeId);

	        //Messaging
	        //TODO: Create a messaging service/widget
	        var $message = $('

');
    		var submitError = false;
    		$.each(result.messages, function(key, message){
    			if (message.type === 2) {
    				$message.html($message.html()+'
'+message.message+'
');
    			} else if (message.type === 0) {
    				$message.html($message.html()+'
'+message.message+'
');
    			}
    		});
    		if (!submitError && X.ActionTypes.create.indexOf(this.action.command) != -1 ) form.reset();

    		if (X.ActionTypes.remove.indexOf(this.action.command) != -1) {
				$form.after($message);
    			$message.show();
    			$form.hide();
    			setTimeout(function(){this.close();}.bind(this), 4000);

			} else {
				$form.find('.action_header').after($message);
    			$message.slideDown();
    			setTimeout(function(){$message.slideUp().remove;}, 4000);
			}
		},

		getForm: function(name) {
			name = name || null;
			return name === null ? this.forms : (this.forms[name] || null);
		},

		_createUrl: function() {

			var action = '';
			if (this.action['actionid']) {

				action = 'actionid=' + this.action.actionid;
			} else if (this.action['command']) {

				action = 'action=' + this.action.command;
			} else {

				// No defined action
				action = 'action=_NONE_';
			}

			var nodes = [];
			for (var i=0,l=this.nodes.length; i<l; i++) {
				nodes.push('nodes[]=' + this.nodes[i]);
			};

			var actionParams = '';
			if (this.action.params && this.action.params.length > 0) {
				actionParams = this.action.params;
			}

			var qs = [
				nodes.join('&'),
				action,
				'noCacheVar='+(new Date().getTime())
			];

			if (!Object.isEmpty(this.action.method)) qs.push('method='+this.action.method);
			if (!Object.isEmpty(this.action.module)) qs.push('mod='+this.action.module);
			if (!Object.isEmpty(actionParams)) qs.push(actionParams);

			qs = qs.join('&');
			this.url = '%s?%s'.printf(X.restUrl, qs);
		},

		close: function() {
			$(this.container).tabs('remove', this.tabId());
		},

		//onLink: function() {
		//},

		redirect: function(url, params) {
			$('form', this.content).attr('action', url);
		},

		setActionContent: function(content) {
			$(this.content).unbind().empty().append(content);
			this.forms = {};
			this.processAction();
		},

		processAction: function() {

			// jQuery plugin in extensions folder
			$(this.content).labelWidth();

			this._loadAssets();
			this._bindFormEvents();
		},

		_bindFormEvents: function() {

			// For each form create a FormsManager instance and register events for assets management
			$('form', this.content).each(function(index, form) {

				var name = !Object.isEmpty($(form).attr('id'))
					? $(form).attr('id')
					: (!Object.isEmpty($(form).attr('name'))
						? $(form).attr('name')
						: 'form_' + X.getUID()
						);

				this.forms[name] = form;

				var fm = new X.FormsManager({
					actionView: this,
					actionContainer: this.content,
					form: form
				});

				$(fm).bind('form-loaded', function(event) {
					this.processAction();
				}.bind(this));

			}.bind(this));

			// Close the action tab when the 'Close' button is clicked
			$('fieldset.buttons-form .close-button', this.content).click(function(e) {
				this.close();
				return false;
			}.bind(this));

			// Controls the logic when the 'Go back' button is clicked
			$('fieldset.buttons-form .goback-button', this.content).click(function(e) {

				var history = $('fieldset.buttons-form .history-value', this.content);
				history = history.html() || 1;

				while (this.history.length > 0 && history > 0) {
					this.history.pop();
					history--;
				}

				var url = this.history[this.history.length-1];
				this.content.load(url, function(data, textStatus, xhr) {
					this.processAction();
				}.bind(this));
				return false;
			}.bind(this));
		},

		_loadAssets: function() {

			var css = $('ul.css_to_include li', this.content).map(function(index, item) {
				return $(item).html();
			});
			css = $.makeArray(css);

			var js = $('ul.js_to_include li', this.content).map(function(index, item) {
				var url = $(item).html();
				url = Object.urldecode(url.replace(/&/g, '&', url));
				return url;
			}.bind(this));

			js = {
				onComplete: this._onAssetsCompleted.bind(this),
				//onLoad: this._onScriptLoaded.bind(this),
				js: js
			};

			Object.loadCss(css);

			if (js.js.length > 0) {
				Object.loadScript(js);
			} else {
				this._onAssetsCompleted();
			}
		},

		_onAssetsCompleted: function() {
			X.triggerActionLoaded({
				actionView: this,
				browser: this.browser,
				context: this.content,
				url: this.url,
				action: this.action,
				nodes: this.nodes,
				tabId: this.tabId()
			});
		}
	});

})(com.ximdex);
<commit_msg>Adding actions updating the delete actions<commit_after>/**
 *  \details © 2011  Open Ximdex Evolution SL [http://www.ximdex.org]
 *
 *  Ximdex a Semantic Content Management System (CMS)
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU Affero General Public License as published
 *  by the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Affero General Public License for more details.
 *
 *  See the Affero GNU General Public License for more details.
 *  You should have received a copy of the Affero GNU General Public License
 *  version 3 along with Ximdex (see LICENSE file).
 *
 *  If not, visit http://gnu.org/licenses/agpl-3.0.html.
 *
 *  @author Ximdex DevTeam <dev@ximdex.com>
 *  @version $Revision$
 */

(function(X) {

	var B = X.browser;

	X.ActionTypes = {
		create: [	'addfoldernode',
					'addsectionnode',
					'createlink',
					'newemptynode',
					'copy',
					'createrole',
					'createuser',
					'createxmlcontainer'
				],
		remove: ['deletenode','movenode'],
		reload: ['addximlet', 'linkreport', 'workflow_forward']
	};

	var idCount = 0;

	B.ActionView = Object.xo_create(B.AbstractView, {

		url: null,
		action: null,
		nodes: null,
		forms: null,
		history: null,

		_init: function(options) {
			B.ActionView._construct(this, options);

			this.id = 'browser-action-view-' + (++idCount);
			this.className = 'browser-action-view';
			this.content = $('

')
				.addClass('browser-action-view-content')
				.attr({id: this.id});
			this.forms = {};
			this.history = [];

			this.openAction(options);
		},

		openAction: function(options) {

			this.url = options.url || null;
			this.action = options.action || null;
			this.nodes = options.nodes || [];
			this.nodes = (!Object.isArray(this.nodes) ? [this.nodes] : this.nodes);
			this.label = options.label;

			if (this.url === null && this.action !== null) {
				this._createUrl();
			}

			this.history.push(this.url);

			this.content.load(this.url, function(data, textStatus, xhr) {
				this.processAction();
			}.bind(this));
		},

		actionDoneCallback: function(result, form) {
    		$form = $(form);
    		var nodeId = result.nodeID || result.parentID || result.idNode;
    		//Refresh node
    		if (nodeId) $(this.container).trigger('nodemodified', nodeId);

	        //Messaging
	        //TODO: Create a messaging service/widget
	        var $message = $('

');
    		var submitError = false;
    		$.each(result.messages, function(key, message){
    			if (message.type === 2) {
    				$message.html($message.html()+'
'+message.message+'
');
    			} else if (message.type === 0) {
    				$message.html($message.html()+'
'+message.message+'
');
    			}
    		});
    		if (!submitError && X.ActionTypes.create.indexOf(this.action.command) != -1 ) form.reset();

    		if (X.ActionTypes.remove.indexOf(this.action.command) != -1) {
				$form.after($message);
    			$message.show();
    			$form.hide();
    			setTimeout(function(){this.close();}.bind(this), 4000);

			} else {
				$form.find('.action_header').after($message);
    			$message.slideDown();
    			setTimeout(function(){$message.slideUp().remove;}, 4000);
			}
		},

		getForm: function(name) {
			name = name || null;
			return name === null ? this.forms : (this.forms[name] || null);
		},

		_createUrl: function() {

			var action = '';
			if (this.action['actionid']) {

				action = 'actionid=' + this.action.actionid;
			} else if (this.action['command']) {

				action = 'action=' + this.action.command;
			} else {

				// No defined action
				action = 'action=_NONE_';
			}

			var nodes = [];
			for (var i=0,l=this.nodes.length; i<l; i++) {
				nodes.push('nodes[]=' + this.nodes[i]);
			};

			var actionParams = '';
			if (this.action.params && this.action.params.length > 0) {
				actionParams = this.action.params;
			}

			var qs = [
				nodes.join('&'),
				action,
				'noCacheVar='+(new Date().getTime())
			];

			if (!Object.isEmpty(this.action.method)) qs.push('method='+this.action.method);
			if (!Object.isEmpty(this.action.module)) qs.push('mod='+this.action.module);
			if (!Object.isEmpty(actionParams)) qs.push(actionParams);

			qs = qs.join('&');
			this.url = '%s?%s'.printf(X.restUrl, qs);
		},

		close: function() {
			$(this.container).tabs('remove', this.tabId());
		},

		//onLink: function() {
		//},

		redirect: function(url, params) {
			$('form', this.content).attr('action', url);
		},

		setActionContent: function(content) {
			$(this.content).unbind().empty().append(content);
			this.forms = {};
			this.processAction();
		},

		processAction: function() {

			// jQuery plugin in extensions folder
			$(this.content).labelWidth();

			this._loadAssets();
			this._bindFormEvents();
		},

		_bindFormEvents: function() {

			// For each form create a FormsManager instance and register events for assets management
			$('form', this.content).each(function(index, form) {

				var name = !Object.isEmpty($(form).attr('id'))
					? $(form).attr('id')
					: (!Object.isEmpty($(form).attr('name'))
						? $(form).attr('name')
						: 'form_' + X.getUID()
						);

				this.forms[name] = form;

				var fm = new X.FormsManager({
					actionView: this,
					actionContainer: this.content,
					form: form
				});

				$(fm).bind('form-loaded', function(event) {
					this.processAction();
				}.bind(this));

			}.bind(this));

			// Close the action tab when the 'Close' button is clicked
			$('fieldset.buttons-form .close-button', this.content).click(function(e) {
				this.close();
				return false;
			}.bind(this));

			// Controls the logic when the 'Go back' button is clicked
			$('fieldset.buttons-form .goback-button', this.content).click(function(e) {

				var history = $('fieldset.buttons-form .history-value', this.content);
				history = history.html() || 1;

				while (this.history.length > 0 && history > 0) {
					this.history.pop();
					history--;
				}

				var url = this.history[this.history.length-1];
				this.content.load(url, function(data, textStatus, xhr) {
					this.processAction();
				}.bind(this));
				return false;
			}.bind(this));
		},

		_loadAssets: function() {

			var css = $('ul.css_to_include li', this.content).map(function(index, item) {
				return $(item).html();
			});
			css = $.makeArray(css);

			var js = $('ul.js_to_include li', this.content).map(function(index, item) {
				var url = $(item).html();
				url = Object.urldecode(url.replace(/&/g, '&', url));
				return url;
			}.bind(this));

			js = {
				onComplete: this._onAssetsCompleted.bind(this),
				//onLoad: this._onScriptLoaded.bind(this),
				js: js
			};

			Object.loadCss(css);

			if (js.js.length > 0) {
				Object.loadScript(js);
			} else {
				this._onAssetsCompleted();
			}
		},

		_onAssetsCompleted: function() {
			X.triggerActionLoaded({
				actionView: this,
				browser: this.browser,
				context: this.content,
				url: this.url,
				action: this.action,
				nodes: this.nodes,
				tabId: this.tabId()
			});
		}
	});

})(com.ximdex);
<|endoftext|>


<commit_before>/**
 * Copyright (c) 2017-present, Viro, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

import React, { Component, PropTypes } from 'react';

import {
  AppRegistry,
  Text,
  View,
  StyleSheet,
  PixelRatio,
  ListView,
  Image,
  TouchableHighlight,
  TouchableOpacity,
  ActivityIndicator,
  ActionSheetIOS,
  CameraRoll,
  Alert,
  ScrollView,
} from 'react-native';

var ROW_PREFIX = "ROW_";
var COLUMN_PREFIX = "COLUMN_";

var TAB_STOCK = "stock";
var TAB_360 = "360";
var TAB_RECENT = "recent";
var REF_PREFIX = "ref";
var REF_DELIM = "_";

var STOCK_360_PHOTOS = [
  {
    source : require('../res/360_diving.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_guadalupe.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_space.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_waikiki.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_westlake.jpg'),
    height : 1,
    width : 2,
  },
];

export class PhotosSelector extends Component {
  // -- Props/State/Constructors

  static propTypes = {
    /*
     Number of elements in a row.
     */
    columns : PropTypes.number,
    /*
     rows denotes the number of rows visible, on the screen at one time (height of scroll view)
     */
    rows : PropTypes.number,
    searchIncrement: PropTypes.number,
    searchQuota : PropTypes.number, // number of assets we'll search through to find 360 content.
    /*
     This callback function takes 2 arguments:

     index: index of which photo was selected

     source : pass this into the source property of React or Viro image components
     */
    onPhotoSelected : PropTypes.func,
  }

  // default props
  static defaultProps = {
    columns : 3,
    rows : 2,
    searchIncrement: 20,
    searchQuota : 60,
  }

  // default state
  state = {
    scrollViewWidth : 0, // width of the scrollView itself
    scrollViewHeight : 0, // height of the scrollView itself
  }

  componentDidMount() {
    this._updateDataSource();
    this._getCameraRollAssets();
  }

  constructor(props) {
    super(props);

    // these are kinda like instance variables...
    // just using the fact that everything's an object in JS.
    this.user360Photos = [];
    this.userPhotos = [];
    this.fetchCount = 0;
    this.finishedSearching = false;
    this.selectedTab = TAB_STOCK;
    this.selectedRow = -1;
    this.selectedColumn = -1;
    this.refList = {};

    // initialize some more state
    var dataSource = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state.dataSource = dataSource;

    // binding functions to this
    this._updateDataSource = this._updateDataSource.bind(this)
    this._getPhotos = this._getPhotos.bind(this);
    this._storeDimensions = this._storeDimensions.bind(this);
    this._getRow = this._getRow.bind(this);
    this._getRows = this._getRows.bind(this);
    this._getTabBar = this._getTabBar.bind(this);
    this._getRowElements = this._getRowElements.bind(this);
    this._getImageIndex = this._getImageIndex.bind(this);
    this._getElementClick = this._getElementClick.bind(this);
    this._getCameraRollAssets = this._getCameraRollAssets.bind(this);
    this._getTabPress = this._getTabPress.bind(this);
    this._filterPhotos = this._filterPhotos.bind(this);
    this._storeRef = this._storeRef.bind(this);
    this._getRefKey = this._getRefKey.bind(this);
    this._updatePhotoSelection = this._updatePhotoSelection.bind(this);
  }

  _getPhotos() {
    if (this.selectedTab == TAB_STOCK) {
      return STOCK_360_PHOTOS;
    } else if (this.selectedTab == TAB_360) {
      return this.user360Photos;
    } else if (this.selectedTab == TAB_RECENT) {
      return this.userPhotos;
    }
  }

  _updateDataSource(onStateSetFunc) {
    let data = this._getPhotos();
    let newData = [];
    for (var i = 0; i < data.length; i+=this.props.columns) {
      let subArray = data.slice(i, i + this.props.columns);
      newData.push(subArray);
    }
    this.setState({
      dataSource : this.state.dataSource.cloneWithRows(newData)
    }, onStateSetFunc)
  }

  // -- Render functions --

  render() {

    return(
      <View style={[localStyles.outerContainer, this.props.style]}>
        <ListView style={localStyles.scrollView} dataSource={this.state.dataSource}
          renderRow={this._getRow} onLayout={this._storeDimensions}/>
        {this._getTabBar()}
      </View>
    );
  }

  _storeDimensions(event) {
    var {x, y, width, height} = event.nativeEvent.layout;
    this.setState({
      scrollViewWidth : width,
      scrollViewHeight : height
    })
  }

  _getRow(data, sectionId, rowIndex) {
    let height = this.state.scrollViewHeight / this.props.rows
    return (
      <View key={ROW_PREFIX + rowIndex} style={[localStyles.rowContainer, {height : height}]} >
        {this._getRowElements(data, rowIndex)}
      </View>)
  }

  // row number, 0th indexed.
  _getRowElements(data, rowNumber) {
    let toReturn = [];
    for (var i = 0; i < this.props.columns; i++) {
      toReturn.push((
        <View key={COLUMN_PREFIX + this.selectedTab + i} style={localStyles.rowElement} >
          {this._getImageIndex(data[i], rowNumber, i)}
        </View>
      ));
    }
    return toReturn;
  }

  _getImageIndex(data, row, column) {
    // data could be undefined... if it is, do nothing!
    if (data) {
      let index = this.props.columns * row + column;
      return (
        <TouchableOpacity style={[localStyles.touchElement]} activeOpacity={.5} focusedOpacity={.5} onPress={this._getElementClick(row, column, data)} >
          <Image style={localStyles.fetchedImageStyle} source={data.source} ref={this._storeRef(row, column)}
              onError={(error)=>{console.log("[PhotoSelector] load image error: " + error.nativeEvent.error)}} />
        </TouchableOpacity>
      )
    }
  }

  _storeRef(row, column) {
    return (ref) => {
      let key = this._getRefKey(row, column);
      this.refList[key] = ref;
    }
  }

  _getRefKey(row, column) {
    return REF_PREFIX + REF_DELIM + row + REF_DELIM + column;
  }

  _getElementClick(row, column, source) {
    let photos = this._getPhotos();
    return () => {
      let index = this.props.columns * row + column;
      if (row == this.selectedRow && column == this.selectedColumn) {
        return;
      }
      console.log("[PhotoSelector] user selected index " + index);
      this._updatePhotoSelection(row, column);
      this.props.onPhotoSelected && this.props.onPhotoSelected(index, source);
    }
  }

  _updatePhotoSelection(row, column) {

    let previousSelection = this.refList[this._getRefKey(this.selectedRow, this.selectedColumn)];
    if (previousSelection) {
      previousSelection.setNativeProps({ style : { opacity : 1 } });
    }

    let newSelection = this.refList[this._getRefKey(row, column)];
    if (newSelection) {
      newSelection.setNativeProps({ style: { opacity : .5 } });
    }

    this.selectedRow = row;
    this.selectedColumn = column;
  }

  _getTabBar() {
    return(
      <View style={localStyles.tabBarContainer}>
        <TouchableOpacity style={localStyles.tabTouch} activeOpacity={.5} onPress={this._getTabPress(TAB_STOCK)} >
          <Text style={localStyles.tabBarText}>Stock</Text>
        </TouchableOpacity>

        <TouchableOpacity style={localStyles.tabTouch} activeOpacity={.5} onPress={this._getTabPress(TAB_360)} >
          <Text style={localStyles.tabBarText}>360°</Text>
        </TouchableOpacity>

        <TouchableOpacity style={localStyles.tabTouch} activeOpacity={.5} onPress={this._getTabPress(TAB_RECENT)} >
          <Text style={localStyles.tabBarText}>Recent</Text>
        </TouchableOpacity>
      </View>
    )
  }

  _getTabPress(type) {
    return () => {

      if (this.selectedTab == type) {
        return; // do nothing!
      }

      this.refList = {};
      this.selectedTab = type;
      this._updatePhotoSelection(-1,-1);
      this._updateDataSource();
    }
  }

  // -- Camera Roll processing --
  // https://facebook.github.io/react-native/docs/cameraroll.html

  _getCameraRollAssets() {
    console.log("[PhotoSelector] fetching Camera Roll assets.");
    CameraRoll.getPhotos({
      first: this.props.searchIncrement,
      assetType : 'Photos',
      after : this.endCursor,
      groupTypes : 'All'
    }).then((retValue)=>{
      var numResults = retValue.edges.length;
      console.log("[PhotoSelector] got " + numResults + " Camera Roll assets.");
      var photos360 = [];
      var photos = [];

      // the function takes the two arrays and filters the photos into them and also culls extraneous info.
      this._filterPhotos(retValue.edges, photos360, photos);

      console.log("[PhotoSelector] after filtering, found " + photos360.length + " 360 photos and " + photos.length + " non-360 photos");

      this.user360Photos.push(...photos360);
      this.userPhotos.push(...photos);
      this.endCursor = retValue.page_info.end_cursor;
      this.fetchCount += numResults;

      // continue getting assets if we've not reached the search quota and we didn't run out of results.
      if ((this.fetchCount < this.props.searchQuota) && (numResults == this.props.searchIncrement)) {
        this._updateDataSource(()=>{this._getCameraRollAssets()});
      } else {
        this._updateDataSource();
      }
    }).catch((err)=>{
      console.log("[PhotoSelector] error while fetching Camera Roll assets: " + err.message);
      // TODO: handle photo selector assets error
    })
  }

  _filterPhotos(edges, photos360, photos) {
    for (var i = 0; i < edges.length; i++) {
      let edge = edges[i];

      let image = {
        source : { uri : edge.node.image.uri},
        width : edge.node.image.width,
        height : edge.node.image.height
      }
      if (this._is360Photo(edge)) {
        photos360.push(image);
      } else {
        photos.push(image);
      }
    }
  }

  _is360Photo(edge) {
    let ratio = edge.node.image.width / edge.node.image.height;
    return ratio > 1.9 && ratio < 2.2;
  }

}

var localStyles = StyleSheet.create({
  outerContainer : {
    flexDirection : 'column',
    backgroundColor : 'white',
  },
  scrollView: {
    height: '80%',
    width: '100%',
    borderWidth: 1,
    borderColor: 'white',
  },
  rowContainer : {
    flexDirection : 'row',
    width: '100%'
  },
  tabBarContainer: {
    flex : 1,
    flexDirection : 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
  },
  tabTouch : {
    flex : 1,
    flexDirection : 'row',
    height : '100%',
    alignItems: 'center',
    backgroundColor : '#AAAAAA',
  },
  tabBarText:{
    flex : 1,
    fontSize : 20,
    textAlign: 'center',
  },
  rowElement : {
    flex : 1,
    borderWidth: .5,
    borderColor: 'white',
  },
  touchElement : {
    width : '100%',
    height : '100%',
    justifyContent: 'center',
    alignItems: 'center',
  },
  fetchedImageStyle : {
    position : 'absolute',
    top : 0,
    left : 0,
    bottom : 0,
    right : 0,
    width: undefined, // react-native is weird, for images we need to set width/height undefined.
    height: undefined,
    resizeMode : "cover"
  },
  opaque: {
    position : 'absolute',
    top : 0,
    left : 0,
    bottom : 0,
    right : 0,
    backgroundColor : "#ffffff99"
  },
  invisible: {
    position : 'absolute',
    top : 0,
    left : 0,
    bottom : 0,
    right : 0,
    backgroundColor : "#00000000"
  },
  selectedIcon : {
    position : 'absolute',
    bottom : 0,
    right : 0,
    width : 50,
    height : 50,
    resizeMode : "contain"
  }
});

module.exports = PhotosSelector;
<commit_msg>Fix crash in PhotosSelector<commit_after>/**
 * Copyright (c) 2017-present, Viro, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

import React, { Component, PropTypes } from 'react';

import {
  AppRegistry,
  Text,
  View,
  StyleSheet,
  PixelRatio,
  ListView,
  Image,
  TouchableHighlight,
  TouchableOpacity,
  ActivityIndicator,
  ActionSheetIOS,
  CameraRoll,
  Alert,
  ScrollView,
} from 'react-native';

var ROW_PREFIX = "ROW_";
var COLUMN_PREFIX = "COLUMN_";

var TAB_STOCK = "stock";
var TAB_360 = "360";
var TAB_RECENT = "recent";
var REF_PREFIX = "ref";
var REF_DELIM = "_";

var STOCK_360_PHOTOS = [
  {
    source : require('../res/360_diving.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_guadalupe.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_space.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_waikiki.jpg'),
    height : 1,
    width : 2,
  },
  {
    source : require('../res/360_westlake.jpg'),
    height : 1,
    width : 2,
  },
];

export class PhotosSelector extends Component {
  // -- Props/State/Constructors

  static propTypes = {
    /*
     Number of elements in a row.
     */
    columns : PropTypes.number,
    /*
     rows denotes the number of rows visible, on the screen at one time (height of scroll view)
     */
    rows : PropTypes.number,
    searchIncrement: PropTypes.number,
    searchQuota : PropTypes.number, // number of assets we'll search through to find 360 content.
    /*
     This callback function takes 2 arguments:

     index: index of which photo was selected

     source : pass this into the source property of React or Viro image components
     */
    onPhotoSelected : PropTypes.func,
  }

  // default props
  static defaultProps = {
    columns : 3,
    rows : 2,
    searchIncrement: 20,
    searchQuota : 60,
  }

  // default state
  state = {
    scrollViewWidth : 0, // width of the scrollView itself
    scrollViewHeight : 0, // height of the scrollView itself
  }

  componentDidMount() {
    this._updateDataSource();
    this._getCameraRollAssets();
  }

  constructor(props) {
    super(props);

    // these are kinda like instance variables...
    // just using the fact that everything's an object in JS.
    this.user360Photos = [];
    this.userPhotos = [];
    this.fetchCount = 0;
    this.finishedSearching = false;
    this.selectedTab = TAB_STOCK;
    this.selectedRow = -1;
    this.selectedColumn = -1;
    this.refList = {};

    // initialize some more state
    var dataSource = new ListView.DataSource({rowHasChanged: (r1, r2) => r1 !== r2});
    this.state.dataSource = dataSource;

    // binding functions to this
    this._updateDataSource = this._updateDataSource.bind(this)
    this._getPhotos = this._getPhotos.bind(this);
    this._storeDimensions = this._storeDimensions.bind(this);
    this._getRow = this._getRow.bind(this);
    this._getTabBar = this._getTabBar.bind(this);
    this._getRowElements = this._getRowElements.bind(this);
    this._getImageIndex = this._getImageIndex.bind(this);
    this._getElementClick = this._getElementClick.bind(this);
    this._getCameraRollAssets = this._getCameraRollAssets.bind(this);
    this._getTabPress = this._getTabPress.bind(this);
    this._filterPhotos = this._filterPhotos.bind(this);
    this._storeRef = this._storeRef.bind(this);
    this._getRefKey = this._getRefKey.bind(this);
    this._updatePhotoSelection = this._updatePhotoSelection.bind(this);
  }

  _getPhotos() {
    if (this.selectedTab == TAB_STOCK) {
      return STOCK_360_PHOTOS;
    } else if (this.selectedTab == TAB_360) {
      return this.user360Photos;
    } else if (this.selectedTab == TAB_RECENT) {
      return this.userPhotos;
    }
  }

  _updateDataSource(onStateSetFunc) {
    let data = this._getPhotos();
    let newData = [];
    for (var i = 0; i < data.length; i+=this.props.columns) {
      let subArray = data.slice(i, i + this.props.columns);
      newData.push(subArray);
    }
    this.setState({
      dataSource : this.state.dataSource.cloneWithRows(newData)
    }, onStateSetFunc)
  }

  // -- Render functions --

  render() {

    return(
      <View style={[localStyles.outerContainer, this.props.style]}>
        <ListView style={localStyles.scrollView} dataSource={this.state.dataSource}
          renderRow={this._getRow} onLayout={this._storeDimensions}/>
        {this._getTabBar()}
      </View>
    );
  }

  _storeDimensions(event) {
    var {x, y, width, height} = event.nativeEvent.layout;
    this.setState({
      scrollViewWidth : width,
      scrollViewHeight : height
    })
  }

  _getRow(data, sectionId, rowIndex) {
    let height = this.state.scrollViewHeight / this.props.rows
    return (
      <View key={ROW_PREFIX + rowIndex} style={[localStyles.rowContainer, {height : height}]} >
        {this._getRowElements(data, rowIndex)}
      </View>)
  }

  // row number, 0th indexed.
  _getRowElements(data, rowNumber) {
    let toReturn = [];
    for (var i = 0; i < this.props.columns; i++) {
      toReturn.push((
        <View key={COLUMN_PREFIX + this.selectedTab + i} style={localStyles.rowElement} >
          {this._getImageIndex(data[i], rowNumber, i)}
        </View>
      ));
    }
    return toReturn;
  }

  _getImageIndex(data, row, column) {
    // data could be undefined... if it is, do nothing!
    if (data) {
      let index = this.props.columns * row + column;
      return (
        <TouchableOpacity style={[localStyles.touchElement]} activeOpacity={.5} focusedOpacity={.5} onPress={this._getElementClick(row, column, data)} >
          <Image style={localStyles.fetchedImageStyle} source={data.source} ref={this._storeRef(row, column)}
              onError={(error)=>{console.log("[PhotoSelector] load image error: " + error.nativeEvent.error)}} />
        </TouchableOpacity>
      )
    }
  }

  _storeRef(row, column) {
    return (ref) => {
      let key = this._getRefKey(row, column);
      this.refList[key] = ref;
    }
  }

  _getRefKey(row, column) {
    return REF_PREFIX + REF_DELIM + row + REF_DELIM + column;
  }

  _getElementClick(row, column, source) {
    let photos = this._getPhotos();
    return () => {
      let index = this.props.columns * row + column;
      if (row == this.selectedRow && column == this.selectedColumn) {
        return;
      }
      console.log("[PhotoSelector] user selected index " + index);
      this._updatePhotoSelection(row, column);
      this.props.onPhotoSelected && this.props.onPhotoSelected(index, source);
    }
  }

  _updatePhotoSelection(row, column) {

    let previousSelection = this.refList[this._getRefKey(this.selectedRow, this.selectedColumn)];
    if (previousSelection) {
      previousSelection.setNativeProps({ style : { opacity : 1 } });
    }

    let newSelection = this.refList[this._getRefKey(row, column)];
    if (newSelection) {
      newSelection.setNativeProps({ style: { opacity : .5 } });
    }

    this.selectedRow = row;
    this.selectedColumn = column;
  }

  _getTabBar() {
    return(
      <View style={localStyles.tabBarContainer}>
        <TouchableOpacity style={localStyles.tabTouch} activeOpacity={.5} onPress={this._getTabPress(TAB_STOCK)} >
          <Text style={localStyles.tabBarText}>Stock</Text>
        </TouchableOpacity>

        <TouchableOpacity style={localStyles.tabTouch} activeOpacity={.5} onPress={this._getTabPress(TAB_360)} >
          <Text style={localStyles.tabBarText}>360°</Text>
        </TouchableOpacity>

        <TouchableOpacity style={localStyles.tabTouch} activeOpacity={.5} onPress={this._getTabPress(TAB_RECENT)} >
          <Text style={localStyles.tabBarText}>Recent</Text>
        </TouchableOpacity>
      </View>
    )
  }

  _getTabPress(type) {
    return () => {

      if (this.selectedTab == type) {
        return; // do nothing!
      }

      this.refList = {};
      this.selectedTab = type;
      this._updatePhotoSelection(-1,-1);
      this._updateDataSource();
    }
  }

  // -- Camera Roll processing --
  // https://facebook.github.io/react-native/docs/cameraroll.html

  _getCameraRollAssets() {
    console.log("[PhotoSelector] fetching Camera Roll assets.");
    CameraRoll.getPhotos({
      first: this.props.searchIncrement,
      assetType : 'Photos',
      after : this.endCursor,
      groupTypes : 'All'
    }).then((retValue)=>{
      var numResults = retValue.edges.length;
      console.log("[PhotoSelector] got " + numResults + " Camera Roll assets.");
      var photos360 = [];
      var photos = [];

      // the function takes the two arrays and filters the photos into them and also culls extraneous info.
      this._filterPhotos(retValue.edges, photos360, photos);

      console.log("[PhotoSelector] after filtering, found " + photos360.length + " 360 photos and " + photos.length + " non-360 photos");

      this.user360Photos.push(...photos360);
      this.userPhotos.push(...photos);
      this.endCursor = retValue.page_info.end_cursor;
      this.fetchCount += numResults;

      // continue getting assets if we've not reached the search quota and we didn't run out of results.
      if ((this.fetchCount < this.props.searchQuota) && (numResults == this.props.searchIncrement)) {
        this._updateDataSource(()=>{this._getCameraRollAssets()});
      } else {
        this._updateDataSource();
      }
    }).catch((err)=>{
      console.log("[PhotoSelector] error while fetching Camera Roll assets: " + err.message);
      // TODO: handle photo selector assets error
    })
  }

  _filterPhotos(edges, photos360, photos) {
    for (var i = 0; i < edges.length; i++) {
      let edge = edges[i];

      let image = {
        source : { uri : edge.node.image.uri},
        width : edge.node.image.width,
        height : edge.node.image.height
      }
      if (this._is360Photo(edge)) {
        photos360.push(image);
      } else {
        photos.push(image);
      }
    }
  }

  _is360Photo(edge) {
    let ratio = edge.node.image.width / edge.node.image.height;
    return ratio > 1.9 && ratio < 2.2;
  }

}

var localStyles = StyleSheet.create({
  outerContainer : {
    flexDirection : 'column',
    backgroundColor : 'white',
  },
  scrollView: {
    height: '80%',
    width: '100%',
    borderWidth: 1,
    borderColor: 'white',
  },
  rowContainer : {
    flexDirection : 'row',
    width: '100%'
  },
  tabBarContainer: {
    flex : 1,
    flexDirection : 'row',
    justifyContent: 'space-around',
    alignItems: 'center',
  },
  tabTouch : {
    flex : 1,
    flexDirection : 'row',
    height : '100%',
    alignItems: 'center',
    backgroundColor : '#AAAAAA',
  },
  tabBarText:{
    flex : 1,
    fontSize : 20,
    textAlign: 'center',
  },
  rowElement : {
    flex : 1,
    borderWidth: .5,
    borderColor: 'white',
  },
  touchElement : {
    width : '100%',
    height : '100%',
    justifyContent: 'center',
    alignItems: 'center',
  },
  fetchedImageStyle : {
    position : 'absolute',
    top : 0,
    left : 0,
    bottom : 0,
    right : 0,
    width: undefined, // react-native is weird, for images we need to set width/height undefined.
    height: undefined,
    resizeMode : "cover"
  },
  opaque: {
    position : 'absolute',
    top : 0,
    left : 0,
    bottom : 0,
    right : 0,
    backgroundColor : "#ffffff99"
  },
  invisible: {
    position : 'absolute',
    top : 0,
    left : 0,
    bottom : 0,
    right : 0,
    backgroundColor : "#00000000"
  },
  selectedIcon : {
    position : 'absolute',
    bottom : 0,
    right : 0,
    width : 50,
    height : 50,
    resizeMode : "contain"
  }
});

module.exports = PhotosSelector;
<|endoftext|>


<commit_before>var Discord = require("discord.js");
var fs = require("fs");
var cron = require("node-cron");
var bot = new Discord.Client();
var readyCount = 0;
var watchlist = './watchlist.json';
const prefix = "~";
const friendList = {};

cron.schedule('*/25 * * * *', () => {
    console.log("Keeping SelfBot process alive");
});

bot.on("ready", () => {
    readyCount++;
    console.log("AFlaw is ready");
    console.log(`ID: ${bot.user.id}`);
    console.log(`Username: ${bot.user.username}`);
    // console.log(bot.presences);
    // console.log(bot.user.friends);
    checkFriendsStatuses(readyCount);
    // watchForFriendPresenceUpdate(readyCount);
});

process.on('SIGINT', () => {
    console.log("AFlaw is exiting");
});

bot.on("message", msg => {
    if (msg.author.id != bot.user.id || msg.content.substring(0, 1) != prefix) return;

    let messageContent = msg.content.split(" ");
    let command = messageContent[0].slice(prefix.length);
    let fromBotChannel = msg.channel.id == process.env.BOT_TEST_ID;

    if (command === "watch") {
        // console.log("Watch command");
        // console.log(`Is someone mentioned: ${msg.mentions.users.size!=0}`);
        // console.log(messageContent[1]);
        let user = msg.mentions.users.size!=0 ? msg.mentions.users.first() : messageContent[1];
        if (!fromBotChannel) msg.delete();
        watch(msg.mentions.users.size!=0 ? user.username : user);
        bot.channels.get(process.env.BOT_TEST_ID).sendMessage(`Watching ${msg.mentions.users.size!=0 ? user.username : user}`)
        .then(message => {
            // console.log(`Sent watch message: ${message.content}`);
            message.delete();
        })
        .catch(console.error);
    }

    if (command === "unwatch") {
        let user = msg.mentions.users.size!=0 ? msg.mentions.users.first() : messageContent[1];
        if (msg.channel.id != process.env.BOT_TEST_ID) msg.delete();
        unwatch(msg.mentions.users.size!=0 ? user.username : user);
        bot.channels.get(process.env.BOT_TEST_ID).sendMessage(`No longer watching ${msg.mentions.users.size!=0 ? user.username : user}`)
        .then(message => {
            // console.log(`Sent unwatch message: ${message.content}`);
            message.delete();
        })
        .catch(console.error);
    }

    if (command === "testDM") {
        bot.channels.get(process.env.BOT_TEST_ID).sendMessage("This is a test DM");
        // console.log(`Sending test message`);
    }
});

bot.on("presenceUpdate", (oldUser, newUser) => {
    try {
        // if (oldUser.client.user.id in bot.user.friends.keys) return;
        fs.readFile(watchlist, 'utf8', (err, data) => {
            if (err) throw err;
            var userObject = JSON.parse(data);
            if (userObject[oldUser.user.username.replace(/\s/g, '')]) {
                if ((oldUser.presence.status != "online") && (newUser.presence.status != "offline")) {
                    console.log(`presenceUpdate: ${oldUser.client.user.username}`);
                    sendStatusMessage(`${oldUser.user.username} is ${newUser.presence.status}`);
                }
            }
        });
    } catch (error) {
        console.error(error);
    }
});

bot.login(process.env.USER_TOKEN).catch(err => console.error(err));

function watch(user) {
    try {
        fs.readFile(watchlist, 'utf8', (err, data) => {
            if (err) throw err;
            let userObject = JSON.parse(data);
            userObject[user] = user;
            console.log(userObject);
            fs.writeFile(watchlist, JSON.stringify(userObject));
        });
    } catch (error) {
        console.error(error);
    }
}

function unwatch(user) {
    try {
        fs.readFile(watchlist, 'utf8', (err, data) => {
            if (err) throw err;
            let userObject = JSON.parse(data);
            delete userObject[user];
            console.log(userObject);
            fs.writeFile(watchlist, JSON.stringify(userObject));
        });
    } catch (error) {
        console.error(error);
    }
}

function checkFriendsStatuses(count) {
    if (count > 1) return;
    let friendPresences = bot.presences;
    // console.log(friendPresences.keyArray().toString());
    friendPresences.keyArray().forEach((val, index, presences) => {
        friendList[friendPresences.keyArray()[index]] = friendPresences.get(val)["status"];
    });
    console.log(bot.user.friends);
}

// function watchForFriendPresenceUpdate(count) {
//     if (count > 1) return;
//     while(true) {
//         // let friendPresences = bot.presences;
//         // let friends = bot.user.friends;
//         // let friendPresencesKeys = friendPresences.keys;
//         // let friendKeys = friends.keys;
//         // let guilds = bot.guilds;
//         for ()
//     }
// }

function sendStatusMessage(msg) {
     bot.channels.get(process.env.BOT_TEST_ID).sendMessage(msg)
    .then(message => {
        console.log(message.content);
         message.delete();
    })
    .catch(console.error);
}<commit_msg>avoiding mongodb - let's use env vars instead<commit_after>var Discord = require("discord.js");
var fs = require("fs");
var cron = require("node-cron");
var bot = new Discord.Client();
var readyCount = 0;
var watchlist = './watchlist.json';
const prefix = "~";
const friendList = {};

cron.schedule('*/25 * * * *', () => {
    console.log("Keeping SelfBot process alive");
});

bot.on("ready", () => {
    readyCount++;
    console.log("AFlaw is ready");
    console.log(`ID: ${bot.user.id}`);
    console.log(`Username: ${bot.user.username}`);
    // console.log(bot.presences);
    // console.log(bot.user.friends);
    checkFriendsStatuses(readyCount);
    // watchForFriendPresenceUpdate(readyCount);
});

process.on('SIGTERM', () => {
    console.log("AFlaw is exiting");
});

bot.on("message", msg => {
    if (msg.author.id != bot.user.id || msg.content.substring(0, 1) != prefix) return;

    let messageContent = msg.content.split(" ");
    let command = messageContent[0].slice(prefix.length);
    let fromBotChannel = msg.channel.id == process.env.BOT_TEST_ID;

    if (command === "watch") {
        // console.log("Watch command");
        // console.log(`Is someone mentioned: ${msg.mentions.users.size!=0}`);
        // console.log(messageContent[1]);
        let user = msg.mentions.users.size!=0 ? msg.mentions.users.first() : messageContent[1];
        if (!fromBotChannel) msg.delete();
        watch(msg.mentions.users.size!=0 ? user.username : user);
        sendStatusMessage(`Watching ${msg.mentions.users.size!=0 ? user.username : user}`);
    }

    if (command === "unwatch") {
        let user = msg.mentions.users.size!=0 ? msg.mentions.users.first() : messageContent[1];
        if (msg.channel.id != process.env.BOT_TEST_ID) msg.delete();
        unwatch(msg.mentions.users.size!=0 ? user.username : user);
        sendStatusMessage(`No longer watching ${msg.mentions.users.size!=0 ? user.username : user}`);
    }

    if (command === "testDM") {
        sendStatusMessage("This is a Test DM");
        // console.log(`Sending test message`);
    }
});

bot.on("presenceUpdate", (oldUser, newUser) => {
    try {
        //  if (oldUser.client.user.id in bot.user.friends.keys) return;
        var userObject = JSON.parse(process.env.WATCHLIST);
        if (userObject[oldUser.user.username.replace(/\s/g, '')]) {
            if ((oldUser.presence.status != "online") && (newUser.presence.status != "offline")) {
                 console.log(`presenceUpdate: ${oldUser.client.user.username}`);
                 sendStatusMessage(`${oldUser.user.username} is ${newUser.presence.status}`);
             }
        }
    } catch (error) {
        console.error(error);
    }
});

bot.login(process.env.USER_TOKEN).catch(err => console.error(err));

function watch(user) {
    try {
        let userObject = JSON.parse(process.env.WATCHLIST);
        userObject[user] = user;
        console.log(userObject);
        process.env.WATCHLIST = JSON.stringify(userObject);
    } catch (error) {
        console.error(error);
    }
}

function unwatch(user) {
    try {
        let userObject = JSON.parse(data);
        delete userObject[user];
        console.log(userObject);
            process.env.WATCHLIST = JSON.stringify(userObject);
    } catch (error) {
        console.error(error);
    }
}

function checkFriendsStatuses(count) {
    if (count > 1) return;
    let friendPresences = bot.presences;
    // console.log(friendPresences.keyArray().toString());
    friendPresences.keyArray().forEach((val, index, presences) => {
        friendList[friendPresences.keyArray()[index]] = friendPresences.get(val)["status"];
    });
    console.log(bot.user.friends);
}

// function watchForFriendPresenceUpdate(count) {
//     if (count > 1) return;
//     while(true) {
//         // let friendPresences = bot.presences;
//         // let friends = bot.user.friends;
//         // let friendPresencesKeys = friendPresences.keys;
//         // let friendKeys = friends.keys;
//         // let guilds = bot.guilds;
//         for ()
//     }
// }

function sendStatusMessage(msg) {
     bot.channels.get(process.env.BOT_TEST_ID).sendMessage(msg)
    .then(message => {
        console.log(message.content);
         message.delete();
    })
    .catch(console.error);
}<|endoftext|>


<commit_before>/*
 * flow.js v0.1.0. MIT License.
 * Manage async callbacks.
 */

(function (global) {
    /**
     * Manage asyncronous operations.

    flow(function (c) {
        for (var i = 0; i < 3; i += 1) {
            c.inc();
            ...
            ...
            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (xhr.status === 200) {
                        c.dec(null, JSON.parse(xhr.resposeText));
                    } else {
                        c.dec({errorCode: xhr.status});
                    }
                }
            }
        }
    }, function (c, err, results) {
        //do something
    }, this); //context in which the funtions will run

    //Another way to use
    var tasks = flow.tasks(...);
    tasks.execute();

     * @class flow
     * @singleton
     * @param {Object} [config]
     * @param {Function} functions func1, func2, ... funcN
     * @param {Object} [scope]
     */
    function flow() {
        var callbacks = Array.prototype.slice.call(arguments),
            options = {};
        if (typeof callbacks[0] === 'object') {
            options = callbacks.shift();
        }
        if (typeof callbacks[callbacks.length - 1] === 'object') {
            options.scope = callbacks.pop();
        }

        return function executeTasks(errorsParent, resultsParent, counter) {
            var manager = new Manager(callbacks, options);
            //pipe output from previous callback of parent as input to this task.
            manager.execute(errorsParent, resultsParent, function (errors, results) {
                //once all tasks completed, pipe output from last callback of this
                //task as input to the next callback of parent.
                if (counter) {
                    counter.next(errors, results);
                }
            });
        };
    }

    /**
     * Manager class
     * @private
     */
    function Manager(callbacks, options) {
        this.callbacks = callbacks.slice(0);
        this.lastArgs = [];
        this.errors = [];
        this.results = [];

        this.currentFunc = -1;

        this.set(options);
    }
    Manager.prototype = {
        callbacks: null,

        currentFunc: -1,
        repeatNext: false,
        repeatCount: 0,

        results: null,
        errors: null,
        /**
         * Error tolerance level.
         */
        tolerance: 0, //call the next() method on an error.
        scope: null,
        lastArgs: null, //keeps track of arguments passed to last callback. This is needed in case of repeating a callback.

        /**
         * Decide the flow and behavior of task execution.
         * Tell what to do when counter hits zero or an error condition.
         * @param {Object} config
         * @param {Boolean} [config.repeat=false] If true, repeats the current task once, when counter hits zero.
         * @param {Boolean} [config.tolerance=0]
         */
        set: function (options) {
            if (options.tolerance !== undefined) {
                this.tolerance = !!options.tolerance;
            }
            if (typeof options.repeat === 'boolean') {
                this.repeatNext = options.repeat;
            }
        },
        /**
         * Stores error and result, that are meant to be sent as arguments to the next callback in the list.
         */
        store: function (error, result) {
            this.errors.push(error);
            this.results.push(result);
        },
        /**
         * Execute the next task.
         */
        next: function (error, result) {
            if (error !== undefined || result !== undefined) {
                this.errors.push(error);
                this.results.push(result);
            }

            //TODO: Think about whether to send array when all values are null or not..
            var errs = compact(this.errors), res;
            //don't repeat if !this.tolerance && error.
            if (this.repeatNext && (this.tolerance || (!this.tolerance && !errs))) {
                errs = this.lastArgs[0];
                res = this.lastArgs[1];

                this.repeatCount += 1;
            } else {
                this.currentFunc += 1;
                res = this.results;

                //reset in preparation for the next call in queue
                this.errors = [];
                this.results = [];
                this.repeatCount = 0;
            }
            this.repeatNext = false;

            if (this.callbacks[this.currentFunc]) {
                this.lastArgs = [errs, res];
                if (!this.tolerance && errs && errs[0]) {
                    errs = errs[0];
                }
                var counter = new Counter({manager: this, tolerance: this.tolerance});
                counter.repeatCount = this.repeatCount;
                this.callbacks[this.currentFunc].call(this.scope, errs, res, counter);
            }
        },

        /**
         * Start executing task. Similar signature as next(), but additionally takes
         * a 3rd parameter as callback, that will be called once all the tasks complete.
         */
        execute: function (err, result, cb) {
            this.callbacks.push(cb);
            this.next(err, result);
        }
    };

    /**
     * A counter, that callbacks have access to.
     * This has been moved from Manager, so that decrement operation on a stale counter doesn't affect the
     * flow of control unknowingly. i.e just for robustness.
     */
    function Counter(config) {
        this.manager = config.manager;
        this.tolerance = config.tolerance;
        this.count = 0;
    }

    Counter.prototype = {
        /**
         * Set counter value. Also set the flow and behavior of task execution.
         * @param {Number} val Value of the counter to be set.
         * @param {Boolean} [repeat=false] Check setFlow() method for documentation.
         * @param {Boolean} [tolerance] Check setFlow() method for documentation.
         */
        set: function (val, repeat, tolerance) {
            if (typeof val === 'number' && val > 0) {
                this.count = val;
            }
            this.setFlow({repeat: repeat, tolerance: tolerance});
        },
        /**
         * Decide the flow and behavior of task execution.
         * Tell what to do when counter hits zero or when faced with an error.
         * @param {Object} [config]
         * @param {Boolean} [config.repeat=false] If true, repeats the current task exactly once, when counter hits zero.
         * @param {Boolean} [config.tolerance=manager.tolerance] Error tolerance level.
         * If an error is encountered when tolerance = 0, then a call to tick(err, null) will immediately cause the next task to be executed
         * and the error will be passed to that task.
         * If tolerance = 1, then tick(err, null) will store the error(s) and pass them to the next task only when counter hits zero.
         */
        setFlow: function (config) {
            if (config.tolerance !== undefined) {
                this.tolerance = !!config.tolerance;
            }
            this.manager.set(config);
        },
        /**
         * Increment counter.
         * @param {Number} [val=1]
         */
        inc: function (val) {
            if (typeof val !== 'number' || val <= 0) {
                val = 1;
            }
            this.count += val;
        },
        /**
         * Decrements counter by 1. Send the error or result.
         */
        tick: function (error, result) {
            //prevent invalid state...
            if (this.count > 0) {
                this.count -= 1;
                this.manager.store(error, result);
                if (!this.tolerance && error) {
                    //set to zero so that future decrements, doesn't affect.
                    this.count = 0;
                }
                this.next();
            }
        },
        /**
         * Execute the next task.
         * Note: The next task won't be called if counter is greater than zero.
         */
        next: function (error, result) {
            if (!this.tolerance && error) {
                //set to zero so that future decrements, doesn't affect.
                this.count = 0;
            }
            if (this.count === 0) {
                this.manager.next(error, result);
            }
        }
    };

    /*
     * If all items of array are empty, return null.
     * Else return original unmodified array.
     */
    function compact(arr) {
        var isEmpty = true;
        arr.some(function (a) {
            if (a !== null && a !== undefined) {
                isEmpty = false;
                return true;
            }
        });
        return isEmpty ? null : arr;
    }

    if (typeof module !== 'undefined' && module.exports) {
        module.exports = flow;
    } else if (typeof define === "function" && define.amd) {
        define('flow', [], function () {
            return flow;
        });
    } else {
        //On client-side browsers global = window object.
        global.flow = flow;
    }
}(this));
<commit_msg>Send only one argument to first task if error and result isn't passed.<commit_after>/*
 * flow.js v0.1.0. MIT License.
 * Manage async callbacks.
 */

(function (global) {
    /**
     * Manage asyncronous operations.
     * @class flow
     * @singleton
     * @param {Object} [config]
     * @param {Function} functions func1, func2, ... funcN
     * @param {Object} [scope]
     */
    function flow() {
        var callbacks = Array.prototype.slice.call(arguments),
            options = {};
        if (typeof callbacks[0] === 'object') {
            options = callbacks.shift();
        }
        if (typeof callbacks[callbacks.length - 1] === 'object') {
            options.scope = callbacks.pop();
        }

        return function executeTasks(errorsParent, resultsParent, counter) {
            var manager = new Manager(callbacks, options);
            //pipe output from previous callback of parent as input to this task.
            manager.execute(errorsParent, resultsParent, function (errors, results) {
                //once all tasks completed, pipe output from last callback of this
                //task as input to the next callback of parent.
                if (counter) {
                    counter.next(errors, results);
                }
            });
        };
    }

    /**
     * Manager class
     * @private
     */
    function Manager(callbacks, options) {
        this.callbacks = callbacks.slice(0);
        this.lastArgs = [];
        this.errors = [];
        this.results = [];

        this.currentFunc = -1;

        this.set(options);
    }
    Manager.prototype = {
        callbacks: null,

        currentFunc: -1,
        repeatNext: false,
        repeatCount: 0,

        results: null,
        errors: null,
        /**
         * Error tolerance level.
         */
        tolerance: 0, //call the next() method on an error.
        scope: null,
        lastArgs: null, //keeps track of arguments passed to last callback. This is needed in case of repeating a callback.

        /**
         * Decide the flow and behavior of task execution.
         * Tell what to do when counter hits zero or an error condition.
         * @param {Object} config
         * @param {Boolean} [config.repeat=false] If true, repeats the current task once, when counter hits zero.
         * @param {Boolean} [config.tolerance=0]
         */
        set: function (options) {
            if (options.tolerance !== undefined) {
                this.tolerance = !!options.tolerance;
            }
            if (typeof options.repeat === 'boolean') {
                this.repeatNext = options.repeat;
            }
        },
        /**
         * Stores error and result, that are meant to be sent as arguments to the next callback in the list.
         */
        store: function (error, result) {
            this.errors.push(error);
            this.results.push(result);
        },
        /**
         * Execute the next task.
         */
        next: function (error, result) {
            if (error !== undefined || result !== undefined) {
                this.errors.push(error);
                this.results.push(result);
            }

            var errs = compact(this.errors), res;
            //don't repeat if !this.tolerance && error.
            if (this.repeatNext && (this.tolerance || (!this.tolerance && !errs))) {
                errs = this.lastArgs[0];
                res = this.lastArgs[1];

                this.repeatCount += 1;
            } else {
                this.currentFunc += 1;
                res = this.results;

                //reset in preparation for the next call in queue
                this.errors = [];
                this.results = [];
                this.repeatCount = 0;
            }
            this.repeatNext = false;

            if (this.callbacks[this.currentFunc]) {
                this.lastArgs = [errs, res];
                if (!this.tolerance && errs && errs[0]) {
                    errs = errs[0];
                }
                var counter = new Counter({manager: this, tolerance: this.tolerance});
                counter.repeatCount = this.repeatCount;

                var args = [errs, res, counter];
                if (this.currentFunc === 0) {
                    if (errs === undefined || errs === null) {
                        args.shift();
                        if (result === undefined) {
                            args.shift();
                        }
                    }
                }
                this.callbacks[this.currentFunc].apply(this.scope, args);
            }
        },

        /**
         * Start executing task. Similar signature as next(), but additionally takes
         * a 3rd parameter as callback, that will be called once all the tasks complete.
         */
        execute: function (err, result, cb) {
            this.callbacks.push(cb);
            this.next(err, result);
        }
    };

    /**
     * A counter, that callbacks have access to.
     * This has been moved from Manager, so that decrement operation on a stale counter doesn't affect the
     * flow of control unknowingly. i.e just for robustness.
     */
    function Counter(config) {
        this.manager = config.manager;
        this.tolerance = config.tolerance;
        this.count = 0;
    }

    Counter.prototype = {
        /**
         * Set counter value. Also set the flow and behavior of task execution.
         * @param {Number} val Value of the counter to be set.
         * @param {Boolean} [repeat=false] Check setFlow() method for documentation.
         * @param {Boolean} [tolerance] Check setFlow() method for documentation.
         */
        set: function (val, repeat, tolerance) {
            if (typeof val === 'number' && val > 0) {
                this.count = val;
            }
            this.setFlow({repeat: repeat, tolerance: tolerance});
        },
        /**
         * Decide the flow and behavior of task execution.
         * Tell what to do when counter hits zero or when faced with an error.
         * @param {Object} [config]
         * @param {Boolean} [config.repeat=false] If true, repeats the current task exactly once, when counter hits zero.
         * @param {Boolean} [config.tolerance=manager.tolerance] Error tolerance level.
         * If an error is encountered when tolerance = 0, then a call to tick(err, null) will immediately cause the next task to be executed
         * and the error will be passed to that task.
         * If tolerance = 1, then tick(err, null) will store the error(s) and pass them to the next task only when counter hits zero.
         */
        setFlow: function (config) {
            if (config.tolerance !== undefined) {
                this.tolerance = !!config.tolerance;
            }
            this.manager.set(config);
        },
        /**
         * Increment counter.
         * @param {Number} [val=1]
         */
        inc: function (val) {
            if (typeof val !== 'number' || val <= 0) {
                val = 1;
            }
            this.count += val;
        },
        /**
         * Decrements counter by 1. Send the error or result.
         */
        tick: function (error, result) {
            //prevent invalid state...
            if (this.count > 0) {
                this.count -= 1;
                this.manager.store(error, result);
                if (!this.tolerance && error) {
                    //set to zero so that future decrements, doesn't affect.
                    this.count = 0;
                }
                this.next();
            }
        },
        /**
         * Execute the next task.
         * Note: The next task won't be called if counter is greater than zero.
         */
        next: function (error, result) {
            if (!this.tolerance && error) {
                //set to zero so that future decrements, doesn't affect.
                this.count = 0;
            }
            if (this.count === 0) {
                this.manager.next(error, result);
            }
        }
    };

    /*
     * If all items of array are empty, return null.
     * Else return original unmodified array.
     */
    function compact(arr) {
        var isEmpty = true;
        arr.some(function (a) {
            if (a !== null && a !== undefined) {
                isEmpty = false;
                return true;
            }
        });
        return isEmpty ? null : arr;
    }

    if (typeof module !== 'undefined' && module.exports) {
        module.exports = flow;
    } else if (typeof define === "function" && define.amd) {
        define('flow', [], function () {
            return flow;
        });
    } else {
        //On client-side browsers global = window object.
        global.flow = flow;
    }
}(this));
<|endoftext|>


<commit_before>'use strict';

function thrower(err) {
	return function () { throw err; }
}

function returner(obj) {
	return function () { return obj; }
}

function createSpy(fn) {
	var fake = fn;

	if (fake !== void 0 && typeof(fn) !== 'function') {
		if (fn instanceof Error) {
			fake = thrower(fn);
		} else {
			fake = returner(fn);
		}
	}

	function spy() {
		var spyArgs = [].slice.call(arguments);
		spy.calls.push(spyArgs);
		spy.context = this;
		return fake && fake.apply(spy.context, spyArgs);
	}

	spy.calls = [];

	spy.reset = function reset() {
		spy.calls = [];
	};

	// Checkers
	spy.callCount = function callCount() {
		return spy.calls.length;
	}

	spy.wasCalled = function wasCalled() {
		return spy.calls.length > 0;
	};

	spy.wasNotCalled = function wasNotCalled() {
		return spy.calls.length === 0;
	};

	spy.firstCall = function firstCall() {
		return spy.calls[0];
	;}

	spy.lastCall = function lastCall() {
		return spy.calls[spy.calls.length - 1];
	;}

	// Fluent interface
	spy.thatThrows = function thatThrows(err) {
		fake = thrower(err);
		return spy;
	};

	spy.thatReturns = function thatThrows(err) {
		fake = returner(err);
		return spy;
	};

	spy.thatCalls = function thatCalls(fn) {
		fake = fn;
		return spy;
	};

	return spy;
}

module.exports = {
	createSpy: createSpy
};
<commit_msg>Minor fix - function name was incorrect<commit_after>'use strict';

function thrower(err) {
	return function () { throw err; }
}

function returner(obj) {
	return function () { return obj; }
}

function createSpy(fn) {
	var fake = fn;

	if (fake !== void 0 && typeof(fn) !== 'function') {
		if (fn instanceof Error) {
			fake = thrower(fn);
		} else {
			fake = returner(fn);
		}
	}

	function spy() {
		var spyArgs = [].slice.call(arguments);
		spy.calls.push(spyArgs);
		spy.context = this;
		return fake && fake.apply(spy.context, spyArgs);
	}

	spy.calls = [];

	spy.reset = function reset() {
		spy.calls = [];
	};

	// Checkers
	spy.callCount = function callCount() {
		return spy.calls.length;
	}

	spy.wasCalled = function wasCalled() {
		return spy.calls.length > 0;
	};

	spy.wasNotCalled = function wasNotCalled() {
		return spy.calls.length === 0;
	};

	spy.firstCall = function firstCall() {
		return spy.calls[0];
	};

	spy.lastCall = function lastCall() {
		return spy.calls[spy.calls.length - 1];
	};

	// Fluent interface
	spy.thatThrows = function thatThrows(err) {
		fake = thrower(err);
		return spy;
	};

	spy.thatReturns = function thatReturns(err) {
		fake = returner(err);
		return spy;
	};

	spy.thatCalls = function thatCalls(fn) {
		fake = fn;
		return spy;
	};

	return spy;
}

module.exports = {
	createSpy: createSpy
};
<|endoftext|>


<commit_before>// inbox.js -- update Fluid app dock badge for Google Inbox
//
// *NOT A BOOKMARKLET*, also written in archaic Safari <9 JS,
// because Fluid isn't really maintained anymore and for some
// reason things that should be available in Safari 9 just aren't
// available (looking at you, Array.from).
//
// Copyright (C) 2015 Dan Poggi
//
// This software may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.

(function() {
  var els;

  // Since my Fluid app likes to capture Google Docs links no matter
  // what I do...
  if (window.location.host.indexOf("inbox.google.com") === -1) {
    return;
  }

  function updateBadge(badge) {
    window.fluid.dockBadge = badge;
  }

  function checkInbox() {
    var count, closedItems, i, openItems;

    count = 0;

    // We have to use old-school for loops here or we'll get strange
    // elements in the NodeLists.
    closedItems = document.getElementsByClassName("jS");
    for (i = 0; i < closedItems.length; i++) {
      if ((" " + closedItems[i].className + " ").indexOf(" kl ") === -1) {
        count += 1;
      }
    }

    openItems = document.getElementsByClassName("mh");
    for (i = 0; i < openItems.length; i++) {
      count += 1;
    }

    if (count >= 1) {
      updateBadge(count);
    } else {
      updateBadge("");
    }
  }

  if (typeof window.disconnectObserver !== "function") {
    window.disconnectObserver = function(event) {
      if (typeof window.observer !== "undefined") {
        window.observer.disconnect();
      }
    }
  }

  window.disconnectObserver();
  window.observer = new MutationObserver(function(mutations) {
    checkInbox();
  });
  window.observerConfig = {
    childList: true,
    attributes: false,
    characterData: false,
    subtree: true,
  };

  updateBadge("");
  els = document.querySelectorAll(".yDSKFc.viy5Tb");
  if (els.length >= 1) {
    window.observer.observe(els[0], window.observerConfig);
    checkInbox();
  } else {
    console.log("USERSCRIPT: Couldn't find parent element, dock badging disabled!");
  }

  // For some hope of dock badge predictability when handling multiple
  // tabs (multiple Gmail accounts).
  window.removeEventListener("beforeunload", window.disconnectObserver);
  window.addEventListener("beforeunload", window.disconnectObserver);
  window.removeEventListener("unload", window.disconnectObserver);
  window.addEventListener("unload", window.disconnectObserver);
})();
<commit_msg>Inbox script has onlyCheckUnread option<commit_after>// inbox.js -- update Fluid app dock badge for Google Inbox
//
// *NOT A BOOKMARKLET*, also written in archaic Safari <9 JS,
// because Fluid isn't really maintained anymore and for some
// reason things that should be available in Safari 9 just aren't
// available (looking at you, Array.from).
//
// Copyright (C) 2015 Dan Poggi
//
// This software may be modified and distributed under the terms
// of the MIT license. See the LICENSE file for details.

(function() {
  var onlyCheckUnread, els;

  // If you want the dock badge to only count unread emails instead of
  // all of them (Mailbox behavior), set this to true. I like it the
  // Mailbox way.
  onlyCheckUnread = false;

  // Since my Fluid app likes to capture Google Docs links no matter
  // what I do...
  if (window.location.host.indexOf("inbox.google.com") === -1) {
    return;
  }

  function updateBadge(badge) {
    window.fluid.dockBadge = badge;
  }

  function isNotAttachment(el) {
    var classNames;
    classNames = el.className.replace(/[\t\r\n\f]/g, " ").split(" ");
    return classNames.indexOf("kl") === -1;
  }
  function isUnread(el) {
    return el.getElementsByClassName("qG").length > 0;
  }
  // Highly Scientific Naming (tm)
  function predicateCombinerer() {
    var args;
    args = Array.prototype.slice.call(arguments);
    return function(el) {
      return args.map(function(p) { return p(el); }).indexOf(false) === -1;
    };
  }

  function checkInbox() {
    var predicate, count, closedItems, i, openItems;

    if (onlyCheckUnread) {
      predicate = predicateCombinerer(isNotAttachment, isUnread);
    } else {
      predicate = predicateCombinerer(isNotAttachment);
    }

    // We have to use old-school for loops here or we'll get strange
    // elements in the NodeLists.
    count = 0;
    closedItems = document.getElementsByClassName("jS");
    for (i = 0; i < closedItems.length; i++) {
      if (predicate(closedItems[i])) {
        count += 1;
      }
    }

    if (!onlyCheckUnread) {
      openItems = document.getElementsByClassName("mh");
      for (i = 0; i < openItems.length; i++) {
        count += 1;
      }
    }

    if (count >= 1) {
      updateBadge(count);
    } else {
      updateBadge("");
    }
  }

  if (typeof window.disconnectObserver !== "function") {
    window.disconnectObserver = function(event) {
      if (typeof window.observer !== "undefined") {
        window.observer.disconnect();
      }
    }
  }

  window.disconnectObserver();
  window.observer = new MutationObserver(function(mutations) {
    checkInbox();
  });
  window.observerConfig = {
    childList: true,
    attributes: false,
    characterData: false,
    subtree: true,
  };

  updateBadge("");
  els = document.querySelectorAll(".yDSKFc.viy5Tb");
  if (els.length >= 1) {
    window.observer.observe(els[0], window.observerConfig);
    checkInbox();
  } else {
    console.log("USERSCRIPT: Couldn't find parent element, dock badging disabled!");
  }

  // For some hope of dock badge predictability when handling multiple
  // tabs (multiple Gmail accounts).
  window.removeEventListener("beforeunload", window.disconnectObserver);
  window.addEventListener("beforeunload", window.disconnectObserver);
  window.removeEventListener("unload", window.disconnectObserver);
  window.addEventListener("unload", window.disconnectObserver);
})();
<|endoftext|>


<commit_before>function minimodal(target) {

  var _ = {};

  _.current = target;

  _.node = function(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    return div.firstChild;
  };

  _.setup = function() {
    _.minimodal = _.node('
');
    _.overlay = _.node('
');
    _.viewport = _.node('
');
    _.closeButton = _.node('<button class="minimodal__close">Close</button>');
  };

  _.build = function() {
    _.minimodal.appendChild(_.overlay);
    _.minimodal.appendChild(_.viewport);
    _.minimodal.appendChild(_.closeButton);
    document.body.appendChild(_.minimodal);
  };

  _.close = function() {
    _.minimodal.parentNode.removeChild(_.minimodal);
  };

  _.listen = function() {
    _.closeButton.addEventListener('click', _.close);
  };

  _.youtube = function() {
    var id = _.url.split('v=')[1];
    return '
';
  };

  _.vimeo = function() {
    var id = _.url.split('vimeo.com/')[1];
    return '
';
  };

  _.image = function() {
    return '';
  };

  _.load = function() {
    var element;
    _.url = _.current.getAttribute('href');
    if (_.url.indexOf('youtube.com') > -1) {
      element = _.youtube();
    } else if (_.url.indexOf('vimeo.com') > -1) {
      element = _.vimeo();
    } else {
      element = _.image();
    }
    _.item = _.node('

' + element);
    _.viewport.appendChild(_.item);
  };

  _.open = function() {
    _.setup();
    _.build();
    _.listen();
    _.load();
  };

  return _;

}
<commit_msg>focustrap, keyboard close<commit_after>function minimodal(target) {

  var _ = {};

  _.current = target;

  _.node = function(html) {
    var div = document.createElement('div');
    div.innerHTML = html;
    return div.firstChild;
  };

  _.setup = function() {
    _.minimodal = _.node('
');
    _.overlay = _.node('
');
    _.viewport = _.node('
');
    _.closeButton = _.node('<button class="minimodal__close">Close</button>');
  };

  _.build = function() {
    _.minimodal.appendChild(_.overlay);
    _.minimodal.appendChild(_.viewport);
    _.minimodal.appendChild(_.closeButton);
    document.body.appendChild(_.minimodal);
    _.minimodal.focus();
  };

  _.close = function() {
    _.minimodal.parentNode.removeChild(_.minimodal);
    document.removeEventListener('keydown', _.keydown);
    target.focus();
  };

  _.focusTrap = function(e) {
    if (e.shiftKey) {
      if (_.minimodal === document.activeElement) {
        e.preventDefault();
        _.closeButton.focus();
      }
    } else {
      if (_.closeButton === document.activeElement) {
        e.preventDefault();
        _.minimodal.focus();
      }
    }
  };

  _.keydown = function(e) {
    if (e.keyCode === 9) {
      _.focusTrap(e);
    } else if (e.keyCode === 27) {
      _.close();
    }
  };

  _.listen = function() {
    _.closeButton.addEventListener('click', _.close);
    document.addEventListener('keydown', _.keydown);
  };

  _.youtube = function() {
    var id = _.url.split('v=')[1];
    return '
';
  };

  _.vimeo = function() {
    var id = _.url.split('vimeo.com/')[1];
    return '
';
  };

  _.image = function() {
    return '';
  };

  _.load = function() {
    var element;
    _.url = _.current.getAttribute('href');
    if (_.url.indexOf('youtube.com') > -1) {
      element = _.youtube();
    } else if (_.url.indexOf('vimeo.com') > -1) {
      element = _.vimeo();
    } else {
      element = _.image();
    }
    _.item = _.node('

' + element);
    _.viewport.appendChild(_.item);
  };

  _.open = function() {
    _.setup();
    _.build();
    _.listen();
    _.load();
  };

  return _;

}
<|endoftext|>


<commit_before>var express = require('express');
var request = require('request');
var bodyParser = require('body-parser');
var cron = require('node-cron');
var cheerio = require('cheerio');

var app = express();
app.listen((process.env.PORT || 3000));
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

// Current version
ver = 'v.0.0.19';
// Facebook pageId
pageId = '1167308473348175';
// My user on Facebook
me = '1221242727898531';

// Server endpoint
app.get('/', function (req, res) {
    res.send('JKDbot here! ' + ver);
});

// Facebook webhook
app.get('/webhook', function (req, res) {
    if (req.query['hub.verify_token'] === 'jkdbot_verify_token') {
        res.send(req.query['hub.challenge']);
    } else {
        res.send('Invalid verify token');
    }
});

// Handler receiving messages
app.post('/webhook', function (req, res) {
    var events = req.body.entry[0].messaging;
    for (i = 0; i < events.length; i++) {
        var event = events[i];
        if (event.message && event.message.text) {
            switch(event.message.text) {
                case("cmd_version"):
                    sendMessage(event.sender.id, {text: ver});
                    break;
                case("cmd_subscribers"):
                    sendMessage(event.sender.id, {text: getUserIds()});
                    break;
                case("cmd_timezone"):
                    sendMessage(event.sender.id, {text: getUserTimezone(event.sender.id)});
                    break;
              case("cmd_stop_cron"):
                    task.stop();
                    break;
              case("cmd_stop_restart"):
                    task.start();
                    break;
                default:
                    sendMessage(event.sender.id, {text: "Message received: " + event.message.text + " by " + event.sender.id});
                    break;
            }
        }
    }
    res.sendStatus(200);
});

// Scheduler
var task = cron.schedule('* * 9 * *', function() {
    console.log('Running sendout');
    doSendout();
});
task.start();

// Main task
var doSendout = function() {
    sendMessage(me, {text: "jkdbot here"});
}

// Sends a message to a Facebook user
var sendMessage = function(recipientId, message) {
    request({
        url: 'https://graph.facebook.com/v2.8/me/messages',
        qs: {access_token: process.env.PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: recipientId},
            message: message,
        }
    }, function(error, response, html) {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }
    });
};

// Facebook page with the likes of our app
var url = 'https://www.facebook.com/browse/?type=page_fans&page_id='+pageId;
// Collects the userId's from the page likes html
var getUserIds = function() {
    var userIds = [];
    request(url, function (error, response, html) {
    if (!error && response.statusCode == 200) {
        var $ = cheerio.load(html);
        $('a[data-gt]').each(function(i, a) {
            var gt = JSON.parse(a.dataset.gt);
            if(!gt.engagement || gt.engagement.eng_type !== "1") {
                return;
            }
            console.log(gt.engagement.eng_tid);
            userIds.push(gt.engagement.eng_tid)
        });
    } else {
        console.log('Error retrieving likes: ', error);
    }
    });
};

// Retrieves the timezone of a user
var getUserTimezone = function(userId) {
    request({
        url: 'https://graph.facebook.com/v2.8/' + userId,
        qs: {access_token: process.env.PAGE_ACCESS_TOKEN},
        method: 'GET'
    }, function(error, response, html) {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }
        console.log('*** ' + response.body);
    });
};
<commit_msg>Timezones<commit_after>var express = require('express');
var request = require('request');
var bodyParser = require('body-parser');
var cron = require('node-cron');
var cheerio = require('cheerio');

var app = express();
app.listen((process.env.PORT || 3000));
app.use(bodyParser.urlencoded({extended: false}));
app.use(bodyParser.json());

// Current version
ver = 'v.0.0.19';
// Facebook pageId
pageId = '1167308473348175';
// My user on Facebook
me = '1221242727898531';

// Server endpoint
app.get('/', function (req, res) {
    res.send('JKDbot here! ' + ver);
});

// Facebook webhook
app.get('/webhook', function (req, res) {
    if (req.query['hub.verify_token'] === 'jkdbot_verify_token') {
        res.send(req.query['hub.challenge']);
    } else {
        res.send('Invalid verify token');
    }
});

// Handler receiving messages
app.post('/webhook', function (req, res) {
    var events = req.body.entry[0].messaging;
    for (i = 0; i < events.length; i++) {
        var event = events[i];
        if (event.message && event.message.text) {
            switch(event.message.text) {
                case("cmd_version"):
                    sendMessage(event.sender.id, {text: ver});
                    break;
                case("cmd_subscribers"):
                    sendMessage(event.sender.id, {text: getUserIds()});
                    break;
                case("cmd_timezone"):
                    sendMessage(event.sender.id, {text: getUserTimezone(event.sender.id)});
                    break;
              case("cmd_stop_cron"):
                    task.stop();
                    break;
              case("cmd_stop_restart"):
                    task.start();
                    break;
                default:
                    sendMessage(event.sender.id, {text: "Message received: " + event.message.text + " by " + event.sender.id});
                    break;
            }
        }
    }
    res.sendStatus(200);
});

// Scheduler
var task = cron.schedule('* * 9 * *', function() {
    console.log('Running sendout');
    doSendout();
});
task.start();

// Main task
var doSendout = function() {
    sendMessage(me, {text: "jkdbot here"});
}

// Sends a message to a Facebook user
var sendMessage = function(recipientId, message) {
    request({
        url: 'https://graph.facebook.com/v2.8/me/messages',
        qs: {access_token: process.env.PAGE_ACCESS_TOKEN},
        method: 'POST',
        json: {
            recipient: {id: recipientId},
            message: message,
        }
    }, function(error, response, html) {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }
    });
};

// Facebook page with the likes of our app
var url = 'https://www.facebook.com/browse/?type=page_fans&page_id='+pageId;
// Collects the userId's from the page likes html
var getUserIds = function() {
    var userIds = [];
    request(url, function (error, response, html) {
    if (!error && response.statusCode == 200) {
        var $ = cheerio.load(html);
        $('a[data-gt]').each(function(i, a) {
            var gt = JSON.parse(a.dataset.gt);
            if(!gt.engagement || gt.engagement.eng_type !== "1") {
                return;
            }
            console.log(gt.engagement.eng_tid);
            userIds.push(gt.engagement.eng_tid)
        });
    } else {
        console.log('Error retrieving likes: ', error);
    }
    });
};

// Retrieves the timezone of a user
var getUserTimezone = function(userId) {
    request({
        url: 'https://graph.facebook.com/v2.8/' + userId,
        qs: {access_token: process.env.PAGE_ACCESS_TOKEN},
        method: 'GET'
    }, function(error, response, html) {
        if (error) {
            console.log('Error sending message: ', error);
        } else if (response.body.error) {
            console.log('Error: ', response.body.error);
        }
        console.log('***1 ' + response.body["timezone"]);
    });
};
<|endoftext|>


<commit_before>var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
app.use(express.static(__dirname));
var list = require('badwords-list');
banned = list.array;

var data = fs.readFileSync("config.json", "utf8", function(err, data) {
	if (err) throw err;
});
var config = JSON.parse(data);

app.get('/', function(req, res) {
	var page = fs.readFileSync("./chat.html", "utf8", function(err, data) {
		if (err) throw err;
	});

	page = page.replace("***firebase-url***", config.firebase_url);
	res.send(page);
});

io.on('connection', function(socket){
	socket.on('chat message', function(msg, user, imageLink){
		if(msg == '' || msg == undefined || msg == null) {
			return;
		}

		var allowed = true;
		var m = msg.split(' ');
		m.forEach(function(msg) {
			banned.forEach(function(word) {
				if(wordInString(msg, word)) {
					allowed = false;
				}
			});
		});

		if(allowed == false) {
			return;
		}

		msg = msg.replace(/</g, "<");
		msg = msg.replace(/>/g, ">");

		user = user.replace(/</g, "<");
		user = user.replace(/>/g, ">");

		io.emit('chat message', clean, cleanUser, imageLink);
	});
});

function wordInString(s, word){
	return new RegExp( '\\b' + word + '\\b', 'i').test(s);
}

http.listen(config.port, function(){
    console.log('listening on Port ' + config.port);
});
<commit_msg>Whoops<commit_after>var express = require('express');
var app = express();
var http = require('http').Server(app);
var io = require('socket.io')(http);
var fs = require('fs');
app.use(express.static(__dirname));
var list = require('badwords-list');
banned = list.array;

var data = fs.readFileSync("config.json", "utf8", function(err, data) {
	if (err) throw err;
});
var config = JSON.parse(data);

app.get('/', function(req, res) {
	var page = fs.readFileSync("./chat.html", "utf8", function(err, data) {
		if (err) throw err;
	});

	page = page.replace("***firebase-url***", config.firebase_url);
	res.send(page);
});

io.on('connection', function(socket){
	socket.on('chat message', function(msg, user, imageLink){
		if(msg == '' || msg == undefined || msg == null) {
			return;
		}

		var allowed = true;
		var m = msg.split(' ');
		m.forEach(function(msg) {
			banned.forEach(function(word) {
				if(wordInString(msg, word)) {
					allowed = false;
				}
			});
		});

		if(allowed == false) {
			return;
		}

		msg = msg.replace(/</g, "<");
		msg = msg.replace(/>/g, ">");

		user = user.replace(/</g, "<");
		user = user.replace(/>/g, ">");

		io.emit('chat message', msg, user, imageLink);
	});
});

function wordInString(s, word){
	return new RegExp( '\\b' + word + '\\b', 'i').test(s);
}

http.listen(config.port, function(){
    console.log('listening on Port ' + config.port);
});
<|endoftext|>