<commit_before>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.boot',
  name: 'DAOConfigSummaryView',
  extends: 'foam.u2.Controller',

  documentation: 'Data Management UI for browsing all DAOs.',

  classes: [
    {
      name: 'CustomDAOUpdateView',
      extends: 'foam.comics.v2.DAOUpdateView',

      properties: [
        {
          class: 'foam.u2.ViewSpecWithJava',
          name: 'viewView',
          factory: function() {
            return {
              class: 'foam.u2.view.ObjAltView',
              views: [
                [ {class: 'foam.u2.DetailView'},                 'Detail' ],
                [ {class: 'foam.u2.detail.TabbedDetailView'},    'Tabbed' ],
                [ {class: 'foam.u2.detail.SectionedDetailView'}, 'Sectioned' ],
                [ {class: 'foam.u2.md.DetailView'},              'Material' ]
              ]
            };
          }
        }
      ]
    },

    // TODO: replace with UpdateView
    {
      name: 'CustomDAOSummaryView',
      extends: 'foam.comics.v2.DAOSummaryView',

      properties: [
        {
          class: 'foam.u2.ViewSpecWithJava',
          name: 'viewView',
          factory: function() {
            return {
              class: 'foam.u2.view.ObjAltView',
              views: [
                [ {class: 'foam.u2.DetailView'},                 'Detail' ],
                [ {class: 'foam.u2.detail.TabbedDetailView'},    'Tabbed' ],
                [ {class: 'foam.u2.detail.SectionedDetailView'}, 'Sectioned' ]
              ]
            };
          }
        }
      ]
    },

    {
      name: 'DAOUpdateControllerView',
      extends: 'foam.comics.DAOUpdateControllerView',

      documentation: 'Same as regular UpdateController except it starts in EDIT mode',

      properties: [
        {
          name: 'controllerMode',
          factory: function() {
            return this.ControllerMode.EDIT;
          }
        }
      ]
    },

    {
      name: 'BackBorder',
      extends: 'foam.u2.Element',

      imports: [ 'memento', 'stack' ],

      css: `
        ^title {
          padding: 25px;
          font-size: 26px;
        }
        ^title a {
          color: blue;
          text-decoration: underline;
        }
      `,

      properties: [
        'title',
        {
          class: 'foam.u2.ViewSpec',
          name: 'inner'
        }
      ],

      methods: [
        function initE() {
          this.SUPER();

          this.
            start().
              addClass(this.myClass('title')).
              start('a').
                add('Data Management').on('click', () => { this.memento = ''; }).
              end().
              add(' / ', this.title).
            end().
            tag(this.inner);
        }
      ]
    }
  ],

  css: `
    ^ {
      padding: 6px;
    }
    ^dao, ^header {
      display: inline-block;
      font-size: smaller;
      margin: 2px;
      padding: 2px;
      width: 220px;
    }
    ^dao {
      color: #555;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    ^dao:hover {
      background: lightgray;
    }
    ^section {
      display: inline-grid;
    }
    ^header {
      background: /*%BLACK%*/ #1e1f21;
      color: white;
      font-weight: 800;
    }
    /* TODO: scope this better so it doesn't affect nested AltViews also */
    .foam-u2-view-AltView .property-selectedView {
      margin-left: 24px;
    }
  `,

  requires: [
    'foam.comics.BrowserView',
    'foam.nanos.boot.NSpec',
    'foam.comics.v2.DAOBrowserView'
  ],

  implements: [ 'foam.mlang.Expressions' ],

  imports: [ 'memento', 'nSpecDAO', 'stack' ],

  properties: [
    {
      name: 'data',
      factory: function() {
       return this.nSpecDAO;
      }
    },
    {
      name: 'filteredDAO',
      factory: function() {
       return this.data.where(
         this.AND(
           this.ENDS_WITH(foam.nanos.boot.NSpec.ID, 'DAO'),
           this.EQ(foam.nanos.boot.NSpec.SERVE,     true)
         ));
      }
    },
    {
      class: 'String',
      name: 'search',
      view: {
       class: 'foam.u2.TextField',
       type: 'search',
       onKey: true
      }
    },
    'currentMemento_'
  ],

  methods: [
    function initE() {
      this.SUPER();

      var self          = this;
      var currentLetter = '';
      var section;

      this.addClass(this.myClass()).
      start().
        style({ 'height': '56px'}).
        start().
          style({ 'font-size': '26px', 'width': 'fit-content', 'float': 'left', 'padding-top': '10px' }).
          add('Data Management').
        end()
        .start()
        .style({ 'width': 'fit-content', 'float': 'right', 'margin-right': '40px', 'margin-top': '6px' })
            .start(this.SEARCH).focus().end()
            .addClass('foam-u2-search-TextSearchView')
            .addClass(this.myClass('foam-u2-search-TextSearchView'))
          .end()
        .end()
      .end();

      var updateSections = [];
      var i = 0;

      this.filteredDAO.select().then(function(specs) {
        specs.array.sort(function(o1, o2) { return foam.String.compare(o1.id.toUpperCase(), o2.id.toUpperCase())}).forEach(function(spec) {
          var label = foam.String.capitalize(spec.id.substring(0, spec.id.length-3));
          var l     = label.charAt(0);

          if ( l != currentLetter ) {
            let lSection;
            let showSection = foam.core.SimpleSlot.create({value: true});
            i = updateSections.length;

            var updateSlot = foam.core.SimpleSlot.create({value: false});
            updateSections.push(updateSlot);

            updateSections[i].sub(function() {
              // first child is a header
              for ( var j = 1 ; j < lSection.childNodes.length ; j++ ) {
                if ( lSection.childNodes[j].shown ) {
                  showSection.set(true);
                  return;
                }
              }
              showSection.set(false);
            });

            currentLetter = l;

            section = self.start('span')
              .show(showSection)
              .addClass(self.myClass('section'))
              .start('span')
                .addClass(self.myClass('header'))
                .add(l)
              .end();

            lSection = section;
          }

          var localI    = i.valueOf();
          var localShow = foam.core.SimpleSlot.create({value: true});

          section
            .start('span')
              .show(localShow)
              .addClass(self.myClass('dao'))
              .add(label)
              .attrs({title: spec.description})
              .on('click', function() { self.memento = spec.id; });

              self.search$.sub(function() {
                var contains = false;
                if ( ! self.search ) {
                  contains = true;
                } else if ( label.toLowerCase().includes(self.search.toLowerCase()) ) {
                  contains =  true;
                } else if ( ! contains && spec.keywords && spec.keywords.length > 0 ) {
                  for ( var k in spec.keywords ) {
                    if ( k.toLowerCase().includes(self.search.toLowerCase()) ) {
                      contains  = true;
                      break;
                    }
                  }
                }

                localShow.set(contains);
                updateSections[localI].set(! updateSections[localI].get());
              });
        });
      });

//      this.onDetach(this.memento$.sub(this.mementoChange));
      this.memento$.sub(this.mementoChange);
      this.mementoChange();
    }
  ],

  listeners: [
    function mementoChange() {
      var m = this.memento;

      if ( ! m ) {
        if ( this.currentMemento_ ) this.stack.back();
        this.currentMemento_ = '';
        return;
      }

      this.currentMemento_ = m;

      var x = this.__subContext__.createSubContext();
      x.register(this.DAOUpdateControllerView, 'foam.comics.DAOUpdateControllerView');
      x.register(this.CustomDAOSummaryView,    'foam.comics.v2.DAOSummaryView');
      x.register(this.CustomDAOUpdateView,     'foam.comics.v2.DAOUpdateView');
      x.register(foam.u2.DetailView,           'foam.u2.DetailView');

      this.stack.push({
        class: this.BackBorder,
        title: m,
        inner: {
          class: 'foam.u2.view.AltView',
          data: this.__context__[m],
          views: [
            [
              {
                class: this.BrowserView,
                stack: this.stack
              },
              'Controller 1'
            ],
            [
              {
                class: this.DAOBrowserView,
                stack: this.stack
              },
              'Controller 2'
            ]
          ]
        }
      }, x);
    }
  ]
});
<commit_msg>Add Create button to Data Management 'New Controller' view.<commit_after>/**
 * @license
 * Copyright 2019 The FOAM Authors. All Rights Reserved.
 * http://www.apache.org/licenses/LICENSE-2.0
 */

foam.CLASS({
  package: 'foam.nanos.boot',
  name: 'DAOConfigSummaryView',
  extends: 'foam.u2.Controller',

  documentation: 'Data Management UI for browsing all DAOs.',

  classes: [
    {
      name: 'CustomDAOUpdateView',
      extends: 'foam.comics.v2.DAOUpdateView',

      properties: [
        {
          class: 'foam.u2.ViewSpecWithJava',
          name: 'viewView',
          factory: function() {
            return {
              class: 'foam.u2.view.ObjAltView',
              views: [
                [ {class: 'foam.u2.DetailView'},                 'Detail' ],
                [ {class: 'foam.u2.detail.TabbedDetailView'},    'Tabbed' ],
                [ {class: 'foam.u2.detail.SectionedDetailView'}, 'Sectioned' ],
                [ {class: 'foam.u2.md.DetailView'},              'Material' ]
              ]
            };
          }
        }
      ]
    },

    // TODO: replace with UpdateView
    {
      name: 'CustomDAOSummaryView',
      extends: 'foam.comics.v2.DAOSummaryView',

      properties: [
        {
          class: 'foam.u2.ViewSpecWithJava',
          name: 'viewView',
          factory: function() {
            return {
              class: 'foam.u2.view.ObjAltView',
              views: [
                [ {class: 'foam.u2.DetailView'},                 'Detail' ],
                [ {class: 'foam.u2.detail.TabbedDetailView'},    'Tabbed' ],
                [ {class: 'foam.u2.detail.SectionedDetailView'}, 'Sectioned' ]
              ]
            };
          }
        }
      ]
    },

    {
      name: 'DAOUpdateControllerView',
      extends: 'foam.comics.DAOUpdateControllerView',

      documentation: 'Same as regular UpdateController except it starts in EDIT mode',

      properties: [
        {
          name: 'controllerMode',
          factory: function() {
            return this.ControllerMode.EDIT;
          }
        }
      ]
    },

    {
      name: 'BackBorder',
      extends: 'foam.u2.Element',

      imports: [ 'memento', 'stack' ],

      css: `
        ^title {
          padding: 25px;
          font-size: 26px;
        }
        ^title a {
          color: blue;
          text-decoration: underline;
        }
      `,

      properties: [
        'title',
        {
          class: 'foam.u2.ViewSpec',
          name: 'inner'
        }
      ],

      methods: [
        function initE() {
          this.SUPER();

          this.
            start().
              addClass(this.myClass('title')).
              start('a').
                add('Data Management').on('click', () => { this.memento = ''; }).
              end().
              add(' / ', this.title).
            end().
            tag(this.inner);
        }
      ]
    }
  ],

  css: `
    ^ {
      padding: 6px;
    }
    ^dao, ^header {
      display: inline-block;
      font-size: smaller;
      margin: 2px;
      padding: 2px;
      width: 220px;
    }
    ^dao {
      color: #555;
      overflow: hidden;
      text-overflow: ellipsis;
    }
    ^dao:hover {
      background: lightgray;
    }
    ^section {
      display: inline-grid;
    }
    ^header {
      background: /*%BLACK%*/ #1e1f21;
      color: white;
      font-weight: 800;
    }
    /* TODO: scope this better so it doesn't affect nested AltViews also */
    .foam-u2-view-AltView .property-selectedView {
      margin-left: 24px;
    }
  `,

  requires: [
    'foam.comics.BrowserView',
    'foam.nanos.boot.NSpec',
    'foam.comics.v2.DAOBrowseControllerView'
  ],

  implements: [ 'foam.mlang.Expressions' ],

  imports: [ 'memento', 'nSpecDAO', 'stack' ],

  properties: [
    {
      name: 'data',
      factory: function() {
       return this.nSpecDAO;
      }
    },
    {
      name: 'filteredDAO',
      factory: function() {
       return this.data.where(
         this.AND(
           this.ENDS_WITH(foam.nanos.boot.NSpec.ID, 'DAO'),
           this.EQ(foam.nanos.boot.NSpec.SERVE,     true)
         ));
      }
    },
    {
      class: 'String',
      name: 'search',
      view: {
       class: 'foam.u2.TextField',
       type: 'search',
       onKey: true
      }
    },
    'currentMemento_'
  ],

  methods: [
    function initE() {
      this.SUPER();

      var self          = this;
      var currentLetter = '';
      var section;

      this.addClass(this.myClass()).
      start().
        style({ 'height': '56px'}).
        start().
          style({ 'font-size': '26px', 'width': 'fit-content', 'float': 'left', 'padding-top': '10px' }).
          add('Data Management').
        end()
        .start()
        .style({ 'width': 'fit-content', 'float': 'right', 'margin-right': '40px', 'margin-top': '6px' })
            .start(this.SEARCH).focus().end()
            .addClass('foam-u2-search-TextSearchView')
            .addClass(this.myClass('foam-u2-search-TextSearchView'))
          .end()
        .end()
      .end();

      var updateSections = [];
      var i = 0;

      this.filteredDAO.select().then(function(specs) {
        specs.array.sort(function(o1, o2) { return foam.String.compare(o1.id.toUpperCase(), o2.id.toUpperCase())}).forEach(function(spec) {
          var label = foam.String.capitalize(spec.id.substring(0, spec.id.length-3));
          var l     = label.charAt(0);

          if ( l != currentLetter ) {
            let lSection;
            let showSection = foam.core.SimpleSlot.create({value: true});
            i = updateSections.length;

            var updateSlot = foam.core.SimpleSlot.create({value: false});
            updateSections.push(updateSlot);

            updateSections[i].sub(function() {
              // first child is a header
              for ( var j = 1 ; j < lSection.childNodes.length ; j++ ) {
                if ( lSection.childNodes[j].shown ) {
                  showSection.set(true);
                  return;
                }
              }
              showSection.set(false);
            });

            currentLetter = l;

            section = self.start('span')
              .show(showSection)
              .addClass(self.myClass('section'))
              .start('span')
                .addClass(self.myClass('header'))
                .add(l)
              .end();

            lSection = section;
          }

          var localI    = i.valueOf();
          var localShow = foam.core.SimpleSlot.create({value: true});

          section
            .start('span')
              .show(localShow)
              .addClass(self.myClass('dao'))
              .add(label)
              .attrs({title: spec.description})
              .on('click', function() { self.memento = spec.id; });

              self.search$.sub(function() {
                var contains = false;
                if ( ! self.search ) {
                  contains = true;
                } else if ( label.toLowerCase().includes(self.search.toLowerCase()) ) {
                  contains =  true;
                } else if ( ! contains && spec.keywords && spec.keywords.length > 0 ) {
                  for ( var k in spec.keywords ) {
                    if ( k.toLowerCase().includes(self.search.toLowerCase()) ) {
                      contains  = true;
                      break;
                    }
                  }
                }

                localShow.set(contains);
                updateSections[localI].set(! updateSections[localI].get());
              });
        });
      });

//      this.onDetach(this.memento$.sub(this.mementoChange));
      this.memento$.sub(this.mementoChange);
      this.mementoChange();
    }
  ],

  listeners: [
    function mementoChange() {
      var m = this.memento;

      if ( ! m ) {
        if ( this.currentMemento_ ) this.stack.back();
        this.currentMemento_ = '';
        return;
      }

      this.currentMemento_ = m;

      var x = this.__subContext__.createSubContext();
      x.register(this.DAOUpdateControllerView, 'foam.comics.DAOUpdateControllerView');
      x.register(this.CustomDAOSummaryView,    'foam.comics.v2.DAOSummaryView');
      x.register(this.CustomDAOUpdateView,     'foam.comics.v2.DAOUpdateView');
      x.register(foam.u2.DetailView,           'foam.u2.DetailView');

      this.stack.push({
        class: this.BackBorder,
        title: m,
        inner: {
          class: 'foam.u2.view.AltView',
          data: this.__context__[m],
          views: [
            [
              {
                class: this.BrowserView,
                stack: this.stack
              },
              'Controller 1'
            ],
            [
              {
                class: this.DAOBrowseControllerView,
                stack: this.stack
              },
              'Controller 2'
            ]
          ]
        }
      }, x);
    }
  ]
});
<|endoftext|>


<commit_before>// Mocks http.ServerResponse

module.exports = MockRes;

var Transform = require('stream').Transform,
	util = require('util');

function MockRes(finish) {
	Transform.call(this);

	this.statusCode = 200;

	this._data = [];
	this._headers = {};
	if (typeof finish === 'function')
		this.on('finish', finish);
}

util.inherits(MockRes, Transform);

MockRes.prototype._transform = function(chunk, encoding, next) {
	this.push(chunk);
	next();
};

MockRes.prototype.setHeader = function(name, value) {
	this._headers[name.toLowerCase()] = value;
};

MockRes.prototype.getHeader = function(name) {
	return this._headers[name.toLowerCase()];
};

MockRes.prototype.removeHeader = function(name) {
	delete this._headers[name.toLowerCase()];
};

MockRes.prototype._getString = function() {
	return Buffer.concat(this._readableState.buffer).toString();
};

MockRes.prototype._getJSON = function() {
	return JSON.parse(this._getString());
};

/* Not implemented:
MockRes.prototype.writeContinue()
MockRes.prototype.writeHead(statusCode, [reasonPhrase], [headers])
MockRes.prototype.setTimeout(msecs, callback)
MockRes.prototype.statusCode
MockRes.prototype.headersSent
MockRes.prototype.sendDate
MockRes.prototype.addTrailers(headers)
*/<commit_msg>Lengthened class name<commit_after>// Mocks http.ServerResponse

module.exports = MockServerResponse;

var Transform = require('stream').Transform,
	util = require('util');

function MockServerResponse(finish) {
	Transform.call(this);

	this.statusCode = 200;

	this._headers = {};
	if (typeof finish === 'function')
		this.on('finish', finish);
}

util.inherits(MockServerResponse, Transform);

MockServerResponse.prototype._transform = function(chunk, encoding, next) {
	this.push(chunk);
	next();
};

MockServerResponse.prototype.setHeader = function(name, value) {
	this._headers[name.toLowerCase()] = value;
};

MockServerResponse.prototype.getHeader = function(name) {
	return this._headers[name.toLowerCase()];
};

MockServerResponse.prototype.removeHeader = function(name) {
	delete this._headers[name.toLowerCase()];
};

MockServerResponse.prototype._getString = function() {
	return Buffer.concat(this._readableState.buffer).toString();
};

MockServerResponse.prototype._getJSON = function() {
	return JSON.parse(this._getString());
};

/* Not implemented:
MockServerResponse.prototype.writeContinue()
MockServerResponse.prototype.writeHead(statusCode, [reasonPhrase], [headers])
MockServerResponse.prototype.setTimeout(msecs, callback)
MockServerResponse.prototype.headersSent
MockServerResponse.prototype.sendDate
MockServerResponse.prototype.addTrailers(headers)
*/<|endoftext|>


<commit_before>import React from 'react'
import {BernieText, BernieColors} from './styles/bernie-css'
import {Styles} from 'material-ui'
import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider'
import {BernieTheme} from './styles/bernie-theme'
import {slacks} from './data/slacks'

console.log(slacks)

export default class SlackInviteIndex extends React.Component {
  styles = {
    paragraph: {
      padding: '0.5em'
    },
    introContainer: {
      display: 'flex',
      flexDirection: 'row'
    },
    introTextContainer: {
      flex: 1,
      marginRight: 40
    },
    container: {
      padding: '40px'
    }
  }

  renderSplash() {
    return (

              Organize

              Chat with Bernie 2016 Volunteers

                In order for us to achieve our political revolution, there are many different chat rooms that volunteers use, primarily hosted on the Slack platform.

                  African Americans For Bernie - community of African American activists and allies

                  Bernie 2016 States - interfacing with volunteers and staff in each state

                  Bernie Builders - general volunteer chatter

                  Call For Bernie - phonebanking assistance and chatter

                  Coders for Sanders - site development and design chatter

    )
  }

  render() {
    return (
      <MuiThemeProvider muiTheme={Styles.getMuiTheme(BernieTheme)}>
        {this.renderSplash()}
      </MuiThemeProvider>
    )
  }
}
<commit_msg>Render with slack data & update language<commit_after>import React from 'react'
import {BernieText, BernieColors} from './styles/bernie-css'
import {Styles} from 'material-ui'
import MuiThemeProvider from 'material-ui/lib/MuiThemeProvider'
import {BernieTheme} from './styles/bernie-theme'
import {slacks} from './data/slacks'

export default class SlackInviteIndex extends React.Component {
  styles = {
    paragraph: {
      padding: '0.5em'
    },
    introContainer: {
      display: 'flex',
      flexDirection: 'row'
    },
    introTextContainer: {
      flex: 1,
      marginRight: 40
    },
    container: {
      padding: '40px'
    }
  }

  renderSplash() {
    return (

              Organize

              Chat with Bernie 2016 Volunteers

                Join a slack team to get involved in the revolution!

                {Object.keys(slacks).map((key) =>
<a href={`/slack/${key}`}>{slacks[key].title} - {slacks[key].description}
)}

    )
  }

  render() {
    return (
      <MuiThemeProvider muiTheme={Styles.getMuiTheme(BernieTheme)}>
        {this.renderSplash()}
      </MuiThemeProvider>
    )
  }
}
<|endoftext|>


<commit_before>'use strict';
var os = require('os');

var nameMap = {
	16: 'Sierra',
	15: 'El Capitan',
	14: 'Yosemite',
	13: 'Mavericks',
	12: 'Mountain Lion',
	11: 'Lion',
	10: 'Snow Leopard',
	9: 'Leopard',
	8: 'Tiger',
	7: 'Panther',
	6: 'Jaguar',
	5: 'Puma'
};

module.exports = function (release) {
	release = (release || os.release()).split('.')[0];
	return {
		name: nameMap[release],
		version: '10.' + (Number(release) - 4)
	};
};
<commit_msg>Add macOS High Sierra (#1)<commit_after>'use strict';
var os = require('os');

var nameMap = {
	17: 'High Sierra',
	16: 'Sierra',
	15: 'El Capitan',
	14: 'Yosemite',
	13: 'Mavericks',
	12: 'Mountain Lion',
	11: 'Lion',
	10: 'Snow Leopard',
	9: 'Leopard',
	8: 'Tiger',
	7: 'Panther',
	6: 'Jaguar',
	5: 'Puma'
};

module.exports = function (release) {
	release = (release || os.release()).split('.')[0];
	return {
		name: nameMap[release],
		version: '10.' + (Number(release) - 4)
	};
};
<|endoftext|>


<commit_before>var Duplex = require('stream').Duplex || require('readable-stream').Duplex;
var EventEmitter = require('events').EventEmitter;
var bitfield = require('bitfield');

var MESSAGE_PROTOCOL     = new Buffer('\x13BitTorrent protocol');
var MESSAGE_KEEP_ALIVE   = new Buffer('\x00\x00\x00\x00');
var MESSAGE_CHOKE        = new Buffer('\x00\x00\x00\x01\x00');
var MESSAGE_UNCHOKE      = new Buffer('\x00\x00\x00\x01\x01');
var MESSAGE_INTERESTED   = new Buffer('\x00\x00\x00\x01\x02');
var MESSAGE_UNINTERESTED = new Buffer('\x00\x00\x00\x01\x03');

var noop = function() {};

var pull = function(requests, piece, offset, length) {
	for (var i = 0; i < requests.length; i++) {
		var req = requests[i];
		if (req.piece !== piece || req.offset !== offset || req.length !== length) continue;
		requests.splice(i, 1);
		return req;
	}
	return null;
};

var Request = function(piece, offset, length, callback) {
	this.piece = piece;
	this.offset = offset;
	this.length = length;
	this.callback = callback;
	this.timeout = null;
};

var Wire = function() {
	if (!(this instanceof Wire)) return new Wire();
	Duplex.call(this);
	var self = this;

	this.amChoking = true;
	this.amInterested = false;
	this.peerChoking = true;
	this.peerInterested = false;
	this.peerPieces = [];
	this.peerExtensions = {};
	this.peerAddress = null; // external
	this.peerSpeed = null;   // external

	this.uploaded = 0;
	this.downloaded = 0;

	this.requests = [];
	this.peerRequests = [];

	this._keepAlive = null;
	this._finished = false;

	this.on('finish', function() {
		self._finished = true;
		self.push(null); // cannot be half open
		clearInterval(self._keepAlive);
		self._parse(Number.MAX_VALUE, noop);
		while (self.peerRequests.length) self.peerRequests.pop();
		while (self.requests.length) self._callback(self.requests.shift(), new Error('wire is closed'), null);
	});

	var ontimeout = function() {
		self._callback(self.requests.shift(), new Error('request has timed out'), null);
		self.emit('timeout');
	};

	this._timeout = 0;
	this._ontimeout = ontimeout;

	var onmessagelength = function(buffer) {
		var length = buffer.readUInt32BE(0);
		if (length) return self._parse(length, onmessage);
		self._parse(4, onmessagelength);
		self.emit('keep-alive');
	};

	var onmessage = function(buffer) {
		self._parse(4, onmessagelength);
		switch (buffer[0]) {
			case 0:
			return self._onchoke();
			case 1:
			return self._onunchoke();
			case 2:
			return self._oninterested();
			case 3:
			return self._onuninterested();
			case 4:
			return self._onhave(buffer.readUInt32BE(1));
			case 5:
			return self._onbitfield(buffer.slice(1));
			case 6:
			return self._onrequest(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 7:
			return self._onpiece(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.slice(9));
			case 8:
			return self._oncancel(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 9:
			return self._onport(buffer.readUInt16BE(1));
		}
		self.emit('unknownmessage', buffer);
	};

	this._buffer = [];
	this._bufferSize = 0;
	this._parser = null;
	this._parserSize = 0;

	this._parse(1, function(buffer) {
		var pstrlen = buffer.readUInt8(0);
		self._parse(pstrlen + 48, function(handshake) {
			handshake = handshake.slice(pstrlen);
			self._onhandshake(handshake.slice(8, 28), handshake.slice(28, 48), {dht: !!(handshake[7] & 1)});
			self._parse(4, onmessagelength);
		});
	});
};

Wire.prototype.__proto__ = Duplex.prototype;

Wire.prototype.handshake = function(infoHash, peerId, extensions) {
	if (typeof infoHash === 'string') infoHash = new Buffer(infoHash, 'hex');
	if (typeof peerId === 'string') peerId = new Buffer(peerId);
	if (infoHash.length !== 20 || peerId.length !== 20) throw new Error('infoHash and peerId MUST have length 20');

	var reserved = new Buffer([0,0,0,0,0,0,0,0]);
	if (extensions && extensions.dht) reserved[7] |= 1;

	this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHash, peerId], MESSAGE_PROTOCOL.length+48));
};

Wire.prototype.choke = function() {
	if (this.amChoking) return;
	this.amChoking = true;
	while (self.peerRequests.length) self.peerRequests.pop();
	this._push(MESSAGE_CHOKE);
};

Wire.prototype.unchoke = function() {
	if (!this.amChoking) return;
	this.amChoking = false;
	this._push(MESSAGE_UNCHOKE);
};

Wire.prototype.interested = function() {
	if (this.amInterested) return;
	this.amInterested = true;
	this._push(MESSAGE_INTERESTED);
};

Wire.prototype.uninterested = function() {
	if (!this.amInterested) return;
	this.amInterested = false;
	this._push(MESSAGE_UNINTERESTED);
};

Wire.prototype.have = function(i) {
	this._message(4, [i], null);
};

Wire.prototype.bitfield = function(bitfield) {
	if (bitfield.buffer) bitfield = bitfield.buffer; // support bitfield objects
	this._message(5, [], bitfield);
};

Wire.prototype.request = function(i, offset, length, callback) {
	if (!callback) callback = noop;
	if (this._finished) return callback(new Error('wire is closed'));
	if (this.peerChoking) return callback(new Error('peer is choking'));
	this.requests.push(new Request(i, offset, length, callback));
	this._updateTimeout();
	this._message(6, [i, offset, length], null);
};

Wire.prototype.piece = function(i, offset, buffer) {
	this.uploaded += buffer.length;
	this.emit('upload', buffer.length);
	this._message(7, [i, offset], buffer);
};

Wire.prototype.cancel = function(i, offset, length) {
	this._callback(pull(this.requests, i, offset, length), new Error('request was cancelled'), null);
	this._message(8, [i, offset, length], null);
};

Wire.prototype.port = function(port) {
	var message = new Buffer([0,0,0,3,9,0,0]);
	message.writeUInt16BE(port, 5);
	this._push(message);
};

Wire.prototype.setKeepAlive = function(bool) {
	clearInterval(this._keepAlive);
	if (bool === false) return;
	this._keepAlive = setInterval(this._push.bind(this, MESSAGE_KEEP_ALIVE), 60000);
};

Wire.prototype.setTimeout = function(ms, fn) {
	if (this.requests.length) clearTimeout(this.requests[0].timeout);
	this._timeout = ms;
	this._updateTimeout();
	if (fn) this.on('timeout', fn);
};

Wire.prototype.destroy = function() {
	this.emit('close');
	this.end();
};

// inbound

Wire.prototype._onhandshake = function(infoHash, peerId, extensions) {
	this.peerExtensions = extensions;
	this.emit('handshake', infoHash, peerId, extensions);
};

Wire.prototype._oninterested = function() {
	this.peerInterested = true;
	this.emit('interested');
};

Wire.prototype._onuninterested = function() {
	this.peerInterested = false;
	this.emit('uninterested');
};

Wire.prototype._onchoke = function() {
	this.peerChoking = true;
	this.emit('choke');
	while (this.requests.length) this._callback(this.requests.shift(), new Error('peer is choking'), null);
};

Wire.prototype._onunchoke = function() {
	this.peerChoking = false;
	this.emit('unchoke');
};

Wire.prototype._onbitfield = function(buffer) {
	var pieces = bitfield(buffer);
	for (var i = 0; i < 8 * buffer.length; i++) {
		this.peerPieces[i] = pieces.get(i);
	}
	this.emit('bitfield', buffer);
};

Wire.prototype._onhave = function(i) {
	this.peerPieces[i] = true;
	this.emit('have', i);
};

Wire.prototype._onrequest = function(i, offset, length) {
	if (this.amChoking) return;

	var self = this;
	var respond = function(err, buffer) {
		if (err || request !== pull(self.peerRequests, i, offset, length)) return;
		self.piece(i, offset, buffer);
	};

	var request = new Request(i, offset, length, respond);
	this.peerRequests.push(request);
	this.emit('request', i, offset, length, respond);
};

Wire.prototype._oncancel = function(i, offset, length) {
	pull(this.peerRequests, i, offset, length);
	this.emit('cancel', i, offset, length);
};

Wire.prototype._onpiece = function(i, offset, buffer) {
	this._callback(pull(this.requests, i, offset, buffer.length), null, buffer);
	this.downloaded += buffer.length;
	this.emit('download', buffer.length);
	this.emit('piece', i, offset, buffer);
};

Wire.prototype._onport = function(port) {
	this.emit('port', port);
};

// helpers and streams

Wire.prototype._callback = function(request, err, buffer) {
	if (!request) return;
	if (request.timeout) clearTimeout(request.timeout);
	if (!this.peerChoking && !this._finished) this._updateTimeout();
	request.callback(err, buffer);
};

Wire.prototype._updateTimeout = function() {
	if (!this._timeout || !this.requests.length || this.requests[0].timeout) return;
	this.requests[0].timeout = setTimeout(this._ontimeout, this._timeout);
};

Wire.prototype._message = function(id, numbers, data) {
	var dataLength = data ? data.length : 0;
	var buffer = new Buffer(5 + 4 * numbers.length);

	buffer.writeUInt32BE(buffer.length + dataLength - 4, 0);
	buffer[4] = id;
	for (var i = 0; i < numbers.length; i++) {
		buffer.writeUInt32BE(numbers[i], 5 + 4 * i);
	}

	this._push(buffer);
	if (data) this._push(data);
};

Wire.prototype._push = function(data) {
	if (this._finished) return;
	this.push(data);
};

Wire.prototype._parse = function(size, parser) {
	this._parserSize = size;
	this._parser = parser;
};

Wire.prototype._write = function(data, encoding, callback) {
	this._bufferSize += data.length;
	this._buffer.push(data);

	while (this._bufferSize >= this._parserSize) {
		var buffer = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer, this._bufferSize);
		this._bufferSize -= this._parserSize;
		this._buffer = this._bufferSize ? [buffer.slice(this._parserSize)] : [];
		this._parser(buffer.slice(0, this._parserSize));
	}

	callback();
};

Wire.prototype._read = noop;

module.exports = Wire;<commit_msg>minor style changes<commit_after>var Duplex = require('stream').Duplex || require('readable-stream').Duplex;
var EventEmitter = require('events').EventEmitter;
var bitfield = require('bitfield');

var MESSAGE_PROTOCOL     = new Buffer('\x13BitTorrent protocol');
var MESSAGE_KEEP_ALIVE   = new Buffer('\x00\x00\x00\x00');
var MESSAGE_CHOKE        = new Buffer('\x00\x00\x00\x01\x00');
var MESSAGE_UNCHOKE      = new Buffer('\x00\x00\x00\x01\x01');
var MESSAGE_INTERESTED   = new Buffer('\x00\x00\x00\x01\x02');
var MESSAGE_UNINTERESTED = new Buffer('\x00\x00\x00\x01\x03');
var MESSAGE_RESERVED     = '\x00\x00\x00\x00\x00\x00\x00\x00';
var MESSAGE_PORT         = '\x00\x00\x00\x03\x09\x00\x00';

var noop = function() {};

var pull = function(requests, piece, offset, length) {
	for (var i = 0; i < requests.length; i++) {
		var req = requests[i];
		if (req.piece !== piece || req.offset !== offset || req.length !== length) continue;
		requests.splice(i, 1);
		return req;
	}
	return null;
};

var Request = function(piece, offset, length, callback) {
	this.piece = piece;
	this.offset = offset;
	this.length = length;
	this.callback = callback;
	this.timeout = null;
};

var Wire = function() {
	if (!(this instanceof Wire)) return new Wire();
	Duplex.call(this);
	var self = this;

	this.amChoking = true;
	this.amInterested = false;
	this.peerChoking = true;
	this.peerInterested = false;
	this.peerPieces = [];
	this.peerExtensions = {};
	this.peerAddress = null; // external
	this.peerSpeed = null;   // external

	this.uploaded = 0;
	this.downloaded = 0;

	this.requests = [];
	this.peerRequests = [];

	this._keepAlive = null;
	this._finished = false;

	this.on('finish', function() {
		self._finished = true;
		self.push(null); // cannot be half open
		clearInterval(self._keepAlive);
		self._parse(Number.MAX_VALUE, noop);
		while (self.peerRequests.length) self.peerRequests.pop();
		while (self.requests.length) self._callback(self.requests.shift(), new Error('wire is closed'), null);
	});

	var ontimeout = function() {
		self._callback(self.requests.shift(), new Error('request has timed out'), null);
		self.emit('timeout');
	};

	this._timeout = 0;
	this._ontimeout = ontimeout;

	var onmessagelength = function(buffer) {
		var length = buffer.readUInt32BE(0);
		if (length) return self._parse(length, onmessage);
		self._parse(4, onmessagelength);
		self.emit('keep-alive');
	};

	var onmessage = function(buffer) {
		self._parse(4, onmessagelength);
		switch (buffer[0]) {
			case 0:
			return self._onchoke();
			case 1:
			return self._onunchoke();
			case 2:
			return self._oninterested();
			case 3:
			return self._onuninterested();
			case 4:
			return self._onhave(buffer.readUInt32BE(1));
			case 5:
			return self._onbitfield(buffer.slice(1));
			case 6:
			return self._onrequest(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 7:
			return self._onpiece(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.slice(9));
			case 8:
			return self._oncancel(buffer.readUInt32BE(1), buffer.readUInt32BE(5), buffer.readUInt32BE(9));
			case 9:
			return self._onport(buffer.readUInt16BE(1));
		}
		self.emit('unknownmessage', buffer);
	};

	this._buffer = [];
	this._bufferSize = 0;
	this._parser = null;
	this._parserSize = 0;

	this._parse(1, function(buffer) {
		var pstrlen = buffer.readUInt8(0);
		self._parse(pstrlen + 48, function(handshake) {
			handshake = handshake.slice(pstrlen);
			self._onhandshake(handshake.slice(8, 28), handshake.slice(28, 48), {dht: !!(handshake[7] & 1)});
			self._parse(4, onmessagelength);
		});
	});
};

Wire.prototype.__proto__ = Duplex.prototype;

Wire.prototype.handshake = function(infoHash, peerId, extensions) {
	if (typeof infoHash === 'string') infoHash = new Buffer(infoHash, 'hex');
	if (typeof peerId === 'string') peerId = new Buffer(peerId);
	if (infoHash.length !== 20 || peerId.length !== 20) throw new Error('infoHash and peerId MUST have length 20');

	var reserved = new Buffer(MESSAGE_RESERVED);
	if (extensions && extensions.dht) reserved[7] |= 1;

	this._push(Buffer.concat([MESSAGE_PROTOCOL, reserved, infoHash, peerId], MESSAGE_PROTOCOL.length+48));
};

Wire.prototype.choke = function() {
	if (this.amChoking) return;
	this.amChoking = true;
	while (self.peerRequests.length) self.peerRequests.pop();
	this._push(MESSAGE_CHOKE);
};

Wire.prototype.unchoke = function() {
	if (!this.amChoking) return;
	this.amChoking = false;
	this._push(MESSAGE_UNCHOKE);
};

Wire.prototype.interested = function() {
	if (this.amInterested) return;
	this.amInterested = true;
	this._push(MESSAGE_INTERESTED);
};

Wire.prototype.uninterested = function() {
	if (!this.amInterested) return;
	this.amInterested = false;
	this._push(MESSAGE_UNINTERESTED);
};

Wire.prototype.have = function(i) {
	this._message(4, [i], null);
};

Wire.prototype.bitfield = function(bitfield) {
	if (bitfield.buffer) bitfield = bitfield.buffer; // support bitfield objects
	this._message(5, [], bitfield);
};

Wire.prototype.request = function(i, offset, length, callback) {
	if (!callback) callback = noop;
	if (this._finished)   return callback(new Error('wire is closed'));
	if (this.peerChoking) return callback(new Error('peer is choking'));
	this.requests.push(new Request(i, offset, length, callback));
	this._updateTimeout();
	this._message(6, [i, offset, length], null);
};

Wire.prototype.piece = function(i, offset, buffer) {
	this.uploaded += buffer.length;
	this.emit('upload', buffer.length);
	this._message(7, [i, offset], buffer);
};

Wire.prototype.cancel = function(i, offset, length) {
	this._callback(pull(this.requests, i, offset, length), new Error('request was cancelled'), null);
	this._message(8, [i, offset, length], null);
};

Wire.prototype.port = function(port) {
	var message = new Buffer(MESSAGE_PORT);
	message.writeUInt16BE(port, 5);
	this._push(message);
};

Wire.prototype.setKeepAlive = function(bool) {
	clearInterval(this._keepAlive);
	if (bool === false) return;
	this._keepAlive = setInterval(this._push.bind(this, MESSAGE_KEEP_ALIVE), 60000);
};

Wire.prototype.setTimeout = function(ms, fn) {
	if (this.requests.length) clearTimeout(this.requests[0].timeout);
	this._timeout = ms;
	this._updateTimeout();
	if (fn) this.on('timeout', fn);
};

Wire.prototype.destroy = function() {
	this.emit('close');
	this.end();
};

// inbound

Wire.prototype._onhandshake = function(infoHash, peerId, extensions) {
	this.peerExtensions = extensions;
	this.emit('handshake', infoHash, peerId, extensions);
};

Wire.prototype._oninterested = function() {
	this.peerInterested = true;
	this.emit('interested');
};

Wire.prototype._onuninterested = function() {
	this.peerInterested = false;
	this.emit('uninterested');
};

Wire.prototype._onchoke = function() {
	this.peerChoking = true;
	this.emit('choke');
	while (this.requests.length) this._callback(this.requests.shift(), new Error('peer is choking'), null);
};

Wire.prototype._onunchoke = function() {
	this.peerChoking = false;
	this.emit('unchoke');
};

Wire.prototype._onbitfield = function(buffer) {
	var pieces = bitfield(buffer);
	for (var i = 0; i < 8 * buffer.length; i++) {
		this.peerPieces[i] = pieces.get(i);
	}
	this.emit('bitfield', buffer);
};

Wire.prototype._onhave = function(i) {
	this.peerPieces[i] = true;
	this.emit('have', i);
};

Wire.prototype._onrequest = function(i, offset, length) {
	if (this.amChoking) return;

	var self = this;
	var respond = function(err, buffer) {
		if (err || request !== pull(self.peerRequests, i, offset, length)) return;
		self.piece(i, offset, buffer);
	};

	var request = new Request(i, offset, length, respond);
	this.peerRequests.push(request);
	this.emit('request', i, offset, length, respond);
};

Wire.prototype._oncancel = function(i, offset, length) {
	pull(this.peerRequests, i, offset, length);
	this.emit('cancel', i, offset, length);
};

Wire.prototype._onpiece = function(i, offset, buffer) {
	this._callback(pull(this.requests, i, offset, buffer.length), null, buffer);
	this.downloaded += buffer.length;
	this.emit('download', buffer.length);
	this.emit('piece', i, offset, buffer);
};

Wire.prototype._onport = function(port) {
	this.emit('port', port);
};

// helpers and streams

Wire.prototype._callback = function(request, err, buffer) {
	if (!request) return;
	if (request.timeout) clearTimeout(request.timeout);
	if (!this.peerChoking && !this._finished) this._updateTimeout();
	request.callback(err, buffer);
};

Wire.prototype._updateTimeout = function() {
	if (!this._timeout || !this.requests.length || this.requests[0].timeout) return;
	this.requests[0].timeout = setTimeout(this._ontimeout, this._timeout);
};

Wire.prototype._message = function(id, numbers, data) {
	var dataLength = data ? data.length : 0;
	var buffer = new Buffer(5 + 4 * numbers.length);

	buffer.writeUInt32BE(buffer.length + dataLength - 4, 0);
	buffer[4] = id;
	for (var i = 0; i < numbers.length; i++) {
		buffer.writeUInt32BE(numbers[i], 5 + 4 * i);
	}

	this._push(buffer);
	if (data) this._push(data);
};

Wire.prototype._push = function(data) {
	if (this._finished) return;
	this.push(data);
};

Wire.prototype._parse = function(size, parser) {
	this._parserSize = size;
	this._parser = parser;
};

Wire.prototype._write = function(data, encoding, callback) {
	this._bufferSize += data.length;
	this._buffer.push(data);

	while (this._bufferSize >= this._parserSize) {
		var buffer = this._buffer.length === 1 ? this._buffer[0] : Buffer.concat(this._buffer, this._bufferSize);
		this._bufferSize -= this._parserSize;
		this._buffer = this._bufferSize ? [buffer.slice(this._parserSize)] : [];
		this._parser(buffer.slice(0, this._parserSize));
	}

	callback();
};

Wire.prototype._read = noop;

module.exports = Wire;<|endoftext|>


<commit_before>const fs = require('fs');
const {URL} = require('url');
const path = require('path');
const fetch = require('node-fetch');
const ffmpeg = require('fluent-ffmpeg');
const sqlite = require('sqlite');
const {Composer} = require('micro-bot');
const commandParts = require('telegraf-command-parts');
const mdEscape = require('markdown-escape');

Promise.resolve()
  .then(() => sqlite.open('file_ids.db', {cached: true}))
  .then(() => sqlite.run('CREATE TABLE IF NOT EXISTS files(id INTEGER PRIMARY KEY, file_id TEXT, is_document INTEGER);'))
  .catch((err) => {
    console.error(err);
    process.exit(1);
  })
  .then(() => console.log('Database open'));

const app = new Composer();

const reVideoId = new RegExp('^(?:https?://(?:[a-z-]+\\.)?twitter\\.com/[^/]+/status(?:es)?/|id:)(\\d+)(?:[?#]|$)', 'i');
const reVideoUrl = new RegExp('"video_url":"(.+?)"');
const reVideoDomain = new RegExp('^https?://[^/]+', 'i');

const parseMarkdownExtra = {parse_mode: 'Markdown'};

function formatTime(date) {
  const hours = date.getHours(),
        minutes = date.getMinutes(),
        seconds = date.getSeconds(),
        milliseconds = date.getMilliseconds();

  return ((hours < 10) ? '0' + hours : hours) +
         ':' +
         ((minutes < 10) ? '0' + minutes : minutes) +
         ':' +
         ((seconds < 10) ? '0' + seconds : seconds) +
         '.' +
         ('00' + milliseconds).slice(-3);
}

app.use(commandParts());

app.use((ctx, next) => {
  const start = new Date();

  if (ctx.update.message) {
    console.log('[%s] “%s %s” (@%s, id: %s) sent “%s”', formatTime(start), ctx.chat.first_name, ctx.chat.last_name, ctx.chat.username, ctx.chat.id, ctx.update.message.text);
  }

  return next(ctx).then(() => {
    console.log(`[${ctx.updateType}] Response time %sms`, new Date() - start);
  });
});

async function getM3u8UrlList(url, prefix = '') {
  const m3u8Text = await (await fetch(url)).text();
  const m3u8Lines = m3u8Text.split('\n');

  return m3u8Lines.filter((line) => line && !line.startsWith('#')).map((line) => prefix + line);
}

async function downloadFile(url, folder = '/tmp') {
  const fileName = path.join(folder, path.basename(new URL(url).pathname));
  const buffer = await (await fetch(url)).buffer();

  fs.writeFileSync(fileName, buffer);
  return fileName;
}

async function saveUrlListToFiles(urlList, folder = '/tmp') {
  return Promise.all(urlList.map((url) => downloadFile(url, folder)));
}

function ffmpegAsync(command, outputVideo) {
  return new Promise((resolve, reject) => {
    command.on('end', (stdout, stderr) =>
      resolve([stdout, stderr])
    ).on('error', (err, stdout, stderr) =>
      reject([err, stdout, stderr])
    ).saveToFile(outputVideo);
  });
}

async function fetchVideo(videoId) {
  const playerUrl = 'https://twitter.com/i/videos/' + videoId;
  const playerHtml = await (await fetch(playerUrl)).text();
  const [, videoUrlDirty] = reVideoUrl.exec(playerHtml) || [];

  if (!videoUrlDirty) {
    return null;
  }

  const videoUrl = videoUrlDirty.replace(/\\/g, '');

  console.log(videoUrl);
  if (videoUrl.endsWith('.m3u8')) {
    const [videoDomain] = reVideoDomain.exec(videoUrl);
    const m3u8List = await getM3u8UrlList(videoUrl, videoDomain);
    const m3u8Url = m3u8List[m3u8List.length - 1];
    const tsList = await getM3u8UrlList(m3u8Url, videoDomain);
    const tmpDir = fs.mkdtempSync('/tmp/twvid');
    const videoList = await saveUrlListToFiles(tsList, tmpDir);
    const videoListFile = path.join(tmpDir, 'parts.list');
    const outputVideo = path.join(tmpDir, 'twvid_' + videoId + '.mp4');

    console.log(videoList);
    fs.writeFileSync(videoListFile, videoList.reduce((acc, cur) => acc + 'file ' + cur + '\n', ''));

    const command = ffmpeg(videoListFile).addInputOptions(['-f concat', '-safe 0']).withNoAudio().withVideoCodec('copy');

    console.log('Converting video with FFMpeg...', outputVideo);
    const [stdout, stderr] = await ffmpegAsync(command, outputVideo);
    console.log('Done.');
    //console.log(stdout, stderr);

    videoList.forEach((file) => fs.unlinkSync(file));
    fs.unlinkSync(videoListFile);

    return {video: outputVideo, isLocalFile: true};
  }
  else {
    return {video: videoUrl, isLocalFile: false};
  }
}

async function replyWithError(text, ctx, extra) {
  console.error(`Error: ${text}`);
  await ctx.replyWithMarkdown(`*⚠ ${text}*`, extra);
}

async function editWithError(msg, text, ctx, extra) {
  console.error(`Error: ${text}`);
  await ctx.telegram.editMessageText(msg.chat.id, msg.message_id, null, `*⚠ ${text}*`, Object.assign({}, parseMarkdownExtra, extra));
}

function getVideoId(url) {
  const [, videoId] = reVideoId.exec(url) || [];

  return videoId;
}

async function checkCache(videoId) {
  return (await sqlite.get('SELECT file_id, is_document FROM files WHERE id = ?;', videoId)) || {};
}

async function processStatusId(videoId, ctx, extra = {}, isInline = false) {
  const statusMsg = await ctx.replyWithMarkdown('_Processing your request..._', Object.assign({noWebhook: true}, extra));

  await ctx.replyWithChatAction('upload_video');

  const {file_id, is_document} = await checkCache(videoId);
  const replyExtra = Object.assign(isInline ? {
    reply_markup: {
      inline_keyboard: [
        [
          {
            text: 'Send to chat...',
            switch_inline_query: 'id:' + videoId
          }
        ]
      ]
    }
  } : {}, extra);

  if (file_id) {
    console.log('Cache hit!');
    await ctx[is_document ? 'replyWithDocument' : 'replyWithVideo'](file_id, replyExtra);
    ctx.telegram.deleteMessage(statusMsg.chat.id, statusMsg.message_id);
  }
  else {
    console.log('Cache miss!');
    ctx.telegram.editMessageText(statusMsg.chat.id, statusMsg.message_id, null, '_Downloading video..._', parseMarkdownExtra);

    const {video, isLocalFile} = await fetchVideo(videoId) || {};

    if (!video) {
      await editWithError(statusMsg, 'Tweet is not a video/GIF.', ctx, extra);
      return;
    }
    await ctx.replyWithChatAction('upload_video');

    let message,
        fileId,
        isDocument = false;

    if (isLocalFile) {
      console.log('Uploading video...');
      ctx.telegram.editMessageText(statusMsg.chat.id, statusMsg.message_id, null, '_Uploading video to Telegram..._', parseMarkdownExtra);
      message = await ctx.replyWithVideo({source: video}, Object.assign({noWebhook: true}, replyExtra));
      console.log('Done!');
      ctx.telegram.deleteMessage(statusMsg.chat.id, statusMsg.message_id);

      fs.unlinkSync(video);
      fs.rmdirSync(path.dirname(video));
    }
    else {
      ctx.telegram.editMessageText(statusMsg.chat.id, statusMsg.message_id, null, '_Sending video..._', parseMarkdownExtra);
      message = await ctx.replyWithVideo(video, Object.assign({noWebhook: true}, replyExtra));
      ctx.telegram.deleteMessage(statusMsg.chat.id, statusMsg.message_id);
    }

    if (message.video) {
      fileId = message.video.file_id;
    }
    else {
      fileId = message.document.file_id;
      isDocument = true;
    }

    console.log('isDocument', isDocument);
    await sqlite.run('INSERT OR IGNORE INTO files(id, file_id, is_document) VALUES(?, ?, ?);', videoId, fileId, isDocument ? 1 : 0);
  }
}

async function processUrl(url, ctx) {
  const replyToMessageExtra = {reply_to_message_id: ctx.message.message_id};

  if (!url) {
    await replyWithError('Please provide a video tweet URL.', ctx, replyToMessageExtra);
    return;
  }

  const videoId = getVideoId(url);

  if (!videoId) {
    await replyWithError('Invalid video tweet.', ctx, replyToMessageExtra);
    return;
  }

  await processStatusId(videoId, ctx, replyToMessageExtra);
}

app.command('/start', async (ctx) => {
  const [param] = ctx.state.command.splitArgs;

  if (param) {
    console.log('Deeplinking parameter:', param);
    await processStatusId(param, ctx, {}, true);
  }
  else {
    await ctx.replyWithMarkdown(
`Welcome!

Use /creategif \`<url>\`, or simply send/forward me a message that contains one or more Twitter URLs, and I’ll convert them all to Telegram GIFs for you.

I also work inline, try starting a message with @${mdEscape(ctx.botInfo.username)}, followed by a single video tweet link.

Have fun!`);
  }
});

app.command('/creategif', async (ctx) => {
  const url = ctx.state.command.splitArgs.find((arg) => arg);

  await processUrl(url, ctx);
});

app.hears((text) => !text.startsWith('/'), async (ctx) => {
  const urls = ctx.update.message.text.split(' ').filter((arg) => reVideoId.test(arg));

  if (urls.length < 2) {
    await processUrl(urls[0], ctx);
  }
  else {
    for (const url of urls) {
      await processUrl(url, ctx);
    }
  }
});

app.on('inline_query', async (ctx) => {
  console.log('INLINE QUERY:', ctx.inlineQuery);

  const url = ctx.inlineQuery.query.split(' ').find((arg) => reVideoId.test(arg));
  const videoId = getVideoId(url);

  const results = [];
  const extra = {
    cache_time: 1,
  };

  if (videoId) {
    const {file_id} = await checkCache(videoId);

    if (file_id) {
      results.push({
        type: 'mpeg4_gif',
        id: videoId,
        mpeg4_file_id: file_id
      });
      extra.cache_time = 31536000;
    }
    else {
      Object.assign(extra, {
        switch_pm_text: 'Press here to get your GIF',
        switch_pm_parameter: videoId
      });
    }
  }

  console.log('Inline answer:', results, extra);
  return ctx.answerInlineQuery(results, extra);
});

module.exports = {
  botHandler: app,
  initialize: (bot) => {
    //bot.webhookReply = false;
    return Promise.resolve();
  },
  requestHandler: (req, res) => {
    console.log('HTTP Request:', req.method, req.url, req.headers);
    if (req.url === '/') {
      res.writeHead(303, {location: 'https://t.me/TwitterGif_Bot'});
      res.end();
    }
    else {
      res.writeHead(404);
      res.end('404 Not Found');
    }
  }
};
<commit_msg>Fixed regex to properly support links with /video after the Tweet ID<commit_after>const fs = require('fs');
const {URL} = require('url');
const path = require('path');
const fetch = require('node-fetch');
const ffmpeg = require('fluent-ffmpeg');
const sqlite = require('sqlite');
const {Composer} = require('micro-bot');
const commandParts = require('telegraf-command-parts');
const mdEscape = require('markdown-escape');

Promise.resolve()
  .then(() => sqlite.open('file_ids.db', {cached: true}))
  .then(() => sqlite.run('CREATE TABLE IF NOT EXISTS files(id INTEGER PRIMARY KEY, file_id TEXT, is_document INTEGER);'))
  .catch((err) => {
    console.error(err);
    process.exit(1);
  })
  .then(() => console.log('Database open'));

const app = new Composer();

const reVideoId = new RegExp('^(?:https?://(?:[a-z-]+\\.)?twitter\\.com/[^/]+/status(?:es)?/|id:)(\\d+)(?:[?#/]|$)', 'i');
const reVideoUrl = new RegExp('"video_url":"(.+?)"');
const reVideoDomain = new RegExp('^https?://[^/]+', 'i');

const parseMarkdownExtra = {parse_mode: 'Markdown'};

function formatTime(date) {
  const hours = date.getHours(),
        minutes = date.getMinutes(),
        seconds = date.getSeconds(),
        milliseconds = date.getMilliseconds();

  return ((hours < 10) ? '0' + hours : hours) +
         ':' +
         ((minutes < 10) ? '0' + minutes : minutes) +
         ':' +
         ((seconds < 10) ? '0' + seconds : seconds) +
         '.' +
         ('00' + milliseconds).slice(-3);
}

app.use(commandParts());

app.use((ctx, next) => {
  const start = new Date();

  if (ctx.update.message) {
    console.log('[%s] “%s %s” (@%s, id: %s) sent “%s”', formatTime(start), ctx.chat.first_name, ctx.chat.last_name, ctx.chat.username, ctx.chat.id, ctx.update.message.text);
  }

  return next(ctx).then(() => {
    console.log(`[${ctx.updateType}] Response time %sms`, new Date() - start);
  });
});

async function getM3u8UrlList(url, prefix = '') {
  const m3u8Text = await (await fetch(url)).text();
  const m3u8Lines = m3u8Text.split('\n');

  return m3u8Lines.filter((line) => line && !line.startsWith('#')).map((line) => prefix + line);
}

async function downloadFile(url, folder = '/tmp') {
  const fileName = path.join(folder, path.basename(new URL(url).pathname));
  const buffer = await (await fetch(url)).buffer();

  fs.writeFileSync(fileName, buffer);
  return fileName;
}

async function saveUrlListToFiles(urlList, folder = '/tmp') {
  return Promise.all(urlList.map((url) => downloadFile(url, folder)));
}

function ffmpegAsync(command, outputVideo) {
  return new Promise((resolve, reject) => {
    command.on('end', (stdout, stderr) =>
      resolve([stdout, stderr])
    ).on('error', (err, stdout, stderr) =>
      reject([err, stdout, stderr])
    ).saveToFile(outputVideo);
  });
}

async function fetchVideo(videoId) {
  const playerUrl = 'https://twitter.com/i/videos/' + videoId;
  const playerHtml = await (await fetch(playerUrl)).text();
  const [, videoUrlDirty] = reVideoUrl.exec(playerHtml) || [];

  if (!videoUrlDirty) {
    return null;
  }

  const videoUrl = videoUrlDirty.replace(/\\/g, '');

  console.log(videoUrl);
  if (videoUrl.endsWith('.m3u8')) {
    const [videoDomain] = reVideoDomain.exec(videoUrl);
    const m3u8List = await getM3u8UrlList(videoUrl, videoDomain);
    const m3u8Url = m3u8List[m3u8List.length - 1];
    const tsList = await getM3u8UrlList(m3u8Url, videoDomain);
    const tmpDir = fs.mkdtempSync('/tmp/twvid');
    const videoList = await saveUrlListToFiles(tsList, tmpDir);
    const videoListFile = path.join(tmpDir, 'parts.list');
    const outputVideo = path.join(tmpDir, 'twvid_' + videoId + '.mp4');

    console.log(videoList);
    fs.writeFileSync(videoListFile, videoList.reduce((acc, cur) => acc + 'file ' + cur + '\n', ''));

    const command = ffmpeg(videoListFile).addInputOptions(['-f concat', '-safe 0']).withNoAudio().withVideoCodec('copy');

    console.log('Converting video with FFMpeg...', outputVideo);
    const [stdout, stderr] = await ffmpegAsync(command, outputVideo);
    console.log('Done.');
    //console.log(stdout, stderr);

    videoList.forEach((file) => fs.unlinkSync(file));
    fs.unlinkSync(videoListFile);

    return {video: outputVideo, isLocalFile: true};
  }
  else {
    return {video: videoUrl, isLocalFile: false};
  }
}

async function replyWithError(text, ctx, extra) {
  console.error(`Error: ${text}`);
  await ctx.replyWithMarkdown(`*⚠ ${text}*`, extra);
}

async function editWithError(msg, text, ctx, extra) {
  console.error(`Error: ${text}`);
  await ctx.telegram.editMessageText(msg.chat.id, msg.message_id, null, `*⚠ ${text}*`, Object.assign({}, parseMarkdownExtra, extra));
}

function getVideoId(url) {
  const [, videoId] = reVideoId.exec(url) || [];

  return videoId;
}

async function checkCache(videoId) {
  return (await sqlite.get('SELECT file_id, is_document FROM files WHERE id = ?;', videoId)) || {};
}

async function processStatusId(videoId, ctx, extra = {}, isInline = false) {
  const statusMsg = await ctx.replyWithMarkdown('_Processing your request..._', Object.assign({noWebhook: true}, extra));

  await ctx.replyWithChatAction('upload_video');

  const {file_id, is_document} = await checkCache(videoId);
  const replyExtra = Object.assign(isInline ? {
    reply_markup: {
      inline_keyboard: [
        [
          {
            text: 'Send to chat...',
            switch_inline_query: 'id:' + videoId
          }
        ]
      ]
    }
  } : {}, extra);

  if (file_id) {
    console.log('Cache hit!');
    await ctx[is_document ? 'replyWithDocument' : 'replyWithVideo'](file_id, replyExtra);
    ctx.telegram.deleteMessage(statusMsg.chat.id, statusMsg.message_id);
  }
  else {
    console.log('Cache miss!');
    ctx.telegram.editMessageText(statusMsg.chat.id, statusMsg.message_id, null, '_Downloading video..._', parseMarkdownExtra);

    const {video, isLocalFile} = await fetchVideo(videoId) || {};

    if (!video) {
      await editWithError(statusMsg, 'Tweet is not a video/GIF.', ctx, extra);
      return;
    }
    await ctx.replyWithChatAction('upload_video');

    let message,
        fileId,
        isDocument = false;

    if (isLocalFile) {
      console.log('Uploading video...');
      ctx.telegram.editMessageText(statusMsg.chat.id, statusMsg.message_id, null, '_Uploading video to Telegram..._', parseMarkdownExtra);
      message = await ctx.replyWithVideo({source: video}, Object.assign({noWebhook: true}, replyExtra));
      console.log('Done!');
      ctx.telegram.deleteMessage(statusMsg.chat.id, statusMsg.message_id);

      fs.unlinkSync(video);
      fs.rmdirSync(path.dirname(video));
    }
    else {
      ctx.telegram.editMessageText(statusMsg.chat.id, statusMsg.message_id, null, '_Sending video..._', parseMarkdownExtra);
      message = await ctx.replyWithVideo(video, Object.assign({noWebhook: true}, replyExtra));
      ctx.telegram.deleteMessage(statusMsg.chat.id, statusMsg.message_id);
    }

    if (message.video) {
      fileId = message.video.file_id;
    }
    else {
      fileId = message.document.file_id;
      isDocument = true;
    }

    console.log('isDocument', isDocument);
    await sqlite.run('INSERT OR IGNORE INTO files(id, file_id, is_document) VALUES(?, ?, ?);', videoId, fileId, isDocument ? 1 : 0);
  }
}

async function processUrl(url, ctx) {
  const replyToMessageExtra = {reply_to_message_id: ctx.message.message_id};

  if (!url) {
    await replyWithError('Please provide a video tweet URL.', ctx, replyToMessageExtra);
    return;
  }

  const videoId = getVideoId(url);

  if (!videoId) {
    await replyWithError('Invalid video tweet.', ctx, replyToMessageExtra);
    return;
  }

  await processStatusId(videoId, ctx, replyToMessageExtra);
}

app.command('/start', async (ctx) => {
  const [param] = ctx.state.command.splitArgs;

  if (param) {
    console.log('Deeplinking parameter:', param);
    await processStatusId(param, ctx, {}, true);
  }
  else {
    await ctx.replyWithMarkdown(
`Welcome!

Use /creategif \`<url>\`, or simply send/forward me a message that contains one or more Twitter URLs, and I’ll convert them all to Telegram GIFs for you.

I also work inline, try starting a message with @${mdEscape(ctx.botInfo.username)}, followed by a single video tweet link.

Have fun!`);
  }
});

app.command('/creategif', async (ctx) => {
  const url = ctx.state.command.splitArgs.find((arg) => arg);

  await processUrl(url, ctx);
});

app.hears((text) => !text.startsWith('/'), async (ctx) => {
  const urls = ctx.update.message.text.split(' ').filter((arg) => reVideoId.test(arg));

  if (urls.length < 2) {
    await processUrl(urls[0], ctx);
  }
  else {
    for (const url of urls) {
      await processUrl(url, ctx);
    }
  }
});

app.on('inline_query', async (ctx) => {
  console.log('INLINE QUERY:', ctx.inlineQuery);

  const url = ctx.inlineQuery.query.split(' ').find((arg) => reVideoId.test(arg));
  const videoId = getVideoId(url);

  const results = [];
  const extra = {
    cache_time: 1,
  };

  if (videoId) {
    const {file_id} = await checkCache(videoId);

    if (file_id) {
      results.push({
        type: 'mpeg4_gif',
        id: videoId,
        mpeg4_file_id: file_id
      });
      extra.cache_time = 31536000;
    }
    else {
      Object.assign(extra, {
        switch_pm_text: 'Press here to get your GIF',
        switch_pm_parameter: videoId
      });
    }
  }

  console.log('Inline answer:', results, extra);
  return ctx.answerInlineQuery(results, extra);
});

module.exports = {
  botHandler: app,
  initialize: (bot) => {
    //bot.webhookReply = false;
    return Promise.resolve();
  },
  requestHandler: (req, res) => {
    console.log('HTTP Request:', req.method, req.url, req.headers);
    if (req.url === '/') {
      res.writeHead(303, {location: 'https://t.me/TwitterGif_Bot'});
      res.end();
    }
    else {
      res.writeHead(404);
      res.end('404 Not Found');
    }
  }
};
<|endoftext|>


<commit_before>/**
 * Conditionally concatenates the provided strings
 *
 * @param {object} string Strings to be concatenated. Must be
 * must be in form { "yolo-class": true, "awesome-class": false }
 *
 * @return string The concatenated string
 */
module.exports = function concat(strings) {
  // Let's first check if the provided `strings` is not an object
  if ( typeof strings !== 'object') {
    throw new Error(
      'Strings to be concatenated must be an object, ' +
    	'in form of { "yolo": true }'
  	);
  }

  var keys = Object.keys(strings); // Keys of the object
  var result = ''; // Our resulting string, where each string will be concatenated
  var i = 0; // Used to check if we should add a space before the string is concatenated

  for(key in keys) {
    // We'll concatenate if the value of key (`{yolo: true}`) is `true`
  	if ( !!strings[key] )
      result += (i == 0 ? '' : ' ') + key;

    ++i;
  }

  return result;
}
<commit_msg>Removed feature trying to typecast provided value to boolean.<commit_after>/**
 * Conditionally concatenates the provided strings
 *
 * @param {object} string Strings to be concatenated. Must be
 * must be in form { "yolo-class": true, "awesome-class": false }
 *
 * @return string The concatenated string
 */
module.exports = function concat(strings) {
  // Let's first check if the provided `strings` is not an object
  if ( typeof strings !== 'object') {
    throw new Error(
      'Strings to be concatenated must be an object, ' +
    	'in form of { "yolo": true }'
  	);
  }

  var keys = Object.keys(strings); // Keys of the object
  var result = ''; // Our resulting string, where each string will be concatenated
  var i = 0; // Used to check if we should add a space before the string is concatenated

  for(key in keys) {
    // We'll concatenate if the value of key (`{yolo: true}`) is `true`
  	if ( strings[key] === true )
      result += (i == 0 ? '' : ' ') + key;

    ++i;
  }

  return result;
}
<|endoftext|>


<commit_before>/* jshint node: true */
'use strict';

var RSVP = require('rsvp');
var fs = require('fs');
var path = require('path');
var merge = require('lodash/object/merge');
var template = require('lodash/string/template');
var minimatch = require('minimatch');
var FormData = require('form-data');

var BasePlugin = require('ember-cli-deploy-plugin');

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

  createDeployPlugin: function(options) {
    var DeployPlugin = BasePlugin.extend({
      name: options.name,

      defaultConfig: {
        projectName: function(context) {
          return context.project.pkg.name;
        },
        revisionKey: function(context) {
          return context.revisionData && context.revisionData.revisionKey;
        },
        distFiles: function(context) {
          return context.distFiles;
        },
        distDir: function(context) {
          return context.distDir;
        },
        rollbarConfig: {
          enabled: true,
          environment: 'production',
          captureUncaught: true
        },
        integrateRollbar: true
      },
      requiredConfig: ['accessToken', 'accessServerToken', 'minifiedPrependUrl'],

      willUpload: function(context) {
        if(this.readConfig('integrateRollbar')) {
          // setup rollbarConfig
          var rollbarConfig = {
            accessToken: this.readConfig('accessToken'),
            enabled: this.readConfig('rollbarConfig').enabled,
            captureUncaught: this.readConfig('rollbarConfig').captureUncaught,
            environment: this.readConfig('rollbarConfig').environment,
            payload: {
              client: {
                javascript: {
                  source_map_enabled: true,
                  code_version: this.readConfig('revisionKey'),
                  guess_uncaught_frames: true
                }
              }
            }
          };

          // render rollbar snippet with fulfilled config
          var htmlSnippetPath = path.join(__dirname, 'addon', 'rollbar.html');
          var htmlContent = fs.readFileSync(htmlSnippetPath, 'utf-8');
          var snippetPath = path.join(__dirname, 'addon', 'snippet.js');
          var snippetContent = fs.readFileSync(snippetPath, 'utf-8');

          var rollbarSnippet = template(htmlContent)({
            rollbarConfig: JSON.stringify(rollbarConfig),
            rollbarSnippet: snippetContent
          });

          // replace rollbar metatag with rollbar snippet in index.html
          var indexPath = path.join(context.distDir, "index.html");
          var index = fs.readFileSync(indexPath, 'utf8');
          var index = index.replace('<meta name="rollbar"/>', rollbarSnippet);
          fs.writeFileSync(indexPath, index);
        }
      },

      upload: function(context) {
        var distFiles = this.readConfig('distFiles');
        var projectName = this.readConfig('projectName');

        // fetch vendor and project-specific js and map
        var projectFileJs = distFiles.filter(minimatch.filter(`**/{${projectName},vendor}*.js`, {
          matchBase: true
        }));
        var projectFileMap = distFiles.filter(minimatch.filter(`**/{${projectName},vendor}*.map`, {
          matchBase: true
        }));

        var promiseArray = [];

        for(var i = 0; i < projectFileJs.length; i++) {
          // upload map to Rollbar using form-data

          var mapFilePath = path.join(this.readConfig('distDir'), projectFileMap[i]);
          var formData = new FormData();
          formData.append('access_token', this.readConfig('accessServerToken'));
          formData.append('version', this.readConfig('revisionKey'));
          formData.append('minified_url', this.readConfig('minifiedPrependUrl') + projectFileJs[i]);
          var fileSize = fs.statSync(mapFilePath)['size'];
          formData.append(
            'source_map',
            fs.createReadStream(mapFilePath),
            { knownLength: fileSize }
          );
          var promise = new RSVP.Promise(function(resolve, reject) {
            formData.submit('https://api.rollbar.com/api/1/sourcemap', function(error, result) {
              if(error) {
                reject(error);
              }
              result.resume();

              result.on('end', function() {
                resolve();
              });
            });
          });
          promiseArray.push(promise);
        };
        return RSVP.all(promiseArray);
      }
    });

    return new DeployPlugin();
  },

  contentFor: function(type) {
    if (type === 'head') {
      return '<meta name="rollbar"/>';
    }
  }
};
<commit_msg>Change back ticks to quote marks<commit_after>/* jshint node: true */
'use strict';

var RSVP = require('rsvp');
var fs = require('fs');
var path = require('path');
var merge = require('lodash/object/merge');
var template = require('lodash/string/template');
var minimatch = require('minimatch');
var FormData = require('form-data');

var BasePlugin = require('ember-cli-deploy-plugin');

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

  createDeployPlugin: function(options) {
    var DeployPlugin = BasePlugin.extend({
      name: options.name,

      defaultConfig: {
        projectName: function(context) {
          return context.project.pkg.name;
        },
        revisionKey: function(context) {
          return context.revisionData && context.revisionData.revisionKey;
        },
        distFiles: function(context) {
          return context.distFiles;
        },
        distDir: function(context) {
          return context.distDir;
        },
        rollbarConfig: {
          enabled: true,
          environment: 'production',
          captureUncaught: true
        },
        integrateRollbar: true
      },
      requiredConfig: ['accessToken', 'accessServerToken', 'minifiedPrependUrl'],

      willUpload: function(context) {
        if(this.readConfig('integrateRollbar')) {
          // setup rollbarConfig
          var rollbarConfig = {
            accessToken: this.readConfig('accessToken'),
            enabled: this.readConfig('rollbarConfig').enabled,
            captureUncaught: this.readConfig('rollbarConfig').captureUncaught,
            environment: this.readConfig('rollbarConfig').environment,
            payload: {
              client: {
                javascript: {
                  source_map_enabled: true,
                  code_version: this.readConfig('revisionKey'),
                  guess_uncaught_frames: true
                }
              }
            }
          };

          // render rollbar snippet with fulfilled config
          var htmlSnippetPath = path.join(__dirname, 'addon', 'rollbar.html');
          var htmlContent = fs.readFileSync(htmlSnippetPath, 'utf-8');
          var snippetPath = path.join(__dirname, 'addon', 'snippet.js');
          var snippetContent = fs.readFileSync(snippetPath, 'utf-8');

          var rollbarSnippet = template(htmlContent)({
            rollbarConfig: JSON.stringify(rollbarConfig),
            rollbarSnippet: snippetContent
          });

          // replace rollbar metatag with rollbar snippet in index.html
          var indexPath = path.join(context.distDir, "index.html");
          var index = fs.readFileSync(indexPath, 'utf8');
          var index = index.replace('<meta name="rollbar"/>', rollbarSnippet);
          fs.writeFileSync(indexPath, index);
        }
      },

      upload: function(context) {
        var distFiles = this.readConfig('distFiles');
        var projectName = this.readConfig('projectName');

        // fetch vendor and project-specific js and map
        var projectFileJs = distFiles.filter(minimatch.filter('**/{${projectName},vendor}*.js', {
          matchBase: true
        }));
        var projectFileMap = distFiles.filter(minimatch.filter('**/{${projectName},vendor}*.map', {
          matchBase: true
        }));

        var promiseArray = [];

        for(var i = 0; i < projectFileJs.length; i++) {
          // upload map to Rollbar using form-data

          var mapFilePath = path.join(this.readConfig('distDir'), projectFileMap[i]);
          var formData = new FormData();
          formData.append('access_token', this.readConfig('accessServerToken'));
          formData.append('version', this.readConfig('revisionKey'));
          formData.append('minified_url', this.readConfig('minifiedPrependUrl') + projectFileJs[i]);
          var fileSize = fs.statSync(mapFilePath)['size'];
          formData.append(
            'source_map',
            fs.createReadStream(mapFilePath),
            { knownLength: fileSize }
          );
          var promise = new RSVP.Promise(function(resolve, reject) {
            formData.submit('https://api.rollbar.com/api/1/sourcemap', function(error, result) {
              if(error) {
                reject(error);
              }
              result.resume();

              result.on('end', function() {
                resolve();
              });
            });
          });
          promiseArray.push(promise);
        };
        return RSVP.all(promiseArray);
      }
    });

    return new DeployPlugin();
  },

  contentFor: function(type) {
    if (type === 'head') {
      return '<meta name="rollbar"/>';
    }
  }
};
<|endoftext|>


<commit_before>require('es5-shim');

var inherits = function inherits (ctor, superCtor) {
	if (typeof ctor === 'function') {
		ctor.super_ = superCtor;
		var proto = (superCtor || {}).prototype || {};
		ctor.prototype = Object.create(proto, {
			constructor: {
				value:         ctor,
				enumerable:    false,
				writable:      false,
				configurable:  true
			}});
	}
};

var util = require('util');
util.inherits = inherits;

require.stub('util', util);

if (!window.console.error) {
	var _log = console.log;
	console.info = console.log = console.error = console.warn = function () {
		_log.call(console, util.format.apply(null, arguments));
	};;
}

var path = require('path-browserify');
require.stub('path', path);

var assert = require('assert');
require.stub('assert', assert);

var unimplemented = function () {
	throw new Error('Unimplemented!');
};

var system = require('system');
var events = require('events');
var EventEmitterHack = function () {
	if (this instanceof EventEmitterHack && !this._events) {
		this._events = {};
  		this._maxListeners = this._maxListeners || undefined;
	}
	else {
		return EventEmitterHack;
	}
};
EventEmitterHack.prototype = events.prototype;
EventEmitterHack.EventEmitter = events;
var fs = require('fs');
require.stub('events', EventEmitterHack);

if (typeof window.process === "undefined") {
	var process = window.process = new EventEmitterHack();

	process.chdir = function (directory) {
		if (!fs.changeWorkingDirectory(fs.absolute(directory))) {
			throw new Error("Failed to change directory.");
		}
	};

	process.cwd = function () {
		return fs.absolute('');
	};

	process.env = system.env;
	process.argv = system.args;
	process.stderr = {};
	process.stderr.write = function (data) {
		console.error(data);
	};

	process.stdout = {};
	process.stdout.write = function (data) {
		console.log(data);
	};

	process.exit = function (code) {
		process.emit('exit');
		phantom.exit(code);
	};

	process.version = phantom.version.major + '.' + phantom.version.minor + '.' + phantom.version.patch;
}

module.exports = {
	extend: function (rq) {
		rq.stub('events', EventEmitterHack);
		rq.stub('path', path);
		rq.stub('util', util);
		rq.stub('assert', assert);
	}
}
<commit_msg>Adds path.delimiter and path.separator<commit_after>require('es5-shim');

var inherits = function inherits (ctor, superCtor) {
	if (typeof ctor === 'function') {
		ctor.super_ = superCtor;
		var proto = (superCtor || {}).prototype || {};
		ctor.prototype = Object.create(proto, {
			constructor: {
				value:         ctor,
				enumerable:    false,
				writable:      false,
				configurable:  true
			}});
	}
};

var util = require('util');
util.inherits = inherits;
require.stub('util', util);

if (!window.console.error) {
	var _log = console.log;
	console.info = console.log = console.error = console.warn = function () {
		_log.call(console, util.format.apply(null, arguments));
	};;
}

var system = require("system");
var path = require('path-browserify');
path.delimiter = ({
	linux: ":",
	windows: ";",
	macosx: ":"
})[system.os];
path.separator = fs.separator;

require.stub('path', path);

require.stub('path', path);

var assert = require('assert');
require.stub('assert', assert);

var unimplemented = function () {
	throw new Error('Unimplemented!');
};

var events = require('events');
var EventEmitterHack = function () {
	if (this instanceof EventEmitterHack && !this._events) {
		this._events = {};
  		this._maxListeners = this._maxListeners || undefined;
	}
	else {
		return EventEmitterHack;
	}
};
EventEmitterHack.prototype = events.prototype;
EventEmitterHack.EventEmitter = events;
var fs = require('fs');
require.stub('events', EventEmitterHack);

if (typeof window.process === "undefined") {
	var process = window.process = new EventEmitterHack();

	process.chdir = function (directory) {
		if (!fs.changeWorkingDirectory(fs.absolute(directory))) {
			throw new Error("Failed to change directory.");
		}
	};

	process.cwd = function () {
		return fs.absolute('');
	};

	process.env = system.env;
	process.argv = system.args;
	process.stderr = {};
	process.stderr.write = function (data) {
		console.error(data);
	};

	process.stdout = {};
	process.stdout.write = function (data) {
		console.log(data);
	};

	process.exit = function (code) {
		process.emit('exit');
		phantom.exit(code);
	};

	process.version = phantom.version.major + '.' + phantom.version.minor + '.' + phantom.version.patch;
}

module.exports = {
	extend: function (rq) {
		rq.stub('events', EventEmitterHack);
		rq.stub('path', path);
		rq.stub('util', util);
		rq.stub('assert', assert);
	}
}
<|endoftext|>