diff --git a/Periscope_Web_Client.meta.js b/Periscope_Web_Client.meta.js
index 6679f0b..45cfb94 100644
--- a/Periscope_Web_Client.meta.js
+++ b/Periscope_Web_Client.meta.js
@@ -5,7 +5,7 @@
// @description Periscope client based on API requests. Visit example.net for launch.
// @include https://api.twitter.com/oauth/openperiscope*
// @include http://example.net/*
-// @version 1.2
+// @version 1.3
// @author Pmmlabs@github
// @grant GM_xmlhttpRequest
// @connect periscope.tv
diff --git a/Periscope_Web_Client.user.js b/Periscope_Web_Client.user.js
index d6f8fbb..49e2f82 100644
--- a/Periscope_Web_Client.user.js
+++ b/Periscope_Web_Client.user.js
@@ -1,11 +1,11 @@
-// ==UserScript==
+// ==UserScript==
// @id OpenPeriscope@pmmlabs.ru
// @name Periscope Web Client
// @namespace https://greasyfork.org/users/23
// @description Periscope client based on API requests. Visit example.net for launch.
// @include https://api.twitter.com/oauth/openperiscope*
// @include http://example.net/*
-// @version 1.2
+// @version 1.3
// @author Pmmlabs@github
// @grant GM_xmlhttpRequest
// @connect periscope.tv
@@ -22,7 +22,7 @@
// @noframes
// ==/UserScript==
-var emoji = emoji || new EmojiConvertor(); // js-emoji 3.0 upgrade
+var emoji = new EmojiConvertor();
NODEJS = typeof GM_xmlhttpRequest == 'undefined';
var IMG_PATH = 'https://raw.githubusercontent.com/Pmmlabs/OpenPeriscope/master';
if (NODEJS) { // for NW.js
diff --git a/README.md b/README.md
index ce8696e..ddd49d8 100644
--- a/README.md
+++ b/README.md
@@ -4,7 +4,12 @@ Unofficial in-browser web client for Periscope (userscript)
### Using as standalone application
-If you have [NW.js](http://nwjs.io) installed, you can run ` nw . ` in repo directory
+If you have [NW.js](http://nwjs.io) installed, you can run
+```
+ npm install
+ nw .
+ ```
+ in repo directory
Or, you can use pre-built executables from [Releases page](https://github.com/Pmmlabs/OpenPeriscope/releases)
diff --git a/emoji.js b/emoji.js
index d3979b7..758753c 100644
--- a/emoji.js
+++ b/emoji.js
@@ -1,92 +1,120 @@
-;(function(local_setup) {
+"use strict";
-/**
- * @global
- * @namespace
- */
-function emoji(){}
- /**
- * The set of images to use for graphical emoji.
- *
- * @memberof emoji
- * @type {string}
- */
- emoji.img_set = 'apple';
+;(function() {
- /**
- * Configuration details for different image sets. This includes a path to a directory containing the
- * individual images (`path`) and a URL to sprite sheets (`sheet`). All of these images can be found
- * in the [emoji-data repository]{@link https://github.com/iamcal/emoji-data}. Using a CDN for these
- * is not a bad idea.
- *
- * @memberof emoji
- * @type {
- */
- emoji.img_sets = {
- 'apple' : {'path' : '/emoji-data/img-apple-64/' , 'sheet' : '/emoji-data/sheet_apple_64.png', 'mask' : 1 },
- 'google' : {'path' : '/emoji-data/img-google-64/' , 'sheet' : '/emoji-data/sheet_google_64.png', 'mask' : 2 },
- 'twitter' : {'path' : '/emoji-data/img-twitter-64/' , 'sheet' : '/emoji-data/sheet_twitter_64.png', 'mask' : 4 },
- 'emojione' : {'path' : '/emoji-data/img-emojione-64/', 'sheet' : '/emoji-data/sheet_emojione_64.png', 'mask' : 8 }
- };
+ var root = this;
+ var previous_emoji = root.EmojiConvertor;
- /**
- * Use a CSS class instead of specifying a sprite or background image for
- * the span representing the emoticon. This requires a CSS sheet with
- * emoticon data-uris.
- *
- * @memberof emoji
- * @type bool
- * @todo document how to build the CSS stylesheet this requires.
- */
- emoji.use_css_imgs = false;
/**
- * Instead of replacing emoticons with the appropriate representations,
- * replace them with their colon string representation.
- * @memberof emoji
- * @type bool
+ * @global
+ * @namespace
*/
- emoji.colons_mode = false;
- emoji.text_mode = false;
- /**
- * If true, sets the "title" property on the span or image that gets
- * inserted for the emoticon.
- * @memberof emoji
- * @type bool
- */
- emoji.include_title = false;
+ var emoji = function(){
- /**
- * If the platform supports native emoticons, use those instead
- * of the fallbacks.
- * @memberof emoji
- * @type bool
- */
- emoji.allow_native = true;
+ var self = this;
- /**
- * Set to true to use CSS sprites instead of individual images on
- * platforms that support it.
- *
- * @memberof emoji
- * @type bool
- */
- emoji.use_sheet = false;
+ /**
+ * The set of images to use for graphical emoji.
+ *
+ * @memberof emoji
+ * @type {string}
+ */
+ self.img_set = 'apple';
- /**
- *
- * Set to true to avoid black & white native Windows emoji being used.
- *
- * @memberof emoji
- * @type bool
- */
- emoji.avoid_ms_emoji = true;
+ /**
+ * Configuration details for different image sets. This includes a path to a directory containing the
+ * individual images (`path`) and a URL to sprite sheets (`sheet`). All of these images can be found
+ * in the [emoji-data repository]{@link https://github.com/iamcal/emoji-data}. Using a CDN for these
+ * is not a bad idea.
+ *
+ * @memberof emoji
+ * @type {
+ */
+ self.img_sets = {
+ 'apple' : {'path' : '/emoji-data/img-apple-64/' , 'sheet' : '/emoji-data/sheet_apple_64.png', 'mask' : 1 },
+ 'google' : {'path' : '/emoji-data/img-google-64/' , 'sheet' : '/emoji-data/sheet_google_64.png', 'mask' : 2 },
+ 'twitter' : {'path' : '/emoji-data/img-twitter-64/' , 'sheet' : '/emoji-data/sheet_twitter_64.png', 'mask' : 4 },
+ 'emojione' : {'path' : '/emoji-data/img-emojione-64/', 'sheet' : '/emoji-data/sheet_emojione_64.png', 'mask' : 8 }
+ };
+
+ /**
+ * Use a CSS class instead of specifying a sprite or background image for
+ * the span representing the emoticon. This requires a CSS sheet with
+ * emoticon data-uris.
+ *
+ * @memberof emoji
+ * @type bool
+ * @todo document how to build the CSS stylesheet self requires.
+ */
+ self.use_css_imgs = false;
+
+ /**
+ * Instead of replacing emoticons with the appropriate representations,
+ * replace them with their colon string representation.
+ * @memberof emoji
+ * @type bool
+ */
+ self.colons_mode = false;
+ self.text_mode = false;
+
+ /**
+ * If true, sets the "title" property on the span or image that gets
+ * inserted for the emoticon.
+ * @memberof emoji
+ * @type bool
+ */
+ self.include_title = false;
+
+ /**
+ * If the platform supports native emoticons, use those instead
+ * of the fallbacks.
+ * @memberof emoji
+ * @type bool
+ */
+ self.allow_native = true;
+
+ /**
+ * Set to true to use CSS sprites instead of individual images on
+ * platforms that support it.
+ *
+ * @memberof emoji
+ * @type bool
+ */
+ self.use_sheet = false;
+
+ /**
+ *
+ * Set to true to avoid black & white native Windows emoji being used.
+ *
+ * @memberof emoji
+ * @type bool
+ */
+ self.avoid_ms_emoji = true;
+
+ /**
+ *
+ * Set to true to allow :CAPITALIZATION:
+ *
+ * @memberof emoji
+ * @type bool
+ */
+ self.allow_caps = false;
+
+ // Keeps track of what has been initialized.
+ /** @private */
+ self.inits = {};
+ self.map = {};
+
+ return self;
+ }
+
+ emoji.prototype.noConflict = function(){
+ root.EmojiConvertor = previous_emoji;
+ return emoji;
+ }
- // Keeps track of what has been initialized.
- /** @private */
- emoji.inits = {};
- emoji.map = {};
/**
* @memberof emoji
@@ -97,12 +125,10 @@ function emoji(){}
* replaced by a representatation that's supported by the current
* environtment.
*/
- emoji.replace_emoticons = function(str){
- emoji.init_emoticons();
- return str.replace(emoji.rx_emoticons, function(m, $1, $2){
- var val = emoji.map.emoticons[$2];
- return val ? $1+emoji.replacement(val, $2) : m;
- });
+ emoji.prototype.replace_emoticons = function(str){
+ var self = this;
+ var colonized = self.replace_emoticons_with_colons(str);
+ return self.replace_colons(colonized);
};
/**
@@ -113,12 +139,63 @@ function emoji(){}
* @returns {string} A new string with all emoticons in `str`
* replaced by their colon string representations (ie. `:smile:`)
*/
- emoji.replace_emoticons_with_colons = function(str){
- emoji.init_emoticons();
- return str.replace(emoji.rx_emoticons, function(m, $1, $2){
- var val = emoji.data[emoji.map.emoticons[$2]][3][0];
+ emoji.prototype.replace_emoticons_with_colons = function(str){
+ var self = this;
+ self.init_emoticons();
+ var _prev_offset = 0;
+ var emoticons_with_parens = [];
+ var str_replaced = str.replace(self.rx_emoticons, function(m, $1, emoticon, offset){
+ var prev_offset = _prev_offset;
+ _prev_offset = offset + m.length;
+
+ var has_open_paren = emoticon.indexOf('(') !== -1;
+ var has_close_paren = emoticon.indexOf(')') !== -1;
+
+ /*
+ * Track paren-having emoticons for fixing later
+ */
+ if ((has_open_paren || has_close_paren) && emoticons_with_parens.indexOf(emoticon) == -1) {
+ emoticons_with_parens.push(emoticon);
+ }
+
+ /*
+ * Look for preceding open paren for emoticons that contain a close paren
+ * This prevents matching "8)" inside "(around 7 - 8)"
+ */
+ if (has_close_paren && !has_open_paren) {
+ var piece = str.substring(prev_offset, offset);
+ if (piece.indexOf('(') !== -1 && piece.indexOf(')') === -1) return m;
+ }
+
+ /*
+ * See if we're in a numbered list
+ * This prevents matching "8)" inside "7) foo\n8) bar"
+ */
+ if (m === '\n8)') {
+ var before_match = str.substring(0, offset);
+ if (/\n?(6\)|7\))/.test(before_match)) return m;
+ }
+
+ var val = self.data[self.map.emoticons[emoticon]][3][0];
return val ? $1+':'+val+':' : m;
});
+
+ /*
+ * Come back and fix emoticons we ignored because they were inside parens.
+ * It's useful to do self at the end so we don't get tripped up by other,
+ * normal emoticons
+ */
+ if (emoticons_with_parens.length) {
+ var escaped_emoticons = emoticons_with_parens.map(self.escape_rx);
+ var parenthetical_rx = new RegExp('(\\(.+)('+escaped_emoticons.join('|')+')(.+\\))', 'g');
+
+ str_replaced = str_replaced.replace(parenthetical_rx, function(m, $1, emoticon, $2) {
+ var val = self.data[self.map.emoticons[emoticon]][3][0];
+ return val ? $1+':'+val+':'+$2 : m;
+ });
+ }
+
+ return str_replaced;
};
/**
@@ -129,34 +206,36 @@ function emoji(){}
* @returns {string} A new string with all colon string emoticons replaced
* with the appropriate representation.
*/
- emoji.replace_colons = function(str){
- emoji.init_colons();
+ emoji.prototype.replace_colons = function(str){
+ var self = this;
+ self.init_colons();
- return str.replace(emoji.rx_colons, function(m){
+ return str.replace(self.rx_colons, function(m){
var idx = m.substr(1, m.length-2);
+ if (self.allow_caps) idx = idx.toLowerCase();
// special case - an emoji with a skintone modified
if (idx.indexOf('::skin-tone-') > -1){
var skin_tone = idx.substr(-1, 1);
var skin_idx = 'skin-tone-'+skin_tone;
- var skin_val = emoji.map.colons[skin_idx];
+ var skin_val = self.map.colons[skin_idx];
idx = idx.substr(0, idx.length - 13);
- var val = emoji.map.colons[idx];
+ var val = self.map.colons[idx];
if (val){
- return emoji.replacement(val, idx, ':', {
+ return self.replacement(val, idx, ':', {
'idx' : skin_val,
'actual' : skin_idx,
'wrapper' : ':'
});
}else{
- return ':' + idx + ':' + emoji.replacement(skin_val, skin_idx, ':');
+ return ':' + idx + ':' + self.replacement(skin_val, skin_idx, ':');
}
}else{
- var val = emoji.map.colons[idx];
- return val ? emoji.replacement(val, idx, ':') : m;
+ var val = self.map.colons[idx];
+ return val ? self.replacement(val, idx, ':') : m;
}
});
};
@@ -169,10 +248,11 @@ function emoji(){}
* @returns {string} A new string with all unicode emoticons replaced with
* the appropriate representation for the current environment.
*/
- emoji.replace_unified = function(str){
- emoji.init_unified();
- return str.replace(emoji.rx_unified, function(m, p1, p2){
- var val = emoji.map.unified[p1];
+ emoji.prototype.replace_unified = function(str){
+ var self = this;
+ self.init_unified();
+ return str.replace(self.rx_unified, function(m, p1, p2){
+ var val = self.map.unified[p1];
if (!val) return m;
var idx = null;
if (p2 == '\uD83C\uDFFB') idx = '1f3fb';
@@ -181,19 +261,20 @@ function emoji(){}
if (p2 == '\uD83C\uDFFE') idx = '1f3fe';
if (p2 == '\uD83C\uDFFF') idx = '1f3ff';
if (idx){
- return emoji.replacement(val, null, null, {
+ return self.replacement(val, null, null, {
idx : idx,
actual : p2,
wrapper : ''
});
}
- return emoji.replacement(val);
+ return self.replacement(val);
});
};
// Does the actual replacement of a character with the appropriate
/** @private */
- emoji.replacement = function(idx, actual, wrapper, variation){
+ emoji.prototype.replacement = function(idx, actual, wrapper, variation){
+ var self = this;
// for emoji with variation modifiers, set `etxra` to the standalone output for the
// modifier (used if we can't combine the glyph) and set variation_idx to key of the
@@ -201,55 +282,64 @@ function emoji(){}
var extra = '';
var variation_idx = 0;
if (typeof variation === 'object'){
- extra = emoji.replacement(variation.idx, variation.actual, variation.wrapper);
+ extra = self.replacement(variation.idx, variation.actual, variation.wrapper);
variation_idx = idx + '-' + variation.idx;
}
+ var img_set = self.img_set;
+
+ // When not using sheets (which all contain all emoji),
+ // make sure we use an img_set that contains this emoji.
+ // For now, assume set "apple" has all individual images.
+ if ((!self.use_sheet || !self.supports_css) && !(self.data[idx][6] & self.img_sets[self.img_set].mask)) {
+ img_set = 'apple';
+ }
+
// deal with simple modes (colons and text) first
wrapper = wrapper || '';
- if (emoji.colons_mode) return ':'+emoji.data[idx][3][0]+':'+extra;
- var text_name = (actual) ? wrapper+actual+wrapper : emoji.data[idx][8] || wrapper+emoji.data[idx][3][0]+wrapper;
- if (emoji.text_mode) return text_name + extra;
+ if (self.colons_mode) return ':'+self.data[idx][3][0]+':'+extra;
+ var text_name = (actual) ? wrapper+actual+wrapper : self.data[idx][8] || wrapper+self.data[idx][3][0]+wrapper;
+ if (self.text_mode) return text_name + extra;
// native modes next.
// for variations selectors, we just need to output them raw, which `extra` will contain.
- emoji.init_env();
- if (emoji.replace_mode == 'unified' && emoji.allow_native && emoji.data[idx][0][0]) return emoji.data[idx][0][0] + extra;
- if (emoji.replace_mode == 'softbank' && emoji.allow_native && emoji.data[idx][1]) return emoji.data[idx][1] + extra;
- if (emoji.replace_mode == 'google' && emoji.allow_native && emoji.data[idx][2]) return emoji.data[idx][2] + extra;
+ self.init_env();
+ if (self.replace_mode == 'unified' && self.allow_native && self.data[idx][0][0]) return self.data[idx][0][0] + extra;
+ if (self.replace_mode == 'softbank' && self.allow_native && self.data[idx][1]) return self.data[idx][1] + extra;
+ if (self.replace_mode == 'google' && self.allow_native && self.data[idx][2]) return self.data[idx][2] + extra;
// finally deal with image modes.
// variation selectors are more complex here - if the image set and particular emoji supports variations, then
// use the variation image. otherwise, return it as a separate image (already calculated in `extra`).
// first we set up the params we'll use if we can't use a variation.
- var img = emoji.data[idx][7] || emoji.img_sets[emoji.img_set].path+idx+'.png';
- var title = emoji.include_title ? ' title="'+(actual || emoji.data[idx][3][0])+'"' : '';
- var text = emoji.include_text ? wrapper+(actual || emoji.data[idx][3][0])+wrapper : '';
- var px = emoji.data[idx][4];
- var py = emoji.data[idx][5];
+ var img = self.data[idx][7] || self.img_sets[img_set].path+idx+'.png';
+ var title = self.include_title ? ' title="'+(actual || self.data[idx][3][0])+'"' : '';
+ var text = self.include_text ? wrapper+(actual || self.data[idx][3][0])+wrapper : '';
+ var px = self.data[idx][4];
+ var py = self.data[idx][5];
// now we'll see if we can use a varition. if we can, we can override the params above and blank
// out `extra` so we output a sinlge glyph.
// we need to check that:
// * we requested a variation
// * such a variation exists in `emoji.variations_data`
- // * we're not using a custom image for this glyph
+ // * we're not using a custom image for self glyph
// * the variation has an image defined for the current image set
- if (variation_idx && emoji.variations_data[variation_idx] && emoji.variations_data[variation_idx][2] && !emoji.data[idx][9]){
- if (emoji.variations_data[variation_idx][2] & emoji.img_sets[emoji.img_set].mask){
- img = emoji.img_sets[emoji.img_set].path+variation_idx+'.png';
- px = emoji.variations_data[variation_idx][0];
- py = emoji.variations_data[variation_idx][1];
+ if (variation_idx && self.variations_data[variation_idx] && self.variations_data[variation_idx][2] && !self.data[idx][7]){
+ if (self.variations_data[variation_idx][2] & self.img_sets[self.img_set].mask){
+ img = self.img_sets[self.img_set].path+variation_idx+'.png';
+ px = self.variations_data[variation_idx][0];
+ py = self.variations_data[variation_idx][1];
extra = '';
}
}
- if (emoji.supports_css) {
- if (emoji.use_sheet && px != null && py != null){
- var mul = 100 / (emoji.sheet_size - 1);
- var style = 'background: url('+emoji.img_sets[emoji.img_set].sheet+');background-position:'+(mul*px)+'% '+(mul*py)+'%;background-size:'+emoji.sheet_size+'00%';
+ if (self.supports_css) {
+ if (self.use_sheet && px != null && py != null){
+ var mul = 100 / (self.sheet_size - 1);
+ var style = 'background: url('+self.img_sets[img_set].sheet+');background-position:'+(mul*px)+'% '+(mul*py)+'%;background-size:'+self.sheet_size+'00%';
return ''+text+''+extra;
- }else if (emoji.use_css_imgs){
+ }else if (self.use_css_imgs){
return ''+text+''+extra;
}else{
return ''+text+''+extra;
@@ -260,52 +350,55 @@ function emoji(){}
// Initializes the text emoticon data
/** @private */
- emoji.init_emoticons = function(){
- if (emoji.inits.emoticons) return;
- emoji.init_colons(); // we require this for the emoticons map
- emoji.inits.emoticons = 1;
+ emoji.prototype.init_emoticons = function(){
+ var self = this;
+ if (self.inits.emoticons) return;
+ self.init_colons(); // we require this for the emoticons map
+ self.inits.emoticons = 1;
var a = [];
- emoji.map.emoticons = {};
- for (var i in emoji.emoticons_data){
+ self.map.emoticons = {};
+ for (var i in self.emoticons_data){
// because we never see some characters in our text except as entities, we must do some replacing
var emoticon = i.replace(/\&/g, '&').replace(/\/g, '>');
- if (!emoji.map.colons[emoji.emoticons_data[i]]) continue;
+ if (!self.map.colons[self.emoticons_data[i]]) continue;
- emoji.map.emoticons[emoticon] = emoji.map.colons[emoji.emoticons_data[i]];
- a.push(emoji.escape_rx(emoticon));
+ self.map.emoticons[emoticon] = self.map.colons[self.emoticons_data[i]];
+ a.push(self.escape_rx(emoticon));
}
- emoji.rx_emoticons = new RegExp(('(^|\\s)('+a.join('|')+')(?=$|[\\s|\\?\\.,!])'), 'g');
+ self.rx_emoticons = new RegExp(('(^|\\s)('+a.join('|')+')(?=$|[\\s|\\?\\.,!])'), 'g');
};
// Initializes the colon string data
/** @private */
- emoji.init_colons = function(){
- if (emoji.inits.colons) return;
- emoji.inits.colons = 1;
- emoji.rx_colons = new RegExp('\:[a-zA-Z0-9-_+]+\:(\:skin-tone-[2-6]\:)?', 'g');
- emoji.map.colons = {};
- for (var i in emoji.data){
- for (var j=0; j