<commit_before>import Component from '@ember/component';
import { computed } from 'ember-decorators/object';
import { alias, equal } from 'ember-decorators/object/computed';

import moment from 'moment';

const NOVEMBER_2017_RETIREMENT = '2017-11-28T12:00:00-08:00';
const LATEST_TRUSTY_RELEASE = '2017-12-11T00:00:00-00:00';

export default Component.extend({
  @alias('job.queue') queue: null,
  @alias('job.config') jobConfig: null,

  @computed('job.isFinished')
  conjugatedRun(isFinished) {
    return isFinished ? 'ran' : 'is running';
  },

  @equal('queue', 'builds.linux') isLegacyInfrastructure: null,
  @equal('queue', 'builds.ec2') isTrustySudoFalse: null,

  @computed('job.startedAt', 'queue', 'job.config')
  isTrustySudoRequired(startedAt, queue, config = {}) {
    if (queue === 'builds.gce' && config.dist === 'trusty') {
      const jobRanAfterReleaseDate = Date.parse(startedAt) > Date.parse(LATEST_TRUSTY_RELEASE);
      if (jobRanAfterReleaseDate) {
        return true;
      }
    }

    return false;
  },

  @equal('queue', 'builds.macstadium6') isMacStadium6: null,

  @computed('queue', 'job.config')
  isPreciseEOL(queue, config = {}) {
    if (queue === 'builds.gce' && config.dist === 'precise') {
      if (config.language !== 'android') {
        return true;
      }
    }
  },

  @alias('jobConfig.osx_image') macOSImage: null,

  deprecatedXcodeImages: ['xcode8', 'xcode8.1', 'xcode8.2'],

  imageToRetirementDate: {
    'xcode8': NOVEMBER_2017_RETIREMENT,
    'xcode8.1': NOVEMBER_2017_RETIREMENT,
    'xcode8.2': NOVEMBER_2017_RETIREMENT
  },

  imageToNewImage: {
    'xcode8': 'xcode8.3',
    'xcode8.1': 'xcode8.3',
    'xcode8.2': 'xcode8.3'
  },

  newImageStrings: {
    'xcode8.3': 'Xcode 8.3',
    'xcode7.3': 'Xcode 7.3.1',
    'xcode6.4': 'Xcode 6.4'
  },

  @computed('isMacStadium6', 'macOSImage')
  isDeprecatedOrRetiredMacImage(isMacStadium6, macOSImage) {
    return isMacStadium6 && this.get('deprecatedXcodeImages').includes(macOSImage);
  },

  // eslint-disable-next-line
  @computed('job.startedAt', 'macOSImage', 'job.isFinished', 'conjugatedRun', 'isDeprecatedOrRetiredMacImage')
  deprecatedOrRetiredMacImageMessage(startedAt, image, isFinished, conjugatedRun) {
    const retirementDate = Date.parse(this.get('imageToRetirementDate')[image]);

    const newImage = this.get('imageToNewImage')[image];
    const newImageString = this.get('newImageStrings')[newImage];
    const newImageAnchor = newImageString.replace(' ', '-');
    const newImageURLString = `<a href='https://docs.travis-ci.com/user/reference/osx/#${newImageAnchor}'>${newImageString}</a>`;
    const imageRetirementAnnouncementURL = 'https://blog.travis-ci.com/2017-11-21-xcode8-3-default-image-announce';

    const jobRanBeforeRetirementDate = Date.parse(startedAt) < retirementDate;
    const retirementDateIsInTheFuture = retirementDate > new Date();

    const formattedRetirementDate = moment(retirementDate).format('MMMM D, YYYY');

    const retirementLink =
      `
      ${retirementDateIsInTheFuture ? 'will be retired' : 'was retired'}
      on ${formattedRetirementDate}`;

    let retirementSentence, routingSentence;

    if (retirementDateIsInTheFuture) {
      retirementSentence = `This job ${conjugatedRun} on an OS X image that ${retirementLink}.`;
    } else {
      retirementSentence = `
        This job ${isFinished ? 'was configured to run on' : 'is configured to run on'}
        an OS X image that ${retirementLink}.`;
    }

    if (retirementDateIsInTheFuture) {
      routingSentence =
        `After that, it will route to our ${newImageURLString} image.`;
    } else if (jobRanBeforeRetirementDate) {
      routingSentence = `New jobs will route to our ${newImageURLString} image.`;
    } else {
      routingSentence = `It was routed to our ${newImageURLString} image.`;
    }

    return `${retirementSentence} ${routingSentence}`;
  }
});
<commit_msg>Tweak date again. Remove param default value. Will it show now?<commit_after>import Component from '@ember/component';
import { computed } from 'ember-decorators/object';
import { alias, equal } from 'ember-decorators/object/computed';

import moment from 'moment';

const NOVEMBER_2017_RETIREMENT = '2017-11-28T12:00:00-08:00';
const LATEST_TRUSTY_RELEASE = '2017-12-10T00:00:00-00:00';

export default Component.extend({
  @alias('job.queue') queue: null,
  @alias('job.config') jobConfig: null,

  @computed('job.isFinished')
  conjugatedRun(isFinished) {
    return isFinished ? 'ran' : 'is running';
  },

  @equal('queue', 'builds.linux') isLegacyInfrastructure: null,
  @equal('queue', 'builds.ec2') isTrustySudoFalse: null,

  @computed('job.startedAt', 'queue', 'job.config')
  isTrustySudoRequired(startedAt, queue, config) {
    if (queue === 'builds.gce' && config.dist === 'trusty') {
      const jobRanAfterReleaseDate = Date.parse(startedAt) > Date.parse(LATEST_TRUSTY_RELEASE);
      if (jobRanAfterReleaseDate) {
        return true;
      }
    }

    return false;
  },

  @equal('queue', 'builds.macstadium6') isMacStadium6: null,

  @computed('queue', 'job.config')
  isPreciseEOL(queue, config = {}) {
    if (queue === 'builds.gce' && config.dist === 'precise') {
      if (config.language !== 'android') {
        return true;
      }
    }
  },

  @alias('jobConfig.osx_image') macOSImage: null,

  deprecatedXcodeImages: ['xcode8', 'xcode8.1', 'xcode8.2'],

  imageToRetirementDate: {
    'xcode8': NOVEMBER_2017_RETIREMENT,
    'xcode8.1': NOVEMBER_2017_RETIREMENT,
    'xcode8.2': NOVEMBER_2017_RETIREMENT
  },

  imageToNewImage: {
    'xcode8': 'xcode8.3',
    'xcode8.1': 'xcode8.3',
    'xcode8.2': 'xcode8.3'
  },

  newImageStrings: {
    'xcode8.3': 'Xcode 8.3',
    'xcode7.3': 'Xcode 7.3.1',
    'xcode6.4': 'Xcode 6.4'
  },

  @computed('isMacStadium6', 'macOSImage')
  isDeprecatedOrRetiredMacImage(isMacStadium6, macOSImage) {
    return isMacStadium6 && this.get('deprecatedXcodeImages').includes(macOSImage);
  },

  // eslint-disable-next-line
  @computed('job.startedAt', 'macOSImage', 'job.isFinished', 'conjugatedRun', 'isDeprecatedOrRetiredMacImage')
  deprecatedOrRetiredMacImageMessage(startedAt, image, isFinished, conjugatedRun) {
    const retirementDate = Date.parse(this.get('imageToRetirementDate')[image]);

    const newImage = this.get('imageToNewImage')[image];
    const newImageString = this.get('newImageStrings')[newImage];
    const newImageAnchor = newImageString.replace(' ', '-');
    const newImageURLString = `<a href='https://docs.travis-ci.com/user/reference/osx/#${newImageAnchor}'>${newImageString}</a>`;
    const imageRetirementAnnouncementURL = 'https://blog.travis-ci.com/2017-11-21-xcode8-3-default-image-announce';

    const jobRanBeforeRetirementDate = Date.parse(startedAt) < retirementDate;
    const retirementDateIsInTheFuture = retirementDate > new Date();

    const formattedRetirementDate = moment(retirementDate).format('MMMM D, YYYY');

    const retirementLink =
      `
      ${retirementDateIsInTheFuture ? 'will be retired' : 'was retired'}
      on ${formattedRetirementDate}`;

    let retirementSentence, routingSentence;

    if (retirementDateIsInTheFuture) {
      retirementSentence = `This job ${conjugatedRun} on an OS X image that ${retirementLink}.`;
    } else {
      retirementSentence = `
        This job ${isFinished ? 'was configured to run on' : 'is configured to run on'}
        an OS X image that ${retirementLink}.`;
    }

    if (retirementDateIsInTheFuture) {
      routingSentence =
        `After that, it will route to our ${newImageURLString} image.`;
    } else if (jobRanBeforeRetirementDate) {
      routingSentence = `New jobs will route to our ${newImageURLString} image.`;
    } else {
      routingSentence = `It was routed to our ${newImageURLString} image.`;
    }

    return `${retirementSentence} ${routingSentence}`;
  }
});
<|endoftext|>


<commit_before>
/*
Integration for Google Maps in the django admin.

How it works:

You have an address field on the page.
Enter an address and an on change event will update the map
with the address. A marker will be placed at the address.
If the user needs to move the marker, they can and the geolocation
field will be updated.

Only one marker will remain present on the map at a time.

This script expects:

<input type="text" name="address" id="id_address" />
<input type="text" name="geolocation" id="id_geolocation" />

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

*/

function googleMapAdmin() {

    var geocoder = new google.maps.Geocoder();
    var map;
    var marker;

    var self = {
        initialize: function() {
            var lat = 0;
            var lng = 0;
            var zoom = 2;
            // set up initial map to be world view. also, add change
            // event so changing address will update the map
            existinglocation = self.getExistingLocation();
            if (existinglocation) {
                lat = existinglocation[0];
                lng = existinglocation[1];
                zoom = 18;
            }

            var latlng = new google.maps.LatLng(lat,lng);
            var myOptions = {
              zoom: zoom,
              center: latlng,
              mapTypeId: google.maps.MapTypeId.HYBRID
            };
            map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
            if (existinglocation) {
                self.setMarker(latlng);
            }

            $("#id_address").change(function() {self.codeAddress();});
        },

        getExistingLocation: function() {
            var geolocation = $("#id_geolocation").val();
            if (geolocation) {
                return geolocation.split(',');
            }
        },

        codeAddress: function() {
            var address = $("#id_address").val();
            geocoder.geocode({'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    var latlng = results[0].geometry.location;
                    map.setCenter(latlng);
                    map.setZoom(18);

                    self.setMarker(latlng);
                    self.updateGeolocation(latlng);
                } else {
                    alert("Geocode was not successful for the following reason: " + status);
                }
            });
        },

        setMarker: function(latlng) {
            if (marker) {
                self.updateMarker(latlng);
            } else {
                self.addMarker({'latlng': latlng, 'draggable': true});
            }
        },

        addMarker: function(Options) {
            marker = new google.maps.Marker({
                map: map,
                position: Options.latlng
            });

            var draggable = Options.draggable || false;
            if (draggable) {
                self.addMarkerDrag(marker);
            }
        },

        addMarkerDrag: function() {
            marker.setDraggable(true);
            google.maps.event.addListener(marker, 'dragend', function(new_location) {
                self.updateGeolocation(new_location.latLng);
            });
        },

        updateMarker: function(latlng) {
            marker.setPosition(latlng);
        },

        updateGeolocation: function(latlng) {
            $("#id_geolocation").val(latlng.lat() + "," + latlng.lng());
        }
    }

    return self;
}

$(document).ready(function() {
    var googlemap = googleMapAdmin();
    googlemap.initialize();
});<commit_msg>Trigger JS event on geolocation change<commit_after>
/*
Integration for Google Maps in the django admin.

How it works:

You have an address field on the page.
Enter an address and an on change event will update the map
with the address. A marker will be placed at the address.
If the user needs to move the marker, they can and the geolocation
field will be updated.

Only one marker will remain present on the map at a time.

This script expects:

<input type="text" name="address" id="id_address" />
<input type="text" name="geolocation" id="id_geolocation" />

<script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=false"></script>

*/

function googleMapAdmin() {

    var geocoder = new google.maps.Geocoder();
    var map;
    var marker;

    var self = {
        initialize: function() {
            var lat = 0;
            var lng = 0;
            var zoom = 2;
            // set up initial map to be world view. also, add change
            // event so changing address will update the map
            existinglocation = self.getExistingLocation();
            if (existinglocation) {
                lat = existinglocation[0];
                lng = existinglocation[1];
                zoom = 18;
            }

            var latlng = new google.maps.LatLng(lat,lng);
            var myOptions = {
              zoom: zoom,
              center: latlng,
              mapTypeId: google.maps.MapTypeId.HYBRID
            };
            map = new google.maps.Map(document.getElementById("map_canvas"), myOptions);
            if (existinglocation) {
                self.setMarker(latlng);
            }

            $("#id_address").change(function() {self.codeAddress();});
        },

        getExistingLocation: function() {
            var geolocation = $("#id_geolocation").val();
            if (geolocation) {
                return geolocation.split(',');
            }
        },

        codeAddress: function() {
            var address = $("#id_address").val();
            geocoder.geocode({'address': address}, function(results, status) {
                if (status == google.maps.GeocoderStatus.OK) {
                    var latlng = results[0].geometry.location;
                    map.setCenter(latlng);
                    map.setZoom(18);

                    self.setMarker(latlng);
                    self.updateGeolocation(latlng);
                } else {
                    alert("Geocode was not successful for the following reason: " + status);
                }
            });
        },

        setMarker: function(latlng) {
            if (marker) {
                self.updateMarker(latlng);
            } else {
                self.addMarker({'latlng': latlng, 'draggable': true});
            }
        },

        addMarker: function(Options) {
            marker = new google.maps.Marker({
                map: map,
                position: Options.latlng
            });

            var draggable = Options.draggable || false;
            if (draggable) {
                self.addMarkerDrag(marker);
            }
        },

        addMarkerDrag: function() {
            marker.setDraggable(true);
            google.maps.event.addListener(marker, 'dragend', function(new_location) {
                self.updateGeolocation(new_location.latLng);
            });
        },

        updateMarker: function(latlng) {
            marker.setPosition(latlng);
        },

        updateGeolocation: function(latlng) {
            $("#id_geolocation")
                .val(latlng.lat() + "," + latlng.lng())
                .trigger('change');
        }
    }

    return self;
}

$(document).ready(function() {
    var googlemap = googleMapAdmin();
    googlemap.initialize();
});<|endoftext|>


<commit_before>var notification = require('./notification');

var Hyperty = function(request, url, notificationCallback) {
  this._request = request;
  this._url = url
  this._notificationsEnabled = typeof notificationCallback !== 'undefined';

  if(this._notificationsEnabled) {
    this._notificationCallback = notificationCallback;
  }
};

Hyperty.prototype.search = function(body, callback) {

  var endpoint;
  var prefix;
  var id;

  if(body.resource.startsWith('/')) {
    var urlComponents = body.resource.split('/');
    prefix = urlComponents[2];
    id = urlComponents[3];
  } else {
    prefix = body.resource.split('://')[0];
  }

  if(prefix === 'user') {
    endpoint = '/hyperty/user/' + encodeURIComponent(body.resource);
  } else if(prefix === 'user-guid') {
    endpoint = '/hyperty/guid/' + encodeURIComponent(body.resource);
  } else if(prefix === 'idp-identifier') {
    endpoint = '/hyperty/email/' + encodeURIComponent(id);
  } else {
    endpoint = '/hyperty/url/' + encodeURIComponent(body.resource);
  }

  this._request.get(this._url + endpoint, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else if(statusCode == 200) {
      var body = {
        'code': statusCode,
        'value': response
      };
    }else if(statusCode == 408) {
      var body = {
        'code': statusCode,
        'description': 'Temporarily Unavailable',
        'value': response
      };
    }else {
      var body = {
        'code': statusCode,
        'description': JSON.parse(response).message
      }
    }

    callback(body);
  });
};

Hyperty.prototype.advancedSearch = function(body, callback) {

  if(body.resource.startsWith('/')) {
    endpoint = '/hyperty/email/' + encodeURIComponent(body.resource.split('/')[3]) + '/hyperty';
  } else {
    endpoint = '/hyperty/user/' + encodeURIComponent(body.resource) + '/hyperty';
  }

  var resources = body.criteria.resources;
  var dataschemes = body.criteria.dataSchemes;

  var qsResources = '';
  var qsDataschemes = '';
  var querystring = '';

  if(typeof resources != "undefined" && resources != null && resources.length > 0) {
    var qsResources = 'resources=' + resources.join(',');
  }

  if(typeof dataschemes != "undefined" && dataschemes != null && dataschemes.length > 0) {
    var qsDataschemes = 'dataSchemes=' + dataschemes.join(',');
  }

  if(qsResources != "" && qsDataschemes != "") {
    var querystring = '?' + qsResources + '&' + qsDataschemes;
  }else if(qsResources != "") {
    var querystring = '?' + qsResources;
  }else if(qsDataschemes != "") {
    var querystring = '?' + qsDataschemes;
  }

  this._request.get(this._url + endpoint + querystring, function(err, response, statusCode) {
    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else if(statusCode == 200) {
      var body = {
        'code': statusCode,
        'value': response
      };
    }else if(statusCode == 408) {
      var body = {
        'code': statusCode,
        'description': 'Temporarily Unavailable',
        'value': response
      };
    }else {
      var body = {
        'code': statusCode,
        'description': response.message
      }
    }

    callback(body);
  });

};

Hyperty.prototype.read = function(body, isAdvanced, callback) {
  if(isAdvanced) {
    this.advancedSearch(body, callback);
  }else {
    this.search(body, callback);
  }
};

Hyperty.prototype.create = function(body, callback) {
  var endpoint = '/hyperty/user/' + encodeURIComponent(body.value.user) + '/' + encodeURIComponent(body.value.url);

  var data = {
    'descriptor': body.value.descriptor,
    'expires': body.value.expires,
    'resources': body.value.resources,
    'dataSchemes': body.value.dataSchemes,
    'status': body.value.status,
    'runtime': body.value.runtime,
    'p2pRequester': body.value.p2pRequester,
    'p2pHandler': body.value.p2pHandler,
    'guid': body.value.guid
  };

  this._request.put(this._url + endpoint, data, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else {
      var body = {
        'code': statusCode
      };
    }

    callback(body);
  });
};

Hyperty.prototype.update = function(body, callback) {

  var endpoint = '/hyperty/url/' + encodeURIComponent(body.resource);
  var data;

  if(typeof body.value != "undefined" && body.value != null) {
    data = {
      'descriptor': body.value.descriptor,
      'expires': body.value.expires,
      'resources': body.value.resources,
      'dataSchemes': body.value.dataSchemes,
      'status': body.value.status,
      'runtime': body.value.runtime,
      'p2pRequester': body.value.p2pRequester,
      'p2pHandler': body.value.p2pHandler
    };
  } else {
    data = {};
  }

  this._request.put(this._url + endpoint, data, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else {
      var body = {
        'code': statusCode
      };
    }

    //check if notify
    if(notification.checkNotification(statusCode, data, this._notificationsEnabled)) {
      const notificationBody = {
        'updated': {}
      };

      notificationBody.updated[body.resource] = {
        'status': data.status
      };

      this._notificationCallback(null, notificationBody);
    }

    callback(body);
  }.bind(this));
};

Hyperty.prototype.del = function(body, callback) {
  var endpoint = '/hyperty/user/' + encodeURIComponent(body.value.user) + '/' + encodeURIComponent(body.value.url);

  this._request.del(this._url + endpoint, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else {
      var body = {
        'code': statusCode
      };
    }

    callback(body);
  });

};

module.exports = Hyperty;
<commit_msg>connector: fix notification update callback<commit_after>var notification = require('./notification');

var Hyperty = function(request, url, notificationCallback) {
  this._request = request;
  this._url = url
  this._notificationsEnabled = typeof notificationCallback !== 'undefined';

  if(this._notificationsEnabled) {
    this._notificationCallback = notificationCallback;
  }
};

Hyperty.prototype.search = function(body, callback) {

  var endpoint;
  var prefix;
  var id;

  if(body.resource.startsWith('/')) {
    var urlComponents = body.resource.split('/');
    prefix = urlComponents[2];
    id = urlComponents[3];
  } else {
    prefix = body.resource.split('://')[0];
  }

  if(prefix === 'user') {
    endpoint = '/hyperty/user/' + encodeURIComponent(body.resource);
  } else if(prefix === 'user-guid') {
    endpoint = '/hyperty/guid/' + encodeURIComponent(body.resource);
  } else if(prefix === 'idp-identifier') {
    endpoint = '/hyperty/email/' + encodeURIComponent(id);
  } else {
    endpoint = '/hyperty/url/' + encodeURIComponent(body.resource);
  }

  this._request.get(this._url + endpoint, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else if(statusCode == 200) {
      var body = {
        'code': statusCode,
        'value': response
      };
    }else if(statusCode == 408) {
      var body = {
        'code': statusCode,
        'description': 'Temporarily Unavailable',
        'value': response
      };
    }else {
      var body = {
        'code': statusCode,
        'description': JSON.parse(response).message
      }
    }

    callback(body);
  });
};

Hyperty.prototype.advancedSearch = function(body, callback) {

  if(body.resource.startsWith('/')) {
    endpoint = '/hyperty/email/' + encodeURIComponent(body.resource.split('/')[3]) + '/hyperty';
  } else {
    endpoint = '/hyperty/user/' + encodeURIComponent(body.resource) + '/hyperty';
  }

  var resources = body.criteria.resources;
  var dataschemes = body.criteria.dataSchemes;

  var qsResources = '';
  var qsDataschemes = '';
  var querystring = '';

  if(typeof resources != "undefined" && resources != null && resources.length > 0) {
    var qsResources = 'resources=' + resources.join(',');
  }

  if(typeof dataschemes != "undefined" && dataschemes != null && dataschemes.length > 0) {
    var qsDataschemes = 'dataSchemes=' + dataschemes.join(',');
  }

  if(qsResources != "" && qsDataschemes != "") {
    var querystring = '?' + qsResources + '&' + qsDataschemes;
  }else if(qsResources != "") {
    var querystring = '?' + qsResources;
  }else if(qsDataschemes != "") {
    var querystring = '?' + qsDataschemes;
  }

  this._request.get(this._url + endpoint + querystring, function(err, response, statusCode) {
    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else if(statusCode == 200) {
      var body = {
        'code': statusCode,
        'value': response
      };
    }else if(statusCode == 408) {
      var body = {
        'code': statusCode,
        'description': 'Temporarily Unavailable',
        'value': response
      };
    }else {
      var body = {
        'code': statusCode,
        'description': response.message
      }
    }

    callback(body);
  });

};

Hyperty.prototype.read = function(body, isAdvanced, callback) {
  if(isAdvanced) {
    this.advancedSearch(body, callback);
  }else {
    this.search(body, callback);
  }
};

Hyperty.prototype.create = function(body, callback) {
  var endpoint = '/hyperty/user/' + encodeURIComponent(body.value.user) + '/' + encodeURIComponent(body.value.url);

  var data = {
    'descriptor': body.value.descriptor,
    'expires': body.value.expires,
    'resources': body.value.resources,
    'dataSchemes': body.value.dataSchemes,
    'status': body.value.status,
    'runtime': body.value.runtime,
    'p2pRequester': body.value.p2pRequester,
    'p2pHandler': body.value.p2pHandler,
    'guid': body.value.guid
  };

  this._request.put(this._url + endpoint, data, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else {
      var body = {
        'code': statusCode
      };
    }

    callback(body);
  });
};

Hyperty.prototype.update = function(body, callback) {

  var endpoint = '/hyperty/url/' + encodeURIComponent(body.resource);
  var data;

  if(typeof body.value != "undefined" && body.value != null) {
    data = {
      'descriptor': body.value.descriptor,
      'expires': body.value.expires,
      'resources': body.value.resources,
      'dataSchemes': body.value.dataSchemes,
      'status': body.value.status,
      'runtime': body.value.runtime,
      'p2pRequester': body.value.p2pRequester,
      'p2pHandler': body.value.p2pHandler
    };
  } else {
    data = {};
  }

  this._request.put(this._url + endpoint, data, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else {
      var body = {
        'code': statusCode
      };
    }

    //check if notify
    if(notification.checkNotification(statusCode, data, this._notificationsEnabled)) {
      var notificationBody = {
        'updated': {}
      };

      notificationBody.updated[body.resource] = {
        'status': data.status
      };

      this._notificationCallback(null, notificationBody);
    }

    callback(body);
  }.bind(this));
};

Hyperty.prototype.del = function(body, callback) {
  var endpoint = '/hyperty/user/' + encodeURIComponent(body.value.user) + '/' + encodeURIComponent(body.value.url);

  this._request.del(this._url + endpoint, function(err, response, statusCode) {

    if(err) {
      var body = {
        'code': 504,
        'description': 'Error contacting the domain registry.'
      };
    }else {
      var body = {
        'code': statusCode
      };
    }

    callback(body);
  });

};

module.exports = Hyperty;
<|endoftext|>


<commit_before>/* globals module */

/*  DISCLAIMER:

    There are two recommended methods of running a CryptPad instance:

    1. Using a standalone nodejs server without HTTPS (suitable for local development)
    2. Using NGINX to serve static assets and to handle HTTPS for API server's websocket traffic

    We do not officially recommend or support Apache, Docker, Kubernetes, Traefik, or any other configuration.
    Support requests for such setups should be directed to their authors.

    If you're having difficulty difficulty configuring your instance
    we suggest that you join the project's IRC/Matrix channel.

    If you don't have any difficulty configuring your instance and you'd like to
    support us for the work that went into making it pain-free we are quite happy
    to accept donations via our opencollective page: https://opencollective.com/cryptpad

*/
module.exports = {
/*  CryptPad is designed to serve its content over two domains.
 *  Account passwords and cryptographic content is handled on the 'main' domain,
 *  while the user interface is loaded on a 'sandbox' domain
 *  which can only access information which the main domain willingly shares.
 *
 *  In the event of an XSS vulnerability in the UI (that's bad)
 *  this system prevents attackers from gaining access to your account (that's good).
 *
 *  Most problems with new instances are related to this system blocking access
 *  because of incorrectly configured sandboxes. If you only see a white screen
 *  when you try to load CryptPad, this is probably the cause.
 *
 *  PLEASE READ THE FOLLOWING COMMENTS CAREFULLY.
 *
 */

/*  httpUnsafeOrigin is the URL that clients will enter to load your instance.
 *  Any other URL that somehow points to your instance is supposed to be blocked.
 *  The default provided below assumes you are loading CryptPad from a server
 *  which is running on the same machine, using port 3000.
 *
 *  In a production instance this should be available ONLY over HTTPS
 *  using the default port for HTTPS (443) ie. https://cryptpad.fr
 *  In such a case this should be handled by NGINX, as documented in
 *  cryptpad/docs/example.nginx.conf (see the $main_domain variable)
 *
 */
    httpUnsafeOrigin: 'http://localhost:3000/',

/*  httpSafeOrigin is the URL that is used for the 'sandbox' described above.
 *  If you're testing or developing with CryptPad on your local machine then
 *  it is appropriate to leave this blank. The default behaviour is to serve
 *  the main domain over port 3000 and to serve the content over port 3001.
 *
 *  This is not appropriate in a production environment where invasive networks
 *  may filter traffic going over abnormal ports.
 *  To correctly configure your production instance you must provide a URL
 *  with a different domain (a subdomain is sufficient).
 *  It will be used to load the UI in our 'sandbox' system.
 *
 *  This value corresponds to the $sandbox_domain variable
 *  in the example nginx file.
 *
 *  CUSTOMIZE AND UNCOMMENT THIS FOR PRODUCTION INSTALLATIONS.
 */
    // httpSafeOrigin: "https://some-other-domain.xyz",

/*  httpAddress specifies the address on which the nodejs server
 *  should be accessible. By default it will listen on 127.0.0.1
 *  (IPv4 localhost on most systems). If you want it to listen on
 *  all addresses, including IPv6, set this to '::'.
 *
 */
    //httpAddress: '::',

/*  httpPort specifies on which port the nodejs server should listen.
 *  By default it will serve content over port 3000, which is suitable
 *  for both local development and for use with the provided nginx example,
 *  which will proxy websocket traffic to your node server.
 *
 */
    //httpPort: 3000,

/*  httpSafePort allows you to specify an alternative port from which
 *  the node process should serve sandboxed assets. The default value is
 *  that of your httpPort + 1. You probably don't need to change this.
 *
 */
    //httpSafePort: 3001,

/*  CryptPad will launch a child process for every core available
 *  in order to perform CPU-intensive tasks in parallel.
 *  Some host environments may have a very large number of cores available
 *  or you may want to limit how much computing power CryptPad can take.
 *  If so, set 'maxWorkers' to a positive integer.
 */
    // maxWorkers: 4,

    /* =====================
     *         Admin
     * ===================== */

    /*
     *  CryptPad contains an administration panel. Its access is restricted to specific
     *  users using the following list.
     *  To give access to the admin panel to a user account, just add their user id,
     *  which can be found on the settings page for registered users.
     *  Entries should be strings separated by a comma.
     */
/*
    adminKeys: [
        //"https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=",
    ],
*/

    /*  CryptPad's administration panel includes a "support" tab
     *  wherein administrators with a secret key can view messages
     *  sent from users via the encrypted forms on the /support/ page
     *
     *  To enable this functionality:
     *    run `node ./scripts/generate-admin-keys.js`
     *    save the public key in your config in the value below
     *    add the private key via the admin panel
     *    and back it up in a secure manner
     *
     */
    // supportMailboxPublicKey: "",

    /*  We're very proud that CryptPad is available to the public as free software!
     *  We do, however, still need to pay our bills as we develop the platform.
     *
     *  By default CryptPad will prompt users to consider donating to
     *  our OpenCollective campaign. We publish the state of our finances periodically
     *  so you can decide for yourself whether our expenses are reasonable.
     *
     *  You can disable any solicitations for donations by setting 'removeDonateButton' to true,
     *  but we'd appreciate it if you didn't!
     */
    //removeDonateButton: false,

    /*  CryptPad will display a point of contact for your instance on its contact page
     *  (/contact.html) if you provide it below.
     */
    adminEmail: 'i.did.not.read.my.config@cryptpad.fr',

    /*
     *  By default, CryptPad contacts one of our servers once a day.
     *  This check-in will also send some very basic information about your instance including its
     *  version and the adminEmail so we can reach you if we are aware of a serious problem.
     *  We will never sell it or send you marketing mail.
     *
     *  If you want to block this check-in and remain set 'blockDailyCheck' to true.
     */
    //blockDailyCheck: false,

    /*
     *  By default users get 50MB of storage by registering on an instance.
     *  You can set this value to whatever you want.
     *
     *  hint: 50MB is 50 * 1024 * 1024
     */
    //defaultStorageLimit: 50 * 1024 * 1024,

    /* =====================
     *        STORAGE
     * ===================== */

    /*  Pads that are not 'pinned' by any registered user can be set to expire
     *  after a configurable number of days of inactivity (default 90 days).
     *  The value can be changed or set to false to remove expiration.
     *  Expired pads can then be removed using a cron job calling the
     *  `evict-inactive.js` script with node
     *
     *  defaults to 90 days if nothing is provided
     */
    //inactiveTime: 90, // days

    /*  CryptPad archives some data instead of deleting it outright.
     *  This archived data still takes up space and so you'll probably still want to
     *  remove these files after a brief period.
     *
     *  cryptpad/scripts/evict-inactive.js is intended to be run daily
     *  from a crontab or similar scheduling service.
     *
     *  The intent with this feature is to provide a safety net in case of accidental
     *  deletion. Set this value to the number of days you'd like to retain
     *  archived data before it's removed permanently.
     *
     *  defaults to 15 days if nothing is provided
     */
    //archiveRetentionTime: 15,

    /*  It's possible to configure your instance to remove data
     *  stored on behalf of inactive accounts. Set 'accountRetentionTime'
     *  to the number of days an account can remain idle before its
     *  documents and other account data is removed.
     *
     *  Leave this value commented out to preserve all data stored
     *  by user accounts regardless of inactivity.
     */
     //accountRetentionTime: 365,

    /*  Starting with CryptPad 3.23.0, the server automatically runs
     *  the script responsible for removing inactive data according to
     *  your configured definition of inactivity. Set this value to `true`
     *  if you prefer not to remove inactive data, or if you prefer to
     *  do so manually using `scripts/evict-inactive.js`.
     */
    //disableIntegratedEviction: true,

    /*  Max Upload Size (bytes)
     *  this sets the maximum size of any one file uploaded to the server.
     *  anything larger than this size will be rejected
     *  defaults to 20MB if no value is provided
     */
    //maxUploadSize: 20 * 1024 * 1024,

    /*
     *  CryptPad allows administrators to give custom limits to their friends.
     *  add an entry for each friend, identified by their user id,
     *  which can be found on the settings page. Include a 'limit' (number of bytes),
     *  a 'plan' (string), and a 'note' (string).
     *
     *  hint: 1GB is 1024 * 1024 * 1024 bytes
     */
/*
    customLimits: {
        "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
            limit: 20 * 1024 * 1024 * 1024,
            plan: 'insider',
            note: 'storage space donated by my.awesome.website'
        },
        "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": {
            limit: 10 * 1024 * 1024 * 1024,
            plan: 'insider',
            note: 'storage space donated by my.awesome.website'
        }
    },
*/

    /*  Users with premium accounts (those with a plan included in their customLimit)
     *  can benefit from an increased upload size limit. By default they are restricted to the same
     *  upload size as any other registered user.
     *
     */
    //premiumUploadSize: 100 * 1024 * 1024,

    /* =====================
     *   DATABASE VOLUMES
     * ===================== */

    /*
     *  CryptPad stores each document in an individual file on your hard drive.
     *  Specify a directory where files should be stored.
     *  It will be created automatically if it does not already exist.
     */
    filePath: './datastore/',

    /*  CryptPad offers the ability to archive data for a configurable period
     *  before deleting it, allowing a means of recovering data in the event
     *  that it was deleted accidentally.
     *
     *  To set the location of this archive directory to a custom value, change
     *  the path below:
     */
    archivePath: './data/archive',

    /*  CryptPad allows logged in users to request that particular documents be
     *  stored by the server indefinitely. This is called 'pinning'.
     *  Pin requests are stored in a pin-store. The location of this store is
     *  defined here.
     */
    pinPath: './data/pins',

    /*  if you would like the list of scheduled tasks to be stored in
        a custom location, change the path below:
    */
    taskPath: './data/tasks',

    /*  if you would like users' authenticated blocks to be stored in
        a custom location, change the path below:
    */
    blockPath: './block',

    /*  CryptPad allows logged in users to upload encrypted files. Files/blobs
     *  are stored in a 'blob-store'. Set its location here.
     */
    blobPath: './blob',

    /*  CryptPad stores incomplete blobs in a 'staging' area until they are
     *  fully uploaded. Set its location here.
     */
    blobStagingPath: './data/blobstage',

    /* CryptPad supports logging events directly to the disk in a 'logs' directory
     * Set its location here, or set it to false (or nothing) if you'd rather not log
     */
    logPath: './data/logs',

    /* =====================
     *       Debugging
     * ===================== */

    /*  CryptPad can log activity to stdout
     *  This may be useful for debugging
     */
    logToStdout: false,

    /* CryptPad can be configured to log more or less
     * the various settings are listed below by order of importance
     *
     * silly, verbose, debug, feedback, info, warn, error
     *
     * Choose the least important level of logging you wish to see.
     * For example, a 'silly' logLevel will display everything,
     * while 'info' will display 'info', 'warn', and 'error' logs
     *
     * This will affect both logging to the console and the disk.
     */
    logLevel: 'info',

    /*  clients can use the /settings/ app to opt out of usage feedback
     *  which informs the server of things like how much each app is being
     *  used, and whether certain clientside features are supported by
     *  the client's browser. The intent is to provide feedback to the admin
     *  such that the service can be improved. Enable this with `true`
     *  and ignore feedback with `false` or by commenting the attribute
     *
     *  You will need to set your logLevel to include 'feedback'. Set this
     *  to false if you'd like to exclude feedback from your logs.
     */
    logFeedback: false,

    /*  CryptPad supports verbose logging
     *  (false by default)
     */
    verbose: false,
};
<commit_msg>Use the new public key format in config.example.js #626<commit_after>/* globals module */

/*  DISCLAIMER:

    There are two recommended methods of running a CryptPad instance:

    1. Using a standalone nodejs server without HTTPS (suitable for local development)
    2. Using NGINX to serve static assets and to handle HTTPS for API server's websocket traffic

    We do not officially recommend or support Apache, Docker, Kubernetes, Traefik, or any other configuration.
    Support requests for such setups should be directed to their authors.

    If you're having difficulty difficulty configuring your instance
    we suggest that you join the project's IRC/Matrix channel.

    If you don't have any difficulty configuring your instance and you'd like to
    support us for the work that went into making it pain-free we are quite happy
    to accept donations via our opencollective page: https://opencollective.com/cryptpad

*/
module.exports = {
/*  CryptPad is designed to serve its content over two domains.
 *  Account passwords and cryptographic content is handled on the 'main' domain,
 *  while the user interface is loaded on a 'sandbox' domain
 *  which can only access information which the main domain willingly shares.
 *
 *  In the event of an XSS vulnerability in the UI (that's bad)
 *  this system prevents attackers from gaining access to your account (that's good).
 *
 *  Most problems with new instances are related to this system blocking access
 *  because of incorrectly configured sandboxes. If you only see a white screen
 *  when you try to load CryptPad, this is probably the cause.
 *
 *  PLEASE READ THE FOLLOWING COMMENTS CAREFULLY.
 *
 */

/*  httpUnsafeOrigin is the URL that clients will enter to load your instance.
 *  Any other URL that somehow points to your instance is supposed to be blocked.
 *  The default provided below assumes you are loading CryptPad from a server
 *  which is running on the same machine, using port 3000.
 *
 *  In a production instance this should be available ONLY over HTTPS
 *  using the default port for HTTPS (443) ie. https://cryptpad.fr
 *  In such a case this should be handled by NGINX, as documented in
 *  cryptpad/docs/example.nginx.conf (see the $main_domain variable)
 *
 */
    httpUnsafeOrigin: 'http://localhost:3000/',

/*  httpSafeOrigin is the URL that is used for the 'sandbox' described above.
 *  If you're testing or developing with CryptPad on your local machine then
 *  it is appropriate to leave this blank. The default behaviour is to serve
 *  the main domain over port 3000 and to serve the content over port 3001.
 *
 *  This is not appropriate in a production environment where invasive networks
 *  may filter traffic going over abnormal ports.
 *  To correctly configure your production instance you must provide a URL
 *  with a different domain (a subdomain is sufficient).
 *  It will be used to load the UI in our 'sandbox' system.
 *
 *  This value corresponds to the $sandbox_domain variable
 *  in the example nginx file.
 *
 *  CUSTOMIZE AND UNCOMMENT THIS FOR PRODUCTION INSTALLATIONS.
 */
    // httpSafeOrigin: "https://some-other-domain.xyz",

/*  httpAddress specifies the address on which the nodejs server
 *  should be accessible. By default it will listen on 127.0.0.1
 *  (IPv4 localhost on most systems). If you want it to listen on
 *  all addresses, including IPv6, set this to '::'.
 *
 */
    //httpAddress: '::',

/*  httpPort specifies on which port the nodejs server should listen.
 *  By default it will serve content over port 3000, which is suitable
 *  for both local development and for use with the provided nginx example,
 *  which will proxy websocket traffic to your node server.
 *
 */
    //httpPort: 3000,

/*  httpSafePort allows you to specify an alternative port from which
 *  the node process should serve sandboxed assets. The default value is
 *  that of your httpPort + 1. You probably don't need to change this.
 *
 */
    //httpSafePort: 3001,

/*  CryptPad will launch a child process for every core available
 *  in order to perform CPU-intensive tasks in parallel.
 *  Some host environments may have a very large number of cores available
 *  or you may want to limit how much computing power CryptPad can take.
 *  If so, set 'maxWorkers' to a positive integer.
 */
    // maxWorkers: 4,

    /* =====================
     *         Admin
     * ===================== */

    /*
     *  CryptPad contains an administration panel. Its access is restricted to specific
     *  users using the following list.
     *  To give access to the admin panel to a user account, just add their public signing
     *  key, which can be found on the settings page for registered users.
     *  Entries should be strings separated by a comma.
     */
/*
    adminKeys: [
        //"[cryptpad-user1@my.awesome.website/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=]",
    ],
*/

    /*  CryptPad's administration panel includes a "support" tab
     *  wherein administrators with a secret key can view messages
     *  sent from users via the encrypted forms on the /support/ page
     *
     *  To enable this functionality:
     *    run `node ./scripts/generate-admin-keys.js`
     *    save the public key in your config in the value below
     *    add the private key via the admin panel
     *    and back it up in a secure manner
     *
     */
    // supportMailboxPublicKey: "",

    /*  We're very proud that CryptPad is available to the public as free software!
     *  We do, however, still need to pay our bills as we develop the platform.
     *
     *  By default CryptPad will prompt users to consider donating to
     *  our OpenCollective campaign. We publish the state of our finances periodically
     *  so you can decide for yourself whether our expenses are reasonable.
     *
     *  You can disable any solicitations for donations by setting 'removeDonateButton' to true,
     *  but we'd appreciate it if you didn't!
     */
    //removeDonateButton: false,

    /*  CryptPad will display a point of contact for your instance on its contact page
     *  (/contact.html) if you provide it below.
     */
    adminEmail: 'i.did.not.read.my.config@cryptpad.fr',

    /*
     *  By default, CryptPad contacts one of our servers once a day.
     *  This check-in will also send some very basic information about your instance including its
     *  version and the adminEmail so we can reach you if we are aware of a serious problem.
     *  We will never sell it or send you marketing mail.
     *
     *  If you want to block this check-in and remain set 'blockDailyCheck' to true.
     */
    //blockDailyCheck: false,

    /*
     *  By default users get 50MB of storage by registering on an instance.
     *  You can set this value to whatever you want.
     *
     *  hint: 50MB is 50 * 1024 * 1024
     */
    //defaultStorageLimit: 50 * 1024 * 1024,

    /* =====================
     *        STORAGE
     * ===================== */

    /*  Pads that are not 'pinned' by any registered user can be set to expire
     *  after a configurable number of days of inactivity (default 90 days).
     *  The value can be changed or set to false to remove expiration.
     *  Expired pads can then be removed using a cron job calling the
     *  `evict-inactive.js` script with node
     *
     *  defaults to 90 days if nothing is provided
     */
    //inactiveTime: 90, // days

    /*  CryptPad archives some data instead of deleting it outright.
     *  This archived data still takes up space and so you'll probably still want to
     *  remove these files after a brief period.
     *
     *  cryptpad/scripts/evict-inactive.js is intended to be run daily
     *  from a crontab or similar scheduling service.
     *
     *  The intent with this feature is to provide a safety net in case of accidental
     *  deletion. Set this value to the number of days you'd like to retain
     *  archived data before it's removed permanently.
     *
     *  defaults to 15 days if nothing is provided
     */
    //archiveRetentionTime: 15,

    /*  It's possible to configure your instance to remove data
     *  stored on behalf of inactive accounts. Set 'accountRetentionTime'
     *  to the number of days an account can remain idle before its
     *  documents and other account data is removed.
     *
     *  Leave this value commented out to preserve all data stored
     *  by user accounts regardless of inactivity.
     */
     //accountRetentionTime: 365,

    /*  Starting with CryptPad 3.23.0, the server automatically runs
     *  the script responsible for removing inactive data according to
     *  your configured definition of inactivity. Set this value to `true`
     *  if you prefer not to remove inactive data, or if you prefer to
     *  do so manually using `scripts/evict-inactive.js`.
     */
    //disableIntegratedEviction: true,

    /*  Max Upload Size (bytes)
     *  this sets the maximum size of any one file uploaded to the server.
     *  anything larger than this size will be rejected
     *  defaults to 20MB if no value is provided
     */
    //maxUploadSize: 20 * 1024 * 1024,

    /*
     *  CryptPad allows administrators to give custom limits to their friends.
     *  add an entry for each friend, identified by their user id,
     *  which can be found on the settings page. Include a 'limit' (number of bytes),
     *  a 'plan' (string), and a 'note' (string).
     *
     *  hint: 1GB is 1024 * 1024 * 1024 bytes
     */
/*
    customLimits: {
        "https://my.awesome.website/user/#/1/cryptpad-user1/YZgXQxKR0Rcb6r6CmxHPdAGLVludrAF2lEnkbx1vVOo=": {
            limit: 20 * 1024 * 1024 * 1024,
            plan: 'insider',
            note: 'storage space donated by my.awesome.website'
        },
        "https://my.awesome.website/user/#/1/cryptpad-user2/GdflkgdlkjeworijfkldfsdflkjeEAsdlEnkbx1vVOo=": {
            limit: 10 * 1024 * 1024 * 1024,
            plan: 'insider',
            note: 'storage space donated by my.awesome.website'
        }
    },
*/

    /*  Users with premium accounts (those with a plan included in their customLimit)
     *  can benefit from an increased upload size limit. By default they are restricted to the same
     *  upload size as any other registered user.
     *
     */
    //premiumUploadSize: 100 * 1024 * 1024,

    /* =====================
     *   DATABASE VOLUMES
     * ===================== */

    /*
     *  CryptPad stores each document in an individual file on your hard drive.
     *  Specify a directory where files should be stored.
     *  It will be created automatically if it does not already exist.
     */
    filePath: './datastore/',

    /*  CryptPad offers the ability to archive data for a configurable period
     *  before deleting it, allowing a means of recovering data in the event
     *  that it was deleted accidentally.
     *
     *  To set the location of this archive directory to a custom value, change
     *  the path below:
     */
    archivePath: './data/archive',

    /*  CryptPad allows logged in users to request that particular documents be
     *  stored by the server indefinitely. This is called 'pinning'.
     *  Pin requests are stored in a pin-store. The location of this store is
     *  defined here.
     */
    pinPath: './data/pins',

    /*  if you would like the list of scheduled tasks to be stored in
        a custom location, change the path below:
    */
    taskPath: './data/tasks',

    /*  if you would like users' authenticated blocks to be stored in
        a custom location, change the path below:
    */
    blockPath: './block',

    /*  CryptPad allows logged in users to upload encrypted files. Files/blobs
     *  are stored in a 'blob-store'. Set its location here.
     */
    blobPath: './blob',

    /*  CryptPad stores incomplete blobs in a 'staging' area until they are
     *  fully uploaded. Set its location here.
     */
    blobStagingPath: './data/blobstage',

    /* CryptPad supports logging events directly to the disk in a 'logs' directory
     * Set its location here, or set it to false (or nothing) if you'd rather not log
     */
    logPath: './data/logs',

    /* =====================
     *       Debugging
     * ===================== */

    /*  CryptPad can log activity to stdout
     *  This may be useful for debugging
     */
    logToStdout: false,

    /* CryptPad can be configured to log more or less
     * the various settings are listed below by order of importance
     *
     * silly, verbose, debug, feedback, info, warn, error
     *
     * Choose the least important level of logging you wish to see.
     * For example, a 'silly' logLevel will display everything,
     * while 'info' will display 'info', 'warn', and 'error' logs
     *
     * This will affect both logging to the console and the disk.
     */
    logLevel: 'info',

    /*  clients can use the /settings/ app to opt out of usage feedback
     *  which informs the server of things like how much each app is being
     *  used, and whether certain clientside features are supported by
     *  the client's browser. The intent is to provide feedback to the admin
     *  such that the service can be improved. Enable this with `true`
     *  and ignore feedback with `false` or by commenting the attribute
     *
     *  You will need to set your logLevel to include 'feedback'. Set this
     *  to false if you'd like to exclude feedback from your logs.
     */
    logFeedback: false,

    /*  CryptPad supports verbose logging
     *  (false by default)
     */
    verbose: false,
};
<|endoftext|>


<commit_before>// ==UserScript==
// @name          Get DLC Info from SteamDB
// @namespace     sak32009-get-dlc-info-from-steamdb
// @description   Get DLC Info from SteamDB
// @author        Sak32009
// @year          2016 - 2020
// @version       4.1.2
// @license       MIT
// @homepageURL   https://github.com/Sak32009/GetDLCInfoFromSteamDB/
// @supportURL    https://cs.rin.ru/forum/viewtopic.php?f=10&t=71837
// @updateURL     https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.user.js
// @downloadURL   https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.user.js
// @icon          https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb-icon.png
// @match         *://steamdb.info/app/*
// @match         *://steamdb.info/depot/*
// @match         *://cs.rin.ru/forum/viewtopic.php?*
// @match         *://sak32009.github.io/getdlcinfofromsteamdb/*
// @run-at        document-end
// @require       https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.slim.min.js
// @resource      localIMG https://sak32009.github.io/sak32009.svg
// @resource      f1 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/CreamAPI/V3.4.1.0.txt
// @resource      f2 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/CreamAPI/V4.5.0.0.txt
// @resource      f3 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/3DMGAME.txt
// @resource      f4 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/CODEX_(DLC00000_=_DLCName).txt
// @resource      f5 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/LUMAEMU.txt
// @resource      f6 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/SKIDROW.txt
// @resource      f7 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/APPID_APPIDNAME.txt
// @resource      f8 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/APPIDNAME.txt
// @resource      f9 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/GreenLuma_2020_BATCH_MODE.txt
// @grant         GM_xmlhttpRequest
// @grant         GM_getResourceText
// @grant         GM_addStyle
// @grant         unsafeWindow
// ==/UserScript==

GM_info.script.author = "Sak32009";
GM_info.script.year = "2016 - 2020";
GM_info.script.homepage = "https://github.com/Sak32009/GetDLCInfoFromSteamDB/";
GM_info.script.supportURL = "https://cs.rin.ru/forum/viewtopic.php?f=10&t=71837";

class m {
    constructor() {
        this.data = [{
                name: "CreamAPI v4.5.0.0",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "cream_api",
                    ext: "ini",
                    text: GM_getResourceText("f1")
                }
            },
            {
                name: "CreamAPI v3.4.1.0",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "cream_api",
                    ext: "ini",
                    text: GM_getResourceText("f2")
                }
            },
            {
                name: "GreenLuma 2020 [BATCH MODE]",
                header: {
                    show: true,
                    comment: ":: "
                },
                file: {
                    name: "[steamdb]appID[/steamdb]_GreenLuma",
                    ext: "bat",
                    text: GM_getResourceText("f9")
                }
            },
            {
                name: "LUMAEMU (ONLY DLCS LIST)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f5")
                }
            },
            {
                name: "CODEX (DLC00000 = DLCName)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f4")
                }
            },
            {
                name: "3DMGAME (ONLY DLCS LIST)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f3")
                }
            },
            {
                name: "SKIDROW (ONLY DLCS LIST)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f6")
                }
            },
            {
                name: "APPID = APPIDNAME",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f7")
                }
            },
            {
                name: "APPIDNAME",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f8")
                }
            }
        ];
        this.steamDB = {
            appID: "",
            name: "",
            count: 0,
            dlcs: {},
            countUnknowns: 0,
            dlcsUnknowns: {},
            appURL: "https://steamdb.info/app/",
            depotURL: "https://steamdb.info/depot/",
            linkedURL: "https://steamdb.info/search/?a=linked&q="
        };
        this.localURL = "https://sak32009.github.io/getdlcinfofromsteamdb/";
        this.localIMG = "https://sak32009.github.io/sak32009.svg";
        const url = new URL(window.location.href);
        this.$_GET = new URLSearchParams(url.search);
        this.isCSRINRU = url.hostname == "cs.rin.ru";
        this.isSTEAMDBApp = url.pathname.startsWith("/app/");
        this.isSTEAMDBDepot = url.pathname.startsWith("/depot/") && this.$_GET.has("show_hashes");
        this.isLocal = url.hostname == "sak32009.github.io" && url.pathname = "/getdlcinfofromsteamdb/" && this.$_GET.has("appid");
    }
    getData() {
        const self = this;
        if (self.isLocal) {
            const appID = self.$_GET.get("appid");
            if (appID.length) {
                self.steamDB.appID = appID;
                self.setDLCSRequests();
            }
        } else if (self.isSTEAMDBApp || self.isCSRINRU) {
            if (self.isCSRINRU) {
                // TODO: APPID ISN'T ACCURATE
                const $findAppID = $("#pagecontent > .tablebg:nth-of-type(3) .postbody:first-child a.postlink[href^='http://store.steampowered.com/app/']");
                if ($findAppID.length > 0) {
                    self.steamDB.appID = new URL($findAppID.attr("href")).pathname.split("/")[2];
                }
            } else {
                self.steamDB.appID = $(".scope-app[data-appid]").data("appid");
            }
            self.createInterfaceButtons();
        } else if (self.isSTEAMDBDepot) {
            self.createInterfaceDepots();
        }
    }
    setDLCSRequests() {
        const self = this;
        GM_xmlhttpRequest({
            url: `${self.steamDB.appURL + self.steamDB.appID}`,
            method: "GET",
            onload({responseText}) {
                const $dom = $($.parseHTML(responseText));
                self.steamDB.name = $dom.find(".pagehead > h1").text().trim();
                $dom.find("tr.app[data-appid] td.muted:nth-of-type(2)").each((_index, _dom) => {
                    const $dom = $(_dom).closest("tr");
                    const appID = $dom.attr("data-appid");
                    const appName = $dom.find("td:nth-of-type(2)").text().trim();
                    self.steamDB.dlcsUnknowns[appID] = appName;
                    self.steamDB.countUnknowns += 1;
                });
                self.setLinkedDLCSRequest();
            }
        });
    }
    setLinkedDLCSRequest() {
        const self = this;
        GM_xmlhttpRequest({
            url: `${self.steamDB.linkedURL + self.steamDB.appID}`,
            method: "GET",
            onload({responseText}) {
                const $dom = $($.parseHTML(responseText));
                $dom.find("tr.app[data-appid] td:nth-of-type(2):contains('DLC')").each((_index, _dom) => {
                    const $dom = $(_dom).closest("tr");
                    const appID = $dom.attr("data-appid");
                    const appName = $dom.find("td:nth-of-type(3)").text().trim();
                    self.steamDB.dlcs[appID] = appName;
                    self.steamDB.count += 1;
                });
                self.afterDLCSRequests();
            }
        });
    }
    afterDLCSRequests() {
        const self = this;
        unsafeWindow.getdlcinfofromsteamdb = {
            data: self.data,
            steamDB: self.steamDB,
            userscript: GM_info
        };
    }
    createInterfaceButtons() {
        const self = this;
        GM_addStyle(`#WIZZpeAmov{display:block;margin:15px auto;background-color:#4b2e52;padding:12px;border:1px solid #000;border-radius:5px;text-decoration:none;color:#fff;text-align:center;font-weight:700}#WIZZpeAmov:hover{color:#fff;background-color:#522e47}#WIZZpeAmov.fixed{position:fixed;bottom:0;right:0;margin:0;margin-right:10px;z-index:999;border-bottom-left-radius:0;border-bottom-right-radius:0}#WIZZpeAmov img{max-width:70px;margin:auto;display:block;height:auto}`);
        const $a = $(`${GM_info.script.name} v${GM_info.script.version} by ${GM_info.script.author} | ${GM_info.script.year}`);
        if (self.isCSRINRU) {
            $a.appendTo("#pagecontent > .tablebg:nth-of-type(3) .postbody:first-child").find("img").attr("src", self.localIMG);
        } else {
            $a.addClass("fixed").appendTo("body").find("img").attr("src", `${self.convertSVGToBase64(GM_getResourceText("localIMG"))}`);
        }
    }
    createInterfaceDepots() {
        const self = this;
        $(document).on("change", `div#files select[name="DataTables_Table_0_length"]`, (e) => {
            const depotID = $(`div[data-depotid]`).data("depotid");
            const entries = $(`div#files select[name="DataTables_Table_0_length"] option:selected`).val();
            const check = $("div#files > h2:first-child a").length;
            let output = `; ${GM_info.script.name} v${GM_info.script.version} by ${GM_info.script.author} | ${GM_info.script.year} | DEPOT URL: ${self.steamDB.depotURL + depotID}\n`;
            if (entries == "-1" && !check.length) {
                $(`div#files #DataTables_Table_0 tbody tr`).each((_index, _value) => {
                    const $dom = $(_value);
                    const filename = $dom.find("td:nth-of-type(1)").text().trim();
                    const filechecksum = $dom.find("td.code").text().trim();
                    if (filechecksum != "NULL") {
                        output += `${filechecksum} *${filename}\n`;
                    }
                });
                const toBlob = self.toBlob(depotID, output, "sha1");
                $(`<a href="${toBlob.blob}" download="${toBlob.name}" style="float:right">Download .sha1</a>`).appendTo("div#files > h2:first-child");
                $(`<textarea rows="20" style="width:100%;resize:none"></textarea>`).text(output).insertAfter("div#files > h2:first-child");
            }
        });
    }
    convertSVGToBase64(str) {
        return `data:image/svg+xml;base64,${window.btoa(str)}`;
    }
    toBlob(name, content, extension) {
        return {
            name: `${name.toString().length > 0 ? name : Math.random().toString(36).substring(2)}.${extension}`,
            blob: window.URL.createObjectURL(new Blob([content.replace(/\n/g, "\r\n")], {
                type: "application/octet-stream;charset=utf-8"
            }))
        };
    }
}

new m().getData();
<commit_msg>Final 4.1.3<commit_after>// ==UserScript==
// @name          Get DLC Info from SteamDB
// @namespace     sak32009-get-dlc-info-from-steamdb
// @description   Get DLC Info from SteamDB
// @author        Sak32009
// @year          2016 - 2020
// @version       4.1.3
// @license       MIT
// @homepageURL   https://github.com/Sak32009/GetDLCInfoFromSteamDB/
// @supportURL    https://cs.rin.ru/forum/viewtopic.php?f=10&t=71837
// @updateURL     https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.user.js
// @downloadURL   https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb.user.js
// @icon          https://github.com/Sak32009/GetDLCInfoFromSteamDB/raw/master/sak32009-get-dlc-info-from-steamdb-icon.png
// @match         *://steamdb.info/app/*
// @match         *://steamdb.info/depot/*
// @match         *://cs.rin.ru/forum/viewtopic.php?*
// @match         *://sak32009.github.io/getdlcinfofromsteamdb/*
// @run-at        document-end
// @require       https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.slim.min.js
// @resource      localIMG https://sak32009.github.io/sak32009.svg
// @resource      f1 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/CreamAPI/V3.4.1.0.txt
// @resource      f2 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/CreamAPI/V4.5.0.0.txt
// @resource      f3 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/3DMGAME.txt
// @resource      f4 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/CODEX_(DLC00000_=_DLCName).txt
// @resource      f5 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/LUMAEMU.txt
// @resource      f6 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/Only_DLCS_List/SKIDROW.txt
// @resource      f7 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/APPID_APPIDNAME.txt
// @resource      f8 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/APPIDNAME.txt
// @resource      f9 https://rawcdn.githack.com/Sak32009/GetDLCInfoFromSteamDB/767f614f2b834aa497eb39d35e2359ab3e72f7fb/data/GreenLuma_2020_BATCH_MODE.txt
// @grant         GM_xmlhttpRequest
// @grant         GM_getResourceText
// @grant         GM_addStyle
// @grant         unsafeWindow
// ==/UserScript==

GM_info.script.author = "Sak32009";
GM_info.script.year = "2016 - 2020";
GM_info.script.homepage = "https://github.com/Sak32009/GetDLCInfoFromSteamDB/";
GM_info.script.supportURL = "https://cs.rin.ru/forum/viewtopic.php?f=10&t=71837";

class m {
    constructor() {
        this.data = [{
                name: "CreamAPI v4.5.0.0",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "cream_api",
                    ext: "ini",
                    text: GM_getResourceText("f1")
                }
            },
            {
                name: "CreamAPI v3.4.1.0",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "cream_api",
                    ext: "ini",
                    text: GM_getResourceText("f2")
                }
            },
            {
                name: "GreenLuma 2020 [BATCH MODE]",
                header: {
                    show: true,
                    comment: ":: "
                },
                file: {
                    name: "[steamdb]appID[/steamdb]_GreenLuma",
                    ext: "bat",
                    text: GM_getResourceText("f9")
                }
            },
            {
                name: "LUMAEMU (ONLY DLCS LIST)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f5")
                }
            },
            {
                name: "CODEX (DLC00000 = DLCName)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f4")
                }
            },
            {
                name: "3DMGAME (ONLY DLCS LIST)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f3")
                }
            },
            {
                name: "SKIDROW (ONLY DLCS LIST)",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f6")
                }
            },
            {
                name: "APPID = APPIDNAME",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f7")
                }
            },
            {
                name: "APPIDNAME",
                header: {
                    show: true,
                    comment: "; "
                },
                file: {
                    name: "",
                    ext: "ini",
                    text: GM_getResourceText("f8")
                }
            }
        ];
        this.steamDB = {
            appID: "",
            name: "",
            count: 0,
            dlcs: {},
            countUnknowns: 0,
            dlcsUnknowns: {},
            appURL: "https://steamdb.info/app/",
            depotURL: "https://steamdb.info/depot/",
            linkedURL: "https://steamdb.info/search/?a=linked&q="
        };
        this.localURL = "https://sak32009.github.io/getdlcinfofromsteamdb/";
        this.localIMG = "https://sak32009.github.io/sak32009.svg";
        const url = new URL(window.location.href);
        this.$_GET = new URLSearchParams(url.search);
        this.isCSRINRU = url.hostname == "cs.rin.ru";
        this.isSTEAMDBApp = url.pathname.startsWith("/app/");
        this.isSTEAMDBDepot = url.pathname.startsWith("/depot/") && this.$_GET.has("show_hashes");
        this.isLocal = url.hostname == "sak32009.github.io" && url.pathname == "/getdlcinfofromsteamdb/" && this.$_GET.has("appid");
    }
    getData() {
        const self = this;
        if (self.isLocal) {
            const appID = self.$_GET.get("appid");
            if (appID.length) {
                self.steamDB.appID = appID;
                self.setDLCSRequests();
            }
        } else if (self.isSTEAMDBApp || self.isCSRINRU) {
            if (self.isCSRINRU) {
                // TODO: APPID ISN'T ACCURATE
                const $findAppID = $("#pagecontent > .tablebg:nth-of-type(3) .postbody:first-child a.postlink[href^='http://store.steampowered.com/app/']");
                if ($findAppID.length > 0) {
                    self.steamDB.appID = new URL($findAppID.attr("href")).pathname.split("/")[2];
                }
            } else {
                self.steamDB.appID = $(".scope-app[data-appid]").data("appid");
            }
            self.createInterfaceButtons();
        } else if (self.isSTEAMDBDepot) {
            self.createInterfaceDepots();
        }
    }
    setDLCSRequests() {
        const self = this;
        GM_xmlhttpRequest({
            url: `${self.steamDB.appURL + self.steamDB.appID}`,
            method: "GET",
            onload({responseText}) {
                const $dom = $($.parseHTML(responseText));
                self.steamDB.name = $dom.find(".pagehead > h1").text().trim();
                $dom.find("tr.app[data-appid] td.muted:nth-of-type(2)").each((_index, _dom) => {
                    const $dom = $(_dom).closest("tr");
                    const appID = $dom.attr("data-appid");
                    const appName = $dom.find("td:nth-of-type(2)").text().trim();
                    self.steamDB.dlcsUnknowns[appID] = appName;
                    self.steamDB.countUnknowns += 1;
                });
                self.setLinkedDLCSRequest();
            }
        });
    }
    setLinkedDLCSRequest() {
        const self = this;
        GM_xmlhttpRequest({
            url: `${self.steamDB.linkedURL + self.steamDB.appID}`,
            method: "GET",
            onload({responseText}) {
                const $dom = $($.parseHTML(responseText));
                $dom.find("tr.app[data-appid] td:nth-of-type(2):contains('DLC')").each((_index, _dom) => {
                    const $dom = $(_dom).closest("tr");
                    const appID = $dom.attr("data-appid");
                    const appName = $dom.find("td:nth-of-type(3)").text().trim();
                    self.steamDB.dlcs[appID] = appName;
                    self.steamDB.count += 1;
                });
                self.afterDLCSRequests();
            }
        });
    }
    afterDLCSRequests() {
        const self = this;
        unsafeWindow.getdlcinfofromsteamdb = {
            data: self.data,
            steamDB: self.steamDB,
            userscript: GM_info
        };
    }
    createInterfaceButtons() {
        const self = this;
        GM_addStyle(`#WIZZpeAmov{display:block;margin:15px auto;background-color:#4b2e52;padding:12px;border:1px solid #000;border-radius:5px;text-decoration:none;color:#fff;text-align:center;font-weight:700}#WIZZpeAmov:hover{color:#fff;background-color:#522e47}#WIZZpeAmov.fixed{position:fixed;bottom:0;right:0;margin:0;margin-right:10px;z-index:999;border-bottom-left-radius:0;border-bottom-right-radius:0}#WIZZpeAmov img{max-width:70px;margin:auto;display:block;height:auto}`);
        const $a = $(`${GM_info.script.name} v${GM_info.script.version} by ${GM_info.script.author} | ${GM_info.script.year}`);
        if (self.isCSRINRU) {
            $a.appendTo("#pagecontent > .tablebg:nth-of-type(3) .postbody:first-child").find("img").attr("src", self.localIMG);
        } else {
            $a.addClass("fixed").appendTo("body").find("img").attr("src", `${self.convertSVGToBase64(GM_getResourceText("localIMG"))}`);
        }
    }
    createInterfaceDepots() {
        const self = this;
        $(document).on("change", `div#files select[name="DataTables_Table_0_length"]`, (e) => {
            const depotID = $(`div[data-depotid]`).data("depotid");
            const entries = $(`div#files select[name="DataTables_Table_0_length"] option:selected`).val();
            const check = $("div#files > h2:first-child a").length;
            let output = `; ${GM_info.script.name} v${GM_info.script.version} by ${GM_info.script.author} | ${GM_info.script.year} | DEPOT URL: ${self.steamDB.depotURL + depotID}\n`;
            if (entries == "-1" && !check.length) {
                $(`div#files #DataTables_Table_0 tbody tr`).each((_index, _value) => {
                    const $dom = $(_value);
                    const filename = $dom.find("td:nth-of-type(1)").text().trim();
                    const filechecksum = $dom.find("td.code").text().trim();
                    if (filechecksum != "NULL") {
                        output += `${filechecksum} *${filename}\n`;
                    }
                });
                const toBlob = self.toBlob(depotID, output, "sha1");
                $(`<a href="${toBlob.blob}" download="${toBlob.name}" style="float:right">Download .sha1</a>`).appendTo("div#files > h2:first-child");
                $(`<textarea rows="20" style="width:100%;resize:none"></textarea>`).text(output).insertAfter("div#files > h2:first-child");
            }
        });
    }
    convertSVGToBase64(str) {
        return `data:image/svg+xml;base64,${window.btoa(str)}`;
    }
    toBlob(name, content, extension) {
        return {
            name: `${name.toString().length > 0 ? name : Math.random().toString(36).substring(2)}.${extension}`,
            blob: window.URL.createObjectURL(new Blob([content.replace(/\n/g, "\r\n")], {
                type: "application/octet-stream;charset=utf-8"
            }))
        };
    }
}

new m().getData();
<|endoftext|>


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

    angular.module('Chat', [
        'Chat.controllers',
        'Chat.services',
        'ngRoute',
        'ngSanitize'
    ])

    .config(['$routeProvider', function ($routeProvider) {
        $routeProvider

            .when('/', {
                templateUrl: '/templates/index.html',
                controller: 'IndexCtrl'
            })

            .when('/profile', {
                templateUrl: '/templates/profile.html',
                controller: 'ProfileCtrl'
            })

            .when('/channel', {
                templateUrl: '/templates/channel.html',
                controller: 'ChannelCtrl'
            })

            .when('/chat', {
                templateUrl: '/templates/chat.html',
                controller: 'ChatCtrl'
            })

            .otherwise({redirectTo: '/'});
    }])

    .run([
        'Channel', '$location', '$rootScope', 'Storage', 'Chat', '$log',
        function (Channel, $location, $rootScope, Storage, Chat, $log) {

            var user = Storage.user.get();
            var channel = Storage.channel.get();

            //TODO: use MenuCtrl surrounding the top menu only, no rootScope
            $rootScope.Channel = Channel;
            $rootScope.user = user.username;

            if (!user || !user.uuid) {
                Storage.user.set({});
                Storage.channel.set({});
                socket.emit('create user');
            } else {
                socket.emit('known user', user);
                if (channel && channel.name) {
                    $location.path('/chat');
                    socket.emit('join channel', user, channel);
                }
            }

            ///socket messages

            //TODO: socket error messages to handle

            socket.on('joined channel', function (channel) {
                $log.info('socket:joined channel', channel);
                $rootScope.$apply(function () {
                    $rootScope.$broadcast('joined channel', channel);
                });
            });

            socket.on('user created', function (newUser) {
                $log.info('socket:user created', newUser);
                user = newUser;
                Storage.user.set(user);
                Storage.channel.set({});
            });

            socket.on('channel users list', function (users) {
                $log.info('socket:channel users list', users);
                $rootScope.$broadcast('channel users list', users);
            });

            //TODO: combine channel and user messages with a flag: message.channel = true/false
            socket.on('new channel message', function (message) {
                $log.info('socket:new channel message'/*, message*/);
                Chat.addText(message.text);
                $rootScope.$broadcast('new channel message', message);
            });

            socket.on('new message', function (message) {
                $log.info('socket:new message'/*, message*/);
                Chat.addText(message.text);
                $rootScope.$broadcast('new message', message);
            });

            //TODO: rename it to left channel
            socket.on('channel left', function (channel) {
                $log.info('socket:channel left', channel);
                $rootScope.$apply(function () {
                    $rootScope.$broadcast('channel left', channel);
                });
            });

            socket.on('user updated', function (uuid, oldUsername, newUsername) {
                $log.info('socket:user updated', uuid, oldUsername, newUsername);
                $rootScope.$apply(function () {
                    $rootScope.$broadcast('user updated', uuid, oldUsername, newUsername);
                });
            });

            socket.on('channel messages', function (messages) {
                $log.info('socket:channel messages', 'len = ' + messages.length);
                $rootScope.$broadcast('channel messages', messages);
            });

        }
    ])

    ;

})(angular);
<commit_msg>added error receiving, for now in the console<commit_after>(function (angular) {
    'use strict';

    angular.module('Chat', [
        'Chat.controllers',
        'Chat.services',
        'ngRoute',
        'ngSanitize'
    ])

    .config(['$routeProvider', function ($routeProvider) {
        $routeProvider

            .when('/', {
                templateUrl: '/templates/index.html',
                controller: 'IndexCtrl'
            })

            .when('/profile', {
                templateUrl: '/templates/profile.html',
                controller: 'ProfileCtrl'
            })

            .when('/channel', {
                templateUrl: '/templates/channel.html',
                controller: 'ChannelCtrl'
            })

            .when('/chat', {
                templateUrl: '/templates/chat.html',
                controller: 'ChatCtrl'
            })

            .otherwise({redirectTo: '/'});
    }])

    .run([
        'Channel', '$location', '$rootScope', 'Storage', 'Chat', '$log',
        function (Channel, $location, $rootScope, Storage, Chat, $log) {

            var user = Storage.user.get();
            var channel = Storage.channel.get();

            //TODO: use MenuCtrl surrounding the top menu only, no rootScope
            $rootScope.Channel = Channel;
            $rootScope.user = user.username;

            if (!user || !user.uuid) {
                Storage.user.set({});
                Storage.channel.set({});
                socket.emit('create user');
            } else {
                socket.emit('known user', user);
                if (channel && channel.name) {
                    $location.path('/chat');
                    socket.emit('join channel', user, channel);
                }
            }

            ///socket messages

            //TODO: socket error messages to handle

            socket.on('joined channel', function (channel) {
                $log.info('socket:joined channel', channel);
                $rootScope.$apply(function () {
                    $rootScope.$broadcast('joined channel', channel);
                });
            });

            socket.on('user created', function (newUser) {
                $log.info('socket:user created', newUser);
                user = newUser;
                Storage.user.set(user);
                Storage.channel.set({});
            });

            socket.on('channel users list', function (users) {
                $log.info('socket:channel users list', users);
                $rootScope.$broadcast('channel users list', users);
            });

            //TODO: combine channel and user messages with a flag: message.channel = true/false
            socket.on('new channel message', function (message) {
                $log.info('socket:new channel message'/*, message*/);
                Chat.addText(message.text);
                $rootScope.$broadcast('new channel message', message);
            });

            socket.on('new message', function (message) {
                $log.info('socket:new message'/*, message*/);
                Chat.addText(message.text);
                $rootScope.$broadcast('new message', message);
            });

            //TODO: rename it to left channel
            socket.on('channel left', function (channel) {
                $log.info('socket:channel left', channel);
                $rootScope.$apply(function () {
                    $rootScope.$broadcast('channel left', channel);
                });
            });

            socket.on('user updated', function (uuid, oldUsername, newUsername) {
                $log.info('socket:user updated', uuid, oldUsername, newUsername);
                $rootScope.$apply(function () {
                    $rootScope.$broadcast('user updated', uuid, oldUsername, newUsername);
                });
            });

            socket.on('channel messages', function (messages) {
                $log.info('socket:channel messages', 'len = ' + messages.length);
                $rootScope.$broadcast('channel messages', messages);
            });

            socket.on('error', function (error) {
                $log.info('socket:error', error);
                $rootScope.$broadcast('error', error);
            });

        }
    ])

    ;

})(angular);
<|endoftext|>