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

  var user = module.parent.require('./user'),
    meta = module.parent.require('./meta'),
    db = module.parent.require('../src/database'),
    passport = module.parent.require('passport'),
    passportClientCert = require('passport-client-cert').Strategy,
    nconf = module.parent.require('nconf'),
    async = module.parent.require('async'),
    winston = module.parent.require('winston');

  var constants = Object.freeze({
    'name': 'ClientCert',
    'icon': 'icon-client-cert-auth'
  });

  var ClientCert = {};

  ClientCert.getStrategy = function(strategies, callback) {
    passport.use(new passportClientCert({ passReqToCallback: true }, function(req, cert, done) {
      var subject = cert.subject;

      if (!subject) {
        winston.error('[sso-client-cert] Client Certificate Subject missing!');
        return done(null, false);
      } else if (!subject.CN) {
        winston.error('[sso-client-cert] Client Certificate CN missing!');
        return done(null, false);
      }

      if (req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') && req.user.uid > 0) {
        // Save cert CN to the user
        user.setUserField(req.user.uid, 'certcn', subject.CN);
        db.setObjectField('certcn:uid', subject.CN, req.user.uid);
        return done(null, req.user);
      }

      var name = subject.GN.charAt(0).toUpperCase() + subject.GN.slice(1).toLowerCase();
      var email = cert.subjectaltname.split('email:')[1];

      ClientCert.login(subject.CN, name, email, function(err, user) {
        if (err) {
          return done(err);
        }

        done(null, user);
      });
    }));

    strategies.push({
      name: 'ClientCert',
      url: '/auth/client-cert',
      callbackURL: '/auth/client-cert/callback',
      icon: constants.icon,
      scope: 'email'
    });

    callback(null, strategies);
  };

  ClientCert.getAssociation = function(data, callback) {
    user.getUserField(data.uid, 'certcn', function(err, CN) {
      if (err) {
        return callback(err, data);
      }

      if (CN) {
        data.associations.push({
          associated: true,
          name: constants.name,
          icon: constants.icon
        });
      } else {
        data.associations.push({
          associated: false,
          url: nconf.get('url') + '/auth/client-cert',
          name: constants.name,
          icon: constants.icon
        });
      }

      callback(null, data);
    })
  };

  ClientCert.login = function(CN, name, email, callback) {
    ClientCert.getUidByCertCN(CN, function(err, uid) {
      if(err) {
        return callback(err);
      }

      if (uid !== null) {
        // Existing User
        callback(null, {
          uid: uid
        });
      } else {
        // New User
        var success = function(uid) {
          // Save cert CN to the user
          user.setUserField(uid, 'certcn', CN);
          db.setObjectField('certcn:uid', CN, uid);
          user.setUserField(uid, 'email:confirmed', 1);

          callback(null, {
            uid: uid
          });
        };

        user.getUidByEmail(email, function(err, uid) {
          if(err) {
            return callback(err);
          }

          if (!uid) {
            user.create({username: name, email: email}, function(err, uid) {
              if(err) {
                return callback(err);
              }

              success(uid);
            });
          } else {
            success(uid); // Existing account -- merge
          }
        });
      }
    });
  };

  ClientCert.getUidByCertCN = function(CN, callback) {
    db.getObjectField('certcn:uid', CN, function(err, uid) {
      if (err) {
        return callback(err);
      }
      callback(null, uid);
    });
  };

  ClientCert.deleteUserData = function(data, callback) {
    var uid = data.uid;

    async.waterfall([
      async.apply(user.getUserField, uid, 'certcn'),
      function(CNToDelete, next) {
        db.deleteObjectField('certcn:uid', CNToDelete, next);
      }
    ], function(err) {
      if (err) {
        winston.error('[sso-client-cert] Could not remove cert CN for uid ' + uid + '. Error: ' + err);
        return callback(err);
      }
      callback(null, uid);
    });
  };

  module.exports = ClientCert;
}(module));<commit_msg>Small update<commit_after>(function(module) {
  'use strict';

  var user = module.parent.require('./user'),
    meta = module.parent.require('./meta'),
    db = module.parent.require('../src/database'),
    passport = module.parent.require('passport'),
    passportClientCert = require('passport-client-cert').Strategy,
    nconf = module.parent.require('nconf'),
    async = module.parent.require('async'),
    winston = module.parent.require('winston');

  var constants = Object.freeze({
    'name': 'client-cert',
    'icon': 'icon-client-cert-auth'
  });

  var ClientCert = {};

  ClientCert.getStrategy = function(strategies, callback) {
    passport.use(constants.name, new passportClientCert({
        passReqToCallback: true,
        renegotiation: true
    }, function(req, cert, done) {
      var subject = cert.subject;

      if (!subject) {
        winston.error('[sso-client-cert] Client Certificate Subject missing!');
        return done(null, false);
      } else if (!subject.CN) {
        winston.error('[sso-client-cert] Client Certificate CN missing!');
        return done(null, false);
      }

      if (req.hasOwnProperty('user') && req.user.hasOwnProperty('uid') && req.user.uid > 0) {
        // Save cert CN to the user
        user.setUserField(req.user.uid, 'certcn', subject.CN);
        db.setObjectField('certcn:uid', subject.CN, req.user.uid);
        return done(null, req.user);
      }

      var name = subject.GN.charAt(0).toUpperCase() + subject.GN.slice(1).toLowerCase();
      var email = cert.subjectaltname.split('email:')[1];

      ClientCert.login(subject.CN, name, email, function(err, user) {
        if (err) {
          return done(err);
        }

        done(null, user);
      });
    }));

    strategies.push({
      name: 'client-cert',
      url: '/auth/client-cert',
      callbackURL: '/auth/client-cert/callback',
      icon: constants.icon,
      scope: ''
    });

    callback(null, strategies);
  };

  ClientCert.getAssociation = function(data, callback) {
    user.getUserField(data.uid, 'certcn', function(err, CN) {
      if (err) {
        return callback(err, data);
      }

      if (CN) {
        data.associations.push({
          associated: true,
          name: constants.name,
          icon: constants.icon
        });
      } else {
        data.associations.push({
          associated: false,
          url: nconf.get('url') + '/auth/client-cert',
          name: constants.name,
          icon: constants.icon
        });
      }

      callback(null, data);
    })
  };

  ClientCert.login = function(CN, name, email, callback) {
    ClientCert.getUidByCertCN(CN, function(err, uid) {
      if(err) {
        return callback(err);
      }

      if (uid !== null) {
        // Existing User
        callback(null, {
          uid: uid
        });
      } else {
        // New User
        var success = function(uid) {
          // Save cert CN to the user
          user.setUserField(uid, 'certcn', CN);
          db.setObjectField('certcn:uid', CN, uid);
          user.setUserField(uid, 'email:confirmed', 1);

          callback(null, {
            uid: uid
          });
        };

        user.getUidByEmail(email, function(err, uid) {
          if(err) {
            return callback(err);
          }

          if (!uid) {
            user.create({username: name, email: email}, function(err, uid) {
              if(err) {
                return callback(err);
              }

              success(uid);
            });
          } else {
            success(uid); // Existing account -- merge
          }
        });
      }
    });
  };

  ClientCert.getUidByCertCN = function(CN, callback) {
    db.getObjectField('certcn:uid', CN, function(err, uid) {
      if (err) {
        return callback(err);
      }
      callback(null, uid);
    });
  };

  ClientCert.deleteUserData = function(data, callback) {
    var uid = data.uid;

    async.waterfall([
      async.apply(user.getUserField, uid, 'certcn'),
      function(CNToDelete, next) {
        db.deleteObjectField('certcn:uid', CNToDelete, next);
      }
    ], function(err) {
      if (err) {
        winston.error('[sso-client-cert] Could not remove cert CN for uid ' + uid + '. Error: ' + err);
        return callback(err);
      }
      callback(null, uid);
    });
  };

  module.exports = ClientCert;
}(module));<|endoftext|>


<commit_before>"use strict";

var plugin = {},
    fields = [
        'website',
        'location',
        'birthday',
        'signature',
        'aboutme'
    ],
	meta = module.parent.require('./meta');

plugin.init = function(params, callback) {
	var app = params.router,
		middleware = params.middleware,
		controllers = params.controllers;

	app.get('/admin/registration-profile-field', middleware.admin.buildHeader, renderAdmin);
	app.get('/api/admin/registration-profile-field', renderAdmin);

	callback();
};

plugin.addAdminNavigation = function(header, callback) {
	header.plugins.push({
		route: '/registration-profile-field',
		icon: 'fa-tint',
		name: 'Registration Profile Field'
	});

	callback(null, header);
};

plugin.addCaptcha = function(params, callback) {
	var field = meta.config['registration-profile-field:question'];
    if (field === 'aboutme') {
        var html = '<textarea class="form-control" name="registration-profile-field" id="registration-profile-field"></textarea>';
    } else {
        var html = '<input class="form-control" name="registration-profile-field" id="registration-profile-field" />';
    }

	var captcha = {
        label: '[[user:' + field + ']]',
		html: html
	};

	if (params.templateData.regFormEntry && Array.isArray(params.templateData.regFormEntry)) {
		params.templateData.regFormEntry.push(captcha);
	} else {
		params.templateData.captcha = captcha;
	}

	callback(null, params);
};

plugin.createUser = function(params, callback) {
	var field = meta.config['registration-profile-field:question'];
    var fieldData = params.data['registration-profile-field'];
    var userData = params.user;
    userData[field] = fieldData;
    /* if (!field || !fieldData) {
        callback(*/
    callback(null, userData);

};

function renderAdmin(req, res, next) {
	res.render('admin/registration-profile-field', {fields:fields});
}

module.exports = plugin;
<commit_msg>Cleared code<commit_after>"use strict";

var plugin = {},
    fields = [
        'website',
        'location',
        'birthday',
        'signature',
        'aboutme'
    ],
	meta = module.parent.require('./meta');

plugin.init = function(params, callback) {
	var app = params.router,
		middleware = params.middleware,
		controllers = params.controllers;

	app.get('/admin/registration-profile-field', middleware.admin.buildHeader, renderAdmin);
	app.get('/api/admin/registration-profile-field', renderAdmin);

	callback();
};

plugin.addAdminNavigation = function(header, callback) {
	header.plugins.push({
		route: '/registration-profile-field',
		icon: 'fa-tint',
		name: 'Registration Profile Field'
	});

	callback(null, header);
};

plugin.addCaptcha = function(params, callback) {
	var field = meta.config['registration-profile-field:question'];
    if (field === 'aboutme') {
        var html = '<textarea class="form-control" name="registration-profile-field" id="registration-profile-field"></textarea>';
    } else {
        var html = '<input class="form-control" name="registration-profile-field" id="registration-profile-field" />';
    }

	var captcha = {
        label: '[[user:' + field + ']]',
		html: html
	};

	if (params.templateData.regFormEntry && Array.isArray(params.templateData.regFormEntry)) {
		params.templateData.regFormEntry.push(captcha);
	} else {
		params.templateData.captcha = captcha;
	}

	callback(null, params);
};

plugin.createUser = function(params, callback) {
	var field = meta.config['registration-profile-field:question'];
    var fieldData = params.data['registration-profile-field'];
    var userData = params.user;
    userData[field] = fieldData;
    callback(null, userData);

};

function renderAdmin(req, res, next) {
	res.render('admin/registration-profile-field', {fields:fields});
}

module.exports = plugin;
<|endoftext|>


<commit_before>var fs = require('fsplus');
var path = require('path');
var geci = require('geci');
var open = require('open');
var home = require('home');
var mkdirp = require('mkdirp');
var Player = require('player');
var color = require('colorful');
var consoler = require('consoler');
var termList = require('term-list-enhanced');

var sdk = require('./sdk');
var utils = require('./utils');
var pkg = require('../package');
var errors = require('./errors');
var template = require('./template');

// Keypress shortcut list
var shorthands = {
  'return': 'play',
  'backspace': 'stop',
  'g': 'go',
  'l': 'loving',
  'n': 'next',
  'q': 'quit',
  's': 'share',
  'r': 'showLrc'
};

module.exports = Fm;

// Class Douban.fm
function Fm() {
  // Fetch user's home
  this.USERHOME = home();

  // Resolve config files' path
  this.path = {};
  this.path.profile = home.resolve('~/.douban.fm.profile.json');
  this.path.history = home.resolve('~/.douban.fm.history.json');

  // Read configs from JSON files
  try {
    this.profile = fs.readJSON(this.path.profile);
  } catch (err) {
    // Ingore missing profile
  }

  // Get music download folder as `this.home`
  this.home = this.profile ?
    this.profile.home :
    home.resolve('~/douban.fm');

  // Get favourite music download folder
  this.love = path.join(this.home, 'love');

  // Get `http_proxy` options
  this.http_proxy = this.profile ?
    this.profile.http_proxy :
    null;

  // Disable Lrc by default
  this.isShowLrc = false;

  // Update UI
  template.updateTab('Douban FM');

  // Ensure music download folder exists,
  // If not, Mkdir of it.
  try {
    mkdirp.sync(this.love);
  } catch (err) {
    consoler.error(errors.setup_fail);
    throw err;
  }
};

Fm.prototype.go = go;
Fm.prototype.init = init;
Fm.prototype.play = play;
Fm.prototype.next = next;
Fm.prototype.stop = stop;
Fm.prototype.quit = quit;
Fm.prototype.fetch = fetch;
Fm.prototype.loving = loving;
Fm.prototype.share = share;
Fm.prototype.showLrc = showLrc;
Fm.prototype.createMenu = createMenu;

/**
 * [Fetch songs and add them to playlist]
 * @param  {Object}   channel
 * @param  {Object}   account
 * @param  {Function}   callback
 * @return {Object}
 */
function fetch(channel, account, callback) {
  var self = this;

  var query = {};
  query.kbps = 192;
  query.history = self.path.history;
  query.channel = channel.channel_id;
  query.local = isChannel('local', channel.channel_id) ? self.home : false;

  if (account) {
    query.token = account.token;
    query.user_id = account.user_id;
    query.expire = account.expire;
  }

  return sdk.songs(query, utils.isFunction(callback) ? callback : cb);

  function cb(err, songs, result) {
    if (err) return;
    if (!songs || songs.length === 0) return;
    if (!self.player) return;

    songs.forEach(function(song) {
      self.player.add(song);
    });
  }
}

/**
 * [Playing songs when everything is ready]
 * @param  {Object} channel
 * @param  {Object} account
 * @return {}
 */
function play(channel, account) {
  var self = this;
  var menu = self.menu;
  var isVaildAccount = account && account.token;
  var privateMhz = isChannel('private', channel.channel_id) && !isVaildAccount;

  // Check if this kind of mHz is private
  if (privateMhz)
    return menu.update('header', errors.account_missing);

  // Clear last label
  if (self.status === 'fetching' || self.status === 'downloading')
    return;

  if (self.status === 'playing' || self.status === 'error') {
    if (typeof(self.channel) != undefined) menu.clear(self.channel);
    if (self.player) {
      self.player.stop();
      delete self.player;
    }
  }

  // Clear label status
  menu.clear('header');
  self.channel = channel.index;
  self.status = 'fetching';
  menu.update(channel.index, template.listing());

  try {
    fs.updateJSON(self.path.profile, { lastChannel: channel });
  } catch (err) {};

  // Start fetching songs
  self.fetch(channel, account, function(err, songs, result) {
    if (err) {
      self.status = 'error';
      return menu.update(channel.index, color.red(err.toString()));
    }

    // Mark a `PRO` label on logo
    if (result && !result.warning)
      menu.update('header', color.inverse(' PRO '));

    self.status = 'ready';
    self.player = new Player(songs, {
      src: 'url',
      cache: true,
      downloads: self.home,
      http_proxy: self.http_proxy
    });

    self.player.play();
    self.player.on('downloading', onDownloading);
    self.player.on('playing', onPlaying);

    function onDownloading(url) {
      self.status = 'downloading';
      menu.update(channel.index, template.loading());
    }

    function onPlaying(song) {
      var isValidSong = song.title && song.sid;
      self.status = 'playing';
      // Update playing label
      menu.update('header', color.green('>'));
      // Update song infomation
      menu.update(channel.index, template.song(song));
      // Logging songs history
      if (isValidSong) {
        var updates = {};
        updates[song.sid] = song;
        try {
          fs.updateJSON(self.path.history, updates);
        } catch (err) {
          // Errors must be logged in a private place.
        }
      }

      // Print LRC if needed.
      if (self.isShowLrc) {
        if (self.lrc)
          self.lrc.stop();

        geci.fetch(song, printLrc);

        function printLrc(err, lrc) {
          if (err)
            return menu.update('header', color.grey(errors.lrc_notfound + err.toString()));
          if (!lrc)
            return menu.update('header', color.grey(errors.lrc_notfound));

          self.lrc = geci.print(lrc, function(line, extra) {
            menu.update(channel.index, template.song(song, line));
          });
        }
      }

      // TODO: Still trying after failed two times.
      if (song._id < self.player.list.length - 1)
        return false;

      return self.fetch(channel, account);
    }
  });
}

/**
*
* [Add current song to lovelist when pressing `L`]
* @param channel {Object}
* @param account {Object}
*
**/
function loving(channel, account) {
  if (!this.player)
    return false;
  if (!this.player.playing)
    return false;
  if (!this.player.playing.sid)
    return this.menu.update('header', errors.love_fail);
  if (!account)
    return this.menu.update('header', errors.account_missing);

  var self = this;
  var menu = self.menu;
  var song = self.player.playing;
  var query = {
    sid: song.sid,
    channel: self.channel,
    user_id: account.user_id,
    expire: account.expire,
    token: account.token
  };

  if (song.like)
    query.type = 'u';

  menu.update('header', '正在加载...');

  sdk.love(query, function(err, result) {
    menu.clear('header');
    if (err)
      menu.update('header', errors.normal);
    if (!err)
      self.player.playing.like = !song.like;

    return menu.update(
      self.channel,
      // keep silence, do not notify
      template.song(self.player.playing, null, true)
    );
  });
}

/**
*
* Play the next song in the playlist
* @param channel {Object}
* @param account {Object}
*
**/
function next(channel, account) {
  if (!this.player)
    return false;

  var menu = this.menu;

  this.player.next(function(err, song) {
    if (err) menu.update('header', errors.last_song);
    return false;
  });
}

/**
*
* [Stop playing,]
* [And show the stopped status on logo.]
* @param channel {Object}
* @param account {Object}
*
**/
function stop(channel, account) {
  if (!this.player)
    return false;
  if (this.status === 'stopped')
    return this.play(channel, account);

  var menu = this.menu;
  menu.clear('header');
  menu.update('header', template.pause());
  this.status = 'stopped';

  return this.player.stop();
}

/**
*
* [Quit the Fm,]
* [And kill the process when pressing `Q`]
*
**/
function quit() {
  this.menu.stop();
  return process.exit();
}

/**
*
* [Goto the music album page when pressing `G`]
* @param {Object} channel
* @param {Object} account
*
**/
function go(channel, account) {
  if (!this.player)
    return false;
  if (!this.player.playing)
    return false;
  if (channel.channel_id == -99)
    return false;

  return open(
    utils.album(this.player.playing.album)
  );
}

/**
*
* [Show lrc when when pressing `R`.]
* @param channel {Object}
* @param account {Object}
*
**/
function showLrc(channel, account) {
  if (channel.channel_id == -99)
    return false;

  this.isShowLrc = !!!this.isShowLrc;
  this.menu.clear('header');
  this.menu.update('header', this.isShowLrc ? '歌词开启' : '歌词关闭');

  return false;
}

/**
*
* [Share the current playing songs to Weibo when pressing `S`.]
* @param channel {Object}
* @param account {Object}
*
**/
function share(channel, account) {
  if (!this.player || !this.player.playing)
    return false;

  return open(
    template.share(this.player.playing)
  );
}

/**
*
* [Create command line interface menu,]
* [Using term-list-enhanced module]
* @param {Function} callback [The callback function when all set done]
*
**/
function createMenu(callback) {
  var self = this;

  // Fetch channels
  sdk.fm.channels(function(err, list) {
    if (err)
      consoler.error(errors.turn_to_local_mode);

    // Fetch configs, Show user's infomation
    fs.readJSON(self.path.profile, function(e, user) {
      var vaildAccount = user && user.account && user.account.user_name;
      var account = vaildAccount ? user.account : null;

      // Init menu
      self.menu = new termList();
      self.menu.header(template.logo(account));
      self.menu.adds(
        [sdk.mhz.localMhz].concat(!err ? [sdk.mhz.privateMhz].concat(list) : [])
      );

      // Bind keypress events
      self.menu.on('keypress', function(key, index) {
        if (!shorthands[key.name])
          return false;

        return self[shorthands[key.name]](self.menu.items[index], account);
      });

      self.menu.on('empty', function() {
        self.menu.stop();
      });

      // Check last played channel,
      // If it existed, play this channel instantly.
      if (user && user.lastChannel) {
        self.play(user.lastChannel, account);
        self.menu.start(user.lastChannel.index);
        return false;
      }

      // Start menu at line 2,
      // Which below the logo.
      self.menu.start(1);
    });
  });

  // Trigger callback if necessary.
  return callback && callback();
}

/**
*
* Init douban.fm command line interface.
* @param {Function} callback [The callback function when all set done]
*
**/
function init(callback) {
  var self = this;

  fs.exists(self.home, function(exist) {
    if (exist)
      return self.createMenu(callback);

    mkdirp(self.love, function(err) {
      if (err)
        return consoler.error(errors.mkdir_fail);

      return self.createMenu(callback);
    });
  });
}

/**
 * [Check if a object is channel object]
 * @param  {String}  alias [The channel type]
 * @param  {Int}     id    [The channel ID]
 * @return {Boolean}
 */
function isChannel(alias, id) {
  if (alias === 'local' && id == -99)
    return true;
  if (alias === 'private' && (id == 0 || id == -3))
    return true;

  return false;
}
<commit_msg>bug fix 配置项没有配置下载目录时，会报错<commit_after>var fs = require('fsplus');
var path = require('path');
var geci = require('geci');
var open = require('open');
var home = require('home');
var mkdirp = require('mkdirp');
var Player = require('player');
var color = require('colorful');
var consoler = require('consoler');
var termList = require('term-list-enhanced');

var sdk = require('./sdk');
var utils = require('./utils');
var pkg = require('../package');
var errors = require('./errors');
var template = require('./template');

// Keypress shortcut list
var shorthands = {
  'return': 'play',
  'backspace': 'stop',
  'g': 'go',
  'l': 'loving',
  'n': 'next',
  'q': 'quit',
  's': 'share',
  'r': 'showLrc'
};

module.exports = Fm;

// Class Douban.fm
function Fm() {
  // Fetch user's home
  this.USERHOME = home();

  // Resolve config files' path
  this.path = {};
  this.path.profile = home.resolve('~/.douban.fm.profile.json');
  this.path.history = home.resolve('~/.douban.fm.history.json');

  // Read configs from JSON files
  try {
    this.profile = fs.readJSON(this.path.profile);
  } catch (err) {
    // Ingore missing profile
  }

  // Get music download folder as `this.home`
  this.home = this.profile && this.profile.home ?
    this.profile.home :
    home.resolve('~/douban.fm');

  // Get favourite music download folder
  this.love = path.join(this.home, 'love');

  // Get `http_proxy` options
  this.http_proxy = this.profile ?
    this.profile.http_proxy :
    null;

  // Disable Lrc by default
  this.isShowLrc = false;

  // Update UI
  template.updateTab('Douban FM');

  // Ensure music download folder exists,
  // If not, Mkdir of it.
  try {
    mkdirp.sync(this.love);
  } catch (err) {
    consoler.error(errors.setup_fail);
    throw err;
  }
};

Fm.prototype.go = go;
Fm.prototype.init = init;
Fm.prototype.play = play;
Fm.prototype.next = next;
Fm.prototype.stop = stop;
Fm.prototype.quit = quit;
Fm.prototype.fetch = fetch;
Fm.prototype.loving = loving;
Fm.prototype.share = share;
Fm.prototype.showLrc = showLrc;
Fm.prototype.createMenu = createMenu;

/**
 * [Fetch songs and add them to playlist]
 * @param  {Object}   channel
 * @param  {Object}   account
 * @param  {Function}   callback
 * @return {Object}
 */
function fetch(channel, account, callback) {
  var self = this;

  var query = {};
  query.kbps = 192;
  query.history = self.path.history;
  query.channel = channel.channel_id;
  query.local = isChannel('local', channel.channel_id) ? self.home : false;

  if (account) {
    query.token = account.token;
    query.user_id = account.user_id;
    query.expire = account.expire;
  }

  return sdk.songs(query, utils.isFunction(callback) ? callback : cb);

  function cb(err, songs, result) {
    if (err) return;
    if (!songs || songs.length === 0) return;
    if (!self.player) return;

    songs.forEach(function(song) {
      self.player.add(song);
    });
  }
}

/**
 * [Playing songs when everything is ready]
 * @param  {Object} channel
 * @param  {Object} account
 * @return {}
 */
function play(channel, account) {
  var self = this;
  var menu = self.menu;
  var isVaildAccount = account && account.token;
  var privateMhz = isChannel('private', channel.channel_id) && !isVaildAccount;

  // Check if this kind of mHz is private
  if (privateMhz)
    return menu.update('header', errors.account_missing);

  // Clear last label
  if (self.status === 'fetching' || self.status === 'downloading')
    return;

  if (self.status === 'playing' || self.status === 'error') {
    if (typeof(self.channel) != undefined) menu.clear(self.channel);
    if (self.player) {
      self.player.stop();
      delete self.player;
    }
  }

  // Clear label status
  menu.clear('header');
  self.channel = channel.index;
  self.status = 'fetching';
  menu.update(channel.index, template.listing());

  try {
    fs.updateJSON(self.path.profile, { lastChannel: channel });
  } catch (err) {};

  // Start fetching songs
  self.fetch(channel, account, function(err, songs, result) {
    if (err) {
      self.status = 'error';
      return menu.update(channel.index, color.red(err.toString()));
    }

    // Mark a `PRO` label on logo
    if (result && !result.warning)
      menu.update('header', color.inverse(' PRO '));

    self.status = 'ready';
    self.player = new Player(songs, {
      src: 'url',
      cache: true,
      downloads: self.home,
      http_proxy: self.http_proxy
    });

    self.player.play();
    self.player.on('downloading', onDownloading);
    self.player.on('playing', onPlaying);

    function onDownloading(url) {
      self.status = 'downloading';
      menu.update(channel.index, template.loading());
    }

    function onPlaying(song) {
      var isValidSong = song.title && song.sid;
      self.status = 'playing';
      // Update playing label
      menu.update('header', color.green('>'));
      // Update song infomation
      menu.update(channel.index, template.song(song));
      // Logging songs history
      if (isValidSong) {
        var updates = {};
        updates[song.sid] = song;
        try {
          fs.updateJSON(self.path.history, updates);
        } catch (err) {
          // Errors must be logged in a private place.
        }
      }

      // Print LRC if needed.
      if (self.isShowLrc) {
        if (self.lrc)
          self.lrc.stop();

        geci.fetch(song, printLrc);

        function printLrc(err, lrc) {
          if (err)
            return menu.update('header', color.grey(errors.lrc_notfound + err.toString()));
          if (!lrc)
            return menu.update('header', color.grey(errors.lrc_notfound));

          self.lrc = geci.print(lrc, function(line, extra) {
            menu.update(channel.index, template.song(song, line));
          });
        }
      }

      // TODO: Still trying after failed two times.
      if (song._id < self.player.list.length - 1)
        return false;

      return self.fetch(channel, account);
    }
  });
}

/**
*
* [Add current song to lovelist when pressing `L`]
* @param channel {Object}
* @param account {Object}
*
**/
function loving(channel, account) {
  if (!this.player)
    return false;
  if (!this.player.playing)
    return false;
  if (!this.player.playing.sid)
    return this.menu.update('header', errors.love_fail);
  if (!account)
    return this.menu.update('header', errors.account_missing);

  var self = this;
  var menu = self.menu;
  var song = self.player.playing;
  var query = {
    sid: song.sid,
    channel: self.channel,
    user_id: account.user_id,
    expire: account.expire,
    token: account.token
  };

  if (song.like)
    query.type = 'u';

  menu.update('header', '正在加载...');

  sdk.love(query, function(err, result) {
    menu.clear('header');
    if (err)
      menu.update('header', errors.normal);
    if (!err)
      self.player.playing.like = !song.like;

    return menu.update(
      self.channel,
      // keep silence, do not notify
      template.song(self.player.playing, null, true)
    );
  });
}

/**
*
* Play the next song in the playlist
* @param channel {Object}
* @param account {Object}
*
**/
function next(channel, account) {
  if (!this.player)
    return false;

  var menu = this.menu;

  this.player.next(function(err, song) {
    if (err) menu.update('header', errors.last_song);
    return false;
  });
}

/**
*
* [Stop playing,]
* [And show the stopped status on logo.]
* @param channel {Object}
* @param account {Object}
*
**/
function stop(channel, account) {
  if (!this.player)
    return false;
  if (this.status === 'stopped')
    return this.play(channel, account);

  var menu = this.menu;
  menu.clear('header');
  menu.update('header', template.pause());
  this.status = 'stopped';

  return this.player.stop();
}

/**
*
* [Quit the Fm,]
* [And kill the process when pressing `Q`]
*
**/
function quit() {
  this.menu.stop();
  return process.exit();
}

/**
*
* [Goto the music album page when pressing `G`]
* @param {Object} channel
* @param {Object} account
*
**/
function go(channel, account) {
  if (!this.player)
    return false;
  if (!this.player.playing)
    return false;
  if (channel.channel_id == -99)
    return false;

  return open(
    utils.album(this.player.playing.album)
  );
}

/**
*
* [Show lrc when when pressing `R`.]
* @param channel {Object}
* @param account {Object}
*
**/
function showLrc(channel, account) {
  if (channel.channel_id == -99)
    return false;

  this.isShowLrc = !!!this.isShowLrc;
  this.menu.clear('header');
  this.menu.update('header', this.isShowLrc ? '歌词开启' : '歌词关闭');

  return false;
}

/**
*
* [Share the current playing songs to Weibo when pressing `S`.]
* @param channel {Object}
* @param account {Object}
*
**/
function share(channel, account) {
  if (!this.player || !this.player.playing)
    return false;

  return open(
    template.share(this.player.playing)
  );
}

/**
*
* [Create command line interface menu,]
* [Using term-list-enhanced module]
* @param {Function} callback [The callback function when all set done]
*
**/
function createMenu(callback) {
  var self = this;

  // Fetch channels
  sdk.fm.channels(function(err, list) {
    if (err)
      consoler.error(errors.turn_to_local_mode);

    // Fetch configs, Show user's infomation
    fs.readJSON(self.path.profile, function(e, user) {
      var vaildAccount = user && user.account && user.account.user_name;
      var account = vaildAccount ? user.account : null;

      // Init menu
      self.menu = new termList();
      self.menu.header(template.logo(account));
      self.menu.adds(
        [sdk.mhz.localMhz].concat(!err ? [sdk.mhz.privateMhz].concat(list) : [])
      );

      // Bind keypress events
      self.menu.on('keypress', function(key, index) {
        if (!shorthands[key.name])
          return false;

        return self[shorthands[key.name]](self.menu.items[index], account);
      });

      self.menu.on('empty', function() {
        self.menu.stop();
      });

      // Check last played channel,
      // If it existed, play this channel instantly.
      if (user && user.lastChannel) {
        self.play(user.lastChannel, account);
        self.menu.start(user.lastChannel.index);
        return false;
      }

      // Start menu at line 2,
      // Which below the logo.
      self.menu.start(1);
    });
  });

  // Trigger callback if necessary.
  return callback && callback();
}

/**
*
* Init douban.fm command line interface.
* @param {Function} callback [The callback function when all set done]
*
**/
function init(callback) {
  var self = this;

  fs.exists(self.home, function(exist) {
    if (exist)
      return self.createMenu(callback);

    mkdirp(self.love, function(err) {
      if (err)
        return consoler.error(errors.mkdir_fail);

      return self.createMenu(callback);
    });
  });
}

/**
 * [Check if a object is channel object]
 * @param  {String}  alias [The channel type]
 * @param  {Int}     id    [The channel ID]
 * @return {Boolean}
 */
function isChannel(alias, id) {
  if (alias === 'local' && id == -99)
    return true;
  if (alias === 'private' && (id == 0 || id == -3))
    return true;

  return false;
}
<|endoftext|>


<commit_before>// +--------------------------------------+
// | douban.fm                192kbps PRO |
// |--------------------------------------|
// |                                      |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// |                                      |
// |                          +---+ +-+-+ |
// | a tiny and smart cli     |+++| | + | |
// | player based on Node.js  +---+ +-+-+ |
// |                                      |
// +--------------------------------------+

var fs = require('fs'),
    path = require('path'),
    _ = require('underscore'),
    mkdirp = require('mkdirp'),
    Player = require('player'),
    color = require('colorful'),
    List = require('term-list'),
    printf = require('sprintf').sprintf,
    params = require('paramrule'),
    consoler = require('consoler'),
    exeq = require('exeq'),
    sys = require('../package'),
    sdk = require('./sdk'),
    utils = require('./utils'),
    errors = require('./errors');

var shorthands = {
    'return': 'play',
    'backspace': 'stop',
    'g': 'go',
    'l': 'loving',
    'n': 'next',
    'q': 'quit',
    's': 'share'
};

var Fm = function(params) {
    this.home = params && params.home ? params.home : path.join(utils.home(), 'douban.fm');
    this.love = path.join(this.home, 'love');
    this.shorthands = shorthands;
};

Fm.prototype.play = function(channel, user) {

    var self = this,
        account = user && user.account ? user.account : {},
        privateHz = (channel.channel_id == 0 || channel.channel_id == -3) && !account.token;

    // 检查是否是私人兆赫，如果没有设置账户直接返回
    if (privateHz) return self.update(channel.index, color.yellow(errors.account_missing));
    if (self.status === 'fetching' || self.status === 'downloading') return false;
    if (self.status === 'playing') {
        if (typeof(self.channel) != undefined) self.update(self.channel, '');
        self.player.stop();
        self.player.status = 'stoped';
        self.player = null;
    }

    self.clear(-1, color.yellow('||'));
    self.clear(-1, color.yellow('>>'));
    self.channel = channel.index;
    self.status = 'fetching';
    self.update(channel.index, color.grey('加载列表中，请稍等...'));

    // 获取相应频道的曲目
    sdk.fetch({
        channel: channel.channel_id,
        user_id: account.user_id,
        expire: account.expire,
        token: account.token,
        kbps: 192
    }, function(err, songs, result) {
        if (err) return self.update(channel.index, color.red(err.toString()));
        if (result && !result.warning) self.label(-1, color.inverse(' PRO '));
        self.status = 'ready';
        self.player = new Player(songs, {
            srckey: 'url',
            downloads: self.home
        });
        self.player.play();
        // 同步下载不太好，但是在解决 stream 的无法获取抛错之前没有好办法
        self.player.on('downloading', function(url) {
            self.status = 'downloading';
            self.update(channel.index, color.grey('下载歌曲中，请稍等...'));
        });
        // 更新歌单
        self.player.on('playing', function(song) {
            self.status = 'playing';
            self.label(-1, color.yellow('>>'));
            self.update(
                channel.index,
                printf(
                    '%s %s %s %s %s %s %s %s',
                    song.like == 1 ? color.red('♥') : color.grey('♥'),
                    color.green(song.title),
                    color.grey(song.kbps + 'kbps'),
                    color.grey('... ♪ ♫ ♫ ♪ ♫ ♫ ♪ ♪ ...'),
                    color.yellow(song.albumtitle),
                    color.grey('•'),
                    song.artist,
                    color.grey(song.public_time)
                )
            );
            if (song._id < self.player.list.length - 1) return false;
            self.status = 'switching';
            return sdk.fetch({
                channel: channel.channel_id,
                user_id: account.user_id,
                expire: account.expire,
                token: account.token,
                kbps: 192
            }, function(err, songs) {
                if (err) return false;
                // 没有对尝试获取列表失败进行处理，如果失败2次，则不会再播放任何歌曲
                if (!songs) return false;
                return songs.forEach(function(s, index) {
                    s._id = self.player.list.length;
                    self.player.add(s);
                });
            });
        });
    });
}

Fm.prototype.loving = function(channel, user) {
    if (!this.player) return false;
    if (!this.player.playing) return false;
    if (!user || !user.account) return false;
    var self = this;
    var account = user && user.account ? user.account : {};
    var song = self.player.playing;
    var query = {
        sid: song.sid,
        channel: self.channel,
        user_id: account.user_id,
        expire: account.expire,
        token: account.token
    };
    if (song.like) query.type = 'u';
    self.label(-1, '正在加载...');
    sdk.love(query, function(err, result) {
        var tips = !(song.like) ? color.red('♥') : color.grey('♥');
        if (err) tips = color.red('x');
        if (!err) self.player.playing.like = !song.like;
        // TODO: 这里有冗余代码
        self.clear(-1, '正在加载...');
        return self.update(
            self.channel,
            printf(
                '%s %s %s %s %s %s %s %s',
                tips,
                color.green(song.title),
                color.grey(song.kbps + 'kbps'),
                color.grey('... ♪ ♫ ♫ ♪ ♫ ♫ ♪ ♪ ...'),
                color.yellow(song.albumtitle),
                color.grey('•'),
                song.artist,
                color.grey(song.public_time)
            )
        );
    });
}

Fm.prototype.next = function() {
    if (!this.player) return false;
    return this.player.next();
}

Fm.prototype.stop = function() {
    if (!this.player) return false;
    this.clear(-1, color.yellow('>>'));
    this.label(-1, color.yellow('||'));
    return this.player.stop();
}

Fm.prototype.quit = function() {
    if (this.menu) this.menu.stop();
    return process.exit();
}

Fm.prototype.label = function(index, banner) {
    if (!this.menu) return false;
    var original = this.menu.at(index + 2).label;
    if (original.indexOf(banner) > -1) return false;
    this.menu.at(index + 2).label = original + ' ' + banner;
    return this.redraw();
}

Fm.prototype.update = function(index, banner) {
    if (!this.menu) return false;
    if (!this.channels[index]) return this.label(index, banner);
    this.menu.at(index + 2).label = this.channels[index].name + ' ' + banner;
    return this.redraw();
}

Fm.prototype.clear = function(index, banner) {
    if (!this.menu) return false;
    var item = this.menu.at(index + 2);
    if (item.label.indexOf(banner) === -1) return false;
    item.label = item.label.substr(0, item.label.indexOf(' ' + banner));
    return this.redraw();
}

Fm.prototype.redraw = function() {
    if (!this.menu) return false;
    this.menu.draw();
    return false;
}

Fm.prototype.album = function(link) {
    if (!link) return false;
    return link.indexOf('http') === -1 ? 'http://music.douban.com' + link : link;
}

Fm.prototype.go = function(channel, user, link) {
    if (!this.player) return false;
    if (!this.player.playing) return false;
    return exeq([
        'open ' + (link ? link : this.album(this.player.playing.album))
    ]).run();
}

Fm.prototype.share = function(channel, user) {
    if (!this.player) return false;
    if (!this.player.playing) return false;
    var self = this;
    var song = self.player.playing;
    return self.go(null, null,
        'http://service.weibo.com/share/share.php?' +
        '&type=button' +
        '&style=number' +
        '&appkey=5rjNpN' +
        '&ralateUid=1644105187' +
        '&url=' +
        sys.repository.url +
        '&pic=' +
        (song.picture ? song.picture.replace('mpic', 'lpic') : '') +
        '%7C%7C' +
        'http://ww1.sinaimg.cn/large/61ff0de3tw1ecij3dq80bj20m40ez75u.jpg' +
        '&title=' +
        encodeURIComponent(
            [
                '我正在用豆瓣电台命令行版 v' + sys.version + ' 收听 ',
                song.like ? '[心]' : '',
                song.title,
                song.kbps + 'kbps',
                '... ♪ ♫ ♫ ♪ ♫ ♫ ♪ ♪ ...',
                song.albumtitle,
                '•',
                song.artist,
                song.public_time,
                self.album(song.album)
            ].join(' ')
        )
    );
}

Fm.prototype.createMenu = function(callback) {
    var self = this;
    var shorthands = self.shorthands;
    sdk.channels(function(err, list) {
        if (err) return consoler.error('获取豆瓣电台频道出错，请稍后再试');
        self.configs(function(err, user) {
            // init menu
            self.channels = {};
            self.menu = new List({
                marker: '\033[36m› \033[0m',
                markerLength: 2
            });
            // add padding-top
            self.menu.add(-2, '');
            // add logo
            self.menu.add(-1, printf(
                '%s %s %s',
                color.yellow('Douban FM'),
                color.grey('v' + sys.version),
                user && user.account && user.account.user_name ?
                color.grey('/ ' + user.account.user_name) :
                ''
            ));
            // add channels
            _.each(list, function(channel, index) {
                if (index > 15) return false; // 屏幕里放不下那么多电台的 -,-||
                channel.index = index;
                self.menu.add(index, channel.name);
                self.channels[index] = channel;
            });
            // start menu
            self.menu.start();
            self.menu.select(-1);
            // bind events
            self.menu.on('keypress', function(key, index) {
                if (!shorthands[key.name]) return false;
                if (index < 0 && key.name != 'q') return exeq(['open ' + sys.repository.url]).run();
                return self[shorthands[key.name]](self.channels[index], user);
            });
            self.menu.on('empty', function() {
                menu.stop();
            });
        });
    });
    if (callback && typeof(callback) === 'function') return callback();
};

Fm.prototype.auth = function(params, callback) {
    var self = this;
    sdk.auth(params, function(err, user) {
        if (err) return callback(err);
        self.configs({
            account: {
                email: user.email,
                token: user.token,
                expire: user.expire,
                user_name: user.user_name,
                user_id: user.user_id
            }
        }, callback);
    });
};

Fm.prototype.configs = function() {
    var self = this;
    params.parse(arguments, ['', '*'], function(params, callback) {
        if (!params) {
            // read configs
            fs.readFile(path.join(self.home, '.configs.json'), function(err, data) {
                if (err) return callback(err, null);
                try {
                    callback(err, JSON.parse(data));
                } catch (err) {
                    callback(err);
                }
            });
        } else {
            // save params
            fs.writeFile(path.join(self.home, '.configs.json'), JSON.stringify(params), function(err) {
                callback(err, params);
            });
        }
    });
};

// init player
Fm.prototype.init = function(callback) {
    var self = this;
    fs.exists(self.home, function(exist) {
        if (exist) return self.createMenu(callback);
        mkdirp(self.love, function(err) {
            if (err) return consoler.error('创建歌曲文件夹出错，请检查权限');
            return self.createMenu(callback);
        });
    })
};

Fm.prototype.sdk = sdk;

exports = module.exports = Fm;
<commit_msg>分享模块更新支持windows<commit_after>// +--------------------------------------+
// | douban.fm                192kbps PRO |
// |--------------------------------------|
// |                                      |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// | ++++++++++++++++++++++++++++++++++++ |
// |                                      |
// |                          +---+ +-+-+ |
// | a tiny and smart cli     |+++| | + | |
// | player based on Node.js  +---+ +-+-+ |
// |                                      |
// +--------------------------------------+

var fs = require('fs'),
    path = require('path'),
    _ = require('underscore'),
    mkdirp = require('mkdirp'),
    Player = require('player'),
    color = require('colorful'),
    List = require('term-list'),
    printf = require('sprintf').sprintf,
    params = require('paramrule'),
    consoler = require('consoler'),
    exeq = require('exeq'),
    sys = require('../package'),
    sdk = require('./sdk'),
    utils = require('./utils'),
    errors = require('./errors'),
    openBrowser = process.platform === 'win32' ? 'start' : 'open';

var shorthands = {
    'return': 'play',
    'backspace': 'stop',
    'g': 'go',
    'l': 'loving',
    'n': 'next',
    'q': 'quit',
    's': 'share'
};

var Fm = function(params) {
    this.home = params && params.home ? params.home : path.join(utils.home(), 'douban.fm');
    this.love = path.join(this.home, 'love');
    this.shorthands = shorthands;
};

Fm.prototype.play = function(channel, user) {

    var self = this,
        account = user && user.account ? user.account : {},
        privateHz = (channel.channel_id == 0 || channel.channel_id == -3) && !account.token;

    // 检查是否是私人兆赫，如果没有设置账户直接返回
    if (privateHz) return self.update(channel.index, color.yellow(errors.account_missing));
    if (self.status === 'fetching' || self.status === 'downloading') return false;
    if (self.status === 'playing') {
        if (typeof(self.channel) != undefined) self.update(self.channel, '');
        self.player.stop();
        self.player.status = 'stoped';
        self.player = null;
    }

    self.clear(-1, color.yellow('||'));
    self.clear(-1, color.yellow('>>'));
    self.channel = channel.index;
    self.status = 'fetching';
    self.update(channel.index, color.grey('加载列表中，请稍等...'));

    // 获取相应频道的曲目
    sdk.fetch({
        channel: channel.channel_id,
        user_id: account.user_id,
        expire: account.expire,
        token: account.token,
        kbps: 192
    }, function(err, songs, result) {
        if (err) return self.update(channel.index, color.red(err.toString()));
        if (result && !result.warning) self.label(-1, color.inverse(' PRO '));
        self.status = 'ready';
        self.player = new Player(songs, {
            srckey: 'url',
            downloads: self.home
        });
        self.player.play();
        // 同步下载不太好，但是在解决 stream 的无法获取抛错之前没有好办法
        self.player.on('downloading', function(url) {
            self.status = 'downloading';
            self.update(channel.index, color.grey('下载歌曲中，请稍等...'));
        });
        // 更新歌单
        self.player.on('playing', function(song) {
            self.status = 'playing';
            self.label(-1, color.yellow('>>'));
            self.update(
                channel.index,
                printf(
                    '%s %s %s %s %s %s %s %s',
                    song.like == 1 ? color.red('♥') : color.grey('♥'),
                    color.green(song.title),
                    color.grey(song.kbps + 'kbps'),
                    color.grey('... ♪ ♫ ♫ ♪ ♫ ♫ ♪ ♪ ...'),
                    color.yellow(song.albumtitle),
                    color.grey('•'),
                    song.artist,
                    color.grey(song.public_time)
                )
            );
            if (song._id < self.player.list.length - 1) return false;
            self.status = 'switching';
            return sdk.fetch({
                channel: channel.channel_id,
                user_id: account.user_id,
                expire: account.expire,
                token: account.token,
                kbps: 192
            }, function(err, songs) {
                if (err) return false;
                // 没有对尝试获取列表失败进行处理，如果失败2次，则不会再播放任何歌曲
                if (!songs) return false;
                return songs.forEach(function(s, index) {
                    s._id = self.player.list.length;
                    self.player.add(s);
                });
            });
        });
    });
}

Fm.prototype.loving = function(channel, user) {
    if (!this.player) return false;
    if (!this.player.playing) return false;
    if (!user || !user.account) return false;
    var self = this;
    var account = user && user.account ? user.account : {};
    var song = self.player.playing;
    var query = {
        sid: song.sid,
        channel: self.channel,
        user_id: account.user_id,
        expire: account.expire,
        token: account.token
    };
    if (song.like) query.type = 'u';
    self.label(-1, '正在加载...');
    sdk.love(query, function(err, result) {
        var tips = !(song.like) ? color.red('♥') : color.grey('♥');
        if (err) tips = color.red('x');
        if (!err) self.player.playing.like = !song.like;
        // TODO: 这里有冗余代码
        self.clear(-1, '正在加载...');
        return self.update(
            self.channel,
            printf(
                '%s %s %s %s %s %s %s %s',
                tips,
                color.green(song.title),
                color.grey(song.kbps + 'kbps'),
                color.grey('... ♪ ♫ ♫ ♪ ♫ ♫ ♪ ♪ ...'),
                color.yellow(song.albumtitle),
                color.grey('•'),
                song.artist,
                color.grey(song.public_time)
            )
        );
    });
}

Fm.prototype.next = function() {
    if (!this.player) return false;
    return this.player.next();
}

Fm.prototype.stop = function() {
    if (!this.player) return false;
    this.clear(-1, color.yellow('>>'));
    this.label(-1, color.yellow('||'));
    return this.player.stop();
}

Fm.prototype.quit = function() {
    if (this.menu) this.menu.stop();
    return process.exit();
}

Fm.prototype.label = function(index, banner) {
    if (!this.menu) return false;
    var original = this.menu.at(index + 2).label;
    if (original.indexOf(banner) > -1) return false;
    this.menu.at(index + 2).label = original + ' ' + banner;
    return this.redraw();
}

Fm.prototype.update = function(index, banner) {
    if (!this.menu) return false;
    if (!this.channels[index]) return this.label(index, banner);
    this.menu.at(index + 2).label = this.channels[index].name + ' ' + banner;
    return this.redraw();
}

Fm.prototype.clear = function(index, banner) {
    if (!this.menu) return false;
    var item = this.menu.at(index + 2);
    if (item.label.indexOf(banner) === -1) return false;
    item.label = item.label.substr(0, item.label.indexOf(' ' + banner));
    return this.redraw();
}

Fm.prototype.redraw = function() {
    if (!this.menu) return false;
    this.menu.draw();
    return false;
}

Fm.prototype.album = function(link) {
    if (!link) return false;
    return link.indexOf('http') === -1 ? 'http://music.douban.com' + link : link;
}

Fm.prototype.go = function(channel, user, link) {
    if (!this.player) return false;
    if (!this.player.playing) return false;
    return exeq([
        openBrowser + ' ' + (link ? link : this.album(this.player.playing.album))
    ]).run();
}

Fm.prototype.share = function(channel, user) {
    if (!this.player) return false;
    if (!this.player.playing) return false;
    var self = this;
    var song = self.player.playing;

    var shareText = 'http://service.weibo.com/share/share.php?' +
        '&type=button' +
        '&style=number' +
        '&appkey=5rjNpN' +
        '&ralateUid=1644105187' +
        '&url=' +
        sys.repository.url +
        '&pic=' +
        (song.picture ? song.picture.replace('mpic', 'lpic') : '') +
        '%7C%7C' +
        'http://ww1.sinaimg.cn/large/61ff0de3tw1ecij3dq80bj20m40ez75u.jpg' +
        '&title=' +
        encodeURIComponent(
            [
                '我正在用豆瓣电台命令行版 v' + sys.version + ' 收听 ',
                song.like ? '[心]' : '',
                song.title,
                song.kbps + 'kbps',
                '... ♪ ♫ ♫ ♪ ♫ ♫ ♪ ♪ ...',
                song.albumtitle,
                '•',
                song.artist,
                song.public_time,
                self.album(song.album)
            ].join(' ')
        );

    if(process.platform === 'win32'){
        //win 下终端 & 需要转义
        shareText = shareText.replace(/&/g, '^&');
    }

    return self.go(null, null, shareText);
}

Fm.prototype.createMenu = function(callback) {
    var self = this;
    var shorthands = self.shorthands;
    sdk.channels(function(err, list) {
        if (err) return consoler.error('获取豆瓣电台频道出错，请稍后再试');
        self.configs(function(err, user) {
            // init menu
            self.channels = {};
            self.menu = new List({
                marker: '\033[36m› \033[0m',
                markerLength: 2
            });
            // add padding-top
            self.menu.add(-2, '');
            // add logo
            self.menu.add(-1, printf(
                '%s %s %s',
                color.yellow('Douban FM'),
                color.grey('v' + sys.version),
                user && user.account && user.account.user_name ?
                color.grey('/ ' + user.account.user_name) :
                ''
            ));
            // add channels
            _.each(list, function(channel, index) {
                if (index > 15) return false; // 屏幕里放不下那么多电台的 -,-||
                channel.index = index;
                self.menu.add(index, channel.name);
                self.channels[index] = channel;
            });
            // start menu
            self.menu.start();
            self.menu.select(-1);
            // bind events
            self.menu.on('keypress', function(key, index) {
                if (!shorthands[key.name]) return false;
                if (index < 0 && key.name != 'q') return exeq(['open ' + sys.repository.url]).run();
                return self[shorthands[key.name]](self.channels[index], user);
            });
            self.menu.on('empty', function() {
                menu.stop();
            });
        });
    });
    if (callback && typeof(callback) === 'function') return callback();
};

Fm.prototype.auth = function(params, callback) {
    var self = this;
    sdk.auth(params, function(err, user) {
        if (err) return callback(err);
        self.configs({
            account: {
                email: user.email,
                token: user.token,
                expire: user.expire,
                user_name: user.user_name,
                user_id: user.user_id
            }
        }, callback);
    });
};

Fm.prototype.configs = function() {
    var self = this;
    params.parse(arguments, ['', '*'], function(params, callback) {
        if (!params) {
            // read configs
            fs.readFile(path.join(self.home, '.configs.json'), function(err, data) {
                if (err) return callback(err, null);
                try {
                    callback(err, JSON.parse(data));
                } catch (err) {
                    callback(err);
                }
            });
        } else {
            // save params
            fs.writeFile(path.join(self.home, '.configs.json'), JSON.stringify(params), function(err) {
                callback(err, params);
            });
        }
    });
};

// init player
Fm.prototype.init = function(callback) {
    var self = this;
    fs.exists(self.home, function(exist) {
        if (exist) return self.createMenu(callback);
        mkdirp(self.love, function(err) {
            if (err) return consoler.error('创建歌曲文件夹出错，请检查权限');
            return self.createMenu(callback);
        });
    })
};

Fm.prototype.sdk = sdk;

exports = module.exports = Fm;
<|endoftext|>


<commit_before>var util = require('util');

var noflo     = require('noflo');
var Port      = noflo.Port;
var Component = noflo.Component;

var Lemox = require('lemox');

var Select = function() {
  var self = this;

  self.inPorts = {
    "in": new Port(),
    node: new Port()
  };
  self.outPorts = {
    out:   new Port(),
    error: new Port()
  };

  var parser;
  self.inPorts.in.on('data', function (data) {
    parser.write(data);
  });
  Component.call(this);

  self.inPorts.in.on('disconnect', function () {
    parser.end();
  });
  self.inPorts.node.on('data', function (data) {
    parser = new Lemox({ selector: data });
    parser.on('readable', function () {
      self.outPorts.out.send(parser.read());
    });
    parser.on('end', function () {
      self.outPorts.out.disconnect();
    });
    parser.on('error', function (err) {
      self.outPorts.error.send(err);
    });
  });
};

util.inherits(Select, Component);

exports.getComponent = function() {
  return new Select();
};
<commit_msg>make Select component async<commit_after>var util = require('util');

var noflo     = require('noflo');
var Port      = noflo.Port;
var Component = noflo.AsyncComponent;

var Lemox = require('lemox');

var Select = function() {
  var self = this;

  self.inPorts = {
    "in": new Port(),
    node: new Port()
  };
  self.outPorts = {
    out:   new Port(),
    error: new Port()
  };
  var parser;
  self.doAsync = function(xml, done) {
    parser.write(xml);
  };

  Component.call(this);

  self.inPorts.in.on('disconnect', function () {
    parser.end();
  });
  self.inPorts.node.on('data', function (data) {
    parser = new Lemox({ selector: data });
    parser.on('readable', function () {
      self.outPorts.out.send(parser.read());
    });
    parser.on('end', function () {
      self.outPorts.out.disconnect();
    });
    parser.on('error', function (err) {
      self.outPorts.error.send(err);
    });
  });
};

util.inherits(Select, Component);

exports.getComponent = function() {
  return new Select();
};
<|endoftext|>


<commit_before>(function () {

L.Handler.MarkerSnap = L.Handler.extend({
    options: {
        snapDistance: 15, // in pixels
        snapVertices: true
    },

    initialize: function (map, marker, options) {
        L.Handler.prototype.initialize.call(this, map);
        this._markers = [];
        this._guides = [];

        if (arguments.length == 2) {
            if (!(marker instanceof L.Class)) {
                options = marker;
                marker = null;
            }
        }

        L.Util.setOptions(this, options || {});

        if (marker) {
            // new markers should be draggable !
            if (!marker.dragging) marker.dragging = new L.Handler.MarkerDrag(marker);
            marker.dragging.enable();
            this.watchMarker(marker);
        }

        // Convert snap distance in pixels into buffer in degres, for searching around mouse
        // It changes at each zoom change.
        function computeBuffer() {
            this._buffer = map.layerPointToLatLng(new L.Point(0,0)).lat -
                           map.layerPointToLatLng(new L.Point(this.options.snapDistance, 0)).lat;
        }
        map.on('zoomend', computeBuffer, this);
        map.whenReady(computeBuffer, this);
        computeBuffer.call(this);
    },

    enable: function () {
        this.disable();
        for (var i=0; i<this._markers.length; i++) {
            this.watchMarker(this._markers[i]);
        }
    },

    disable: function () {
        for (var i=0; i<this._markers.length; i++) {
            this.unwatchMarker(this._markers[i]);
        }
    },

    watchMarker: function (marker) {
        if (this._markers.indexOf(marker) == -1)
            this._markers.push(marker);
        marker.on('move', this._snapMarker, this);
    },

    unwatchMarker: function (marker) {
        marker.off('move', this._snapMarker, this);
        delete marker['snap'];
    },

    addGuideLayer: function (layer) {
        this._guides.push(layer);
    },

    _snapMarker: function(e) {
        var marker = e.target,
            latlng = marker.getLatLng(),
            snaplist = [];

        function processGuide(guide) {
            if ((guide._layers !== undefined) &&
                (typeof guide.searchBuffer !== 'function')) {
                // Guide is a layer group and has no L.LayerIndexMixin (from Leaflet.LayerIndex)
                for (var id in guide._layers) {
                    processGuide(guide._layers[id]);
                }
            }
            else if (typeof guide.searchBuffer === 'function') {
                // Search snaplist around mouse
                snaplist = snaplist.concat(guide.searchBuffer(latlng, this._buffer));
            }
            else {
                snaplist.push(guide);
            }
        }

        for (var i=0, n = this._guides.length; i < n; i++) {
            var guide = this._guides[i];
            processGuide.call(this, guide);
        }

        var closest = L.GeometryUtil.closestLayerSnap(this._map,
                                                      snaplist,
                                                      latlng,
                                                      this.options.snapDistance,
                                                      this.options.snapVertices);
        closest = closest || {layer: null, latlng: null};
        this._updateSnap(marker, closest.layer, closest.latlng);
    },

    _updateSnap: function (marker, layer, latlng) {
        if (layer && latlng) {
            marker._latlng = L.latLng(latlng);
            marker.update();
            if (marker.snap != layer) {
                marker.snap = layer;
                L.DomUtil.addClass(marker._icon, 'marker-snapped');
                marker.fire('snap', {layer:layer, latlng: latlng});
            }
        }
        else {
            if (marker.snap) {
                L.DomUtil.removeClass(marker._icon, 'marker-snapped');
                marker.fire('unsnap', {layer:marker.snap});
            }
            delete marker['snap'];
        }
    }
});

if (!L.Edit.Poly) {
    // Leaflet.Draw not available.
    return;
}

L.Handler.PolylineSnap = L.Edit.Poly.extend({

    initialize: function (map, poly, options) {
        L.Edit.Poly.prototype.initialize.call(this, poly, options);
        this._snapper = new L.Handler.MarkerSnap(map, options);
    },

    addGuideLayer: function (layer) {
        this._snapper.addGuideLayer(layer);
    },

    _createMarker: function (latlng, index) {
        var marker = L.Edit.Poly.prototype._createMarker.call(this, latlng, index);

        // Treat middle markers differently
        var isMiddle = index === undefined;
        if (isMiddle) {
            // Snap middle markers, only once they were touched
            marker.on('dragstart', function () {
                this._snapper.watchMarker(marker);
            }, this);
        }
        else {
            this._snapper.watchMarker(marker);
        }
        return marker;
    }
});

})();
<commit_msg>First attempt of snapping while drawing<commit_after>(function () {

L.Handler.MarkerSnap = L.Handler.extend({
    options: {
        snapDistance: 15, // in pixels
        snapVertices: true
    },

    initialize: function (map, marker, options) {
        L.Handler.prototype.initialize.call(this, map);
        this._markers = [];
        this._guides = [];

        if (arguments.length == 2) {
            if (!(marker instanceof L.Class)) {
                options = marker;
                marker = null;
            }
        }

        L.Util.setOptions(this, options || {});

        if (marker) {
            // new markers should be draggable !
            if (!marker.dragging) marker.dragging = new L.Handler.MarkerDrag(marker);
            marker.dragging.enable();
            this.watchMarker(marker);
        }

        // Convert snap distance in pixels into buffer in degres, for searching around mouse
        // It changes at each zoom change.
        function computeBuffer() {
            this._buffer = map.layerPointToLatLng(new L.Point(0,0)).lat -
                           map.layerPointToLatLng(new L.Point(this.options.snapDistance, 0)).lat;
        }
        map.on('zoomend', computeBuffer, this);
        map.whenReady(computeBuffer, this);
        computeBuffer.call(this);
    },

    enable: function () {
        this.disable();
        for (var i=0; i<this._markers.length; i++) {
            this.watchMarker(this._markers[i]);
        }
    },

    disable: function () {
        for (var i=0; i<this._markers.length; i++) {
            this.unwatchMarker(this._markers[i]);
        }
    },

    watchMarker: function (marker) {
        if (this._markers.indexOf(marker) == -1)
            this._markers.push(marker);
        marker.on('move', this._snapMarker, this);
    },

    unwatchMarker: function (marker) {
        marker.off('move', this._snapMarker, this);
        delete marker['snap'];
    },

    addGuideLayer: function (layer) {
        this._guides.push(layer);
    },

    _snapMarker: function(e) {
        var marker = e.target,
            latlng = marker.getLatLng(),
            snaplist = [];

        function processGuide(guide) {
            if ((guide._layers !== undefined) &&
                (typeof guide.searchBuffer !== 'function')) {
                // Guide is a layer group and has no L.LayerIndexMixin (from Leaflet.LayerIndex)
                for (var id in guide._layers) {
                    processGuide(guide._layers[id]);
                }
            }
            else if (typeof guide.searchBuffer === 'function') {
                // Search snaplist around mouse
                snaplist = snaplist.concat(guide.searchBuffer(latlng, this._buffer));
            }
            else {
                snaplist.push(guide);
            }
        }

        for (var i=0, n = this._guides.length; i < n; i++) {
            var guide = this._guides[i];
            processGuide.call(this, guide);
        }

        var closest = L.GeometryUtil.closestLayerSnap(this._map,
                                                      snaplist,
                                                      latlng,
                                                      this.options.snapDistance,
                                                      this.options.snapVertices);
        closest = closest || {layer: null, latlng: null};
        this._updateSnap(marker, closest.layer, closest.latlng);
    },

    _updateSnap: function (marker, layer, latlng) {
        if (layer && latlng) {
            marker._latlng = L.latLng(latlng);
            marker.update();
            if (marker.snap != layer) {
                marker.snap = layer;
                L.DomUtil.addClass(marker._icon, 'marker-snapped');
                marker.fire('snap', {layer:layer, latlng: latlng});
            }
        }
        else {
            if (marker.snap) {
                L.DomUtil.removeClass(marker._icon, 'marker-snapped');
                marker.fire('unsnap', {layer:marker.snap});
            }
            delete marker['snap'];
        }
    }
});

if (!L.Edit.Poly) {
    // Leaflet.Draw not available.
    return;
}

L.Handler.PolylineSnap = L.Edit.Poly.extend({

    initialize: function (map, poly, options) {
        L.Edit.Poly.prototype.initialize.call(this, poly, options);
        this._snapper = new L.Handler.MarkerSnap(map, options);
    },

    addGuideLayer: function (layer) {
        this._snapper.addGuideLayer(layer);
    },

    _createMarker: function (latlng, index) {
        var marker = L.Edit.Poly.prototype._createMarker.call(this, latlng, index);

        // Treat middle markers differently
        var isMiddle = index === undefined;
        if (isMiddle) {
            // Snap middle markers, only once they were touched
            marker.on('dragstart', function () {
                this._snapper.watchMarker(marker);
            }, this);
        }
        else {
            this._snapper.watchMarker(marker);
        }
        return marker;
    }
});

var __Draw_Polyline_addHooks = L.Draw.Polyline.prototype.addHooks;

L.Draw.Polyline.include({
    addHooks: function () {
        __Draw_Polyline_addHooks.call(this);

        if (this.options.guideLayer) {

            if (!this._snapper) {
                this._snapper = new L.Handler.MarkerSnap(this._map);
                this._snapper.addGuideLayer(this.options.guideLayer);
            }

            if (this._mouseMarker) {
                this._snapper.watchMarker(this._mouseMarker);
            }
        }

    }
});

})();
<|endoftext|>


<commit_before>/*
	unFocusFlash, version 1.7.2 (beta) (2006/04/26)
	Copyright: 2004-2006, Kevin Newman (http://www.unfocus.com/)
	License: http://creativecommons.org/licenses/LGPL/2.1/

	Version History
	1.0 - initial release
	1.1 - added mousewheel event forwarder - updates the flash variable UnfocusFlashWheelEvent
	1.2 - removed large portions (migrated to unFocusFlashCommunicator).

	1.5a - complete rewrite/rename getting ready for 2.0
*/
// Package: unFocus.SwfTools
if (!window.unFocus) var unFocus = {};

/*
Class: SwfHTML
	A class for assembling and outputting html for a Swf file.

References:
	<http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=tn_12701>
*/
unFocus.SwfHTML = function() {
	this._properties = {};
	this._params = {};
	this._flashvars = "";
	this._flashvarPairs = {};
	this._version = 0;
	this._majorRevision = 0;
	this._minorRevision = 0;
	this._betaVersion = 0;
	this._src = "";
};
// setters check against valid values
// ref http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=tn_12701
unFocus.SwfHTML.prototype = {
	// Method: setSrc
	//	Sets the Src of the swf file.
	setSrc: function($src) {
		this._src = $src;
	},
	// Method: setWidth
	//	Sets the Width of the swf file.
	setWidth: function($width) {
		this._properties.width = $width;
	},
	setHeight: function($height) {
		this._properties.height = $height;
	},
	setId: function($id) {
		this._properties.id = $id;
	},
	getId: function() {
		return this._properties.id;
	},
	setName: function($name) {
		this._properties.name = $name;
	},

	setMovie: function($src) {
		this.setSrc($src);
	},
	setSwliveconnect: function($swliveconnect) {
		if (typeof $swliveconnect == "boolean")
			this._params.swliveconnect = $swliveconnect;
		else
			throw new Error("InvalidValue", "Valid Values for swliveconnect: true, false");
	},
	setPlay: function($play) {
		if (typeof $play == "boolean")
			this._params.play= $play;
		else
			throw new Error("InvalidValue", "Valid Values for play: true, false");
	},
	setLoop: function($loop) {
		if (typeof $loop == "boolean")
			this._params.loop= $loop;
		else
			throw new Error("InvalidValue", "Valid Values for loop: true, false");
	},
	setMenu: function($menu) {
		if (typeof $menu == "boolean")
			this._params.menu= $menu;
		else
			throw new Error("InvalidValue", "Valid Values for menu: true, false");
	},
	setQuality: function($quality) {
		switch ($quality) {
			case "low":
			case "medium":
			case "high":
			case "autolow":
			case "autohigh":
			case "best":
				this._params.quality = $quality; /* low, medium, high, autolow, autohigh, best */
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for quality: low, medium, high, autolow, autohigh, best");
		}
	},
	setScale: function($scale) {
		switch ($scale) {
			case "showall":
			case "noborder":
			case "exactfit":
			case "noscale":
				this._params.scale = $scale; /* showall, noborder, exactfit, noscale (missing from the documentation) */
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for scale: showall, noborder, exactfit, noscale");
		}
	},
	setAlign: function($align) { // :NOTE: There is a conflict here - you can set align on the html element, as well as for the movie. Hhow does this sort out for embed?
		switch ($align) {
			case "l":
			case "t":
			case "r":
			case "b":
				this._params.align = $align; /* l, t, r, b (defaults to center, which isn't in the list) */
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for align: l, t, r, b");
		}
	},
	setSAlign: function($salign) {
		switch ($salign) {
			case "l":
			case "t":
			case "r":
			case "b":
			case "tl":
			case "tr":
			case "bl":
			case "br":
				this._params.salign = $salign;
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for salign: l, t, r, b, tl, tr, bl, br");
		}
	},
	setWMode: function($wmode) {
		switch($wmode) {
			case "window":
			case "opaque":
			case "transparent":
				this._params.wmode = $wmode;
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for wmode: window, opaque, transparent");
		}
	},
	setBGColor: function($bgcolor) {
		// /^#[\dA-Fa-f]{6}$/
		if (/^#[\dA-F]{6}$/.test($bgcolor))
			this._params.bgcolor = $bgcolor; /* #RRGGBB, hexadecimal RGB value */
		else
			throw new Error("InvalidValue", "Valid Values for bgcolor: a valid html color hex value (#0099FF)");
	},
	setBase: function($base) {
		this._params.base = $base;
	},
	setFlashvars: function($flashvars) {
		this._flashvars = $flashvars;
	},
	addFlashvar: function($name, $value) {
		if ($value)
			this.setFlashvars($name + "=" + escape($value) + "&" + this._flashvars);
	},

	// http://www.macromedia.com/cfusion/knowledgebase/index.cfm?id=tn_16494
	setAllowscriptaccess: function($allowscriptaccess) {
		switch ($allowscriptaccess) {
			case "never":
			case "always":
				this._params.allowscriptaccess = $allowscriptaccess;
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for allowscriptaccess: never, always");
		}
	},

	// for version stuffs
	setVersion: function($version) {
		this._version = $version;
	},
	setMajorRevision: function($majorRevision) {
		this._majorRevision = $majorRevision;
	},
	setMinorRevision: function($minorRevision) {
		this._mainorRevision = $minorRevision;
	},
	setBetaVersion: function($betaVersion) {
		this._betaVersion = $betaVersion;
	},

	// misc
	setAdditionalParam: function($name, $value) {
		this._params[$name] = $value;
	},
	setAdditionalProperty: function($name, $value) {
		this._properties[$name] = $value;
	},

	// new allowFullScreen prop
	setAllowFullScreen: function($allowFullScreen) {
		if (typeof $allowFullScreen == "boolean")
			this._params.allowFullScreen= $allowFullScreen;
		else
			throw new Error("InvalidValue", "Valid Values for allowFullScreen: true, false");
	},

	/**
	 * Generates the platform specific HTML for the flash movie
	 *
	 * @return String containing the platform specific HTML for the flash movie
	 */
	getHTML: function() {
		// initialize local variables
		var $key, $html, $ActiveX = window.ActiveXObject && window.print && !window.opera;

		if (this._src) {
			if ($ActiveX)
				this._params.movie = this._src;
			else
				this._properties.src = this._src;
		}

		if ($ActiveX) {
			$html = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"';
			$html += ' codebase="http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version='+this._version+","+this.majorRevision+","+this._minorRevision+","+this._betaVersion+'"';
		} else
			$html = "<embed";

		// output  properties
		for ($key in this._properties)
			$html += ' ' + $key + '="' + this._properties[$key]+'"';

		if ($ActiveX) {
			$html += ">";
			for ($key in this._params)
				$html += '<param name="' + $key + '" value="' + this._params[$key] + '">';
			if (this._flashvars)
				$html += '<param name="flashvars" value="' + this._flashvars + '">';
		} else {
			for ($key in this._params)
				$html += " " + $key + '="' + this._params[$key]+'"';
			if (this._flashvars)
				$html += ' flashvars="' + this._flashvars + '"';
		}

		// outputs the rest of the platform specific stuff
		if ($ActiveX)
			$html += '"></object>';
		else
			$html += '" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>';
		return $html;
	}/*,

	// much information for the following methods came from the sifr project
	// :TODO: replace this with a simple xhtml compliant node generator - leave the detection to another method
	getNodeObject: function() {
		// initialize local variables
		var $key, _nodeObject, $ActiveX = typeof ActiveXObject != "undefined" && window.print && !window.opera;

		if (this._src) {
			if ($ActiveX)
				this._params.movie = this._src;
			else
				this._properties.src = this._src;
		}

		if ($ActiveX) {
			_nodeObject = document.createElement("object");
			_nodeObject.classid = "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000";
			_nodeObject.codebase = "http://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version="+this._version+","+this.majorRevision+","+this._minorRevision+","+this._betaVersion;
		} else {
			_nodeObject = document.createElement("embed");
			_nodeObject.type = "application/x-shockwave-flash";
			_nodeObject.pluginspage = "http://www.macromedia.com/go/getflashplayer";
		}

		// output  properties
		for ($key in this._properties)
			_nodeObject[$key] = this._properties[$key];

		if ($ActiveX) {
			var _param = document.createElement("param");
			for ($key in this._params) {
				_param.name = $key;
				_param.value = this._params[$key];
				_nodeObject.appendChild(_param);
			}
			if (this._flashvars) {
				_param.name = "flashvars";
				_param.value = this._flashvars;
				_nodeObject.appendChild(_param);
			}
		} else {
			for ($key in this._params)
				_nodeObject[$key] = this._params[$key];
			if (this._flashvars)
				_nodeObject.flashvars = this._flashvars;
		}

		return _nodeObject;

	},
	appendToNode: function($node) {
		return false;
	}*/
};

// :NOTE: These can't be compressed within an eval, if they are it triggers the IE patent thingy
unFocus.SwfHTML.prototype.writeToDocument = function($document) {
	$document.write(this.getHTML());
};
unFocus.SwfHTML.prototype.outputToInnerHTML = function($element) {
	$element.innerHTML = this.getHTML();
};
unFocus.SwfHTML.prototype.outputToOuterHTML = function($element) {
	$element.outerHTML = this.getHTML();
};
<commit_msg>Fixed https security popup in IE 6+ (xp sp2). Updated reference urls to use permanent links. Update Copyright and license info.<commit_after>/*
	unFocus.SwfHTML, version 2.0 (beta 1) (2007/07/13)
	Copyright: 2004-2007, Kevin Newman (http://www.unfocus.com/projects/)
	License: http://www.gnu.org/licenses/lgpl.html
*/
// Package: unFocus.SwfTools
if (!window.unFocus) var unFocus = {};

/*
Class: SwfHTML
	A class for assembling and outputting html for a Swf file.

References:
	<http://www.adobe.com/go/tn_12701>
	<http://www.adobe.com/go/tn_16588>
*/
unFocus.SwfHTML = function() {
	this._properties = {};
	this._params = {};
	this._flashvars = "";
	this._flashvarPairs = {};
	this._version = 0;
	this._majorRevision = 0;
	this._minorRevision = 0;
	this._betaVersion = 0;
	this._src = "";
};
// setters check against valid values http://www.adobe.com/go/tn_12701
unFocus.SwfHTML.prototype = {
	// Method: setSrc
	//	Sets the Src of the swf file.
	setSrc: function($src) {
		this._src = $src;
	},
	// Method: setWidth
	//	Sets the Width of the swf file.
	setWidth: function($width) {
		this._properties.width = $width;
	},
	setHeight: function($height) {
		this._properties.height = $height;
	},
	setId: function($id) {
		this._properties.id = $id;
	},
	getId: function() {
		return this._properties.id;
	},
	setName: function($name) {
		this._properties.name = $name;
	},

	setMovie: function($src) {
		this.setSrc($src);
	},
	setSwliveconnect: function($swliveconnect) {
		if (typeof $swliveconnect == "boolean")
			this._params.swliveconnect = $swliveconnect;
		else
			throw new Error("InvalidValue", "Valid Values for swliveconnect: true, false");
	},
	setPlay: function($play) {
		if (typeof $play == "boolean")
			this._params.play= $play;
		else
			throw new Error("InvalidValue", "Valid Values for play: true, false");
	},
	setLoop: function($loop) {
		if (typeof $loop == "boolean")
			this._params.loop= $loop;
		else
			throw new Error("InvalidValue", "Valid Values for loop: true, false");
	},
	setMenu: function($menu) {
		if (typeof $menu == "boolean")
			this._params.menu= $menu;
		else
			throw new Error("InvalidValue", "Valid Values for menu: true, false");
	},
	setQuality: function($quality) {
		switch ($quality) {
			case "low":
			case "medium":
			case "high":
			case "autolow":
			case "autohigh":
			case "best":
				this._params.quality = $quality; /* low, medium, high, autolow, autohigh, best */
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for quality: low, medium, high, autolow, autohigh, best");
		}
	},
	setScale: function($scale) {
		switch ($scale) {
			case "showall":
			case "noborder":
			case "exactfit":
			case "noscale":
				this._params.scale = $scale; /* showall, noborder, exactfit, noscale (missing from the documentation) */
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for scale: showall, noborder, exactfit, noscale");
		}
	},
	setAlign: function($align) { // :NOTE: There is a conflict here - you can set align on the html element, as well as for the movie. Hhow does this sort out for embed?
		switch ($align) {
			case "l":
			case "t":
			case "r":
			case "b":
				this._params.align = $align; /* l, t, r, b (defaults to center, which isn't in the list) */
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for align: l, t, r, b");
		}
	},
	setSAlign: function($salign) {
		switch ($salign) {
			case "l":
			case "t":
			case "r":
			case "b":
			case "tl":
			case "tr":
			case "bl":
			case "br":
				this._params.salign = $salign;
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for salign: l, t, r, b, tl, tr, bl, br");
		}
	},
	setWMode: function($wmode) {
		switch($wmode) {
			case "window":
			case "opaque":
			case "transparent":
				this._params.wmode = $wmode;
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for wmode: window, opaque, transparent");
		}
	},
	setBGColor: function($bgcolor) {
		// /^#[\dA-Fa-f]{6}$/
		if (/^#[\dA-F]{6}$/.test($bgcolor))
			this._params.bgcolor = $bgcolor; /* #RRGGBB, hexadecimal RGB value */
		else
			throw new Error("InvalidValue", "Valid Values for bgcolor: a valid html color hex value (#0099FF)");
	},
	setBase: function($base) {
		this._params.base = $base;
	},
	setFlashvars: function($flashvars) {
		this._flashvars = $flashvars;
	},
	addFlashvar: function($name, $value) {
		if ($value)
			this.setFlashvars($name + "=" + escape($value) + "&" + this._flashvars);
	},

	// http://www.macromedia.com/cfusion/knowledgebase/index.cfm?id=tn_16494
	setAllowscriptaccess: function($allowscriptaccess) {
		switch ($allowscriptaccess) {
			case "never":
			case "always":
				this._params.allowscriptaccess = $allowscriptaccess;
				break;
			default:
				throw new Error("InvalidValue", "Valid Values for allowscriptaccess: never, always");
		}
	},

	// for version stuffs
	setVersion: function($version) {
		this._version = $version;
	},
	setMajorRevision: function($majorRevision) {
		this._majorRevision = $majorRevision;
	},
	setMinorRevision: function($minorRevision) {
		this._mainorRevision = $minorRevision;
	},
	setBetaVersion: function($betaVersion) {
		this._betaVersion = $betaVersion;
	},

	// misc
	setAdditionalParam: function($name, $value) {
		this._params[$name] = $value;
	},
	setAdditionalProperty: function($name, $value) {
		this._properties[$name] = $value;
	},

	// new allowFullScreen prop
	setAllowFullScreen: function($allowFullScreen) {
		if (typeof $allowFullScreen == "boolean")
			this._params.allowFullScreen= $allowFullScreen;
		else
			throw new Error("InvalidValue", "Valid Values for allowFullScreen: true, false");
	},

	/**
	 * Generates the platform specific HTML for the flash movie
	 *
	 * @return String containing the platform specific HTML for the flash movie
	 */
	getHTML: function() {
		// initialize local variables
		var $key, $html, $ActiveX = window.ActiveXObject && window.print && !window.opera;

		if (this._src) {
			if ($ActiveX)
				this._params.movie = this._src;
			else
				this._properties.src = this._src;
		}

		if ($ActiveX) {
			$html = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" codebase="';
			// solve https popup issue http://www.adobe.com/go/tn_16588
			$html += /^https/.test(window.location)?"https":"http";
			$html += '://download.macromedia.com/pub/shockwave/cabs/flash/swflash.cab#version='+this._version+","+this.majorRevision+","+this._minorRevision+","+this._betaVersion+'"';
		} else
			$html = "<embed";

		// output  properties
		for ($key in this._properties)
			$html += ' ' + $key + '="' + this._properties[$key]+'"';

		if ($ActiveX) {
			$html += ">";
			for ($key in this._params)
				$html += '<param name="' + $key + '" value="' + this._params[$key] + '">';
			if (this._flashvars)
				$html += '<param name="flashvars" value="' + this._flashvars + '">';
		} else {
			for ($key in this._params)
				$html += " " + $key + '="' + this._params[$key]+'"';
			if (this._flashvars)
				$html += ' flashvars="' + this._flashvars + '"';
		}

		// outputs the rest of the platform specific stuff
		if ($ActiveX)
			$html += '"></object>';
		else
			$html += '" type="application/x-shockwave-flash" pluginspage="http://www.macromedia.com/go/getflashplayer"></embed>';
		return $html;
	}
};

// :NOTE: These can't be compressed within an eval, if they are it triggers the IE patent thingy
unFocus.SwfHTML.prototype.writeToDocument = function($document) {
	$document.write(this.getHTML());
};
unFocus.SwfHTML.prototype.outputToInnerHTML = function($element) {
	$element.innerHTML = this.getHTML();
};
unFocus.SwfHTML.prototype.outputToOuterHTML = function($element) {
	$element.outerHTML = this.getHTML();
};
<|endoftext|>