<commit_before>/*
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as studio from '../studio';

import {BaseGenerator} from '../base-generator';

const ICON_SIZE = { w: 48, h: 48 };

const TARGET_RECTS_BY_SHAPE = {
  none: { x:  3, y:  3, w:  42, h:  42 },
  circle: { x:  2, y:  2, w:  44, h:  44 },
  square: { x:  5, y:  5, w:  38, h:  38 },
  vrect: { x:  8, y:  2, w:  32, h:  44 },
  hrect: { x:  2, y:  8, w:  44, h:  32 },
};

const GRID_OVERLAY_SVG =
    `<svg width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <g fill="none" fill-rule="evenodd">
            <rect vector-effect="non-scaling-stroke" x="8" y="2" width="32" height="44" rx="3"/>
            <rect vector-effect="non-scaling-stroke" x="5" y="5" width="38" height="38" rx="3"/>
            <rect vector-effect="non-scaling-stroke" x="2" y="8" width="44" height="32" rx="3"/>
            <circle vector-effect="non-scaling-stroke" cx="24" cy="24" r="10"/>
            <circle vector-effect="non-scaling-stroke" cx="24" cy="24" r="22"/>
            <path vector-effect="non-scaling-stroke" d="M0 48L48 0M0 0l48 48M24 48V0M17 0v48M31 0v48M48 24H0M0 31h48M0 17h48"/>
        </g>
    </svg>`;

const DEFAULT_EFFECT_OPTIONS = [
  { id: 'none', title: 'None' },
  { id: 'elevate', title: 'Elevate' },
  { id: 'shadow', title: 'Cast shadow' },
  { id: 'score', title: 'Score' }
];

const NO_SHAPE_EFFECT_OPTIONS = [
  { id: 'none', title: 'None' },
  { id: 'score', title: 'Score' }
];

export class LauncherIconGenerator extends BaseGenerator {
  get densities() {
    return new Set(['xxxhdpi' /* must be first */, 'web', 'xxhdpi', 'xhdpi', 'hdpi', 'mdpi']);
  }

  get gridOverlaySvg() {
    return GRID_OVERLAY_SVG;
  }

  setupForm() {
    let backColorField, effectsField;
    this.form = new studio.Form({
      id: 'iconform',
      container: '#inputs-form',
      fields: [
        new studio.ImageField('foreground', {
          title: 'Foreground',
          maxFinalSize: { w: 720, h: 720 }, // max render size, for SVGs
          defaultValueTrim: 1,
          defaultValuePadding: .25,
          defaultValueClipart: 'android',
          dropTarget: document.body
        }),
        new studio.ColorField('foreColor', {
          newGroup: true,
          title: 'Color',
          helpText: 'Set to transparent to use original colors',
          alpha: true,
          defaultValue: 'rgba(96, 125, 139, 0)'
        }),
        (backColorField = new studio.ColorField('backColor', {
          title: 'Background color',
          defaultValue: '#448aff'
        })),
        new studio.BooleanField('crop', {
          title: 'Scaling',
          defaultValue: false,
          offText: 'Center',
          onText: 'Crop'
        }),
        new studio.EnumField('backgroundShape', {
          title: 'Shape',
          helpText: 'Web version will always be square',
          options: [
            { id: 'none', title: 'None' },
            { id: 'square', title: 'Square' },
            { id: 'circle', title: 'Circle' },
            { id: 'vrect', title: 'Tall rect' },
            { id: 'hrect', title: 'Wide rect' }
          ],
          defaultValue: 'square',
          onChange: newValue => {
            backColorField.setEnabled(newValue != 'none');
            let newEffectsOptions = newValue == 'none'
                ? NO_SHAPE_EFFECT_OPTIONS
                : DEFAULT_EFFECT_OPTIONS;
            if (!newEffectsOptions.find(e => e.id == effectsField.getValue())) {
              effectsField.setValue(newEffectsOptions[0].id);
            }
            effectsField.setOptions(newEffectsOptions);
          }
        }),
        (effectsField = new studio.EnumField('effects', {
          title: 'Effect',
          buttons: true,
          options: DEFAULT_EFFECT_OPTIONS,
          defaultValue: 'none'
        })),
        new studio.TextField('name', {
          title: 'Name',
          defaultValue: 'ic_launcher'
        })
      ]
    });
    this.form.onChange(field => this.regenerateDebounced_());
  }

  regenerate() {
    let values = this.form.getValues();
    values.name = values.name || 'ic_launcher';

    this.zipper.clear();
    this.zipper.setZipFilename(`${values.name}.zip`);

    let xxxhdpiCtx = null;

    this.densities.forEach(density => {
      let ctx;
      if (density == 'xxxhdpi' || density == 'web') {
        ctx = this.regenerateRawAtDensity_(density);
        if (density == 'xxxhdpi') {
          xxxhdpiCtx = ctx;
        }
      } else {
        // just scale down xxxhdpi
        let mult = studio.Util.getMultBaseMdpi(density);
        let iconSize = studio.Util.multRound(ICON_SIZE, mult);
        ctx = studio.Drawing.context(iconSize);
        studio.Drawing.drawImageScaled(
            ctx, xxxhdpiCtx,
            0, 0, 192, 192,
            0, 0, iconSize.w, iconSize.h);
      }

      this.zipper.add({
        name: (density == 'web')
            ? 'web_hi_res_512.png'
            : `res/mipmap-${density}/${values.name}.png`,
        canvas: ctx.canvas
      });

      this.setImageForSlot_(density, ctx.canvas.toDataURL());
    });
  }

  regenerateRawAtDensity_(density) {
    let values = this.form.getValues();
    let foreSrcCtx = values.foreground ? values.foreground.ctx : null;
    let mult = studio.Util.getMultBaseMdpi(density);
    if (density == 'web') {
      mult = 512 / 48;
    }

    let iconSize = studio.Util.multRound(ICON_SIZE, mult);
    let targetRect = TARGET_RECTS_BY_SHAPE[values.backgroundShape];
    if (density == 'web') {
      targetRect = {x: 0, y: 0, w: 48, h: 48};
    }

    let outCtx = studio.Drawing.context(iconSize);

    let roundRectPath_ = (ctx, {x, y, w, h}, r) => {
      ctx.beginPath();
      ctx.moveTo(x + w - r, y);
      ctx.arcTo(x + w, y, x + w, y + r, r);
      ctx.lineTo(x + w, y + h - r);
      ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
      ctx.lineTo(x + r, y + h);
      ctx.arcTo(x, y + h, x, y + h - r, r);
      ctx.lineTo(x, y + r);
      ctx.arcTo(x, y, x + r, y, r);
      ctx.closePath();
    };

    let backgroundLayer = {
      // background layer
      draw: ctx => {
        ctx.scale(mult, mult);
        values.backColor.setAlpha(1);
        ctx.fillStyle = values.backColor.toRgbString();
        if (density == 'web') {
          if (values.backgroundShape == 'none') {
            ctx.fillStyle = 'white';
          }
          ctx.fillRect(0, 0, 48, 48);
          return;
        }

        let targetRect = TARGET_RECTS_BY_SHAPE[values.backgroundShape];
        switch (values.backgroundShape) {
          case 'square':
          case 'vrect':
          case 'hrect':
            roundRectPath_(ctx, targetRect, 3);
            ctx.fill();
            break;

          case 'circle':
            ctx.beginPath();
            ctx.arc(
                targetRect.x + targetRect.w / 2,
                targetRect.y + targetRect.h / 2,
                targetRect.w / 2,
                0, 2 * Math.PI, false);
            ctx.closePath();
            ctx.fill();
            break;
        }
      },
      mask: true
    };

    let foregroundLayer = {
      // foreground content layer
      draw: ctx => {
        if (!foreSrcCtx) {
          return;
        }

        let drawFn_ = studio.Drawing[values.crop ? 'drawCenterCrop' : 'drawCenterInside'];
        drawFn_(ctx, foreSrcCtx, studio.Util.mult(targetRect, mult),
            {x: 0, y: 0, w: foreSrcCtx.canvas.width, h: foreSrcCtx.canvas.height});
      },
      effects: [],
      mask: !!(values.backgroundShape == 'none')
    };

    if (values.backgroundShape != 'none' && values.effects == 'shadow') {
      foregroundLayer.effects.push({effect: 'cast-shadow'});
    }

    if (values.foreColor.getAlpha()) {
      foregroundLayer.effects.push({
        effect: 'fill-color',
        color: values.foreColor.toRgbString()
      });
    }

    if (values.backgroundShape != 'none' &&
        (values.effects == 'elevate' || values.effects == 'shadow')) {
      foregroundLayer.effects = [
        ...foregroundLayer.effects,
        {
          effect: 'outer-shadow',
          color: 'rgba(0, 0, 0, 0.2)',
          translateY: .25 * mult
        },
        {
          effect: 'outer-shadow',
          color: 'rgba(0, 0, 0, 0.2)',
          blur: 1 * mult,
          translateY: 1 * mult
        }
      ];
    }

    let scoreLayer = {
      draw: ctx => {
        ctx.fillStyle = 'rgba(0, 0, 0, .1)';
        ctx.fillRect(0, 0, iconSize.w, iconSize.h / 2);
      }
    };

    let finalEffects = [
      {
        effect: 'inner-shadow',
        color: 'rgba(255, 255, 255, 0.2)',
        translateY: .25 * mult
      },
      {
        effect: 'inner-shadow',
        color: 'rgba(0, 0, 0, 0.2)',
        translateY: -.25 * mult
      },
      {
        effect: 'outer-shadow',
        color: 'rgba(0, 0, 0, 0.3)',
        blur: .7 * mult,
        translateY: .7 * mult
      },
      {
        effect: 'fill-radialgradient',
        centerX: 0,
        centerY: 0,
        radius: iconSize.w,
        colors: [
          { offset: 0, color: 'rgba(255,255,255,.1)' },
          { offset: 1.0, color: 'rgba(255,255,255,0)' }
        ]
      }
    ];

    if (density == 'web') {
      if (values.backgroundShape == 'none') {
        foregroundLayer.effects = [...foregroundLayer.effects, ...finalEffects];
        finalEffects = [];
      } else {
        finalEffects = finalEffects.filter(e => e.effect.match(/fill/));
      }
    }

    studio.Drawing.drawLayers(outCtx, iconSize, {
      children: [
        (density == 'web' || values.backgroundShape != 'none')
            ? backgroundLayer : null,
        foregroundLayer,
        values.effects == 'score' ? scoreLayer : null,
      ],
      effects: finalEffects,
    });

    return outCtx;
  }
}
<commit_msg>Also export 1024px web version in the launcher icon generator (#242)<commit_after>/*
 * Copyright 2016 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as studio from '../studio';

import {BaseGenerator} from '../base-generator';

const ICON_SIZE = { w: 48, h: 48 };

const TARGET_RECTS_BY_SHAPE = {
  none: { x:  3, y:  3, w:  42, h:  42 },
  circle: { x:  2, y:  2, w:  44, h:  44 },
  square: { x:  5, y:  5, w:  38, h:  38 },
  vrect: { x:  8, y:  2, w:  32, h:  44 },
  hrect: { x:  2, y:  8, w:  44, h:  32 },
};

const GRID_OVERLAY_SVG =
    `<svg width="48" height="48" viewBox="0 0 48 48" xmlns="http://www.w3.org/2000/svg">
        <g fill="none" fill-rule="evenodd">
            <rect vector-effect="non-scaling-stroke" x="8" y="2" width="32" height="44" rx="3"/>
            <rect vector-effect="non-scaling-stroke" x="5" y="5" width="38" height="38" rx="3"/>
            <rect vector-effect="non-scaling-stroke" x="2" y="8" width="44" height="32" rx="3"/>
            <circle vector-effect="non-scaling-stroke" cx="24" cy="24" r="10"/>
            <circle vector-effect="non-scaling-stroke" cx="24" cy="24" r="22"/>
            <path vector-effect="non-scaling-stroke" d="M0 48L48 0M0 0l48 48M24 48V0M17 0v48M31 0v48M48 24H0M0 31h48M0 17h48"/>
        </g>
    </svg>`;

const DEFAULT_EFFECT_OPTIONS = [
  { id: 'none', title: 'None' },
  { id: 'elevate', title: 'Elevate' },
  { id: 'shadow', title: 'Cast shadow' },
  { id: 'score', title: 'Score' }
];

const NO_SHAPE_EFFECT_OPTIONS = [
  { id: 'none', title: 'None' },
  { id: 'score', title: 'Score' }
];

export class LauncherIconGenerator extends BaseGenerator {
  get densities() {
    return new Set(['xxxhdpi' /* must be first */, 'web', 'webx', 'xxhdpi', 'xhdpi', 'hdpi', 'mdpi']);
  }

  get gridOverlaySvg() {
    return GRID_OVERLAY_SVG;
  }

  setupForm() {
    let backColorField, effectsField;
    this.form = new studio.Form({
      id: 'iconform',
      container: '#inputs-form',
      fields: [
        new studio.ImageField('foreground', {
          title: 'Foreground',
          maxFinalSize: { w: 720, h: 720 }, // max render size, for SVGs
          defaultValueTrim: 1,
          defaultValuePadding: .25,
          defaultValueClipart: 'android',
          dropTarget: document.body
        }),
        new studio.ColorField('foreColor', {
          newGroup: true,
          title: 'Color',
          helpText: 'Set to transparent to use original colors',
          alpha: true,
          defaultValue: 'rgba(96, 125, 139, 0)'
        }),
        (backColorField = new studio.ColorField('backColor', {
          title: 'Background color',
          defaultValue: '#448aff'
        })),
        new studio.BooleanField('crop', {
          title: 'Scaling',
          defaultValue: false,
          offText: 'Center',
          onText: 'Crop'
        }),
        new studio.EnumField('backgroundShape', {
          title: 'Shape',
          helpText: 'Web version will always be square',
          options: [
            { id: 'none', title: 'None' },
            { id: 'square', title: 'Square' },
            { id: 'circle', title: 'Circle' },
            { id: 'vrect', title: 'Tall rect' },
            { id: 'hrect', title: 'Wide rect' }
          ],
          defaultValue: 'square',
          onChange: newValue => {
            backColorField.setEnabled(newValue != 'none');
            let newEffectsOptions = newValue == 'none'
                ? NO_SHAPE_EFFECT_OPTIONS
                : DEFAULT_EFFECT_OPTIONS;
            if (!newEffectsOptions.find(e => e.id == effectsField.getValue())) {
              effectsField.setValue(newEffectsOptions[0].id);
            }
            effectsField.setOptions(newEffectsOptions);
          }
        }),
        (effectsField = new studio.EnumField('effects', {
          title: 'Effect',
          buttons: true,
          options: DEFAULT_EFFECT_OPTIONS,
          defaultValue: 'none'
        })),
        new studio.TextField('name', {
          title: 'Name',
          defaultValue: 'ic_launcher'
        })
      ]
    });
    this.form.onChange(field => this.regenerateDebounced_());
  }

  regenerate() {
    let values = this.form.getValues();
    values.name = values.name || 'ic_launcher';

    this.zipper.clear();
    this.zipper.setZipFilename(`${values.name}.zip`);

    let xxxhdpiCtx = null;

    this.densities.forEach(density => {
      let ctx;
      if (density == 'xxxhdpi' || density == 'web' || density == 'webx') {
        ctx = this.regenerateRawAtDensity_(density);
        if (density == 'xxxhdpi') {
          xxxhdpiCtx = ctx;
        }
      } else {
        // just scale down xxxhdpi
        let mult = studio.Util.getMultBaseMdpi(density);
        let iconSize = studio.Util.multRound(ICON_SIZE, mult);
        ctx = studio.Drawing.context(iconSize);
        studio.Drawing.drawImageScaled(
            ctx, xxxhdpiCtx,
            0, 0, 192, 192,
            0, 0, iconSize.w, iconSize.h);
      }

      let name = `res/mipmap-${density}/${values.name}.png`;
      if(density == 'web') {
        name = 'web_hi_res_512.png';
      } else if(density == 'webx') {
        name = 'web_hi_res_1024.png';
      }
      this.zipper.add({
        name,
        canvas: ctx.canvas
      });

      this.setImageForSlot_(density, ctx.canvas.toDataURL());
    });
  }

  regenerateRawAtDensity_(density) {
    let values = this.form.getValues();
    let foreSrcCtx = values.foreground ? values.foreground.ctx : null;
    let mult = studio.Util.getMultBaseMdpi(density);
    if (density == 'web') {
      mult = 512 / 48;
    } else if (density == 'webx') {
      mult = 1024 / 48;
    }

    let iconSize = studio.Util.multRound(ICON_SIZE, mult);
    let targetRect = TARGET_RECTS_BY_SHAPE[values.backgroundShape];
    if (density == 'web' || density == 'webx') {
      targetRect = {x: 0, y: 0, w: 48, h: 48};
    }

    let outCtx = studio.Drawing.context(iconSize);

    let roundRectPath_ = (ctx, {x, y, w, h}, r) => {
      ctx.beginPath();
      ctx.moveTo(x + w - r, y);
      ctx.arcTo(x + w, y, x + w, y + r, r);
      ctx.lineTo(x + w, y + h - r);
      ctx.arcTo(x + w, y + h, x + w - r, y + h, r);
      ctx.lineTo(x + r, y + h);
      ctx.arcTo(x, y + h, x, y + h - r, r);
      ctx.lineTo(x, y + r);
      ctx.arcTo(x, y, x + r, y, r);
      ctx.closePath();
    };

    let backgroundLayer = {
      // background layer
      draw: ctx => {
        ctx.scale(mult, mult);
        values.backColor.setAlpha(1);
        ctx.fillStyle = values.backColor.toRgbString();
        if (density == 'web' || density == 'webx') {
          if (values.backgroundShape == 'none') {
            ctx.fillStyle = 'white';
          }
          ctx.fillRect(0, 0, 48, 48);
          return;
        }

        let targetRect = TARGET_RECTS_BY_SHAPE[values.backgroundShape];
        switch (values.backgroundShape) {
          case 'square':
          case 'vrect':
          case 'hrect':
            roundRectPath_(ctx, targetRect, 3);
            ctx.fill();
            break;

          case 'circle':
            ctx.beginPath();
            ctx.arc(
                targetRect.x + targetRect.w / 2,
                targetRect.y + targetRect.h / 2,
                targetRect.w / 2,
                0, 2 * Math.PI, false);
            ctx.closePath();
            ctx.fill();
            break;
        }
      },
      mask: true
    };

    let foregroundLayer = {
      // foreground content layer
      draw: ctx => {
        if (!foreSrcCtx) {
          return;
        }

        let drawFn_ = studio.Drawing[values.crop ? 'drawCenterCrop' : 'drawCenterInside'];
        drawFn_(ctx, foreSrcCtx, studio.Util.mult(targetRect, mult),
            {x: 0, y: 0, w: foreSrcCtx.canvas.width, h: foreSrcCtx.canvas.height});
      },
      effects: [],
      mask: !!(values.backgroundShape == 'none')
    };

    if (values.backgroundShape != 'none' && values.effects == 'shadow') {
      foregroundLayer.effects.push({effect: 'cast-shadow'});
    }

    if (values.foreColor.getAlpha()) {
      foregroundLayer.effects.push({
        effect: 'fill-color',
        color: values.foreColor.toRgbString()
      });
    }

    if (values.backgroundShape != 'none' &&
        (values.effects == 'elevate' || values.effects == 'shadow')) {
      foregroundLayer.effects = [
        ...foregroundLayer.effects,
        {
          effect: 'outer-shadow',
          color: 'rgba(0, 0, 0, 0.2)',
          translateY: .25 * mult
        },
        {
          effect: 'outer-shadow',
          color: 'rgba(0, 0, 0, 0.2)',
          blur: 1 * mult,
          translateY: 1 * mult
        }
      ];
    }

    let scoreLayer = {
      draw: ctx => {
        ctx.fillStyle = 'rgba(0, 0, 0, .1)';
        ctx.fillRect(0, 0, iconSize.w, iconSize.h / 2);
      }
    };

    let finalEffects = [
      {
        effect: 'inner-shadow',
        color: 'rgba(255, 255, 255, 0.2)',
        translateY: .25 * mult
      },
      {
        effect: 'inner-shadow',
        color: 'rgba(0, 0, 0, 0.2)',
        translateY: -.25 * mult
      },
      {
        effect: 'outer-shadow',
        color: 'rgba(0, 0, 0, 0.3)',
        blur: .7 * mult,
        translateY: .7 * mult
      },
      {
        effect: 'fill-radialgradient',
        centerX: 0,
        centerY: 0,
        radius: iconSize.w,
        colors: [
          { offset: 0, color: 'rgba(255,255,255,.1)' },
          { offset: 1.0, color: 'rgba(255,255,255,0)' }
        ]
      }
    ];

    if (density == 'web' || density == 'webx') {
      if (values.backgroundShape == 'none') {
        foregroundLayer.effects = [...foregroundLayer.effects, ...finalEffects];
        finalEffects = [];
      } else {
        finalEffects = finalEffects.filter(e => e.effect.match(/fill/));
      }
    }

    studio.Drawing.drawLayers(outCtx, iconSize, {
      children: [
        (density == 'web' || density == 'webx' || values.backgroundShape != 'none')
            ? backgroundLayer : null,
        foregroundLayer,
        values.effects == 'score' ? scoreLayer : null,
      ],
      effects: finalEffects,
    });

    return outCtx;
  }
}
<|endoftext|>


<commit_before>var hdv = {
    init: function() {
        if (gBrowser) gBrowser.addEventListener("DOMContentLoaded", this.onDOMContentLoaded);
    },
    onDOMContentLoaded: function(event) {
        var doc = event.originalTarget;
        if (!doc || !doc.body) return;

        var script = doc.createElement('script');
        script.setAttribute("type", "application/javascript");
        script.setAttribute("src", "chrome://hdv/content/hdv.js");
        doc.body.appendChild(script);
    }
}

window.addEventListener("load", function() {
    hdv.init();
    window.removeEventListener("load", this, false);
});
<commit_msg>Minor: referencing Greasemonkey in comment.<commit_after>/**
 * @see http://wiki.greasespot.net/Content_Script_Injection
 */
var hdv = {
    init: function() {
        if (gBrowser) gBrowser.addEventListener("DOMContentLoaded", this.onDOMContentLoaded);
    },
    onDOMContentLoaded: function(event) {
        var doc = event.originalTarget;
        if (!doc || !doc.body) return;

        var script = doc.createElement('script');
        script.setAttribute("type", "application/javascript");
        script.setAttribute("src", "chrome://hdv/content/hdv.js");
        doc.body.appendChild(script);
    }
}

window.addEventListener("load", function() {
    hdv.init();
    window.removeEventListener("load", this, false);
});
<|endoftext|>


<commit_before>import Vue from 'vue'
import Router from 'vue-router'

import ListView from '../views/List.vue'
import PostView from '../views/Post.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: [
    { path: '/', name: 'list', component: ListView },
    { path: 'post/:hash', name: 'post', component: PostView },
  ]
})
<commit_msg>Router configuration correction<commit_after>import Vue from 'vue'
import Router from 'vue-router'

import ListView from '../views/List.vue'
import PostView from '../views/Post.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  scrollBehavior: () => ({ y: 0 }),
  routes: [
    { path: '/', name: 'list', component: ListView },
    { path: '/post/:hash', name: 'post', component: PostView }
  ]
})
<|endoftext|>


<commit_before>'use strict';

/**
 * Module dependencies
 */

const co = require('co');
const phantom = require('phantom');
const Router = require('koa-router');

/**
 * Create router
 */

const router = new Router();

/**
 * Index page
 */

router.get('/', co.wrap(function* (ctx, next) {
  let ph, page, url = ctx.query.url;
  if (!url){
    return ctx.body = {
      error: {
        message: 'url query param is required'
      }
    }
  }
  return phantom.create()
    .then(p => {
      ph = p;
      return ph.createPage();
    })
    .then(p => {
      page = p;
      return page.open(url);
    })
    .then(status => {
      if (status === 'success') {
        return page.property('content');
      }
      return null;
    })
    .then(content => {
      let message = content ? content : 'Failed';
      page.close();
      ph.exit();
      ctx.body = {
        message: message
      }
    });
}));

/**
 * Healthcheck
 */

router.get('/ping', co.wrap(function* (ctx, next) {
  ctx.body = {
    message: 'pong'
  };
}));

/**
 * Expose `router`
 */

module.exports = router;
<commit_msg>capture screenshot<commit_after>'use strict';

/**
 * Module dependencies
 */

const co = require('co');
const phantom = require('phantom');
const Router = require('koa-router');

/**
 * Create router
 */

const router = new Router();

/**
 * Index page
 */

router.get('/', co.wrap(function* (ctx, next) {
  let ph, page, url = ctx.query.url;
  if (!url) {
    return ctx.body = {
      error: {
        message: 'url query param is required'
      }
    }
  }
  return phantom.create()
    .then(p => {
      ph = p;
      return ph.createPage();
    })
    .then(p => {
      page = p;
      return page.open(url);
    })
    .then(status => {
      if (status === 'success') {
        return page.renderBase64('PNG');
      }
      return null;
    })
    .then(base64 => {
      ctx.type = 'html';
      ctx.body = `<img src="data:image/gif;base64,${base64}">`
      page.close();
      ph.exit();
    });
}));

/**
 * Healthcheck
 */

router.get('/ping', co.wrap(function* (ctx, next) {
  ctx.body = {
    message: 'pong'
  };
}));

/**
 * Expose `router`
 */

module.exports = router;
<|endoftext|>


<commit_before>var _ = require('lodash'),
	ffmpeg = require('fluent-ffmpeg');
var ShowImporter = require('../importers/ShowImporter.js');
var traktConfig = require('../../config/trakt');
var directoriesConfig = require('../../config/directories');
module.exports = function home(app, models){
	app.get('/shows', function(req, res){
		var Show = models.Show;
		if(req.isAuthenticated()){
			Show.find(function(err, shows){
				res.render('shows/index', {
					shows: shows
				});
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/show/:show', function(req, res){
		var Show = models.Show,
			Episode = models.Episode;
		if(req.isAuthenticated()){
			Show.findOne({url: req.params.show}, function(err, show){
				if(!show || show.length == 0){
					return res.render('error.html', {
						message: 'Show not found'
					});
				}
				//console.log(show)
				if(show && show.seasons){
					show.seasons = show.seasons.sort(function(a, b){
							if(a.season > b.season){
								return 1;
							}else if(a.season < b.season){
								return -1;
							}
							return 0;
					});
				}
				Episode.find({'show': show._id}, function(err, episodes){
					if(err){
						console.log(err);
					}
					//console.log('lol', episodes)
					var seasons = {};
					_.each(episodes, function (el){
						var s = el.season,
							e = el.episode;
						if(!seasons[s]){
							seasons[s] = {};
							seasons[s].num = s;
						}
						var season = seasons[s];
						if(!season.episodes){
							season.episodes = [];
						}
						if(show && show.seasons){
							if(show.seasons && show.seasons[s] && show.seasons[s].episodes){
								el = _.merge(el, show.seasons[s].episodes[e - 1])
								//console.log(show.seasons[s].episodes[e])
							}
						}
						season.episodes.push(el)
					});
					seasons = _.each(seasons, function(season){
						if(season){
							season.episodes = season.episodes.sort(function(a, b){
								if(a.episode > b.episode){
									return 1;
								}else if(a.episode < b.episode){
									return -1;
								}
								return 0;
							});
						}
					});
					res.render('shows/single', {
						show: show,
						seasons: seasons
					});
				})
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/watch/:episodeId', function(req, res){
		var dir = directoriesConfig.tv,
			Show = models.Show,
			Episode = models.Episode;
		if(req.isAuthenticated()){
			Episode
			.findOne({'_id': req.params.episodeId})
			.exec(function(err, ep){
				console.log(ep);
				if(err){
					return res.redirect('/');
				}
				Show.findOne({'_id': ep.show}, function(err, show){
					if(err){
						return res.redirect('/');
					}
					console.log(show)
					seasons = show.seasons.sort(function(a, b){
							if(a.season > b.season){
								return 1;
							}else if(a.season < b.season){
								return -1;
							}
							return 0;
					});
					seasons = _.each(show.seasons, function(season){
						if(season){
							season.episodes = season.episodes.sort(function(a, b){
								if(a.episode > b.episode){
									return 1;
								}else if(a.episode < b.episode){
									return -1;
								}
								return 0;
							});
						}
					});
					if(ep.season
						&& ep.episode
						&& seasons
						&& seasons[ep.season]
						&& seasons[ep.season].episodes
						&& seasons[ep.season].episodes[ep.episode - 1]){
						ep.overview = seasons[ep.season].episodes[ep.episode - 1].overview;
					}
					res.render('shows/episode', {
						show: show,
						ep: ep
					});
				})
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/transcode/:episodeId', function(req, res){
		var dir = directoriesConfig.tv,
			Episode = models.Episode;
		if(req.isAuthenticated()){
			Episode
			.findOne({'_id': req.params.episodeId})
			.populate('show')
			.exec(function(err, ep){
				if(ep && ep.file){
					var options = [
						'-threads 0',
						'-coder 0',
						'-flags -loop',
						'-pix_fmt yuv420p',
						'-subq 0',
						'-sc_threshold 0',
						'-keyint_min 150',
						'-deinterlace',
						'-maxrate 10000000',
						'-bufsize 10000000',
						'-acodec aac',
						'-strict experimental',
						'-frag_duration 1000',
						'-movflags +frag_keyframe+empty_moov',
						'-profile:v baseline'
					];
					// High quality, not lossless
					options.push('-g 52', '-crf 22', '-ar 48000', '-ab 320k');
					res.contentType('video/mp4');
					console.log(ep.file)
					var proc = ffmpeg({source: dir + ep.file})
					.format('mp4')
					.audioCodec('aac')
					.addOptions(options)
					.on('start', function(commandLine) {
						console.log('Spawned Ffmpeg with command: ' + commandLine);
					})
					.on('end', function(){
						console.log('file has been converted succesfully');
					})
					.on('error', function(err){
						console.log('an error happened: ' + err.message);
					})
					.pipe(res, {end:true});

				}
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/import', function(req, res){
		var dir = directoriesConfig.tv,
			importer = new ShowImporter(traktConfig.apiKey, models);
		importer.importAll(dir, function(){});
		res.end();
	});
};<commit_msg>Let anyone stream/transcode<commit_after>var _ = require('lodash'),
	ffmpeg = require('fluent-ffmpeg');
var ShowImporter = require('../importers/ShowImporter.js');
var traktConfig = require('../../config/trakt');
var directoriesConfig = require('../../config/directories');
module.exports = function home(app, models){
	app.get('/shows', function(req, res){
		var Show = models.Show;
		if(req.isAuthenticated()){
			Show.find(function(err, shows){
				res.render('shows/index', {
					shows: shows
				});
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/show/:show', function(req, res){
		var Show = models.Show,
			Episode = models.Episode;
		if(req.isAuthenticated()){
			Show.findOne({url: req.params.show}, function(err, show){
				if(!show || show.length == 0){
					return res.render('error.html', {
						message: 'Show not found'
					});
				}
				//console.log(show)
				if(show && show.seasons){
					show.seasons = show.seasons.sort(function(a, b){
							if(a.season > b.season){
								return 1;
							}else if(a.season < b.season){
								return -1;
							}
							return 0;
					});
				}
				Episode.find({'show': show._id}, function(err, episodes){
					if(err){
						console.log(err);
					}
					//console.log('lol', episodes)
					var seasons = {};
					_.each(episodes, function (el){
						var s = el.season,
							e = el.episode;
						if(!seasons[s]){
							seasons[s] = {};
							seasons[s].num = s;
						}
						var season = seasons[s];
						if(!season.episodes){
							season.episodes = [];
						}
						if(show && show.seasons){
							if(show.seasons && show.seasons[s] && show.seasons[s].episodes){
								el = _.merge(el, show.seasons[s].episodes[e - 1])
								//console.log(show.seasons[s].episodes[e])
							}
						}
						season.episodes.push(el)
					});
					seasons = _.each(seasons, function(season){
						if(season){
							season.episodes = season.episodes.sort(function(a, b){
								if(a.episode > b.episode){
									return 1;
								}else if(a.episode < b.episode){
									return -1;
								}
								return 0;
							});
						}
					});
					res.render('shows/single', {
						show: show,
						seasons: seasons
					});
				})
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/watch/:episodeId', function(req, res){
		var dir = directoriesConfig.tv,
			Show = models.Show,
			Episode = models.Episode;
		if(req.isAuthenticated()){
			Episode
			.findOne({'_id': req.params.episodeId})
			.exec(function(err, ep){
				console.log(ep);
				if(err){
					return res.redirect('/');
				}
				Show.findOne({'_id': ep.show}, function(err, show){
					if(err){
						return res.redirect('/');
					}
					console.log(show)
					seasons = show.seasons.sort(function(a, b){
							if(a.season > b.season){
								return 1;
							}else if(a.season < b.season){
								return -1;
							}
							return 0;
					});
					seasons = _.each(show.seasons, function(season){
						if(season){
							season.episodes = season.episodes.sort(function(a, b){
								if(a.episode > b.episode){
									return 1;
								}else if(a.episode < b.episode){
									return -1;
								}
								return 0;
							});
						}
					});
					if(ep.season
						&& ep.episode
						&& seasons
						&& seasons[ep.season]
						&& seasons[ep.season].episodes
						&& seasons[ep.season].episodes[ep.episode - 1]){
						ep.overview = seasons[ep.season].episodes[ep.episode - 1].overview;
					}
					res.render('shows/episode', {
						show: show,
						ep: ep
					});
				})
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/transcode/:episodeId', function(req, res){
		var dir = directoriesConfig.tv,
			Episode = models.Episode;
		if(/*req.isAuthenticated()*/true){
			Episode
			.findOne({'_id': req.params.episodeId})
			.populate('show')
			.exec(function(err, ep){
				if(ep && ep.file){
					var options = [
						'-threads 0',
						'-coder 0',
						'-flags -loop',
						'-pix_fmt yuv420p',
						'-subq 0',
						'-sc_threshold 0',
						'-keyint_min 150',
						'-deinterlace',
						'-maxrate 10000000',
						'-bufsize 10000000',
						'-acodec aac',
						'-strict experimental',
						'-frag_duration 1000',
						'-movflags +frag_keyframe+empty_moov',
						'-profile:v baseline'
					];
					// High quality, not lossless
					options.push('-g 52', '-crf 22', '-ar 48000', '-ab 320k');
					res.contentType('video/mp4');
					console.log(ep.file)
					var proc = ffmpeg({source: dir + ep.file})
					.format('mp4')
					.audioCodec('aac')
					.addOptions(options)
					.on('start', function(commandLine) {
						console.log('Spawned Ffmpeg with command: ' + commandLine);
					})
					.on('end', function(){
						console.log('file has been converted succesfully');
					})
					.on('error', function(err){
						console.log('an error happened: ' + err.message);
					})
					.pipe(res, {end:true});

				}
			});
		}else{
			res.redirect('/auth/login');
		}
	});
	app.get('/import', function(req, res){
		var dir = directoriesConfig.tv,
			importer = new ShowImporter(traktConfig.apiKey, models);
		importer.importAll(dir, function(){});
		res.end();
	});
};<|endoftext|>


<commit_before>import debug from 'debug';
import createRouter from 'router';

import protect from '../middleware/protect';
import * as controller from '../controllers/users';
import { checkFields } from '../utils';
import handleError from '../errors';
import { ROLE_MANAGER, ROLE_MODERATOR } from '../roles';

const log = debug('uwave:api:v1:users');

export default function userRoutes() {
  const router = createRouter();

  router.get('/', protect(ROLE_MANAGER), (req, res) => {
    const { page, limit } = req.query;

    controller.getUsers(req.uwave, parseInt(page, 10), parseInt(limit, 10))
    .then(users => res.status(200).json(users))
    .catch(e => handleError(res, e, log));
  });

  router.get('/:id', (req, res) => {
    controller.getUser(req.uwave, req.params.id)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.post('/:id/ban', protect(ROLE_MODERATOR), (req, res) => {
    if (!checkFields(res, req.body, { time: 'number', exiled: 'boolean' })) {
      return null;
    }
    if (req.user.id === req.params.id) {
      return res.status(403, 'you can\'t ban yourself');
    }
    if (isNaN(req.body.time)) {
      return res.status(422).json('time can only be a number');
    }

    controller.banUser(req.uwave, req.user.id, req.params.id, req.body.time, req.body.exiled)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.delete('/:id/ban', protect(ROLE_MANAGER), (req, res) => {
    if (req.user.id === req.params.id) {
      return res.status(403, 'you can\'t unban yourself');
    }

    controller.banUser(req.uwave, req.user.id, req.params.id, 0, false)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.post('/:id/mute', protect(ROLE_MODERATOR), (req, res) => {
    if (typeof req.body.time === 'undefined') {
      return res.status(422).json('time is not set');
    }
    if (req.user.id === req.params.id) {
      return res.status(403).json('you can\'t mute yourself');
    }

    if (typeof req.body.time !== 'number' || isNaN(req.body.time)) {
      return res.status(422).json('time is not set');
    }

    controller.muteUser(req.uwave, req.user.id, req.params.id, req.body.time)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.delete('/:id/mute', protect(ROLE_MODERATOR), (req, res) => {
    if (req.user.id === req.params.id) {
      return res.status(403, 'you can\'t unmute yourself');
    }

    controller.unmuteUser(req.uwave, req.user.id, req.params.id)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.put('/:id/role', protect(ROLE_MANAGER), (req, res) => {
    if (typeof req.body.role !== 'number' || isNaN(req.body.role)) {
      return res.status(422).json('expected role to be a number');
    }

    if (req.user.role < req.body.role) {
      return res.status(403).json('you can\'t promote users above or equal to your own level');
    }

    controller.changeRole(req.uwave, req.user.id, req.params.id, req.body.role)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.put('/:id/username', (req, res, next) => {
    if (!req.body.username) {
      return res.status(422).json('username is not set');
    }

    if (typeof req.body.username !== 'string') {
      return res.status(422).json('username has to be of type string');
    }

    controller.changeUsername(req.uwave, req.user.id, req.params.id, req.body.username)
      .then(user => res.status(200).json(user))
      .catch(err => next(err));
  });

  router.put('/:id/avatar', (req, res) => {
    if (!req.body.avatar) {
      return res.status(422).json('avatar is not set');
    }

    if (typeof req.body.avatar !== 'string') {
      return res.status(422).json('avatar has to be of type string');
    }

    if (!req.user.id !== req.params.id && req.user.role < ROLE_MANAGER) {
      return res.status(403).json('you need to be a manager to do this');
    }

    controller.setAvatar(req.uwave, req.user.id, req.params.id, req.body.avatar)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.put('/:id/status', (req, res) => {
    if (typeof req.body.status === 'undefined') {
      return res.status(422).json('status is not set');
    }

    if (typeof req.body.status !== 'number' || isNaN(req.body.status)) {
      return res.status(422).json('status has to be a number and not NaN');
    }

    if (req.user.id !== req.params.id) {
      return res.status(403).json('you can\'t change the status of another user');
    }

    controller.setStatus(req.uwave, req.user.id, req.body.status)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.get('/:id/history', (req, res) => {
    const { page, limit } = req.query;
    controller.getHistory(req.uwave, req.params.id, parseInt(page, 10), parseInt(limit, 10))
    .then(history => res.status(200).json(history))
    .catch(e => handleError(res, e, log));
  });

  return router;
}
<commit_msg>users: allow up to 5 username changes per hour<commit_after>import debug from 'debug';
import createRouter from 'router';

import protect from '../middleware/protect';
import rateLimit from '../middleware/rateLimit';
import * as controller from '../controllers/users';
import { checkFields } from '../utils';
import handleError from '../errors';
import { ROLE_MANAGER, ROLE_MODERATOR } from '../roles';

const log = debug('uwave:api:v1:users');

export default function userRoutes() {
  const router = createRouter();

  router.get('/', protect(ROLE_MANAGER), (req, res) => {
    const { page, limit } = req.query;

    controller.getUsers(req.uwave, parseInt(page, 10), parseInt(limit, 10))
    .then(users => res.status(200).json(users))
    .catch(e => handleError(res, e, log));
  });

  router.get('/:id', (req, res) => {
    controller.getUser(req.uwave, req.params.id)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.post('/:id/ban', protect(ROLE_MODERATOR), (req, res) => {
    if (!checkFields(res, req.body, { time: 'number', exiled: 'boolean' })) {
      return null;
    }
    if (req.user.id === req.params.id) {
      return res.status(403, 'you can\'t ban yourself');
    }
    if (isNaN(req.body.time)) {
      return res.status(422).json('time can only be a number');
    }

    controller.banUser(req.uwave, req.user.id, req.params.id, req.body.time, req.body.exiled)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.delete('/:id/ban', protect(ROLE_MANAGER), (req, res) => {
    if (req.user.id === req.params.id) {
      return res.status(403, 'you can\'t unban yourself');
    }

    controller.banUser(req.uwave, req.user.id, req.params.id, 0, false)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.post('/:id/mute', protect(ROLE_MODERATOR), (req, res) => {
    if (typeof req.body.time === 'undefined') {
      return res.status(422).json('time is not set');
    }
    if (req.user.id === req.params.id) {
      return res.status(403).json('you can\'t mute yourself');
    }

    if (typeof req.body.time !== 'number' || isNaN(req.body.time)) {
      return res.status(422).json('time is not set');
    }

    controller.muteUser(req.uwave, req.user.id, req.params.id, req.body.time)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.delete('/:id/mute', protect(ROLE_MODERATOR), (req, res) => {
    if (req.user.id === req.params.id) {
      return res.status(403, 'you can\'t unmute yourself');
    }

    controller.unmuteUser(req.uwave, req.user.id, req.params.id)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.put('/:id/role', protect(ROLE_MANAGER), (req, res) => {
    if (typeof req.body.role !== 'number' || isNaN(req.body.role)) {
      return res.status(422).json('expected role to be a number');
    }

    if (req.user.role < req.body.role) {
      return res.status(403).json('you can\'t promote users above or equal to your own level');
    }

    controller.changeRole(req.uwave, req.user.id, req.params.id, req.body.role)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.put('/:id/username',
    rateLimit('change-username', {
      max: 5,
      duration: 60 * 60 * 1000,
      error: (_, retryAfter) =>
        `You can only change your username five times per hour. Try again in ${retryAfter}.`
    }),
    (req, res, next) => {
      if (!req.body.username) {
        return res.status(422).json('username is not set');
      }

      if (typeof req.body.username !== 'string') {
        return res.status(422).json('username has to be of type string');
      }

      controller.changeUsername(req.uwave, req.user.id, req.params.id, req.body.username)
        .then(user => res.status(200).json(user))
        .catch(err => next(err));
    }
  );

  router.put('/:id/avatar', (req, res) => {
    if (!req.body.avatar) {
      return res.status(422).json('avatar is not set');
    }

    if (typeof req.body.avatar !== 'string') {
      return res.status(422).json('avatar has to be of type string');
    }

    if (!req.user.id !== req.params.id && req.user.role < ROLE_MANAGER) {
      return res.status(403).json('you need to be a manager to do this');
    }

    controller.setAvatar(req.uwave, req.user.id, req.params.id, req.body.avatar)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.put('/:id/status', (req, res) => {
    if (typeof req.body.status === 'undefined') {
      return res.status(422).json('status is not set');
    }

    if (typeof req.body.status !== 'number' || isNaN(req.body.status)) {
      return res.status(422).json('status has to be a number and not NaN');
    }

    if (req.user.id !== req.params.id) {
      return res.status(403).json('you can\'t change the status of another user');
    }

    controller.setStatus(req.uwave, req.user.id, req.body.status)
    .then(user => res.status(200).json(user))
    .catch(e => handleError(res, e, log));
  });

  router.get('/:id/history', (req, res) => {
    const { page, limit } = req.query;
    controller.getHistory(req.uwave, req.params.id, parseInt(page, 10), parseInt(limit, 10))
    .then(history => res.status(200).json(history))
    .catch(e => handleError(res, e, log));
  });

  return router;
}
<|endoftext|>


<commit_before>const utils = require("../utils");

/** In auspice v1, the `prettyString` function was used extensively to transform values
 * for "nicer" display. v2 JSONs intentially avoid this -- the strings are intended to
 * be displayed as-is. This function is preserved here to aid in converting v1 JSONs
 * to v2 JSONs.
 */
const prettyString = (x, {multiplier = false, trim = 0, camelCase = true, removeComma = false, stripEtAl = false, lowerEtAl = false} = {}) => {
  if (!x && x!== 0) {
    return "";
  }
  if (typeof x === "string") {
    if (trim > 0 && x.length > trim) {
      x = x.slice(0, trim) + "...";
    }
    if (["usvi", "usa", "uk"].indexOf(x.toLowerCase()) !== -1) {
      return x.toUpperCase();
    }
    x = x.replace(/_/g, " ");
    if (camelCase) {
      x = x.replace(/\w\S*/g, (y) => y.charAt(0).toUpperCase() + y.substr(1));
    }
    if (removeComma) {
      x = x.replace(/,/g, "");
    }
    if (lowerEtAl) {
      x = x.replace('Et Al', 'et al');
    }
    if (stripEtAl) {
      x = x.replace('et al.', '').replace('Et Al.', '').replace('et al', '').replace('Et Al', '');
    }
    return x;
  } else if (typeof x === "number") {
    const val = parseFloat(x);
    const magnitude = Math.ceil(Math.log10(Math.abs(val) + 1e-10));
    return multiplier ? val.toFixed(5 - magnitude) + "\u00D7" : val.toFixed(5 - magnitude);
  }
  return x;
};

const formatURLString = (x) => {
  let url = x;
  if (url.startsWith("https_")) {
    url = url.replace(/^https_/, "https:");
  } else if (url.startsWith("http_")) {
    url = url.replace(/^https_/, "http:");
  }
  return url;
};

const traverseTree = (node, cb) => {
  cb(node);
  if (node.children) {
    node.children.forEach((n) => traverseTree(n, cb));
  }
};

const setColorings = (v2, meta) => {
  v2.colorings = [];
  const color_options = meta.color_options;
  for (const [key, value] of Object.entries(color_options)) {
    const coloring = {
      key,
      title: prettyString(value.menuItem) || prettyString(value.legendTitle),
      type: value.type === "continuous" ? "continuous" : "categorical"
    };
    if (value.color_map) {
      coloring.scale = value.color_map.map((s) =>
        [prettyString(s[0], {removeComma: true}), s[1]]
      );
    }
    if (key === "authors") {
      coloring.key = "author";
    }
    v2.colorings.push(coloring);
  }
};

const setAuthorInfo = (v2, meta, tree) => {
  /* v1 had an author_info property & the node.attr.authors property
   * v2 has all the info set on the node itself at node.author
   */
  if (!meta.author_info) {
    return;
  }
  traverseTree(tree, (node) => {
    if (node.attr && node.attr.authors) {
      const v1author = node.attr.authors;
      const v1info = meta.author_info[v1author];
      delete node.attr.authors;
      if (!v1info) return;
      node.attr.author = {};
      if (v1info.title) node.attr.author.title = v1info.title;
      if (v1info.journal) node.attr.author.journal = v1info.journal;
      if (v1info.paper_url) node.attr.author.paper_url = formatURLString(v1info.paper_url);
      node.attr.author.author = prettyString(v1author, {camelCase: false});
      node.attr.author.value = v1author;
    }
  });
};

/**
 * The v1 JSON annotations used 0-based starts for the gene positions and `1`/`-1` for
 * the strand. This function converts those to GFF-like format.
 * @param {object} annotations a mapping of gene name (or "nuc") to information about the feature.
 * The feature information was an object with properties `start`, `end` and `strand`
 */
const convertToGffFormat = (annotations) => {
  for (const name in annotations) { // eslint-disable-line
    // Convert from 0-based BED format to 1-based GFF format for start position
    annotations[name].start += 1;
    // half-open 0-based BED end position is the same as 1-based closed ended GFF position.
    // Represent forward(+) and reverse(-) strands
    annotations[name].strand = annotations[name].strand === 1 ? "+" : "-";
  }
  return annotations;
};

const setMiscMetaProperties = (v2, meta) => {
  // TITLE (required)
  v2.title = meta.title;
  // UPDATED (required)
  v2.updated = meta.updated;
  if (!v2.updated) {
    utils.warn("\"Updated\" field not provided in v1 meta JSON but is required");
  }
  // MAINTAINERS (required)
  if (meta.maintainer) {
    v2.maintainers = [
      {name: meta.maintainer[0], url: meta.maintainer[1]}
    ];
  }

  // (GENOME) ANNOTATIONS
  if (meta.annotations) {
    v2.genome_annotations = convertToGffFormat(meta.annotations);
  }
  // FILTERS
  if (meta.filters) {
    v2.filters = meta.filters;
    if (v2.filters.includes("authors")) {
      v2.filters.splice(v2.filters.indexOf("authors"), 1, "author");
    }
  }
  // PANELS
  if (meta.panels) {
    v2.panels = meta.panels;
  }
  // [DISPLAY_]DEFAULTS (v1 this was `defaults`, v2 is `display_defaults`)
  if (meta.defaults) {
    v2.display_defaults = {};
    const v1v2Fields = [
      ["geoResolution", "geo_resolution"], // i.e. v1: meta.defaults.geoResultion, v2: meta.display_defaults.geo_resolution
      ["colorBy", "color_by"],
      ["distanceMeasure", "distance_measure"],
      ["mapTriplicate", "map_triplicate"],
      ["layout", "layout"]
    ];
    for (const v1v2 of v1v2Fields) {
      if (meta.defaults[v1v2[0]]) {
        v2.display_defaults[v1v2[1]] = meta.defaults[v1v2[0]];
      }
    }
    delete meta.defaults;
  }
  // GEO -> GEO_RESOLUTIONS (note that the shape is different)
  if (meta.geo) {
    v2.geo_resolutions = [];
    for (const [key, demes] of Object.entries(meta.geo)) {
      const prettyDemes = {};
      Object.keys(demes).forEach((location) => {
        prettyDemes[prettyString(location, {removeComma: true})] = demes[location];
      });
      v2.geo_resolutions.push({key, demes: prettyDemes});
    }
  }
};

const setVaccineChoicesOnNodes = (meta, tree) => {
  if (meta.vaccine_choices) {
    traverseTree(tree, (n) => {
      if (meta.vaccine_choices[n.strain]) {
        n.vaccine = {
          /* in v1 JSONs the only date provided was (typically) the selecion date,
          but note that the cross is always drawn on the tip (i.e. sample date) */
          selection_date: meta.vaccine_choices[n.strain]
        };
      }
    });
  }
  /* move `node.serum` (if value is true) to `node.vaccine.serum` */
  traverseTree(tree, (n) => {
    if (n.serum) {
      if (!n.vaccine) n.vaccine = {};
      n.vaccine.serum = true;
    }
  });
};

const storeTreeAsV2 = (v2, tree) => {
  // const attrsWhichRemain = new Set();
  // const propsRemoved = new Set();

  const allowedProperties = ["name", "div", "num_date", "vaccine", "labels", "hidden", "mutations", "url", "accession", "traits", "children", "author"];
  const attrsToIgnore = ["clock_length", "date", "raw_date", "strain"];

  traverseTree(tree, (node) => {
    // convert node.strain to node.name
    if (node.strain) {
      node.name = node.strain;
      delete node.strain;
    }
    // vaccine: already set (see above)
    if (node.attr) {
      // author: (key modified previously) needs to move to property on node
      if (node.attr.author) {
        node.attr.author.value = prettyString(node.attr.author.value, { lowerEtAl: true });
        node.author = node.attr.author;
        delete node.attr.author;
      }
      // accession, likewise moves to the node (from `node.attr.accession`)
      if (node.attr.accession) {
        node.accession = node.attr.accession;
        delete node.attr.accession;
      }
      // url, likewise moves to the node (from `node.attr.accession`)
      if (node.attr.url) {
        node.url = node.attr.url;
        delete node.attr.url;
      }
      // branch labels for clade (this behaviour was hardcoded into auspice)
      if (node.attr.clade_name || node.attr.clade_annotation) {
        if (!node.labels) node.labels = {};
        node.labels.clade = node.attr.clade_annotation || node.attr.clade_name;
        delete node.attr.clade_name;
        delete node.attr.clade_annotation;
      }
      // branch labels for "aa" (this behaviour was hardcoded into auspice)
      if (node.aa_muts) {
        const muts = [];
        for (const aa in node.aa_muts) { // eslint-disable-line
          if (node.aa_muts[aa].length) {
            muts.push(`${aa}: ${node.aa_muts[aa].join(", ")}`);
          }
        }
        if (muts.length) {
          if (!node.labels) node.labels = {};
          node.labels.aa = muts.join("; ");
        }
      }
      // num_date
      if (node.attr.num_date) {
        node.num_date = {value: node.attr.num_date};
        delete node.attr.num_date;
        if (node.attr.num_date_confidence) {
          node.num_date.confidence = node.attr.num_date_confidence;
          delete node.attr.num_date_confidence;
        }
      }
      // div (NOTE div can be 0)
      if (node.attr.div !== undefined) {
        node.div = node.attr.div;
        delete node.attr.div;
      }

      /* Transfer the remaining `node.attr[x]` to `node.traits[x]` (different shape) */
      const traitKeys = Object.keys(node.attr)
        .filter((a) => !a.endsWith("_entropy") && !a.endsWith("_confidence"))
        .filter((a) => !attrsToIgnore.includes(a));
      if (traitKeys.length) node.traits = {};
      const traitsToPretty = ["country", "region"]
      for (const trait of traitKeys) {
        const data = traitsToPretty.includes(trait) ? {value: prettyString(node.attr[trait], {removeComma: true})} : {value: node.attr[trait]};
        if (node.attr[`${trait}_confidence`]) {
          Object.keys(node.attr[`${trait}_confidence`]).forEach((key) => {
            const newKey = prettyString(key);
            node.attr[`${trait}_confidence`][newKey] = node.attr[`${trait}_confidence`][key];
            delete node.attr[`${trait}_confidence`][key];
          });
          data.confidence = node.attr[`${trait}_confidence`];
        }
        if (node.attr[`${trait}_entropy`]) {
          data.entropy = node.attr[`${trait}_entropy`];
        }
        node.traits[trait] = data;
      }
      // Object.keys(node.attr).forEach((a) => {attrsWhichRemain.add(a);});
    }

    // NUC + AA MUTATIONS
    if (node.muts || node.aa_muts) {
      node.mutations = {};
      if (node.aa_muts) node.mutations = node.aa_muts;
      if (node.muts) node.mutations.nuc = node.muts;
      delete node.muts;
      delete node.aa_muts;
    }

    // REMOVE NON-V2 PROPS FROM EACH NODE
    Object.keys(node)
      .filter((prop) => !allowedProperties.includes(prop))
      .forEach((prop) => {
        // propsRemoved.add(prop);
        delete node[prop];
      });

  });

  // console.log("These attrs were left over:", attrsWhichRemain);
  // console.log("Props removed (from v1 tree nodes):", propsRemoved);

  v2.tree = tree;
};

const convertFromV1 = ({tree, meta, treeName}) => {
  const v2 = {
    version: "2.0",
    meta: {}
  };
  setColorings(v2["meta"], meta);
  setMiscMetaProperties(v2["meta"], meta);
  setAuthorInfo(v2["meta"], meta, tree);
  setVaccineChoicesOnNodes(meta, tree);
  storeTreeAsV2(v2, tree);
  if (treeName) {
    v2["meta"].tree_name = treeName;
  }
  return v2;
};

module.exports = {
  convertFromV1
};
<commit_msg>bugfix in v1-v2 JSON conversion<commit_after>const utils = require("../utils");

/** In auspice v1, the `prettyString` function was used extensively to transform values
 * for "nicer" display. v2 JSONs intentially avoid this -- the strings are intended to
 * be displayed as-is. This function is preserved here to aid in converting v1 JSONs
 * to v2 JSONs.
 */
const prettyString = (x, {multiplier = false, trim = 0, camelCase = true, removeComma = false, stripEtAl = false, lowerEtAl = false} = {}) => {
  if (!x && x!== 0) {
    return "";
  }
  if (typeof x === "string") {
    if (trim > 0 && x.length > trim) {
      x = x.slice(0, trim) + "...";
    }
    if (["usvi", "usa", "uk"].indexOf(x.toLowerCase()) !== -1) {
      return x.toUpperCase();
    }
    x = x.replace(/_/g, " ");
    if (camelCase) {
      x = x.replace(/\w\S*/g, (y) => y.charAt(0).toUpperCase() + y.substr(1));
    }
    if (removeComma) {
      x = x.replace(/,/g, "");
    }
    if (lowerEtAl) {
      x = x.replace('Et Al', 'et al');
    }
    if (stripEtAl) {
      x = x.replace('et al.', '').replace('Et Al.', '').replace('et al', '').replace('Et Al', '');
    }
    return x;
  } else if (typeof x === "number") {
    const val = parseFloat(x);
    const magnitude = Math.ceil(Math.log10(Math.abs(val) + 1e-10));
    return multiplier ? val.toFixed(5 - magnitude) + "\u00D7" : val.toFixed(5 - magnitude);
  }
  return x;
};

const formatURLString = (x) => {
  let url = x;
  if (url.startsWith("https_")) {
    url = url.replace(/^https_/, "https:");
  } else if (url.startsWith("http_")) {
    url = url.replace(/^https_/, "http:");
  }
  return url;
};

const traverseTree = (node, cb) => {
  cb(node);
  if (node.children) {
    node.children.forEach((n) => traverseTree(n, cb));
  }
};

const setColorings = (v2, meta) => {
  v2.colorings = [];
  const color_options = meta.color_options;
  for (const [key, value] of Object.entries(color_options)) {
    const coloring = {
      key,
      title: prettyString(value.menuItem) || prettyString(value.legendTitle),
      type: value.type === "continuous" ? "continuous" : "categorical"
    };
    if (value.color_map) {
      coloring.scale = value.color_map.map((s) =>
        [prettyString(s[0], {removeComma: true}), s[1]]
      );
    }
    if (key === "authors") {
      coloring.key = "author";
    }
    v2.colorings.push(coloring);
  }
};

const setAuthorInfo = (v2, meta, tree) => {
  /* v1 had an author_info property & the node.attr.authors property
   * v2 has all the info set on the node itself at node.author
   */
  if (!meta.author_info) {
    return;
  }
  traverseTree(tree, (node) => {
    if (node.attr && node.attr.authors) {
      const v1author = node.attr.authors;
      const v1info = meta.author_info[v1author];
      delete node.attr.authors;
      if (!v1info) return;
      node.attr.author = {};
      if (v1info.title) node.attr.author.title = v1info.title;
      if (v1info.journal) node.attr.author.journal = v1info.journal;
      if (v1info.paper_url) node.attr.author.paper_url = formatURLString(v1info.paper_url);
      node.attr.author.author = prettyString(v1author, {camelCase: false});
      node.attr.author.value = v1author;
    }
  });
};

/**
 * The v1 JSON annotations used 0-based starts for the gene positions and `1`/`-1` for
 * the strand. This function converts those to GFF-like format.
 * @param {object} annotations a mapping of gene name (or "nuc") to information about the feature.
 * The feature information was an object with properties `start`, `end` and `strand`
 */
const convertToGffFormat = (annotations) => {
  for (const name in annotations) { // eslint-disable-line
    // Convert from 0-based BED format to 1-based GFF format for start position
    annotations[name].start += 1;
    // half-open 0-based BED end position is the same as 1-based closed ended GFF position.
    // Represent forward(+) and reverse(-) strands
    annotations[name].strand = annotations[name].strand === 1 ? "+" : "-";
  }
  return annotations;
};

const setMiscMetaProperties = (v2, meta) => {
  // TITLE (required)
  v2.title = meta.title;
  // UPDATED (required)
  v2.updated = meta.updated;
  if (!v2.updated) {
    utils.warn("\"Updated\" field not provided in v1 meta JSON but is required");
  }
  // MAINTAINERS (required)
  if (meta.maintainer) {
    v2.maintainers = [
      {name: meta.maintainer[0], url: meta.maintainer[1]}
    ];
  }

  // (GENOME) ANNOTATIONS
  if (meta.annotations) {
    v2.genome_annotations = convertToGffFormat(meta.annotations);
  }
  // FILTERS
  if (meta.filters) {
    v2.filters = meta.filters;
    if (v2.filters.includes("authors")) {
      v2.filters.splice(v2.filters.indexOf("authors"), 1, "author");
    }
  }
  // PANELS
  if (meta.panels) {
    v2.panels = meta.panels;
  }
  // [DISPLAY_]DEFAULTS (v1 this was `defaults`, v2 is `display_defaults`)
  if (meta.defaults) {
    v2.display_defaults = {};
    const v1v2Fields = [
      ["geoResolution", "geo_resolution"], // i.e. v1: meta.defaults.geoResultion, v2: meta.display_defaults.geo_resolution
      ["colorBy", "color_by"],
      ["distanceMeasure", "distance_measure"],
      ["mapTriplicate", "map_triplicate"],
      ["layout", "layout"]
    ];
    for (const v1v2 of v1v2Fields) {
      if (meta.defaults[v1v2[0]]) {
        v2.display_defaults[v1v2[1]] = meta.defaults[v1v2[0]];
      }
    }
    delete meta.defaults;
  }
  // GEO -> GEO_RESOLUTIONS (note that the shape is different)
  if (meta.geo) {
    v2.geo_resolutions = [];
    for (const [key, demes] of Object.entries(meta.geo)) {
      const prettyDemes = {};
      Object.keys(demes).forEach((location) => {
        prettyDemes[prettyString(location, {removeComma: true})] = demes[location];
      });
      v2.geo_resolutions.push({key, demes: prettyDemes});
    }
  }
};

const setVaccineChoicesOnNodes = (meta, tree) => {
  if (meta.vaccine_choices) {
    traverseTree(tree, (n) => {
      if (meta.vaccine_choices[n.strain]) {
        n.vaccine = {
          /* in v1 JSONs the only date provided was (typically) the selecion date,
          but note that the cross is always drawn on the tip (i.e. sample date) */
          selection_date: meta.vaccine_choices[n.strain]
        };
      }
    });
  }
  /* move `node.serum` (if value is true) to `node.vaccine.serum` */
  traverseTree(tree, (n) => {
    if (n.serum) {
      if (!n.vaccine) n.vaccine = {};
      n.vaccine.serum = true;
    }
  });
};

const storeTreeAsV2 = (v2, tree) => {
  // const attrsWhichRemain = new Set();
  // const propsRemoved = new Set();

  const allowedProperties = ["name", "div", "num_date", "vaccine", "labels", "hidden", "mutations", "url", "accession", "traits", "children", "author"];
  const attrsToIgnore = ["clock_length", "date", "raw_date", "strain"];

  traverseTree(tree, (node) => {
    // convert node.strain to node.name
    if (node.strain) {
      node.name = node.strain;
      delete node.strain;
    }
    // vaccine: already set (see above)
    if (node.attr) {
      // author: (key modified previously) needs to move to property on node
      if (node.attr.author) {
        node.attr.author.value = prettyString(node.attr.author.value, { lowerEtAl: true });
        node.author = node.attr.author;
        delete node.attr.author;
      }
      // accession, likewise moves to the node (from `node.attr.accession`)
      if (node.attr.accession) {
        node.accession = node.attr.accession;
        delete node.attr.accession;
      }
      // url, likewise moves to the node (from `node.attr.accession`)
      if (node.attr.url) {
        node.url = node.attr.url;
        delete node.attr.url;
      }
      // branch labels for clade (this behaviour was hardcoded into auspice)
      if (node.attr.clade_name || node.attr.clade_annotation) {
        if (!node.labels) node.labels = {};
        node.labels.clade = node.attr.clade_annotation || node.attr.clade_name;
        delete node.attr.clade_name;
        delete node.attr.clade_annotation;
      }
      // branch labels for "aa" (this behaviour was hardcoded into auspice)
      if (node.aa_muts) {
        const muts = [];
        for (const aa in node.aa_muts) { // eslint-disable-line
          if (node.aa_muts[aa].length) {
            muts.push(`${aa}: ${node.aa_muts[aa].join(", ")}`);
          }
        }
        if (muts.length) {
          if (!node.labels) node.labels = {};
          node.labels.aa = muts.join("; ");
        }
      }
      // num_date
      if (node.attr.num_date) {
        node.num_date = {value: node.attr.num_date};
        delete node.attr.num_date;
        if (node.attr.num_date_confidence) {
          node.num_date.confidence = node.attr.num_date_confidence;
          delete node.attr.num_date_confidence;
        }
      }
      // div (NOTE div can be 0)
      if (node.attr.div !== undefined) {
        node.div = node.attr.div;
        delete node.attr.div;
      }

      /* Transfer the remaining `node.attr[x]` to `node.traits[x]` (different shape) */
      const traitKeys = Object.keys(node.attr)
        .filter((a) => !a.endsWith("_entropy") && !a.endsWith("_confidence"))
        .filter((a) => !(attrsToIgnore.includes(a) || allowedProperties.includes(a) || a==="gt"));
      if (traitKeys.length) node.traits = {};
      for (const trait of traitKeys) {
        const data = {value: prettyString(node.attr[trait], {removeComma: true})};
        if (node.attr[`${trait}_confidence`]) {
          Object.keys(node.attr[`${trait}_confidence`]).forEach((key) => {
            const newKey = prettyString(key);
            node.attr[`${trait}_confidence`][newKey] = node.attr[`${trait}_confidence`][key];
            delete node.attr[`${trait}_confidence`][key];
          });
          data.confidence = node.attr[`${trait}_confidence`];
        }
        if (node.attr[`${trait}_entropy`]) {
          data.entropy = node.attr[`${trait}_entropy`];
        }
        node.traits[trait] = data;
      }
      // Object.keys(node.attr).forEach((a) => {attrsWhichRemain.add(a);});
    }

    // NUC + AA MUTATIONS
    if (node.muts || node.aa_muts) {
      node.mutations = {};
      if (node.aa_muts) node.mutations = node.aa_muts;
      if (node.muts) node.mutations.nuc = node.muts;
      delete node.muts;
      delete node.aa_muts;
    }

    // REMOVE NON-V2 PROPS FROM EACH NODE
    Object.keys(node)
      .filter((prop) => !allowedProperties.includes(prop))
      .forEach((prop) => {
        // propsRemoved.add(prop);
        delete node[prop];
      });

  });

  // console.log("These attrs were left over:", attrsWhichRemain);
  // console.log("Props removed (from v1 tree nodes):", propsRemoved);

  v2.tree = tree;
};

const convertFromV1 = ({tree, meta, treeName}) => {
  const v2 = {
    version: "2.0",
    meta: {}
  };
  setColorings(v2["meta"], meta);
  setMiscMetaProperties(v2["meta"], meta);
  setAuthorInfo(v2["meta"], meta, tree);
  setVaccineChoicesOnNodes(meta, tree);
  storeTreeAsV2(v2, tree);
  if (treeName) {
    v2["meta"].tree_name = treeName;
  }
  return v2;
};

module.exports = {
  convertFromV1
};
<|endoftext|>


<commit_before>// Description:
//   遊ぶ
// Commands:
//   quest attack {user} - attack
//   quest cure {user} - cure
//   quest raise|ザオリク {user} - ふっかつ
//

var Cron = require("cron").CronJob;
var DarkQuest   = require("node-quest");
var Game        = DarkQuest.Game;
var Equipment   = DarkQuest.Equipment;
var Parameter   = DarkQuest.Parameter;
var Weapon      = DarkQuest.Weapon;
var User        = DarkQuest.User;
var Status      = DarkQuest.Status;

var DarkGame    = require("./lib/DarkGame.js");
var NegativeWords = require("./lib/NegativeWords.js");
var NegativeWordsRepository = require("./lib/NegativeWordsRepository.js");

var MAX_HP                  = 1000;
var HUBOT_NODE_QUEST_USERS_HP  = "HUBOT_NODE_QUEST_USERS_HP";

var saveHp = function(robot, users) {
    var us = {};
    users.forEach(function(u) {
        us[u.id] = u.status.currentHp;
    });
    robot.brain.set(HUBOT_NODE_QUEST_USERS_HP, us);
};

var toGameUser = function(users, savedUsers) {
    return Object.keys(users).map(function(id) {
        var user    = users[id];
        var eq      = new Equipment(new Weapon(30, 12));
        var p       = new Parameter(20, 10);
        var hp      = (savedUsers && savedUsers[id] && !isNaN(savedUsers[id])) ? savedUsers[id] : MAX_HP;
        var st      = new Status(game, hp);
        return new User(user.id, user.name, st, eq, p);
    });
};
var game        = new Game(0, MAX_HP);
var darkGame    = new DarkGame(game);
var shakai      = new User(0, "'社会'", game.defaultStatus(), new Equipment(new Weapon(30, 12)), game.defaultStatus());

var resetUserHp = function() {
    game.users.forEach(function(u) {
        u.fullCare(u);
    });
};

new Cron("0 0 * * 1", resetUserHp, null, true, "Asia/Tokyo");

module.exports = function(robot) {
    var negativeWordsRepository = new NegativeWordsRepository("http://yamiga.waka.ru.com/json/darkbot.json");
    var negativeWords = new NegativeWords(negativeWordsRepository, robot.logger);
    var alreadyLoaded = false;
    robot.brain.on("loaded", function(data) {
        if (alreadyLoaded) {
            return
        }
        var savedUsers  = robot.brain.get(HUBOT_NODE_QUEST_USERS_HP);
        savedUsers      = savedUsers ? savedUsers : {};
        var users       = robot.adapter.client ? toGameUser(robot.adapter.client.users, savedUsers) : [];
        game.setUsers(users);
        alreadyLoaded = true;
    });

    robot.hear(/^attack (.+)/i, function(res){
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);

        var result = darkGame.attack(actor, target)
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp(robot, game.users);
    });

    robot.hear(/^cure (.+)/i, function(res){
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);
        var result = darkGame.cure(actor, target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp(robot, game.users);
    });

    robot.hear(/^ザオリク (.+)/i, function(res) {
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);
        var result = darkGame.raise(actor, target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp(robot, game.users);
    });

    robot.hear(/^raise (.+)/i, function(res) {
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);

        var result = darkGame.raise(actor, target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp(robot, game.users);
    });

    robot.hear(/^status (.+)/i, function(res) {
        var target = game.findUser(res.match[1]);
        var result = darkGame.status(target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
    });

    robot.hear(/.*/, function(res) {
        var tokens = res.message.tokenized
        var basicNorms  = tokens ? tokens.map(function(t) {
            return t.basic_form;
        }) : [];
        var negativeCount = negativeWords.countNegativeWords(basicNorms);
        var target = game.findUser(res.message.user.name);
        if(negativeCount == 1) {
            var result = darkGame.attack(shakai, target, negativeCount)
            result.messages.forEach(function(m) {
                res.send(m);
            });
        } else if(negativeCount > 0) {
            var result = darkGame.multipleAttack(shakai, target, negativeCount)
            result.messages.forEach(function(m) {
                res.send(m);
            });
        };
        saveHp(robot, game.users);
    });
}
<commit_msg>Move functions<commit_after>// Description:
//   遊ぶ
// Commands:
//   quest attack {user} - attack
//   quest cure {user} - cure
//   quest raise|ザオリク {user} - ふっかつ
//

var Cron = require("cron").CronJob;
var DarkQuest   = require("node-quest");
var Game        = DarkQuest.Game;
var Equipment   = DarkQuest.Equipment;
var Parameter   = DarkQuest.Parameter;
var Weapon      = DarkQuest.Weapon;
var User        = DarkQuest.User;
var Status      = DarkQuest.Status;

var DarkGame    = require("./lib/DarkGame.js");
var NegativeWords = require("./lib/NegativeWords.js");
var NegativeWordsRepository = require("./lib/NegativeWordsRepository.js");
var MAX_HP                  = 1000;
var HUBOT_NODE_QUEST_USERS_HP  = "HUBOT_NODE_QUEST_USERS_HP";

var toGameUser = function(users, savedUsers) {
    return Object.keys(users).map(function(id) {
        var user    = users[id];
        var eq      = new Equipment(new Weapon(30, 12));
        var p       = new Parameter(20, 10);
        var hp      = (savedUsers && savedUsers[id] && !isNaN(savedUsers[id])) ? savedUsers[id] : MAX_HP;
        var st      = new Status(game, hp);
        return new User(user.id, user.name, st, eq, p);
    });
};
var game        = new Game(0, MAX_HP);
var darkGame    = new DarkGame(game);
var shakai      = new User(0, "'社会'", game.defaultStatus(), new Equipment(new Weapon(30, 12)), game.defaultStatus());

module.exports = function(robot) {
    var resetUserHp = function() {
        game.users.forEach(function(u) {
            u.fullCare(u);
        });
        saveHp();
    };

    new Cron("0 0 * * 1", resetUserHp, null, true, "Asia/Tokyo");
    var saveHp = function() {
        var us = {};
        game.users.forEach(function(u) {
            us[u.id] = u.status.currentHp;
        });
        robot.brain.set(HUBOT_NODE_QUEST_USERS_HP, us);
    };

    var negativeWordsRepository = new NegativeWordsRepository("http://yamiga.waka.ru.com/json/darkbot.json");
    var negativeWords = new NegativeWords(negativeWordsRepository, robot.logger);
    var alreadyLoaded = false;
    robot.brain.on("loaded", function(data) {
        if (alreadyLoaded) {
            return
        }
        var savedUsers  = robot.brain.get(HUBOT_NODE_QUEST_USERS_HP);
        savedUsers      = savedUsers ? savedUsers : {};
        var users       = robot.adapter.client ? toGameUser(robot.adapter.client.users, savedUsers) : [];
        game.setUsers(users);
        alreadyLoaded = true;
    });

    robot.hear(/^attack (.+)/i, function(res){
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);

        var result = darkGame.attack(actor, target)
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp();
    });

    robot.hear(/^cure (.+)/i, function(res){
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);
        var result = darkGame.cure(actor, target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp();
    });

    robot.hear(/^ザオリク (.+)/i, function(res) {
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);
        var result = darkGame.raise(actor, target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp();
    });

    robot.hear(/^raise (.+)/i, function(res) {
        var actor = game.findUser(res.message.user.name);
        var target = game.findUser(res.match[1]);

        var result = darkGame.raise(actor, target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
        saveHp();
    });

    robot.hear(/^status (.+)/i, function(res) {
        var target = game.findUser(res.match[1]);
        var result = darkGame.status(target);
        result.messages.forEach(function(m) {
            res.send(m);
        });
    });

    robot.hear(/.*/, function(res) {
        var tokens = res.message.tokenized
        var basicNorms  = tokens ? tokens.map(function(t) {
            return t.basic_form;
        }) : [];
        var negativeCount = negativeWords.countNegativeWords(basicNorms);
        var target = game.findUser(res.message.user.name);
        if(negativeCount == 1) {
            var result = darkGame.attack(shakai, target, negativeCount)
            result.messages.forEach(function(m) {
                res.send(m);
            });
        } else if(negativeCount > 0) {
            var result = darkGame.multipleAttack(shakai, target, negativeCount)
            result.messages.forEach(function(m) {
                res.send(m);
            });
        };
        saveHp();
    });
}
<|endoftext|>


<commit_before>// Description:
//   遊ぶ
// Commands:
//   attack {user} - attack
//   status {user} - HP,MP,使える魔法の確認
//

"use strict"

const DarkGame  = require("../game/DarkGame.js");
const UserRepository  = require("../game/UserRepository.js");
const BitnessRepository =require("../game/BitnessRepository.js");

module.exports = (robot) => {
    const darkGame = new DarkGame(
            new UserRepository(
                robot.brain,
                robot.adapter.client ? robot.adapter.client.users : {}
                ),
            new BitnessRepository(robot.brain)
            );

    robot.brain.once("loaded", (data) => darkGame.loadUsers());

    robot.hear(/^attack (.+)/i, (res) => {
        darkGame.attackToUser(res.message.user.name.replace(/@/g, ""), res.match[1].replace(/@/g, ""), (m) => res.send(m));
    });

    robot.hear(/^status (.+)/i, (res) => {
        darkGame.statusOfUser(res.match[1].replace(/@/g, ""), (m) => res.send(m))
    });

    robot.hear(/^pray$/i, (res) => {
        darkGame.prayToPriest(res.message.user.name.replace(/@/g, ""), (m) => res.send(m));
    });

    robot.hear(/^神父 (.+)/, (res) => {
        darkGame.prayToPriest(res.message.user.name.replace(/@/g, ""), (m) => res.send(m));
    });

    robot.hear(/.*/, (res) => {
        darkGame.takePainByWorld(
                res.message.user.name.replace(/@/g, ""),
                (res.message.tokenized || []),
                (m) => res.send(m)
                )
    });

    robot.hear(/(.+)/, (res) => {
        const messages = (res.message.text || "").split(" ");
        (messages.length >= 2) && darkGame.castToUser(
                res.message.user.name.replace(/@/g, ""),
                messages[1].replace(/@/g, ""),
                messages[0],
                (m) => res.send(m)
                );
    });
}
<commit_msg>Add trim to game apis<commit_after>// Description:
//   遊ぶ
// Commands:
//   attack {user} - attack
//   status {user} - HP,MP,使える魔法の確認
//

"use strict"

const DarkGame  = require("../game/DarkGame.js");
const UserRepository  = require("../game/UserRepository.js");
const BitnessRepository =require("../game/BitnessRepository.js");

module.exports = (robot) => {
    const darkGame = new DarkGame(
            new UserRepository(
                robot.brain,
                robot.adapter.client ? robot.adapter.client.users : {}
                ),
            new BitnessRepository(robot.brain)
            );

    robot.brain.once("loaded", (data) => darkGame.loadUsers());

    robot.hear(/^attack (.+)/i, (res) => {
        darkGame.attackToUser(res.message.user.name.replace(/@/g, ""), res.match[1].trim().replace(/@/g, ""), (m) => res.send(m));
    });

    robot.hear(/^status (.+)/i, (res) => {
        darkGame.statusOfUser(res.match[1].trim().replace(/@/g, ""), (m) => res.send(m))
    });

    robot.hear(/^pray$/i, (res) => {
        darkGame.prayToPriest(res.message.user.name.replace(/@/g, ""), (m) => res.send(m));
    });

    robot.hear(/^神父 (.+)/, (res) => {
        darkGame.prayToPriest(res.message.user.name.replace(/@/g, ""), (m) => res.send(m));
    });

    robot.hear(/.*/, (res) => {
        darkGame.takePainByWorld(
                res.message.user.name.replace(/@/g, ""),
                (res.message.tokenized || []),
                (m) => res.send(m)
                )
    });

    robot.hear(/(.+)/, (res) => {
        const messages = (res.message.text || "").split(" ");
        (messages.length >= 2) && darkGame.castToUser(
                res.message.user.name.replace(/@/g, ""),
                messages[1].trim().replace(/@/g, ""),
                messages[0],
                (m) => res.send(m)
                );
    });
}
<|endoftext|>