<commit_before>/* eslint-env mocha */
/* global testContext */
/* eslint-disable prefer-arrow-callback, no-unused-expressions, max-nested-callbacks */
import {assert} from 'chai'
import {truncateDBTables} from 'src/test/helpers'
import factory from 'src/test/factories'
import {findProjects} from 'src/server/db/project'
import {GOAL_SELECTION} from 'src/common/models/cycle'

import formProjects from 'src/server/actions/formProjects'

const TEST_ADVANCED_PLAYER_ECC = 5000
const DEFAULT_RECOMMENDED_TEAM_SIZE = 5

describe(testContext(__filename), function () {
  describe('formProjects()', function () {
    context('fewer advanced players than popular votes', function () {
      _itFormsProjectsAsExpected({
        players: {total: 10, advanced: 1},
        votes: {min: 9, popular: [1, 1, 1]}, // a most popular, a 2nd most popular, a 3rd most popular
      })
    })

    context('more advanced players than popular votes', function () {
      _itFormsProjectsAsExpected({
        players: {total: 10, advanced: 4},
        votes: {min: 6, popular: [1]}, // 1 most popular
      })
    })

    context('equal number of advanced players as popular votes', function () {
      _itFormsProjectsAsExpected({
        players: {total: 10, advanced: 3},
        votes: {min: 7, popular: [3]}, // 3 equally popular
      })
    })

    context('lots of players who didn\'t vote', function () {
      _itFormsProjectsAsExpected({
        players: {total: 30, advanced: 3},
        votes: {min: 27, popular: [3]}, // 3 equally popular
      })
    })

    context('all votes equally popular', function () {
      _itFormsProjectsAsExpected({
        players: {total: 24, advanced: 4},
        votes: {min: 20, popular: [10]}, // 10 equally popular
      })
    })
  })
})

function _itFormsProjectsAsExpected(options) {
  before(truncateDBTables)

  before(function () {
    // describe test data
    const {players, votes} = options || {}
    console.log(`        players: ${players.total} total, ${players.advanced} advanced. votes: ${votes.min} min, (${votes.popular}) popular.`)
  })

  before(async function () {
    const {cycle, players, votes} = await _generateTestData(options)
    await formProjects(cycle.id)
    this.data = {cycle, players, votes, projects: await findProjects().run()}
  })

  it('places all players who voted on teams, and ONLY players who voted', function () {
    const {cycle, projects, players} = this.data

    const votingPlayers = players.advanced.concat(players.regular)
    const projectPlayerIds = _extractPlayerIdsFromProjects(cycle.id, projects)

    assert.strictEqual(votingPlayers.length, projectPlayerIds.length,
        'Number of players who voted does not equal number of players assigned to projects')

    votingPlayers.forEach(player => {
      const playerIdInProject = projectPlayerIds.find(playerId => playerId === player.id)
      assert.isOk(playerIdInProject, `Player ${player.id} not assigned to a project`)
    })
  })

  it('creates project teams that all contain at least one advanced player', function () {
    const {cycle, players, projects} = this.data

    const advancedPlayers = players.advanced.reduce((result, player) => {
      result[player.id] = player
      return result
    }, {})

    projects.forEach(project => {
      const playerIds = _extractPlayerIdsFromProjects(cycle.id, [project])
      const advancedPlayerId = playerIds.find(playerId => advancedPlayers[playerId])
      assert.isOk(advancedPlayerId, `Team for project ${project.id} does not include an advanced player`)
    })
  })

  it('creates projects for as many goals as were voted for and can be supported by the number of available advanced players', function () {
    const {players, projects} = this.data

    const projectGoals = _extractGoalsFromProjects(projects)
    const numExpectedGoals = Math.min(projectGoals.length, players.advanced.length)

    assert.strictEqual(projectGoals.length, numExpectedGoals,
        `Should have created projects for exactly ${numExpectedGoals} goals`)
  })

  it('creates projects for the most popular goals', function () {
    const {votes, projects, players} = this.data

    const advancedPlayersById = _mapById(players.advanced)
    const regularPlayerVotes = votes.filter(vote => !advancedPlayersById.has(vote.playerId))

    const projectGoals = _extractGoalsFromProjects(projects)

    const numPopularGoals = options.votes.popular.reduce((result, popularVoteCount) => (result + popularVoteCount), 0)
    const popularGoalUrls = _extractMostPopularGoalsFromVotes(regularPlayerVotes, numPopularGoals).map(goal => goal.url)

    if (popularGoalUrls.length >= projectGoals.length) {
      // more popular goals than projects that could be formed;
      // every project goal should be be popular goal
      projectGoals.forEach(goal => {
        const isPopularGoal = popularGoalUrls.includes(goal.url)
        assert.strictEqual(isPopularGoal, true, `Goal ${goal.url} is not a popular goal`)
      })
    } else {
      // enough teams that all popular goals should have been included;
      // all popular goals should be included in project goals
      popularGoalUrls.forEach(popularGoalUrl => {
        const matchedPopularGoal = projectGoals.find(goal => goal.url === popularGoalUrl)
        assert.isOk(matchedPopularGoal, `Popular goal ${popularGoalUrl} is not included`)
      })
    }
  })
}

async function _generateTestData(options = {}) {
  // generate test cycle
  const cycle = await factory.create('cycle', {state: GOAL_SELECTION})

  // generate test players
  const players = await _generatePlayers(cycle.chapterId, options.players)

  // generate test votes
  // advanced player votes will be discarded, but they must be created
  // in order for the advanced players to be assigned to teams.
  const [regularVotes, advancedVotes] = await Promise.all([
    _generateVotes(cycle.id, players.regular, options.votes),
    _generateVotes(cycle.id, players.advanced, options.votes),
  ])
  const votes = regularVotes.concat(advancedVotes)

  // return test data
  return {cycle, players, votes}
}

async function _generatePlayers(chapterId, options = {}) {
  const numTotal = options.total || 0
  const numAdvanced = options.advanced || 0
  return {
    regular: await factory.createMany('player', {chapterId}, numTotal - numAdvanced),
    advanced: await factory.createMany('player', {chapterId, stats: {ecc: TEST_ADVANCED_PLAYER_ECC}}, numAdvanced)
  }
}

function _generateVotes(cycleId, players, options) {
  let voteData = _createGoalVotes(options)

  if (!(players.length >= voteData.length) && !options.requireAllVotes) {
    voteData = voteData.slice(0, players.length)
  }

  const votes = voteData.map((goalIds, i) => ({
    cycleId,
    playerId: players[i].id,
    goals: goalIds.map(goalId => ({
      url: `http://ex.co/${goalId}`,
      title: `Goal ${goalId}`,
      teamSize: DEFAULT_RECOMMENDED_TEAM_SIZE
    })),
  }))

  return factory.createMany('vote', votes, votes.length)
}

function _extractPlayerIdsFromProjects(cycleId, projects) {
  const playerIds = projects.reduce((result, project) => {
    project.cycleHistory
      .find(projectCycle => projectCycle.cycleId === cycleId)
      .playerIds.forEach(playerId => result.set(playerId, playerId))
    return result
  }, new Map())

  return Array.from(playerIds.values())
}

function _extractGoalsFromProjects(projects) {
  const goals = projects.reduce((result, project) => {
    result.set(project.goal.url, project.goal)
    return result
  }, new Map())

  return Array.from(goals.values())
}

function _extractMostPopularGoalsFromVotes(votes, count) {
  const goalCounts = votes.reduce((result, vote) => {
    vote.goals.forEach(goal => {
      if (!result.has(goal.url)) {
        result.set(goal.url, {goal, count: 0})
      }
      result.get(goal.url).count += 1
    })
    return result
  }, new Map())

  const sortedGoalCounts = Array.from(goalCounts.values()).sort((a, b) => {
    return b.count - a.count
  })

  return sortedGoalCounts.slice(0, count).map(goalCount => goalCount.goal)
}

/**
 * Vote arrangement generator examples.
 * TODO: this is kind of confusing. make it clearer?
 *
 * one first-, two second- and three third-most popular vote across first picks:
 *  config: {first: [1, 1, 1]}
 *  result: [
 *    [7, _], [7, _],                  // 3rd most popular (tie)
 *    [6, _], [6, _],                  // 3rd most popular (tie)
 *    [5, _], [5, _],                  // 3rd most popular (tie)
 *    [4, _], [4, _], [4, _],          // 2nd most popular (tie)
 *    [3, _], [3, _], [3, _],          // 2nd most popular (tie)
 *    [2, _], [2, _], [2, _],          // 2nd most popular (tie)
 *    [1, _], [1, _], [1, _], [1, _],  // 1st most popular
 * ]
 */
function _createGoalVotes(options) {
  const {min, popular} = options || {}
  const totalNumPopularGoals = Array.isArray(popular) ? popular.reduce((result, numPopularVotes) => {
    return result + numPopularVotes
  }, 0) : 0

  if (!min && !totalNumPopularGoals) {
    throw new Error('Must provide either min or popular vote count')
  }

  const votes = []
  let baseGoalId = 0

  // add popular votes (if specified)
  if (popular) {
    let numVotesPerGoal = 2

    // starting from the end, iterate through the collection
    // of specified vote counts for popular goals to be created
    for (let i = popular.length - 1; i >= 0; numVotesPerGoal++, i--) {
      const numGoalsToCreateVotesFor = popular[i]

      // iterate through each of the distinct popular goals that need votes created
      for (let j = 0; j < numGoalsToCreateVotesFor; j++) {
        baseGoalId = _nextGoalSeriesForVotes(baseGoalId)

        for (let k = 0; k < numVotesPerGoal; k++) {
          votes.push([baseGoalId, baseGoalId + j + k + 1])
        }
      }
    }
  }

  // fill as necessary with unique goal votes up to <min>
  while (min > votes.length) {
    baseGoalId = _nextGoalSeriesForVotes(baseGoalId)
    votes.push([baseGoalId, baseGoalId + 1])
  }

  return votes
}

function _nextGoalSeriesForVotes(currentSeries = 0) {
  return currentSeries + 100
}

function _mapById(objects) {
  return objects.reduce((result, obj) => {
    result.set(obj.id, obj)
    return result
  }, new Map())
}
<commit_msg>update project formation tests for Elo ratings<commit_after>/* eslint-env mocha */
/* global testContext */
/* eslint-disable prefer-arrow-callback, no-unused-expressions, max-nested-callbacks */
import {assert} from 'chai'
import {truncateDBTables} from 'src/test/helpers'
import factory from 'src/test/factories'
import {findProjects} from 'src/server/db/project'
import {GOAL_SELECTION} from 'src/common/models/cycle'

import formProjects from 'src/server/actions/formProjects'

const TEST_ADVANCED_PLAYER_ELO = 1001
const DEFAULT_RECOMMENDED_TEAM_SIZE = 5

describe(testContext(__filename), function () {
  describe('formProjects()', function () {
    context('fewer advanced players than popular votes', function () {
      _itFormsProjectsAsExpected({
        players: {total: 10, advanced: 1},
        votes: {min: 9, popular: [1, 1, 1]}, // a most popular, a 2nd most popular, a 3rd most popular
      })
    })

    context('more advanced players than popular votes', function () {
      _itFormsProjectsAsExpected({
        players: {total: 10, advanced: 4},
        votes: {min: 6, popular: [1]}, // 1 most popular
      })
    })

    context('equal number of advanced players as popular votes', function () {
      _itFormsProjectsAsExpected({
        players: {total: 10, advanced: 3},
        votes: {min: 7, popular: [3]}, // 3 equally popular
      })
    })

    context('lots of players who didn\'t vote', function () {
      _itFormsProjectsAsExpected({
        players: {total: 30, advanced: 3},
        votes: {min: 27, popular: [3]}, // 3 equally popular
      })
    })

    context('all votes equally popular', function () {
      _itFormsProjectsAsExpected({
        players: {total: 24, advanced: 4},
        votes: {min: 20, popular: [10]}, // 10 equally popular
      })
    })
  })
})

function _itFormsProjectsAsExpected(options) {
  before(truncateDBTables)

  before(function () {
    // describe test data
    const {players, votes} = options || {}
    console.log(`        players: ${players.total} total, ${players.advanced} advanced. votes: ${votes.min} min, (${votes.popular}) popular.`)
  })

  before(async function () {
    const {cycle, players, votes} = await _generateTestData(options)
    await formProjects(cycle.id)
    this.data = {cycle, players, votes, projects: await findProjects().run()}
  })

  it('places all players who voted on teams, and ONLY players who voted', function () {
    const {cycle, projects, players} = this.data

    const votingPlayers = players.advanced.concat(players.regular)
    const projectPlayerIds = _extractPlayerIdsFromProjects(cycle.id, projects)

    assert.strictEqual(votingPlayers.length, projectPlayerIds.length,
        'Number of players who voted does not equal number of players assigned to projects')

    votingPlayers.forEach(player => {
      const playerIdInProject = projectPlayerIds.find(playerId => playerId === player.id)
      assert.isOk(playerIdInProject, `Player ${player.id} not assigned to a project`)
    })
  })

  it('creates project teams that all contain at least one advanced player', function () {
    const {cycle, players, projects} = this.data

    const advancedPlayers = players.advanced.reduce((result, player) => {
      result[player.id] = player
      return result
    }, {})

    projects.forEach(project => {
      const playerIds = _extractPlayerIdsFromProjects(cycle.id, [project])
      const advancedPlayerId = playerIds.find(playerId => advancedPlayers[playerId])
      assert.isOk(advancedPlayerId, `Team for project ${project.id} does not include an advanced player`)
    })
  })

  it('creates projects for as many goals as were voted for and can be supported by the number of available advanced players', function () {
    const {players, projects} = this.data

    const projectGoals = _extractGoalsFromProjects(projects)
    const numExpectedGoals = Math.min(projectGoals.length, players.advanced.length)

    assert.strictEqual(projectGoals.length, numExpectedGoals,
        `Should have created projects for exactly ${numExpectedGoals} goals`)
  })

  it('creates projects for the most popular goals', function () {
    const {votes, projects, players} = this.data

    const advancedPlayersById = _mapById(players.advanced)
    const regularPlayerVotes = votes.filter(vote => !advancedPlayersById.has(vote.playerId))

    const projectGoals = _extractGoalsFromProjects(projects)

    const numPopularGoals = options.votes.popular.reduce((result, popularVoteCount) => (result + popularVoteCount), 0)
    const popularGoalUrls = _extractMostPopularGoalsFromVotes(regularPlayerVotes, numPopularGoals).map(goal => goal.url)

    if (popularGoalUrls.length >= projectGoals.length) {
      // more popular goals than projects that could be formed;
      // every project goal should be be popular goal
      projectGoals.forEach(goal => {
        const isPopularGoal = popularGoalUrls.includes(goal.url)
        assert.strictEqual(isPopularGoal, true, `Goal ${goal.url} is not a popular goal`)
      })
    } else {
      // enough teams that all popular goals should have been included;
      // all popular goals should be included in project goals
      popularGoalUrls.forEach(popularGoalUrl => {
        const matchedPopularGoal = projectGoals.find(goal => goal.url === popularGoalUrl)
        assert.isOk(matchedPopularGoal, `Popular goal ${popularGoalUrl} is not included`)
      })
    }
  })
}

async function _generateTestData(options = {}) {
  // generate test cycle
  const cycle = await factory.create('cycle', {state: GOAL_SELECTION})

  // generate test players
  const players = await _generatePlayers(cycle.chapterId, options.players)

  // generate test votes
  // advanced player votes will be discarded, but they must be created
  // in order for the advanced players to be assigned to teams.
  const [regularVotes, advancedVotes] = await Promise.all([
    _generateVotes(cycle.id, players.regular, options.votes),
    _generateVotes(cycle.id, players.advanced, options.votes),
  ])
  const votes = regularVotes.concat(advancedVotes)

  // return test data
  return {cycle, players, votes}
}

async function _generatePlayers(chapterId, options = {}) {
  const numTotal = options.total || 0
  const numAdvanced = options.advanced || 0
  return {
    regular: await factory.createMany('player', {chapterId}, numTotal - numAdvanced),
    advanced: await factory.createMany('player', {chapterId, stats: {elo: {rating: TEST_ADVANCED_PLAYER_ELO}}}, numAdvanced)
  }
}

function _generateVotes(cycleId, players, options) {
  let voteData = _createGoalVotes(options)

  if (!(players.length >= voteData.length) && !options.requireAllVotes) {
    voteData = voteData.slice(0, players.length)
  }

  const votes = voteData.map((goalIds, i) => ({
    cycleId,
    playerId: players[i].id,
    goals: goalIds.map(goalId => ({
      url: `http://ex.co/${goalId}`,
      title: `Goal ${goalId}`,
      teamSize: DEFAULT_RECOMMENDED_TEAM_SIZE
    })),
  }))

  return factory.createMany('vote', votes, votes.length)
}

function _extractPlayerIdsFromProjects(cycleId, projects) {
  const playerIds = projects.reduce((result, project) => {
    project.cycleHistory
      .find(projectCycle => projectCycle.cycleId === cycleId)
      .playerIds.forEach(playerId => result.set(playerId, playerId))
    return result
  }, new Map())

  return Array.from(playerIds.values())
}

function _extractGoalsFromProjects(projects) {
  const goals = projects.reduce((result, project) => {
    result.set(project.goal.url, project.goal)
    return result
  }, new Map())

  return Array.from(goals.values())
}

function _extractMostPopularGoalsFromVotes(votes, count) {
  const goalCounts = votes.reduce((result, vote) => {
    vote.goals.forEach(goal => {
      if (!result.has(goal.url)) {
        result.set(goal.url, {goal, count: 0})
      }
      result.get(goal.url).count += 1
    })
    return result
  }, new Map())

  const sortedGoalCounts = Array.from(goalCounts.values()).sort((a, b) => {
    return b.count - a.count
  })

  return sortedGoalCounts.slice(0, count).map(goalCount => goalCount.goal)
}

/**
 * Vote arrangement generator examples.
 * TODO: this is kind of confusing. make it clearer?
 *
 * one first-, two second- and three third-most popular vote across first picks:
 *  config: {first: [1, 1, 1]}
 *  result: [
 *    [7, _], [7, _],                  // 3rd most popular (tie)
 *    [6, _], [6, _],                  // 3rd most popular (tie)
 *    [5, _], [5, _],                  // 3rd most popular (tie)
 *    [4, _], [4, _], [4, _],          // 2nd most popular (tie)
 *    [3, _], [3, _], [3, _],          // 2nd most popular (tie)
 *    [2, _], [2, _], [2, _],          // 2nd most popular (tie)
 *    [1, _], [1, _], [1, _], [1, _],  // 1st most popular
 * ]
 */
function _createGoalVotes(options) {
  const {min, popular} = options || {}
  const totalNumPopularGoals = Array.isArray(popular) ? popular.reduce((result, numPopularVotes) => {
    return result + numPopularVotes
  }, 0) : 0

  if (!min && !totalNumPopularGoals) {
    throw new Error('Must provide either min or popular vote count')
  }

  const votes = []
  let baseGoalId = 0

  // add popular votes (if specified)
  if (popular) {
    let numVotesPerGoal = 2

    // starting from the end, iterate through the collection
    // of specified vote counts for popular goals to be created
    for (let i = popular.length - 1; i >= 0; numVotesPerGoal++, i--) {
      const numGoalsToCreateVotesFor = popular[i]

      // iterate through each of the distinct popular goals that need votes created
      for (let j = 0; j < numGoalsToCreateVotesFor; j++) {
        baseGoalId = _nextGoalSeriesForVotes(baseGoalId)

        for (let k = 0; k < numVotesPerGoal; k++) {
          votes.push([baseGoalId, baseGoalId + j + k + 1])
        }
      }
    }
  }

  // fill as necessary with unique goal votes up to <min>
  while (min > votes.length) {
    baseGoalId = _nextGoalSeriesForVotes(baseGoalId)
    votes.push([baseGoalId, baseGoalId + 1])
  }

  return votes
}

function _nextGoalSeriesForVotes(currentSeries = 0) {
  return currentSeries + 100
}

function _mapById(objects) {
  return objects.reduce((result, obj) => {
    result.set(obj.id, obj)
    return result
  }, new Map())
}
<|endoftext|>


<commit_before>var path = require('path');
var fs = require('fs');
var uuidv4 = require('uuid/v4');
var request = require('superagent');
var logger = require('../common/logger');
var config = require('../config');
var formidable = require('formidable');
var OSS = require('ali-oss').Wrapper;

var client = new OSS(config.aliOSS);

/**
 * 文件上传
 * @param {String} folder 目标文件夹
 * @param {String} file 目标文件字段
 * @return {String} url 文件存放路径
 */
exports.upload = function (req, res, next) {
  // 允许上传的文件类型
  var whiteList = ['image/jpeg', 'image/gif', 'image/png'];

  // 解析form data
  var form = new formidable.IncomingForm();
  form.uploadDir = path.join(__dirname, '../tmpdir');

  form.parse(req, function (err, fields, files) {
    var folder = fields.folder;

    var path = files.file.path;
    var type = files.file.type;
    var size = files.file.size;

    // 生成文件名
    var filename = uuidv4() + '.' + type.split('/')[1];

    // 过滤非白名单文件
    if (!folder || whiteList.find(elem => {
        return elem === type;
      }) === undefined) {
      // 移除临时文件
      if (fs.existsSync(path)) {
        fs.unlink(path);
      }

      res.status(400).json({
        statusCode: 400,
        errMsg: '格式错误'
      });
    }

    // 上传文件
    client
      .put(folder + '/' + filename, path)
      .then(val => {
        // 移除临时文件
        if (fs.existsSync(path)) {
          fs.unlink(path);
        }

        // 返回文件url
        res.status(201).json({
          statusCode: 201,
          errMsg: '上传成功',
          data: {
            urls: val.res.requestUrls
          }
        });
      })
      .catch(err => {
        logger.error(err);
        // 移除临时文件
        if (fs.existsSync(path)) {
          fs.unlink(path);
        }

        res.status(500).json({
          statusCode: 500,
          errMsg: '上传失败'
        });
      })
  });
}<commit_msg>Fix upload temp file<commit_after>var path = require('path');
var fs = require('fs');
var uuidv4 = require('uuid/v4');
var request = require('superagent');
var logger = require('../common/logger');
var config = require('../config');
var formidable = require('formidable');
var OSS = require('ali-oss').Wrapper;

var client = new OSS(config.aliOSS);

/**
 * 文件上传
 * @param {String} folder 目标文件夹
 * @param {String} file 目标文件字段
 * @return {String} url 文件存放路径
 */
exports.upload = function (req, res, next) {
  // 允许上传的文件类型
  var whiteList = ['image/jpeg', 'image/gif', 'image/png'];

  // 解析form data
  var form = new formidable.IncomingForm();
  form.uploadDir = path.join(__dirname, '../tmpdir');

  form.parse(req, function (err, fields, files) {
    var folder = fields.folder;

    var path = files.file.path;
    var type = files.file.type;
    var size = files.file.size;

    // 生成文件名
    var filename = uuidv4() + '.' + type.split('/')[1];

    // 过滤非白名单文件
    if (!folder || whiteList.find(elem => {
        return elem === type;
      }) === undefined) {

      res.status(400).json({
        statusCode: 400,
        errMsg: '格式错误'
      });

      // 移除临时文件
      if (fs.existsSync(path)) {
        fs.unlink(path);
      }
    }

    // 上传文件
    client
      .put(folder + '/' + filename, path)
      .then(val => {
        // 移除临时文件
        if (fs.existsSync(path)) {
          fs.unlink(path);
        }

        // 返回文件url
        res.status(201).json({
          statusCode: 201,
          errMsg: '上传成功',
          data: {
            urls: val.res.requestUrls
          }
        });
      })
      .catch(err => {
        logger.error(err);
        // 移除临时文件
        if (fs.existsSync(path)) {
          fs.unlink(path);
        }

        res.status(500).json({
          statusCode: 500,
          errMsg: '上传失败'
        });
      })
  });
}<|endoftext|>


<commit_before>/*******************************************************************************
 * Copyright (c) 2015-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/

'use strict';

var path = require('path');
var gulp = require('gulp');
var conf = require('./conf');

var browserSync = require('browser-sync');

function isOnlyChange(event) {
  return event.type === 'changed';
}

gulp.task('watch', ['scripts:watch', 'inject'], function () {

  gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject']);

  gulp.watch([
    path.join(conf.paths.src, '/app/**/*.css'),
    path.join(conf.paths.src, '/app/**/*.styl')
  ], function(event) {
    if(isOnlyChange(event)) {
      gulp.start('styles');
    } else {
      gulp.start('inject');
    }
  });

  gulp.watch(path.join(conf.paths.src, '/app/**/*.html'), function(event) {
    browserSync.reload(event.path);
  });
});
<commit_msg>Add tracking components changes<commit_after>/*******************************************************************************
 * Copyright (c) 2015-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/

'use strict';

var path = require('path');
var gulp = require('gulp');
var conf = require('./conf');

var browserSync = require('browser-sync');

function isOnlyChange(event) {
  return event.type === 'changed';
}

gulp.task('watch', ['scripts:watch', 'inject'], function () {

  gulp.watch([path.join(conf.paths.src, '/*.html'), 'bower.json'], ['inject']);

  gulp.watch([
    path.join(conf.paths.src, '/app/**/*.css'),
    path.join(conf.paths.src, '/app/**/*.styl'),
    path.join(conf.paths.src, '/components/**/*.css'),
    path.join(conf.paths.src, '/components/**/*.styl')
  ], function(event) {
    if(isOnlyChange(event)) {
      gulp.start('styles');
    } else {
      gulp.start('inject');
    }
  });

  gulp.watch([
      path.join(conf.paths.src, '/app/**/*.html'),
      path.join(conf.paths.src, '/components/**/*.html')
    ], function(event) {
    browserSync.reload(event.path);
  });
});
<|endoftext|>


<commit_before>/**
 *
 * @para {Client} client - The discord.js client.
 *
 */

module.exports = function (client) {
  client.on('message', message => {
    var h = message.author.lastMessage.createdAt

    if (message.content.startsWith('C\'est quoi mon avatar')) {
      // On envoie son avatar
      message.reply(message.author.avatarURL)
      console.log(h + ' Nom du proprio de l\'avatar: ' + message.author.username)
    }
    if (message.content === 'Avatar ComaBot') {
      message.channel.send('C\'est quoi mon avatar')
    }
  })

  // ARRIVE
  client.on('guildMemberAdd', member => {
    // Envoie un message au channel designer :
    const channel = member.guild.channels.find('name', 'bonjour-bye')
    // Fais rien si le channel existe pas
    if (!channel) return
    // Envoie le message, en mentionnant le membre
    channel.send(`-----------------------------------\n   Bienvenue sur le serveur ${member}\n-----------------------------------`)
  })

  // DEPART
  client.on('guildMemberRemove', member => {
    // Envoie un message au channel designer:
    const channel = member.guild.channels.find('name', 'bonjour-bye')
    // Fais rien si le channel existe pas
    if (!channel) return
    // Envoie le message, en mentionnant le membre
    channel.send(`-----------------------------------\n   ${member} est parti du serveur  \n-----------------------------------`)
  })
}
<commit_msg>Added Files<commit_after>/**
 *
 * @para {Client} client - The discord.js client.
 *
 */

module.exports = function (client) {
  client.on('message', message => {
    var h = message.author.lastMessage.createdAt

    if (message.content.startsWith('C\'est quoi mon avatar')) {
      // On envoie son avatar
      message.reply(message.author.avatarURL)
      console.log(h + ' Nom du proprio de l\'avatar: ' + message.author.username)
    }
    if (message.content === 'Avatar ComaBot') {
      message.channel.send('C\'est quoi mon avatar')
    }
  })

  // ARRIVE
  client.on('guildMemberAdd', member => {
    client.on('message', message => {
    // Envoie un message au channel designer :
      const channel = member.guild.channels.find('name', 'bonjour-bye')
      // Fais rien si le channel existe pas
      if (!channel) return
      // Envoie le message, en mentionnant le membre
      let role = message.guild.roles.find('name', 'Nouveaux retard')
      channel.send(`-----------------------------------\n   Bienvenue sur le serveur ${member}\n-----------------------------------`)
      message.guild.member({member}).addRole(role.id)
    })
  })
  // DEPART
  client.on('guildMemberRemove', member => {
    // Envoie un message au channel designer:
    const channel = member.guild.channels.find('name', 'bonjour-bye')
    // Fais rien si le channel existe pas
    if (!channel) return
    // Envoie le message, en mentionnant le membre
    channel.send(`-----------------------------------\n   ${member} est parti du serveur  \n-----------------------------------`)
  })
}
<|endoftext|>


<commit_before>/*
Requires parameters so load https://megjlow.github.io/extensionloader.js and use that block to load this one.
Parameters are:
name - 	If you have a router set up which will assign the same IP address to a tile every time and will do DNS then the tile can be addressed
		by name e.g. http://SUN/XXX will work if SUN resolves to the IP address of a tile e.g. https://megjlow.github.io/extension2.js?name=SUN
ip -	If you don't have a router set up to assign the same IP address to a tile every time or you don't have DNS which will resolve the tile
		name then use the IP parameter e.g. https://megjlow.github.io/extension2.js?ip=10.0.0.1 will load the tile extension which communicates
		with a tile at the IP address 10.0.0.1

If both name and ip are supplied then name will be used before ip.
Digital pins 1, 2 and 3 map to the Feather Huzzah pins 12, 13 and 14
Pwm pins 1 and 2 map to the Feather Huzza pins 4 and 5
*/

new (function() {
	var ext = this;
	ext.isReady = false;
	ext.socket = null;

 	var getUrlParameter = function getUrlParameter(sParam) {
	    var sPageURL = decodeURIComponent(document.currentScript.src.split("?")[1]),
	        sURLVariables = sPageURL.split('&'),
	        sParameterName,
	        i;

	    for (i = 0; i < sURLVariables.length; i++) {
	        sParameterName = sURLVariables[i].split('=');

	        if (sParameterName[0] === sParam) {
	            return sParameterName[1] === undefined ? true : sParameterName[1];
	        }
	    }
	};

	ext.name = getUrlParameter('name');
	ext.ip = getUrlParameter('ip');

	if(ext.name != undefined) {
		// we'll use the name supplied by preference
		ext.url = "http://" + ext.name;
		console.log('got name');
	}
	else {
		// no name supplied so use the IP address
		ext.url = "http://" + ext.ip;
		console.log("didn't get name");
	}

	console.log('name ' + ext.name);
	console.log('ip ' + ext.ip);
	console.log('using url ' + ext.url);

	var descriptor = {
    	blocks: [
      		[' ', ext.name + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
      		[' ', ext.name + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
      		[' ', ext.name + ': digital pin %m.pin get', 'getDigital', '1'],
      		[' ', ext.name + ': pwm pin %m.ppin get', 'getPwm', '1']
    	],
    	'menus': {
      		'pin': ['1', '2', '3'],
      		'dsetting': ['on', 'off'],
      		'ppin': ['1', '2']
     	},
    	url: 'http://www.warwick.ac.uk/tilesfortales'
  	};

  	if(ext.name == undefined) {
  		var descriptor = {
	    	blocks: [
				[' ', ext.ip + ': connect', 'connect'],
				[' ', ext.ip + ': disconnect', 'disconnect'],
				['b', ext.ip + ': isConnected', 'isConnected'],
				[' ', ext.ip + ': setPinMode %m.pin %m.io %m.ioMode', 'setPinMode', 0, 'output', 'digital'],
	      		[' ', ext.ip + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
	      		[' ', ext.ip + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
	      		[' ', ext.ip + ': digital pin %m.pin get', 'getDigital', '1'],
	      		[' ', ext.ip + ': pwm pin %m.ppin get', 'getPwm', '1']
	    	],
	    	'menus': {
	      		'pin': ['1', '2', '3'],
	      		'dsetting': ['on', 'off'],
	      		'ppin': ['1', '2'],
				'io': ['output', 'input'],
				'ioMode': ['digital', 'pwm']
	     	},
	    	url: 'http://www.warwick.ac.uk/tilesfortales'
	  	};
  	}

  ext._shutdown = function() {};

  ext.setPinMode = function(pin, mode) {
    console.log("setPinMode " + pin);
    console.log("setPinMode " + mode);
    var bytearray = new Uint8Array(3);
    /*
    bytearray[0] = 0xF0;
    bytearray[1] = 0x69;
    bytearray[2] = 0xF7;
    ext.socket.send(bytearray.buffer);
    */

    bytearray[0] = 0xF4;// PIN_MODE;
    bytearray[1] = pin;
    bytearray[2] = 0x01; // output
    ext.socket.send(bytearray.buffer);

    console.log("sent");

  }

  ext.isConnected = function() {
    var retval = false;
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = true;
    }
    return retval;
  }

  ext.disconnect = function() {
    if(ext.socket != null) {
      ext.socket.close();
      ext.socket = null;
    }
  }

  ext.onMessage = function(evt)
  {
    console.log("Received: " + evt.data + " length " + evt.data.size);
    var dv = new DataView(evt.data);
    var r = "received message: ";
    for(var i=0; i<dv.byteLength; i++) {
    	r = r + " " + dv.getUint8(i).toString(16);
    }
   	console.log(r);
  }

  ext.doSend = function(message)
  {
    console.log("SENT: " + message);
    ext.socket.send(message);
  }

  ext.onOpen = function(evt)
  {
    console.log("Connected");
    ext.doSend("WebSocket rocks");
  }

  ext.onClose = function(evt) {
    ext.socket = null;
  }

  ext._getStatus = function() {
    var retval = {status: 1, msg: 'Not Connected'};
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = {status: 2, msg: 'Device connected'};
    }
    return retval;
  };
	//CONNECTING OPEN CLOSING or CLOSED

  ext.connect = function() {
    if(ext.socket == null) {
      ext.socket = new WebSocket("ws://" + ext.ip, 'firmata');
      ext.socket.binaryType = "arraybuffer";
      ext.socket.onopen = function(evt) {ext.onOpen(evt)};
      ext.socket.onmessage = function(evt) {ext.onMessage(evt)};
      ext.socket.onclose = function(evt) {ext.onClose(evt)};
    }
    else {
      if(ext.socket.readyState == ext.socket.CLOSING || ext.socket.readyState == ext.socket.CLOSED) {
	ext.socket = null;
        ext.connect();
      }
    }
    // if socket is in open or connecting state we're not going to do anything
  }

  ext.getPwm = function(pin) {
  };
  ext.setPwm = function(pin, setting) {
    var p = 4;
    if(pin == 2) {
      p = 5;
    }
    var url = ext.url + '/gpio' + p + '/' + setting;
    $.ajax({
      type: 'POST',
      url: url,
      success: function(response) {
      }
    });
    setTimeout(function(){ }, 10);
  };

  	ext.getDigital = function(pin) {
  		var bytearray = new Uint8Array(4);
  		bytearray[0] = 0xF0; // Start sysex
  		bytearray[1] = 0x6D; // PIN_STATE_QUERY
  		bytearray[2] = pin;
  		bytearray[3] = 0xF7; // end sysex
  		ext.socket.send(bytearray);
	};

	ext.setDigital = function(pin, setting) {
	    var s = 1;
	    if(setting == 'off') {
	      s = 0;
	    }
	    var bytearray = new Uint8Array(3);
	    bytearray[0] = 0xF5; // set digital pin value
	    bytearray[1] = pin;
	    bytearray[2] = s;

	    ext.socket.send(bytearray);

	    console.log("setDigital");
	};

	if(ext.name != undefined) {
		ScratchExtensions.register(ext.name, descriptor, ext);
	}
	else {
		ScratchExtensions.register(ext.ip, descriptor, ext);
	}

})();
<commit_msg>Promise<commit_after>/*
Requires parameters so load https://megjlow.github.io/extensionloader.js and use that block to load this one.
Parameters are:
name - 	If you have a router set up which will assign the same IP address to a tile every time and will do DNS then the tile can be addressed
		by name e.g. http://SUN/XXX will work if SUN resolves to the IP address of a tile e.g. https://megjlow.github.io/extension2.js?name=SUN
ip -	If you don't have a router set up to assign the same IP address to a tile every time or you don't have DNS which will resolve the tile
		name then use the IP parameter e.g. https://megjlow.github.io/extension2.js?ip=10.0.0.1 will load the tile extension which communicates
		with a tile at the IP address 10.0.0.1

If both name and ip are supplied then name will be used before ip.
Digital pins 1, 2 and 3 map to the Feather Huzzah pins 12, 13 and 14
Pwm pins 1 and 2 map to the Feather Huzza pins 4 and 5
*/

new (function() {
	var ext = this;
	ext.isReady = false;
	ext.socket = null;

 	var getUrlParameter = function getUrlParameter(sParam) {
	    var sPageURL = decodeURIComponent(document.currentScript.src.split("?")[1]),
	        sURLVariables = sPageURL.split('&'),
	        sParameterName,
	        i;

	    for (i = 0; i < sURLVariables.length; i++) {
	        sParameterName = sURLVariables[i].split('=');

	        if (sParameterName[0] === sParam) {
	            return sParameterName[1] === undefined ? true : sParameterName[1];
	        }
	    }
	};

	ext.name = getUrlParameter('name');
	ext.ip = getUrlParameter('ip');

	if(ext.name != undefined) {
		// we'll use the name supplied by preference
		ext.url = "http://" + ext.name;
		console.log('got name');
	}
	else {
		// no name supplied so use the IP address
		ext.url = "http://" + ext.ip;
		console.log("didn't get name");
	}

	console.log('name ' + ext.name);
	console.log('ip ' + ext.ip);
	console.log('using url ' + ext.url);

	var descriptor = {
    	blocks: [
      		[' ', ext.name + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
      		[' ', ext.name + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
      		[' ', ext.name + ': digital pin %m.pin get', 'getDigital', '1'],
      		[' ', ext.name + ': pwm pin %m.ppin get', 'getPwm', '1']
    	],
    	'menus': {
      		'pin': ['1', '2', '3'],
      		'dsetting': ['on', 'off'],
      		'ppin': ['1', '2']
     	},
    	url: 'http://www.warwick.ac.uk/tilesfortales'
  	};

  	if(ext.name == undefined) {
  		var descriptor = {
	    	blocks: [
				[' ', ext.ip + ': connect', 'connect'],
				[' ', ext.ip + ': disconnect', 'disconnect'],
				['b', ext.ip + ': isConnected', 'isConnected'],
				[' ', ext.ip + ': setPinMode %m.pin %m.io %m.ioMode', 'setPinMode', 0, 'output', 'digital'],
	      		[' ', ext.ip + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
	      		[' ', ext.ip + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
	      		[' ', ext.ip + ': digital pin %m.pin get', 'getDigital', '1'],
	      		[' ', ext.ip + ': pwm pin %m.ppin get', 'getPwm', '1']
	    	],
	    	'menus': {
	      		'pin': ['1', '2', '3'],
	      		'dsetting': ['on', 'off'],
	      		'ppin': ['1', '2'],
				'io': ['output', 'input'],
				'ioMode': ['digital', 'pwm']
	     	},
	    	url: 'http://www.warwick.ac.uk/tilesfortales'
	  	};
  	}

  ext._shutdown = function() {};

  ext.setPinMode = function(pin, mode) {
    console.log("setPinMode " + pin);
    console.log("setPinMode " + mode);
    var bytearray = new Uint8Array(3);
    /*
    bytearray[0] = 0xF0;
    bytearray[1] = 0x69;
    bytearray[2] = 0xF7;
    ext.socket.send(bytearray.buffer);
    */

    bytearray[0] = 0xF4;// PIN_MODE;
    bytearray[1] = pin;
    bytearray[2] = 0x01; // output
    ext.socket.send(bytearray.buffer);

    console.log("sent");

  }

  ext.isConnected = function() {
    var retval = false;
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = true;
    }
    return retval;
  }

  ext.disconnect = function() {
    if(ext.socket != null) {
      ext.socket.close();
      ext.socket = null;
    }
  }

  ext.onMessage = function(evt)
  {
    var dv = new DataView(evt.data);
    var r = "received message: ";
    for(var i=0; i<dv.byteLength; i++) {
    	r = r + " " + dv.getUint8(i).toString(16);
    }
    // 6e PIN_STATE_RESPONSE
   	console.log(r);
  }

  ext.doSend = function(message)
  {
    console.log("SENT: " + message);
    ext.socket.send(message);
  }

  ext.onOpen = function(evt)
  {
    console.log("Connected");
    ext.doSend("WebSocket rocks");
  }

  ext.onClose = function(evt) {
    ext.socket = null;
  }

  ext._getStatus = function() {
    var retval = {status: 1, msg: 'Not Connected'};
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = {status: 2, msg: 'Device connected'};
    }
    return retval;
  };

  ext.connect = function() {
    if(ext.socket == null) {
      ext.socket = new WebSocket("ws://" + ext.ip, 'firmata');
      ext.socket.binaryType = "arraybuffer";
      ext.socket.onopen = function(evt) {ext.onOpen(evt)};
      ext.socket.onmessage = function(evt) {ext.onMessage(evt)};
      ext.socket.onclose = function(evt) {ext.onClose(evt)};
    }
    else {
      if(ext.socket.readyState == ext.socket.CLOSING || ext.socket.readyState == ext.socket.CLOSED) {
	ext.socket = null;
        ext.connect();
      }
    }
    // if socket is in open or connecting state we're not going to do anything
  }

  ext.getPwm = function(pin) {
  };
  ext.setPwm = function(pin, setting) {
    var p = 4;
    if(pin == 2) {
      p = 5;
    }
    var url = ext.url + '/gpio' + p + '/' + setting;
    $.ajax({
      type: 'POST',
      url: url,
      success: function(response) {
      }
    });
    setTimeout(function(){ }, 10);
  };

  	ext.getDigital = function(pin) {
  		var bytearray = new Uint8Array(4);
  		bytearray[0] = 0xF0; // Start sysex
  		bytearray[1] = 0x6D; // PIN_STATE_QUERY
  		bytearray[2] = pin;
  		bytearray[3] = 0xF7; // end sysex
  		ext.socket.send(bytearray);
  		ext.pin-state-1 = new Promise(resolve => {
  			setTimeout(() => resolve(10); }, 5000);
  		});
  		return ext.pin-state-1;
	};

	ext.setDigital = function(pin, setting) {
	    var s = 1;
	    if(setting == 'off') {
	      s = 0;
	    }
	    var bytearray = new Uint8Array(3);
	    bytearray[0] = 0xF5; // set digital pin value
	    bytearray[1] = pin;
	    bytearray[2] = s;

	    ext.socket.send(bytearray);

	    console.log("setDigital");
	};

	if(ext.name != undefined) {
		ScratchExtensions.register(ext.name, descriptor, ext);
	}
	else {
		ScratchExtensions.register(ext.ip, descriptor, ext);
	}

})();
<|endoftext|>


<commit_before>/*
Requires parameters so load https://megjlow.github.io/extensionloader.js and use that block to load this one.
Parameters are:
name - 	If you have a router set up which will assign the same IP address to a tile every time and will do DNS then the tile can be addressed
		by name e.g. http://SUN/XXX will work if SUN resolves to the IP address of a tile e.g. https://megjlow.github.io/extension2.js?name=SUN
ip -	If you don't have a router set up to assign the same IP address to a tile every time or you don't have DNS which will resolve the tile
		name then use the IP parameter e.g. https://megjlow.github.io/extension2.js?ip=10.0.0.1 will load the tile extension which communicates
		with a tile at the IP address 10.0.0.1

If both name and ip are supplied then name will be used before ip.
Digital pins 1, 2 and 3 map to the Feather Huzzah pins 12, 13 and 14
Pwm pins 1 and 2 map to the Feather Huzza pins 4 and 5
*/

new (function() {
	var ext = this;
	ext.isReady = false;
	ext.socket = null;

 	var getUrlParameter = function getUrlParameter(sParam) {
	    var sPageURL = decodeURIComponent(document.currentScript.src.split("?")[1]),
	        sURLVariables = sPageURL.split('&'),
	        sParameterName,
	        i;

	    for (i = 0; i < sURLVariables.length; i++) {
	        sParameterName = sURLVariables[i].split('=');

	        if (sParameterName[0] === sParam) {
	            return sParameterName[1] === undefined ? true : sParameterName[1];
	        }
	    }
	};

	ext.name = getUrlParameter('name');
	ext.ip = getUrlParameter('ip');

	if(ext.name != undefined) {
		// we'll use the name supplied by preference
		ext.url = "http://" + ext.name;
		console.log('got name');
	}
	else {
		// no name supplied so use the IP address
		ext.url = "http://" + ext.ip;
		console.log("didn't get name");
	}

	console.log('name ' + ext.name);
	console.log('ip ' + ext.ip);
	console.log('using url ' + ext.url);

	var descriptor = {
    	blocks: [
      		[' ', ext.name + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
      		[' ', ext.name + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
      		[' ', ext.name + ': digital pin %m.pin get', 'getDigital', '1'],
      		[' ', ext.name + ': pwm pin %m.ppin get', 'getPwm', '1']
    	],
    	'menus': {
      		'pin': ['1', '2', '3'],
      		'dsetting': ['on', 'off'],
      		'ppin': ['1', '2']
     	},
    	url: 'http://www.warwick.ac.uk/tilesfortales'
  	};

  	if(ext.name == undefined) {
  		var descriptor = {
	    	blocks: [
				[' ', ext.ip + ': connect', 'connect'],
				[' ', ext.ip + ': disconnect', 'disconnect'],
				['b', ext.ip + ': isConnected', 'isConnected'],
				[' ', ext.ip + ': setPinMode %m.pin %m.io %m.ioMode', 'setPinMode', 0, 'output', 'digital'],
	      		[' ', ext.ip + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
	      		[' ', ext.ip + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
	      		[' ', ext.ip + ': digital pin %m.pin get', 'getDigital', '1'],
	      		[' ', ext.ip + ': pwm pin %m.ppin get', 'getPwm', '1']
	    	],
	    	'menus': {
	      		'pin': ['1', '2', '3'],
	      		'dsetting': ['on', 'off'],
	      		'ppin': ['1', '2'],
				'io': ['output', 'input'],
				'ioMode': ['digital', 'pwm']
	     	},
	    	url: 'http://www.warwick.ac.uk/tilesfortales'
	  	};
  	}

  ext._shutdown = function() {};

  ext.setPinMode = function(pin, mode) {
    console.log("setPinMode " + pin);
    console.log("setPinMode " + mode);
    var bytearray = new Uint8Array(3);
    /*
    bytearray[0] = 0xF0;
    bytearray[1] = 0x69;
    bytearray[2] = 0xF7;
    ext.socket.send(bytearray.buffer);
    */

    bytearray[0] = 0xF4;// PIN_MODE;
    bytearray[1] = pin;
    bytearray[2] = 0x01; // output
    ext.socket.send(bytearray.buffer);

    console.log("sent");

  }

  ext.isConnected = function() {
    var retval = false;
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = true;
    }
    return retval;
  }

  ext.disconnect = function() {
    if(ext.socket != null) {
      ext.socket.close();
      ext.socket = null;
    }
  }

  ext.onMessage = function(evt)
  {
    console.log("Received: " + evt.data);
  }

  ext.doSend = function(message)
  {
    console.log("SENT: " + message);
    ext.socket.send(message);
  }

  ext.onOpen = function(evt)
  {
    console.log("Connected");
    ext.doSend("WebSocket rocks");
  }

  ext.onClose = function(evt) {
    ext.socket = null;
  }

  ext._getStatus = function() {
    var retval = {status: 1, msg: 'Not Connected'};
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = {status: 2, msg: 'Device connected'};
    }
    return retval;
  };
	//CONNECTING OPEN CLOSING or CLOSED

  ext.connect = function() {
    if(ext.socket == null) {
      ext.socket = new WebSocket("ws://" + ext.ip, 'firmata');
      ext.socket.onopen = function(evt) {ext.onOpen(evt)};
      ext.socket.onmessage = function(evt) {ext.onMessage(evt)};
      ext.socket.onclose = function(evt) {ext.onClose(evt)};
    }
    else {
      if(ext.socket.readyState == ext.socket.CLOSING || ext.socket.readyState == ext.socket.CLOSED) {
	ext.socket = null;
        ext.connect();
      }
    }
    // if socket is in open or connecting state we're not going to do anything
  }

  ext.getPwm = function(pin) {
  };
  ext.setPwm = function(pin, setting) {
    var p = 4;
    if(pin == 2) {
      p = 5;
    }
    var url = ext.url + '/gpio' + p + '/' + setting;
    $.ajax({
      type: 'POST',
      url: url,
      success: function(response) {
      }
    });
    setTimeout(function(){ }, 10);
  };

  	ext.getDigital = function(pin) {
	};

	ext.setDigital = function(pin, setting) {
	    var s = 1;
	    if(setting == 'off') {
	      s = 0;
	    }
	    bytearray[0] = 0xF5; // set digital pin value
	    bytearray[1] = 2;
	    bytearray[2] = s;
	    ext.socket.send(bytearray.buffer);

	    console.log("setDigital");
	};

	if(ext.name != undefined) {
		ScratchExtensions.register(ext.name, descriptor, ext);
	}
	else {
		ScratchExtensions.register(ext.ip, descriptor, ext);
	}

})();
<commit_msg>Set digital pin value<commit_after>/*
Requires parameters so load https://megjlow.github.io/extensionloader.js and use that block to load this one.
Parameters are:
name - 	If you have a router set up which will assign the same IP address to a tile every time and will do DNS then the tile can be addressed
		by name e.g. http://SUN/XXX will work if SUN resolves to the IP address of a tile e.g. https://megjlow.github.io/extension2.js?name=SUN
ip -	If you don't have a router set up to assign the same IP address to a tile every time or you don't have DNS which will resolve the tile
		name then use the IP parameter e.g. https://megjlow.github.io/extension2.js?ip=10.0.0.1 will load the tile extension which communicates
		with a tile at the IP address 10.0.0.1

If both name and ip are supplied then name will be used before ip.
Digital pins 1, 2 and 3 map to the Feather Huzzah pins 12, 13 and 14
Pwm pins 1 and 2 map to the Feather Huzza pins 4 and 5
*/

new (function() {
	var ext = this;
	ext.isReady = false;
	ext.socket = null;

 	var getUrlParameter = function getUrlParameter(sParam) {
	    var sPageURL = decodeURIComponent(document.currentScript.src.split("?")[1]),
	        sURLVariables = sPageURL.split('&'),
	        sParameterName,
	        i;

	    for (i = 0; i < sURLVariables.length; i++) {
	        sParameterName = sURLVariables[i].split('=');

	        if (sParameterName[0] === sParam) {
	            return sParameterName[1] === undefined ? true : sParameterName[1];
	        }
	    }
	};

	ext.name = getUrlParameter('name');
	ext.ip = getUrlParameter('ip');

	if(ext.name != undefined) {
		// we'll use the name supplied by preference
		ext.url = "http://" + ext.name;
		console.log('got name');
	}
	else {
		// no name supplied so use the IP address
		ext.url = "http://" + ext.ip;
		console.log("didn't get name");
	}

	console.log('name ' + ext.name);
	console.log('ip ' + ext.ip);
	console.log('using url ' + ext.url);

	var descriptor = {
    	blocks: [
      		[' ', ext.name + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
      		[' ', ext.name + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
      		[' ', ext.name + ': digital pin %m.pin get', 'getDigital', '1'],
      		[' ', ext.name + ': pwm pin %m.ppin get', 'getPwm', '1']
    	],
    	'menus': {
      		'pin': ['1', '2', '3'],
      		'dsetting': ['on', 'off'],
      		'ppin': ['1', '2']
     	},
    	url: 'http://www.warwick.ac.uk/tilesfortales'
  	};

  	if(ext.name == undefined) {
  		var descriptor = {
	    	blocks: [
				[' ', ext.ip + ': connect', 'connect'],
				[' ', ext.ip + ': disconnect', 'disconnect'],
				['b', ext.ip + ': isConnected', 'isConnected'],
				[' ', ext.ip + ': setPinMode %m.pin %m.io %m.ioMode', 'setPinMode', 0, 'output', 'digital'],
	      		[' ', ext.ip + ': digital pin %m.pin setting %m.dsetting', 'setDigital', '1', 'off'],
	      		[' ', ext.ip + ': pwm pin %m.ppin setting %n', 'setPwm', '1', '100'],
	      		[' ', ext.ip + ': digital pin %m.pin get', 'getDigital', '1'],
	      		[' ', ext.ip + ': pwm pin %m.ppin get', 'getPwm', '1']
	    	],
	    	'menus': {
	      		'pin': ['1', '2', '3'],
	      		'dsetting': ['on', 'off'],
	      		'ppin': ['1', '2'],
				'io': ['output', 'input'],
				'ioMode': ['digital', 'pwm']
	     	},
	    	url: 'http://www.warwick.ac.uk/tilesfortales'
	  	};
  	}

  ext._shutdown = function() {};

  ext.setPinMode = function(pin, mode) {
    console.log("setPinMode " + pin);
    console.log("setPinMode " + mode);
    var bytearray = new Uint8Array(3);
    /*
    bytearray[0] = 0xF0;
    bytearray[1] = 0x69;
    bytearray[2] = 0xF7;
    ext.socket.send(bytearray.buffer);
    */

    bytearray[0] = 0xF4;// PIN_MODE;
    bytearray[1] = pin;
    bytearray[2] = 0x01; // output
    ext.socket.send(bytearray.buffer);

    console.log("sent");

  }

  ext.isConnected = function() {
    var retval = false;
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = true;
    }
    return retval;
  }

  ext.disconnect = function() {
    if(ext.socket != null) {
      ext.socket.close();
      ext.socket = null;
    }
  }

  ext.onMessage = function(evt)
  {
    console.log("Received: " + evt.data);
  }

  ext.doSend = function(message)
  {
    console.log("SENT: " + message);
    ext.socket.send(message);
  }

  ext.onOpen = function(evt)
  {
    console.log("Connected");
    ext.doSend("WebSocket rocks");
  }

  ext.onClose = function(evt) {
    ext.socket = null;
  }

  ext._getStatus = function() {
    var retval = {status: 1, msg: 'Not Connected'};
    if(ext.socket != null && ext.socket.readyState == ext.socket.OPEN) {
      retval = {status: 2, msg: 'Device connected'};
    }
    return retval;
  };
	//CONNECTING OPEN CLOSING or CLOSED

  ext.connect = function() {
    if(ext.socket == null) {
      ext.socket = new WebSocket("ws://" + ext.ip, 'firmata');
      ext.socket.onopen = function(evt) {ext.onOpen(evt)};
      ext.socket.onmessage = function(evt) {ext.onMessage(evt)};
      ext.socket.onclose = function(evt) {ext.onClose(evt)};
    }
    else {
      if(ext.socket.readyState == ext.socket.CLOSING || ext.socket.readyState == ext.socket.CLOSED) {
	ext.socket = null;
        ext.connect();
      }
    }
    // if socket is in open or connecting state we're not going to do anything
  }

  ext.getPwm = function(pin) {
  };
  ext.setPwm = function(pin, setting) {
    var p = 4;
    if(pin == 2) {
      p = 5;
    }
    var url = ext.url + '/gpio' + p + '/' + setting;
    $.ajax({
      type: 'POST',
      url: url,
      success: function(response) {
      }
    });
    setTimeout(function(){ }, 10);
  };

  	ext.getDigital = function(pin) {
	};

	ext.setDigital = function(pin, setting) {
	    var s = 1;
	    if(setting == 'off') {
	      s = 0;
	    }
	    var bytearray = new Uint8Array(3);
	    bytearray[0] = 0xF5; // set digital pin value
	    bytearray[1] = 2;
	    bytearray[2] = s;
	    ext.socket.send(bytearray.buffer);

	    console.log("setDigital");
	};

	if(ext.name != undefined) {
		ScratchExtensions.register(ext.name, descriptor, ext);
	}
	else {
		ScratchExtensions.register(ext.ip, descriptor, ext);
	}

})();
<|endoftext|>


<commit_before>(function(e){if(typeof module!=="undefined"&&module&&module.exports){module.exports=e()}else if(typeof define==="function"&&define.amd){define(e)}else{P=e()}})(function(){"use strict";var e=i(),n,t=null;function r(e){var n=/at .+ \((.+):(\d+):(?:\d+)\)$/.exec(e)||/at ([^ ]+):(\d+):(?:\d+)$/.exec(e)||/.*@(.+):(\d+)$/.exec(e);return n?{fileName:n[1],lineNumber:Number(n[2])}:null}function i(){var e=(new Error).stack;if(!e){return 0}var t=e.split("\n");var i=t[0].indexOf("@")>0?t[1]:t[2];var a=r(i);if(!a){return 0}n=a.fileName;return a.lineNumber}function a(e,n){var t=e.split("\n");var r=[];for(var i=n|0,a=t.length;i<a;++i){var l=t[i];if(l&&!u(l)&&!o(l)){r.push(l)}}return r.join("\n")}function u(e){return e.indexOf("(module.js:")!==-1||e.indexOf("(node.js:")!==-1}function o(t){var i=r(t);return!!i&&i.fileName===n&&i.lineNumber>=e&&i.lineNumber<=on}var l="\nFrom previous event:\n";function f(e){if(e instanceof Error&&e.stack&&e.stack.indexOf(l)===-1){var n=[a(e.stack,0)];var r=t;var i=512;while(r&&--i){var u=r.stack&&a(r.stack,2);if(u){n.push(u)}r=r.parent}var o=n.join(l);e.stack=o}}var s=T(typeof process)&&process!=null&&{}.toString.call(process)==="[object process]",c=typeof setImmediate==="function",p=T(typeof MutationObserver)&&MutationObserver||T(typeof WebKitMutationObserver)&&WebKitMutationObserver,v=new j,d=v,h=false,_=0,m=s?$:p?A(N):K(N),y=[],b=K(E),w,g,k=T.call,x=T.apply;d.next=v;function j(){this.task=null;this.a=null;this.b=null;this.domain=null;this.trace=null;this.next=null}function T(e){return e==="object"||e==="function"}function E(){if(y.length){throw y.shift()}}function N(){while(v!==d){v=v.next;if(_>=1024){d.next=d.next.next}else{++_}t=v.trace;if(v.domain){S(v.domain,v.task,v.a,v.b,void 0);v.domain=null}else{(1,v.task)(v.a,v.b)}v.task=null;v.a=null;v.b=null;v.trace=null}h=false;t=null}function O(){v.task=null;v.a=null;v.b=null;v.domain=null;v.trace=null;m()}function S(e,n,t,r,i){if(e._disposed){return}e.enter();n(t,r,i);e.exit()}function I(e,n,r){var i=s?process.domain:null;var a=B.longStackSupport?{parent:t,stack:(new Error).stack}:null;M(e,n,r,i,a)}function M(e,n,t,r,i){var a=d.next;if(a===v){a=new j;d.next=a;a.next=v}else{--_}d=a;a.task=e;a.a=n;a.b=t;a.domain=r;a.trace=i;if(!h){h=true;m()}}function $(){var e=process.domain;if(e){if(!g)g=(1,require)("domain");g.active=process.domain=null}if(h&&c){setImmediate(N)}else{process.nextTick(N)}if(e){g.active=process.domain=e}}function A(e){var n=1;var t=document.createTextNode("");var r=new p(e);r.observe(t,{characterData:true});return function(){n=-n;t.data=n}}function K(e){return function(){var n=setTimeout(r,0);var t=setInterval(r,50);function r(){clearTimeout(n);clearInterval(t);e()}}}if(s){w=function(e){O();throw e}}else{w=function(e){y.push(e);b()}}function W(e,n){try{e.call()}catch(t){n(t)}}function q(e){I(W,e,w)}function D(e,n){for(var t=0,r=e.length;t<r;++t){if(t in e){n(e[t],t)}}}function F(e){q(function(){if(B.onerror){B.onerror.call(null,e)}else{throw e}})}var P=0;var Y=1;var z=2;function B(e){return e instanceof nn?e:J(new nn,e,false)}B.longStackSupport=false;function C(e,n){if(e._state){return}e._state=Y;e._value=n;if(e._pending){Q(e)}}function G(e,n){if(e._state){return}if(t){f(n)}e._state=z;e._value=n;if(s){e._domain=process.domain}if(e._pending){Q(e)}}function H(e,n){if(n._state){return}n._state=e._state;n._value=e._value;n._domain=e._domain;if(n._pending){Q(n)}}function J(e,n,t){if(e._state){return e}if(n instanceof nn){if(n===e){G(e,new TypeError("You can't resolve a promise with itself"))}else if(n._state){H(n,e)}else{R(e,n)}}else if(n!==Object(n)){C(e,n)}else if(t){L(e,n)}else{I(L,e,n)}return e}function L(e,n){var t,r;try{r=n.then}catch(i){G(e,i);return}if(typeof r==="function"){t=X(e);try{k.call(r,n,t.resolve,t.reject)}catch(a){t.reject(a)}}else{C(e,n)}}function Q(e){var n=e._pending;e._pending=null;if(n instanceof nn){M(U,e,n,null,n._trace);return}for(var t=0,r=n.length;t<r;++t){M(U,e,n[t],null,n[t]._trace)}}function R(e,n){if(!n._pending){n._pending=e}else if(n._pending instanceof nn){n._pending=[n._pending,e]}else{n._pending.push(e)}}function U(e,n){var t=e._state===Y?n._cb:n._eb;n._cb=null;n._eb=null;if(!t){H(e,n);return}var r=e._domain||n._domain;if(r){n._domain=null;S(r,V,t,n,e._value)}else{V(t,n,e._value)}}function V(e,n,t){var r;try{r=e(t)}catch(i){G(n,i);return}J(n,r,true)}function X(e){var n=false;return{promise:e,resolve:function(t){if(!n){n=true;J(e,t,false)}},reject:function(t){if(!n){n=true;G(e,t)}}}}B.defer=Z;function Z(){return X(new nn)}B.reject=en;function en(e){var n=new nn;G(n,e);return n}function nn(){this._state=0;this._value=void 0;this._domain=null;this._cb=null;this._eb=null;this._pending=null;this._trace=null}nn.prototype.then=function(e,n){var r=new nn;if(B.longStackSupport){r._trace={parent:t,stack:(new Error).stack}}r._cb=typeof e==="function"?e:null;r._eb=typeof n==="function"?n:null;if(s){r._domain=process.domain}if(this._state===P){R(r,this)}else{M(U,this,r,null,r._trace)}return r};nn.prototype.done=function(e,n){var t=this;if(e||n){t=t.then(e,n)}t.then(null,F)};nn.prototype.fail=function(e){return this.then(null,e)};nn.prototype._always=function(e){return this.then(e,e)};nn.prototype.spread=function(e,n){return this.then(e&&function(t){return an(t).then(function(n){return x.call(e,void 0,n)},n)},n)};function tn(e,n){if(t){var r=t;return setTimeout(function(){t=r;e();t=null},n)}return setTimeout(e,n)}nn.prototype.timeout=function(e,n){var t=this;var r=new nn;if(t._state!==P){H(t,r)}else{var i=tn(function(){G(r,new Error(n||"Timed out after "+e+" ms"))},e);t._always(function(){clearTimeout(i);H(t,r)})}return r};nn.prototype.delay=function(e){var n=Z();this.then(function(t){tn(function(){n.resolve(t)},e)},n.reject);return n.promise};nn.prototype.inspect=function(){switch(this._state){case P:return{state:"pending"};case Y:return{state:"fulfilled",value:this._value};case z:return{state:"rejected",reason:this._value};default:throw new TypeError("invalid state")}};B.allSettled=rn;function rn(e){var n=new nn;if(typeof e.length!=="number"){G(n,new TypeError("input not array-like"));return n}var t=0;var r=new Array(e.length);D(e,function(e,i){var a=B(e);if(a._state===P){++t;a._always(function(){r[i]=a.inspect();if(--t===0){C(n,r)}})}else{r[i]=a.inspect()}});if(t===0){C(n,r)}return n}B.all=an;function an(e){var n=Z();if(typeof e.length!=="number"){n.reject(new TypeError("input not array-like"));return n.promise}var t=0;var r=new Array(e.length);D(e,function(e,i){var a=B(e);if(a._state===Y){r[i]=a._value}else{++t;a.then(function(e){r[i]=e;if(--t===0){n.resolve(r)}},n.reject)}});if(t===0){n.resolve(r)}return n.promise}B.promised=un;function un(e){function n(n){return x.apply(e,n)}return function(){return an([this,an(arguments)]).then(n)}}B.onerror=null;B.nextTick=q;var on=i();return B});<commit_msg>Minify.<commit_after>(function(e){if(typeof module!=="undefined"&&module&&module.exports){module.exports=e()}else if(typeof define==="function"&&define.amd){define(e)}else{P=e()}})(function(){"use strict";var e=i(),n,t=null;function r(e){var n=/at .+ \((.+):(\d+):(?:\d+)\)$/.exec(e)||/at ([^ ]+):(\d+):(?:\d+)$/.exec(e)||/.*@(.+):(\d+)$/.exec(e);return n?{fileName:n[1],lineNumber:Number(n[2])}:null}function i(){var e=(new Error).stack;if(!e){return 0}var t=e.split("\n");var i=t[0].indexOf("@")>0?t[1]:t[2];var o=r(i);if(!o){return 0}n=o.fileName;return o.lineNumber}function o(e,n){var t=e.split("\n");var r=[];for(var i=n|0,o=t.length;i<o;++i){var l=t[i];if(l&&!u(l)&&!a(l)){r.push(l)}}return r.join("\n")}function u(e){return e.indexOf("(module.js:")!==-1||e.indexOf("(node.js:")!==-1}function a(t){var i=r(t);return!!i&&i.fileName===n&&i.lineNumber>=e&&i.lineNumber<=an}var l="\nFrom previous event:\n";function f(e){if(e instanceof Error&&e.stack&&e.stack.indexOf(l)===-1){var n=[o(e.stack,0)];var r=t;var i=512;while(r&&--i){var u=r.stack&&o(r.stack,2);if(u){n.push(u)}r=r.parent}var a=n.join(l);e.stack=a}}var s=T(typeof process)&&process!=null&&{}.toString.call(process)==="[object process]",c=typeof setImmediate==="function",p=T(typeof MutationObserver)&&MutationObserver||T(typeof WebKitMutationObserver)&&WebKitMutationObserver,v=new j,d=v,h=false,_=0,m=s?$:p?A(N):K(N),y=[],b=K(E),w,g,k=T.call,x=T.apply;d.next=v;function j(){this.task=null;this.a=null;this.b=null;this.domain=null;this.trace=null;this.next=null}function T(e){return e==="object"||e==="function"}function E(){if(y.length){throw y.shift()}}function N(){while(v!==d){v=v.next;if(_>=1024){d.next=d.next.next}else{++_}t=v.trace;if(v.domain){S(v.domain,v.task,v.a,v.b,void 0);v.domain=null}else{(1,v.task)(v.a,v.b)}v.task=null;v.a=null;v.b=null;v.trace=null}h=false;t=null}function O(){v.task=null;v.a=null;v.b=null;v.domain=null;v.trace=null;m()}function S(e,n,t,r,i){if(e._disposed){return}e.enter();n(t,r,i);e.exit()}function I(e,n,r){var i=s?process.domain:null;var o=B.longStackSupport?{parent:t,stack:(new Error).stack}:null;M(e,n,r,i,o)}function M(e,n,t,r,i){var o=d.next;if(o===v){o=new j;d.next=o;o.next=v}else{--_}d=o;o.task=e;o.a=n;o.b=t;o.domain=r;o.trace=i;if(!h){h=true;m()}}function $(){var e=process.domain;if(e){if(!g)g=(1,require)("domain");g.active=process.domain=null}if(h&&c){setImmediate(N)}else{process.nextTick(N)}if(e){g.active=process.domain=e}}function A(e){var n=1;var t=document.createTextNode("");var r=new p(e);r.observe(t,{characterData:true});return function(){n=-n;t.data=n}}function K(e){return function(){var n=setTimeout(r,0);var t=setInterval(r,50);function r(){clearTimeout(n);clearInterval(t);e()}}}if(s){w=function(e){O();throw e}}else{w=function(e){y.push(e);b()}}function W(e,n){try{e.call()}catch(t){n(t)}}function q(e){I(W,e,w)}function D(e,n){for(var t=0,r=e.length;t<r;++t){if(t in e){n(e[t],t)}}}function F(e){q(function(){if(B.onerror){B.onerror.call(null,e)}else{throw e}})}var P=0;var Y=1;var z=2;function B(e){return e instanceof nn?e:J(new nn,e,false)}B.longStackSupport=false;function C(e,n){if(e._state){return}e._state=Y;e._value=n;if(e._pending){Q(e)}}function G(e,n){if(e._state){return}if(t){f(n)}e._state=z;e._value=n;if(s){e._domain=process.domain}if(e._pending){Q(e)}}function H(e,n){if(n._state){return}n._state=e._state;n._value=e._value;n._domain=e._domain;if(n._pending){Q(n)}}function J(e,n,t){if(e._state){return e}if(n instanceof nn){if(n===e){G(e,new TypeError("You can't resolve a promise with itself"))}else if(n._state){H(n,e)}else{R(e,n)}}else if(n!==Object(n)){C(e,n)}else if(t){L(e,n)}else{I(L,e,n)}return e}function L(e,n){var t,r;try{r=n.then}catch(i){G(e,i);return}if(typeof r==="function"){t=X(e);try{k.call(r,n,t.resolve,t.reject)}catch(o){t.reject(o)}}else{C(e,n)}}function Q(e){var n=e._pending;e._pending=null;if(n instanceof nn){M(U,e,n,null,n._trace);return}for(var t=0,r=n.length;t<r;++t){M(U,e,n[t],null,n[t]._trace)}}function R(e,n){if(!n._pending){n._pending=e}else if(n._pending instanceof nn){n._pending=[n._pending,e]}else{n._pending.push(e)}}function U(e,n){var t=e._state===Y?n._cb:n._eb;n._cb=null;n._eb=null;if(!t){H(e,n);return}var r=e._domain||n._domain;if(r){n._domain=null;S(r,V,t,n,e._value)}else{V(t,n,e._value)}}function V(e,n,t){var r;try{r=e(t)}catch(i){G(n,i);return}J(n,r,true)}function X(e){var n=false;return{promise:e,resolve:function(t){if(!n){n=true;J(e,t,false)}},reject:function(t){if(!n){n=true;G(e,t)}}}}B.defer=Z;function Z(){return X(new nn)}B.reject=en;function en(e){var n=new nn;G(n,e);return n}function nn(){this._state=0;this._value=void 0;this._domain=null;this._cb=null;this._eb=null;this._pending=null;this._trace=null}nn.prototype.then=function(e,n){var r=new nn;if(B.longStackSupport){r._trace={parent:t,stack:(new Error).stack}}r._cb=typeof e==="function"?e:null;r._eb=typeof n==="function"?n:null;if(s){r._domain=process.domain}if(this._state===P){R(r,this)}else{M(U,this,r,null,r._trace)}return r};nn.prototype.done=function(e,n){var t=this;if(e||n){t=t.then(e,n)}t.then(null,F)};nn.prototype.fail=function(e){return this.then(null,e)};nn.prototype._always=function(e){return this.then(e,e)};nn.prototype.spread=function(e,n){return this.then(e&&function(t){return on(t).then(function(n){return x.call(e,void 0,n)},n)},n)};function tn(e,n){if(t){var r=t;return setTimeout(function(){t=r;e();t=null},n)}return setTimeout(e,n)}nn.prototype.timeout=function(e,n){var t=this;var r=new nn;if(t._state!==P){H(t,r)}else{var i=tn(function(){G(r,new Error(n||"Timed out after "+e+" ms"))},e);t._always(function(){clearTimeout(i);H(t,r)})}return r};nn.prototype.delay=function(e){var n=Z();this.then(function(t){tn(function(){n.resolve(t)},e)},n.reject);return n.promise};nn.prototype.all=function(){return this.then(on)};nn.prototype.allSettled=function(){return this.then(rn)};nn.prototype.inspect=function(){switch(this._state){case P:return{state:"pending"};case Y:return{state:"fulfilled",value:this._value};case z:return{state:"rejected",reason:this._value};default:throw new TypeError("invalid state")}};nn.prototype.nodeify=function(e){if(e){this.done(function(n){e(null,n)},e);return void 0}else{return this}};B.allSettled=rn;function rn(e){var n=new nn;if(typeof e.length!=="number"){G(n,new TypeError("input not array-like"));return n}var t=0;var r=new Array(e.length);D(e,function(e,i){var o=B(e);if(o._state===P){++t;o._always(function(){r[i]=o.inspect();if(--t===0){C(n,r)}})}else{r[i]=o.inspect()}});if(t===0){C(n,r)}return n}B.all=on;function on(e){var n=Z();if(typeof e.length!=="number"){n.reject(new TypeError("input not array-like"));return n.promise}var t=0;var r=new Array(e.length);D(e,function(e,i){var o=B(e);if(o._state===Y){r[i]=o._value}else{++t;o.then(function(e){r[i]=e;if(--t===0){n.resolve(r)}},n.reject)}});if(t===0){n.resolve(r)}return n.promise}B.promised=un;function un(e){function n(n){return x.apply(e,n)}return function(){return on([this,on(arguments)]).then(n)}}B.onerror=null;B.nextTick=q;var an=i();return B});<|endoftext|>


<commit_before>var animationManager = (function(){
    var moduleOb = {};
    var registeredAnimations = [];
    var initTime = 0;
    var len = 0;
    var idled = true;
    var playingAnimationsNum = 0;

    function removeElement(ev){
        var i = 0;
        var animItem = ev.target;
        while(i<len) {
            if (registeredAnimations[i].animation === animItem) {
                registeredAnimations.splice(i, 1);
                i -= 1;
                len -= 1;
                if(!animItem.isPaused){
                    subtractPlayingCount();
                }
            }
            i += 1;
        }
    }

    function registerAnimation(element, animationData){
        if(!element){
            return null;
        }
        var i=0;
        while(i<len){
            if(registeredAnimations[i].elem == element && registeredAnimations[i].elem !== null ){
                return registeredAnimations[i].animation;
            }
            i+=1;
        }
        var animItem = new AnimationItem();
        animItem.setData(element, animationData);
        setupAnimation(animItem, element);
        return animItem;
    }

    function addPlayingCount(){
        playingAnimationsNum += 1;
        activate();
    }

    function subtractPlayingCount(){
        playingAnimationsNum -= 1;
        if(playingAnimationsNum === 0){
            idled = true;
        }
    }

    function setupAnimation(animItem, element){
        animItem.addEventListener('destroy',removeElement);
        animItem.addEventListener('_active',addPlayingCount);
        animItem.addEventListener('_idle',subtractPlayingCount);
        registeredAnimations.push({elem: element,animation:animItem});
        len += 1;
    }

    function loadAnimation(params){
        var animItem = new AnimationItem();
        setupAnimation(animItem, null);
        animItem.setParams(params);
        return animItem;
    }

    function setSpeed(val,animation){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.setSpeed(val, animation);
        }
    }

    function setDirection(val, animation){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.setDirection(val, animation);
        }
    }

    function play(animation){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.play(animation);
        }
    }

    function moveFrame (value, animation) {
        initTime = Date.now();
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.moveFrame(value,animation);
        }
    }

    function resume(nowTime) {

        var elapsedTime = nowTime - initTime;
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.advanceTime(elapsedTime);
        }
        initTime = nowTime;
        if(!idled) {
            requestAnimationFrame(resume);
        }
    }

    function first(nowTime){
        initTime = nowTime;
        requestAnimationFrame(resume);
    }

    function pause(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.pause(animation);
        }
    }

    function goToAndStop(value,isFrame,animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.goToAndStop(value,isFrame,animation);
        }
    }

    function stop(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.stop(animation);
        }
    }

    function togglePause(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.togglePause(animation);
        }
    }

    function destroy(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.destroy(animation);
        }
    }

    function searchAnimations(animationData, standalone, renderer){
        var animElements = document.getElementsByClassName('bodymovin');
        var i, len = animElements.length;
        for(i=0;i<len;i+=1){
            if(renderer){
                animElements[i].setAttribute('data-bm-type',renderer);
            }
            registerAnimation(animElements[i], animationData);
        }
        if(standalone && len === 0){
            if(!renderer){
                renderer = 'svg';
            }
            var body = document.getElementsByTagName('body')[0];
            body.innerHTML = '';
            var div = document.createElement('div');
            div.style.width = '100%';
            div.style.height = '100%';
            div.setAttribute('data-bm-type',renderer);
            body.appendChild(div);
            registerAnimation(div, animationData);
        }
    }

    function resize(){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.resize();
        }
    }

    function start(){
        requestAnimationFrame(first);
    }

    function activate(){
        if(idled){
            idled = false;
            requestAnimationFrame(first);
        }
    }

    //start();

    setTimeout(start,0);

    moduleOb.registerAnimation = registerAnimation;
    moduleOb.loadAnimation = loadAnimation;
    moduleOb.setSpeed = setSpeed;
    moduleOb.setDirection = setDirection;
    moduleOb.play = play;
    moduleOb.moveFrame = moveFrame;
    moduleOb.pause = pause;
    moduleOb.stop = stop;
    moduleOb.togglePause = togglePause;
    moduleOb.searchAnimations = searchAnimations;
    moduleOb.resize = resize;
    moduleOb.start = start;
    moduleOb.goToAndStop = goToAndStop;
    moduleOb.destroy = destroy;
    return moduleOb;
}());<commit_msg>Counting should be done in reverse<commit_after>var animationManager = (function(){
    var moduleOb = {};
    var registeredAnimations = [];
    var initTime = 0;
    var len = 0;
    var idled = true;
    var playingAnimationsNum = 0;

    function removeElement(ev){
        var i = 0;
        var animItem = ev.target;
        while(i<len) {
            if (registeredAnimations[i].animation === animItem) {
                registeredAnimations.splice(i, 1);
                i -= 1;
                len -= 1;
                if(!animItem.isPaused){
                    subtractPlayingCount();
                }
            }
            i += 1;
        }
    }

    function registerAnimation(element, animationData){
        if(!element){
            return null;
        }
        var i=0;
        while(i<len){
            if(registeredAnimations[i].elem == element && registeredAnimations[i].elem !== null ){
                return registeredAnimations[i].animation;
            }
            i+=1;
        }
        var animItem = new AnimationItem();
        animItem.setData(element, animationData);
        setupAnimation(animItem, element);
        return animItem;
    }

    function addPlayingCount(){
        playingAnimationsNum += 1;
        activate();
    }

    function subtractPlayingCount(){
        playingAnimationsNum -= 1;
        if(playingAnimationsNum === 0){
            idled = true;
        }
    }

    function setupAnimation(animItem, element){
        animItem.addEventListener('destroy',removeElement);
        animItem.addEventListener('_active',addPlayingCount);
        animItem.addEventListener('_idle',subtractPlayingCount);
        registeredAnimations.push({elem: element,animation:animItem});
        len += 1;
    }

    function loadAnimation(params){
        var animItem = new AnimationItem();
        setupAnimation(animItem, null);
        animItem.setParams(params);
        return animItem;
    }

    function setSpeed(val,animation){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.setSpeed(val, animation);
        }
    }

    function setDirection(val, animation){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.setDirection(val, animation);
        }
    }

    function play(animation){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.play(animation);
        }
    }

    function moveFrame (value, animation) {
        initTime = Date.now();
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.moveFrame(value,animation);
        }
    }

    function resume(nowTime) {

        var elapsedTime = nowTime - initTime;
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.advanceTime(elapsedTime);
        }
        initTime = nowTime;
        if(!idled) {
            requestAnimationFrame(resume);
        }
    }

    function first(nowTime){
        initTime = nowTime;
        requestAnimationFrame(resume);
    }

    function pause(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.pause(animation);
        }
    }

    function goToAndStop(value,isFrame,animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.goToAndStop(value,isFrame,animation);
        }
    }

    function stop(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.stop(animation);
        }
    }

    function togglePause(animation) {
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.togglePause(animation);
        }
    }

    function destroy(animation) {
        var i;
        for(i=(len-1);i>=0;i-=1){
            registeredAnimations[i].animation.destroy(animation);
        }
    }

    function searchAnimations(animationData, standalone, renderer){
        var animElements = document.getElementsByClassName('bodymovin');
        var i, len = animElements.length;
        for(i=0;i<len;i+=1){
            if(renderer){
                animElements[i].setAttribute('data-bm-type',renderer);
            }
            registerAnimation(animElements[i], animationData);
        }
        if(standalone && len === 0){
            if(!renderer){
                renderer = 'svg';
            }
            var body = document.getElementsByTagName('body')[0];
            body.innerHTML = '';
            var div = document.createElement('div');
            div.style.width = '100%';
            div.style.height = '100%';
            div.setAttribute('data-bm-type',renderer);
            body.appendChild(div);
            registerAnimation(div, animationData);
        }
    }

    function resize(){
        var i;
        for(i=0;i<len;i+=1){
            registeredAnimations[i].animation.resize();
        }
    }

    function start(){
        requestAnimationFrame(first);
    }

    function activate(){
        if(idled){
            idled = false;
            requestAnimationFrame(first);
        }
    }

    //start();

    setTimeout(start,0);

    moduleOb.registerAnimation = registerAnimation;
    moduleOb.loadAnimation = loadAnimation;
    moduleOb.setSpeed = setSpeed;
    moduleOb.setDirection = setDirection;
    moduleOb.play = play;
    moduleOb.moveFrame = moveFrame;
    moduleOb.pause = pause;
    moduleOb.stop = stop;
    moduleOb.togglePause = togglePause;
    moduleOb.searchAnimations = searchAnimations;
    moduleOb.resize = resize;
    moduleOb.start = start;
    moduleOb.goToAndStop = goToAndStop;
    moduleOb.destroy = destroy;
    return moduleOb;
}());<|endoftext|>


<commit_before>/**
 * Dependencies.
 */
var async = require('async');
var request = require('request');
var fs = require('fs');
var urlParser = require('url');
var entities = require('html-entities');

/**
 * Extracts http links from a message and follow any redirects to find a usable image.
 *
 * @api public
 * @param object configuration
 */
function TweetImages(configuration)
{
	//Configure
	configuration = configuration || {};
	this.logger = (configuration.log4js) ? configuration.log4js.getLogger(configuration.logger || 'selfieshirt.model.TweetImages') : null;
	this.configuration = configuration.configuration || {};
	this.entities = new entities.AllHtmlEntities();
}

/**
 * Fetch the given url and follow all redirects.
 *
 * @api private
 * @todo we need to protect this from endless redirects.
 * @param string url
 * @param function callback Callback in the form of function(error, response, body)
 */
TweetImages.prototype.requestPage = function(url, callback)
{
	var scope = this;

	//Rewrite facebook urls to the mobile version becaus the desktop
	//version does not work without js... yay...
	if (url.match(/www.facebook.com/))
	{
		url = url.replace(/www.facebook.com/, 'm.facebook.com');
	}

	if (this.logger)
	{
		this.logger.debug('requestPage : url =', url);
	}

	//Get the content
	var options =
	{
		url : url,
		method : 'get',
		followRedirect : false,
		jar : true
	};
	request(options, function(error, response, body)
	{
		if (error)
		{
			if (scope.logger)
			{
				scope.logger.error('requestPage : url =', url, ', error =', error);
			}
			callback(error);
			return;
		}

		//Redirect?
		if (response.statusCode == 301 || response.statusCode == 302)
		{
			//Handle relative redirect urls
			var redirectUrl = urlParser.parse(response.headers['location']);
			if (!redirectUrl.host)
			{
				var currentUrl = urlParser.parse(url);
				redirectUrl.protocol = currentUrl.protocol;
				redirectUrl.port = currentUrl.port;
				redirectUrl.host = currentUrl.host;
				redirectUrl.hostname = currentUrl.hostname;
			}

			//Reuest the new url
			scope.requestPage(urlParser.format(redirectUrl), callback);
		}
		else
		{
			//Return the content
			callback(null, response, body);
		}
	})
}

/**
 * Looks for a og.image meta tag in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractOpenGraphImage = function(body)
{
	//Determine image source
	var img = body.match(/<meta\s+(property|name)="og.image"\s+content="([^"]*)"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractOpenGraphImage : image found =', img[2]);
		}
		return img[2];
	}
	img = body.match(/<meta\s+content="([^"]*)"\s+(property|name)="og.image"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractOpenGraphImage : image found =', img[1]);
		}
		return img[1];
	}
	return '';
};

/**
 * Looks for a twitter:image:src meta tag in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractTwitterMetaImage = function(body)
{
	//Determine image source
	var img = body.match(/<meta\s(property|name)="twitter:image:src"\scontent="([^"]*)"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractTwitterMetaImage : image found =', img[2]);
		}
		return img[2];
	}
	img = body.match(/<meta\scontent="([^"]*)"\s(property|name)="twitter:image:src"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractTwitterMetaImage : image found =', img[1]);
		}
		return img[1];
	}
	return '';
};

/**
 * Looks for a data-resolved-url-large data attribute in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractTwitterBodyImage = function(body)
{
	//Determine image source
	var img = body.match(/data\-resolved\-url\-large="([^"]*)"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractTwitterBodyImage : image found =', img[1]);
		}
		return img[1];
	}
	return '';
};

/**
 * Looks for a data-store data attribute in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractFacebookImage = function(body)
{
	//Determine image source
	var img = body.match(/data-store="([^"]*)"/i);
	if (img)
	{
		var store = JSON.parse(this.entities.decode(img[1]));
		if (store && store.imgsrc)
		{
			if (this.logger)
			{
				this.logger.info('extractFacebookImage : image found =', store.imgsrc);
			}
			return store.imgsrc;
		}
	}
	return '';
};

/**
 * Parse the given message for urls and try to find a suitable image.
 *
 * @api public
 * @param string message
 * @param function callback Standard callback in the form of function(error, result)
 */
TweetImages.prototype.extract = function(message, callback)
{
	var scope = this;

	//Get links from message
	//var url = message.match(/(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?/);
	var url = message.match(/(https?:\/\/)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/);
	if (!url)
	{
		scope.logger.info('extract : no url found in message =', message);
		callback(null, '');
		return;
	}

	if (scope.logger)
	{
		scope.logger.debug('extract : url =', url[0]);
	}

	this.requestPage(url[0], function(error, response, body)
	{
		if (error)
		{
			if (scope.logger)
			{
				scope.logger.error('Error : url =', url, ', error =', error);
			}
			callback(error);
	  		return;
		}

		//Check for image response
		if (response.headers['content-type'] == 'image/jpeg' ||
			response.headers['content-type'] == 'image/jpg' ||
			response.headers['content-type'] == 'image/png')
		{
			if (scope.logger)
			{
				scope.logger.debug('extract : image type = direct');
			}
			callback(null, url[0]);
			return;
		}

		//Try image sources
		var image = '';
		if (image == '' && body.match(/<title>[\s\n\t\r]*Twitter/i))
		{
			if (scope.logger)
			{
				scope.logger.info('extract : image type = twitter');
			}
			image = scope.extractTwitterBodyImage(body);
		}
		if (image == '' && body.match(/<head title="Foto"><title>Foto<\/title>/i))
		{
			if (scope.logger)
			{
				scope.logger.info('extract : image type = facebook');
			}
			image = scope.extractFacebookImage(body);
		}

		//Fallbacks
		if (image == '')
		{
			if (scope.logger)
			{
				scope.logger.debug('extract : image type unkown - try opengraph');
			}
			image = scope.extractOpenGraphImage(body);
		}
		if (image == '')
		{
			if (scope.logger)
			{
				scope.logger.debug('extract : image type unkown - try twitter');
			}
			image = scope.extractTwitterMetaImage(body);
		}
		if (image == '')
		{
			if (scope.logger)
			{
				scope.logger.warn('extract : no image type found for ' + url[0]);
			}
		}

		//Done
		callback(null, image);
	});
};

/**
 * Export
 */
module.exports = TweetImages;<commit_msg>Added parsing for twitter:image<commit_after>/**
 * Dependencies.
 */
var async = require('async');
var request = require('request');
var fs = require('fs');
var urlParser = require('url');
var entities = require('html-entities');

/**
 * Extracts http links from a message and follow any redirects to find a usable image.
 *
 * @api public
 * @param object configuration
 */
function TweetImages(configuration)
{
	//Configure
	configuration = configuration || {};
	this.logger = (configuration.log4js) ? configuration.log4js.getLogger(configuration.logger || 'selfieshirt.model.TweetImages') : null;
	this.configuration = configuration.configuration || {};
	this.entities = new entities.AllHtmlEntities();
}

/**
 * Fetch the given url and follow all redirects.
 *
 * @api private
 * @todo we need to protect this from endless redirects.
 * @param string url
 * @param function callback Callback in the form of function(error, response, body)
 */
TweetImages.prototype.requestPage = function(url, callback)
{
	var scope = this;

	//Rewrite facebook urls to the mobile version becaus the desktop
	//version does not work without js... yay...
	if (url.match(/www.facebook.com/))
	{
		url = url.replace(/www.facebook.com/, 'm.facebook.com');
	}

	if (this.logger)
	{
		this.logger.debug('requestPage : url =', url);
	}

	//Get the content
	var options =
	{
		url : url,
		method : 'get',
		followRedirect : false,
		jar : true
	};
	request(options, function(error, response, body)
	{
		if (error)
		{
			if (scope.logger)
			{
				scope.logger.error('requestPage : url =', url, ', error =', error);
			}
			callback(error);
			return;
		}

		//Redirect?
		if (response.statusCode == 301 || response.statusCode == 302)
		{
			//Handle relative redirect urls
			var redirectUrl = urlParser.parse(response.headers['location']);
			if (!redirectUrl.host)
			{
				var currentUrl = urlParser.parse(url);
				redirectUrl.protocol = currentUrl.protocol;
				redirectUrl.port = currentUrl.port;
				redirectUrl.host = currentUrl.host;
				redirectUrl.hostname = currentUrl.hostname;
			}

			//Reuest the new url
			scope.requestPage(urlParser.format(redirectUrl), callback);
		}
		else
		{
			//Return the content
			callback(null, response, body);
		}
	})
}

/**
 * Looks for a og.image meta tag in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractOpenGraphImage = function(body)
{
	//Determine image source
	var img = body.match(/<meta\s+(property|name)="og.image"\s+content="([^"]*)"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractOpenGraphImage : image found =', img[2]);
		}
		return img[2];
	}
	img = body.match(/<meta\s+content="([^"]*)"\s+(property|name)="og.image"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractOpenGraphImage : image found =', img[1]);
		}
		return img[1];
	}
	return '';
};

/**
 * Looks for a twitter:image:src meta tag in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractTwitterMetaImage = function(body)
{
	//Determine image source
	var img = body.match(/<meta\s(property|name)="twitter:image(:src)?"\scontent="([^"]*)"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractTwitterMetaImage : image found =', img[3]);
		}
		return img[2];
	}
	img = body.match(/<meta\scontent="([^"]*)"\s(property|name)="twitter:image(:src)?"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractTwitterMetaImage : image found =', img[1]);
		}
		return img[1];
	}
	return '';
};

/**
 * Looks for a data-resolved-url-large data attribute in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractTwitterBodyImage = function(body)
{
	//Determine image source
	var img = body.match(/data\-resolved\-url\-large="([^"]*)"/i);
	if (img)
	{
		if (this.logger)
		{
			this.logger.info('extractTwitterBodyImage : image found =', img[1]);
		}
		return img[1];
	}
	return '';
};

/**
 * Looks for a data-store data attribute in the given body
 *
 * @api private
 * @param string body
 * @returns string
 */
TweetImages.prototype.extractFacebookImage = function(body)
{
	//Determine image source
	var img = body.match(/data-store="([^"]*)"/i);
	if (img)
	{
		var store = JSON.parse(this.entities.decode(img[1]));
		if (store && store.imgsrc)
		{
			if (this.logger)
			{
				this.logger.info('extractFacebookImage : image found =', store.imgsrc);
			}
			return store.imgsrc;
		}
	}
	return '';
};

/**
 * Parse the given message for urls and try to find a suitable image.
 *
 * @api public
 * @param string message
 * @param function callback Standard callback in the form of function(error, result)
 */
TweetImages.prototype.extract = function(message, callback)
{
	var scope = this;

	//Get links from message
	//var url = message.match(/(https?:\/\/)([\da-z\.-]+)\.([a-z\.]{2,6})([\/\w\.-]*)*\/?/);
	var url = message.match(/(https?:\/\/)[-A-Za-z0-9+&@#/%?=~_|!:,.;]+[-A-Za-z0-9+&@#/%=~_|]/);
	if (!url)
	{
		scope.logger.info('extract : no url found in message =', message);
		callback(null, '');
		return;
	}

	if (scope.logger)
	{
		scope.logger.debug('extract : url =', url[0]);
	}

	this.requestPage(url[0], function(error, response, body)
	{
		if (error)
		{
			if (scope.logger)
			{
				scope.logger.error('Error : url =', url, ', error =', error);
			}
			callback(error);
	  		return;
		}

		//Check for image response
		if (response.headers['content-type'] == 'image/jpeg' ||
			response.headers['content-type'] == 'image/jpg' ||
			response.headers['content-type'] == 'image/png')
		{
			if (scope.logger)
			{
				scope.logger.debug('extract : image type = direct');
			}
			callback(null, url[0]);
			return;
		}

		//Try image sources
		var image = '';
		if (image == '' && body.match(/<title>[\s\n\t\r]*Twitter/i))
		{
			if (scope.logger)
			{
				scope.logger.info('extract : image type = twitter');
			}
			image = scope.extractTwitterBodyImage(body);
		}
		if (image == '' && body.match(/<head title="Foto"><title>Foto<\/title>/i))
		{
			if (scope.logger)
			{
				scope.logger.info('extract : image type = facebook');
			}
			image = scope.extractFacebookImage(body);
		}

		//Fallbacks
		if (image == '')
		{
			if (scope.logger)
			{
				scope.logger.debug('extract : image type unkown - try opengraph');
			}
			image = scope.extractOpenGraphImage(body);
		}
		if (image == '')
		{
			if (scope.logger)
			{
				scope.logger.debug('extract : image type unkown - try twitter');
			}
			image = scope.extractTwitterMetaImage(body);
		}
		if (image == '')
		{
			if (scope.logger)
			{
				scope.logger.warn('extract : no image type found for ' + url[0]);
			}
		}

		//Done
		callback(null, image);
	});
};

/**
 * Export
 */
module.exports = TweetImages;<|endoftext|>