<commit_before>const winston = require('winston');
const url = require('url');
const { MongoClient } = require('mongodb');
const logger = require('./lib/logger');
const settings = require('./lib/settings');

const mongodbConnection = settings.mongodbServerUrl;
const mongoPathName = url.parse(mongodbConnection).pathname;
const dbName = mongoPathName.substring(mongoPathName.lastIndexOf('/') + 1);

const CONNECT_OPTIONS = {
	useNewUrlParser: true
};

const DEFAULT_LANGUAGE = 'english';

const addPage = async (db, pageObject) => {
	const count = await db
		.collection('pages')
		.countDocuments({ slug: pageObject.slug });
	const docExists = +count > 0;
	if (!docExists) {
		winston.info(`Added page: /${pageObject.slug}`);
	}
};

const addAllPages = async db => {
	await addPage(db, {
		slug: '',
		meta_title: 'Home',
		enabled: true,
		is_system: true
	});
	await addPage(db, {
		slug: 'checkout',
		meta_title: 'Checkout',
		enabled: true,
		is_system: true
	});
	await addPage(db, {
		slug: 'checkout-success',
		meta_title: 'Thank You!',
		enabled: true,
		is_system: true
	});
};

const createIndex = (db, collectionName, fields, options) =>
	db.collection(collectionName).createIndex(fields, options);

const createAllIndexes = async db => {
	const pagesIndexes = await db
		.collection('pages')
		.listIndexes()
		.toArray();

	if (pagesIndexes.length === 1) {
		await createIndex(db, 'pages', { enabled: 1 });
		await createIndex(db, 'pages', { slug: 1 });
	}

	const productCategoriesIndexes = await db
		.collection('productCategories')
		.listIndexes()
		.toArray();

	if (productCategoriesIndexes.length === 1) {
		await createIndex(db, 'productCategories', { enabled: 1 });
		await createIndex(db, 'productCategories', { slug: 1 });
	}

	const productsIndexes = await db
		.collection('products')
		.listIndexes()
		.toArray();

	if (productsIndexes.length === 1) {
		await createIndex(db, 'products', { slug: 1 });
		await createIndex(db, 'products', { enabled: 1 });
		await createIndex(db, 'products', { category_id: 1 });
		await createIndex(db, 'products', { sku: 1 });
		await createIndex(db, 'products', {
			'attributes.name': 1,
			'attributes.value': 1
		});
		await createIndex(
			db,
			'products',
			{
				name: 'text',
				description: 'text'
			},
			{ default_language: DEFAULT_LANGUAGE, name: 'textIndex' }
		);
	}

	const customersIndexes = await db
		.collection('customers')
		.listIndexes()
		.toArray();

	if (customersIndexes.length === 1) {
		await createIndex(db, 'customers', { group_id: 1 });
		await createIndex(db, 'customers', { email: 1 });
		await createIndex(db, 'customers', { mobile: 1 });
		await createIndex(
			db,
			'customers',
			{
				full_name: 'text',
				'addresses.address1': 'text'
			},
			{ default_language: DEFAULT_LANGUAGE, name: 'textIndex' }
		);
	}

	const ordersIndexes = await db
		.collection('orders')
		.listIndexes()
		.toArray();

	if (ordersIndexes.length === 1) {
		await createIndex(db, 'orders', { draft: 1 });
		await createIndex(db, 'orders', { number: 1 });
		await createIndex(db, 'orders', { customer_id: 1 });
		await createIndex(db, 'orders', { email: 1 });
		await createIndex(db, 'orders', { mobile: 1 });
		await createIndex(
			db,
			'orders',
			{
				'shipping_address.full_name': 'text',
				'shipping_address.address1': 'text'
			},
			{ default_language: DEFAULT_LANGUAGE, name: 'textIndex' }
		);
	}
};

(async () => {
	let client = null;
	let db = null;

	try {
		client = await MongoClient.connect(
			mongodbConnection,
			CONNECT_OPTIONS
		);
		db = client.db(dbName);
		winston.info('1. MongoDB connected successfully');
	} catch (e) {
		winston.error(`MongoDB connection was failed. ${e.message}`);
		return;
	}

	await addAllPages(db);
	winston.info('2. Pages added');

	await createAllIndexes(db);
	winston.info('3. Indexes created');

	client.close();
})();
<commit_msg>Added more steps to Setup<commit_after>const winston = require('winston');
const url = require('url');
const { MongoClient } = require('mongodb');
const logger = require('./lib/logger');
const settings = require('./lib/settings');

const mongodbConnection = settings.mongodbServerUrl;
const mongoPathName = url.parse(mongodbConnection).pathname;
const dbName = mongoPathName.substring(mongoPathName.lastIndexOf('/') + 1);

const CONNECT_OPTIONS = {
	useNewUrlParser: true
};

const DEFAULT_LANGUAGE = 'english';

const addPage = async (db, pageObject) => {
	const count = await db
		.collection('pages')
		.countDocuments({ slug: pageObject.slug });
	const docExists = +count > 0;
	if (!docExists) {
		await db.collection('pages').insertOne(pageObject);
		winston.info(`- Added page: /${pageObject.slug}`);
	}
};

const addAllPages = async db => {
	await addPage(db, {
		slug: '',
		meta_title: 'Home',
		enabled: true,
		is_system: true
	});
	await addPage(db, {
		slug: 'checkout',
		meta_title: 'Checkout',
		enabled: true,
		is_system: true
	});
	await addPage(db, {
		slug: 'checkout-success',
		meta_title: 'Thank You!',
		enabled: true,
		is_system: true
	});
	await addPage(db, {
		slug: 'about',
		meta_title: 'About us',
		enabled: true,
		is_system: false
	});
};

const addAllProducts = async db => {
	const productCategoriesCount = await db
		.collection('productCategories')
		.countDocuments({});

	const productsCount = await db.collection('products').countDocuments({});

	const productsNotExists = productCategoriesCount === 0 && productsCount === 0;

	if (productsNotExists) {
		const catA = await db.collection('productCategories').insertOne({
			name: 'Category A',
			slug: 'category-a',
			image: '',
			parent_id: null,
			enabled: true
		});

		const catB = await db.collection('productCategories').insertOne({
			name: 'Category B',
			slug: 'category-b',
			image: '',
			parent_id: null,
			enabled: true
		});

		const catC = await db.collection('productCategories').insertOne({
			name: 'Category C',
			slug: 'category-c',
			image: '',
			parent_id: null,
			enabled: true
		});

		const catA1 = await db.collection('productCategories').insertOne({
			name: 'Subcategory 1',
			slug: 'category-a-1',
			image: '',
			parent_id: catA.insertedId,
			enabled: true
		});

		const catA2 = await db.collection('productCategories').insertOne({
			name: 'Subcategory 2',
			slug: 'category-a-2',
			image: '',
			parent_id: catA.insertedId,
			enabled: true
		});

		const catA3 = await db.collection('productCategories').insertOne({
			name: 'Subcategory 3',
			slug: 'category-a-3',
			image: '',
			parent_id: catA.insertedId,
			enabled: true
		});

		await db.collection('products').insertOne({
			name: 'Product A',
			slug: 'product-a',
			category_id: catA.insertedId,
			regular_price: 950,
			stock_quantity: 1,
			enabled: true,
			discontinued: false,
			attributes: [
				{ name: 'Brand', value: 'Brand A' },
				{ name: 'Size', value: 'M' }
			]
		});

		await db.collection('products').insertOne({
			name: 'Product B',
			slug: 'product-b',
			category_id: catA.insertedId,
			regular_price: 1250,
			stock_quantity: 1,
			enabled: true,
			discontinued: false,
			attributes: [
				{ name: 'Brand', value: 'Brand B' },
				{ name: 'Size', value: 'L' }
			]
		});

		winston.info('- Added products');
	}
};

const addEmailTemplates = async db => {
	const emailTemplatesCount = await db
		.collection('emailTemplates')
		.countDocuments({ name: 'order_confirmation' });
	const emailTemplatesNotExists = emailTemplatesCount === 0;
	if (emailTemplatesNotExists) {
		await db.collection('emailTemplates').insertOne({
			name: 'order_confirmation',
			subject: 'Order confirmation',
			body: `

Order number: {{number}}

Shipping method: {{shipping_method}}

Payment method: {{payment_method}}

			  Shipping to

			  Full name: {{shipping_address.full_name}}

			  Address 1: {{shipping_address.address1}}

			  Address 2: {{shipping_address.address2}}

			  Postal code: {{shipping_address.postal_code}}

			  City: {{shipping_address.city}}

			  State: {{shipping_address.state}}

			  Phone: {{shipping_address.phone}}

				Item
				Price
				Qty
				Total

			  {{#each items}}

				{{name}}
{{variant_name}}
				$ {{price}}
				{{quantity}}
				$ {{price_total}}

			  {{/each}}

				Subtotal
				$ {{subtotal}}

				Shipping
				$ {{shipping_total}}

				Grand total
				$ {{grand_total}}

`
		});

		winston.info('- Added email template for Order Confirmation');
	}
};

const addShippingMethods = async db => {
	const shippingMethodsCount = await db
		.collection('shippingMethods')
		.countDocuments({});
	const shippingMethodsNotExists = shippingMethodsCount === 0;
	if (shippingMethodsNotExists) {
		await db.collection('shippingMethods').insertOne({
			name: 'Shipping method A',
			enabled: true,
			conditions: {
				countries: [],
				states: [],
				cities: [],
				subtotal_min: 0,
				subtotal_max: 0,
				weight_total_min: 0,
				weight_total_max: 0
			}
		});
		winston.info('- Added shipping method');
	}
};

const addPaymentMethods = async db => {
	const paymentMethodsCount = await db
		.collection('paymentMethods')
		.countDocuments({});
	const paymentMethodsNotExists = paymentMethodsCount === 0;
	if (paymentMethodsNotExists) {
		await db.collection('paymentMethods').insertOne({
			name: 'PayPal',
			enabled: true,
			conditions: {
				countries: [],
				shipping_method_ids: [],
				subtotal_min: 0,
				subtotal_max: 0
			}
		});
		winston.info('- Added payment method');
	}
};

const createIndex = (db, collectionName, fields, options) =>
	db.collection(collectionName).createIndex(fields, options);

const createAllIndexes = async db => {
	const pagesIndexes = await db
		.collection('pages')
		.listIndexes()
		.toArray();

	if (pagesIndexes.length === 1) {
		await createIndex(db, 'pages', { enabled: 1 });
		await createIndex(db, 'pages', { slug: 1 });
		winston.info('- Created indexes for: pages');
	}

	const productCategoriesIndexes = await db
		.collection('productCategories')
		.listIndexes()
		.toArray();

	if (productCategoriesIndexes.length === 1) {
		await createIndex(db, 'productCategories', { enabled: 1 });
		await createIndex(db, 'productCategories', { slug: 1 });
		winston.info('- Created indexes for: productCategories');
	}

	const productsIndexes = await db
		.collection('products')
		.listIndexes()
		.toArray();

	if (productsIndexes.length === 1) {
		await createIndex(db, 'products', { slug: 1 });
		await createIndex(db, 'products', { enabled: 1 });
		await createIndex(db, 'products', { category_id: 1 });
		await createIndex(db, 'products', { sku: 1 });
		await createIndex(db, 'products', {
			'attributes.name': 1,
			'attributes.value': 1
		});
		await createIndex(
			db,
			'products',
			{
				name: 'text',
				description: 'text'
			},
			{ default_language: DEFAULT_LANGUAGE, name: 'textIndex' }
		);
		winston.info('- Created indexes for: products');
	}

	const customersIndexes = await db
		.collection('customers')
		.listIndexes()
		.toArray();

	if (customersIndexes.length === 1) {
		await createIndex(db, 'customers', { group_id: 1 });
		await createIndex(db, 'customers', { email: 1 });
		await createIndex(db, 'customers', { mobile: 1 });
		await createIndex(
			db,
			'customers',
			{
				full_name: 'text',
				'addresses.address1': 'text'
			},
			{ default_language: DEFAULT_LANGUAGE, name: 'textIndex' }
		);
		winston.info('- Created indexes for: customers');
	}

	const ordersIndexes = await db
		.collection('orders')
		.listIndexes()
		.toArray();

	if (ordersIndexes.length === 1) {
		await createIndex(db, 'orders', { draft: 1 });
		await createIndex(db, 'orders', { number: 1 });
		await createIndex(db, 'orders', { customer_id: 1 });
		await createIndex(db, 'orders', { email: 1 });
		await createIndex(db, 'orders', { mobile: 1 });
		await createIndex(
			db,
			'orders',
			{
				'shipping_address.full_name': 'text',
				'shipping_address.address1': 'text'
			},
			{ default_language: DEFAULT_LANGUAGE, name: 'textIndex' }
		);
		winston.info('- Created indexes for: orders');
	}
};

(async () => {
	let client = null;
	let db = null;

	try {
		client = await MongoClient.connect(
			mongodbConnection,
			CONNECT_OPTIONS
		);
		db = client.db(dbName);
		winston.info(`Successfully connected to ${mongodbConnection}`);
	} catch (e) {
		winston.error(`MongoDB connection was failed. ${e.message}`);
		return;
	}

	await db.createCollection('customers');
	await db.createCollection('orders');
	await addAllPages(db);
	await addAllProducts(db);
	await addEmailTemplates(db);
	await addShippingMethods(db);
	await addPaymentMethods(db);
	await createAllIndexes(db);

	client.close();
})();
<|endoftext|>


<commit_before>/* global malarkey:false, moment:false */

import { config } from './index.config';
import { routerConfig } from './index.route';
import { runBlock } from './index.run';
import MainController from './main/main.controller';
import AboutController from './about/about.controller';
import SelectController from './select/select.controller';

import NavbarDirective from './components/navbar/navbar.directive';

import AudioPlayerService from './components/audioplayer/player.service';

import ModeFactory from './select/mode.factory.js';

var lodash = require('lodash');

angular.module('famousPlacesWeb', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngMessages', 'ngAria', 'angularScreenfull', 'ui.router', 'ui.bootstrap', 'toastr', 'ngAudio', 'ngStorage', 'btford.socket-io'])
  .constant('malarkey', malarkey)
  .constant('moment', moment)
  .constant('_', lodash)
  .constant('baseURLConfig', {
    'rootAPI': 'https://famous-places-api.herokuapp.com/api'
  })

  .constant('baseMusic', "https://dl.dropboxusercontent.com/u/13188176/Famous%20Places/Music/bensound-thejazzpiano.mp3")
  .constant('audioOn', 'volume_up')
  .constant('audioOff', 'volume_off')

  .config(config)
  .config(routerConfig)
  .run(runBlock)
  .controller('MainController', MainController)
  .controller('AboutController', AboutController)
  .controller('SelectController', SelectController)

  .directive('navbar', () => new NavbarDirective())

  .service('AudioPlayerService', AudioPlayerService)

  .factory('ModeFactory', ['$http', 'baseURLConfig', ($http, baseURLConfig) => new ModeFactory($http, baseURLConfig)]);
<commit_msg>Fix  eslint angular dependency error<commit_after>/* global malarkey:false, moment:false */

import { config } from './index.config';
import { routerConfig } from './index.route';
import { runBlock } from './index.run';
import MainController from './main/main.controller';
import AboutController from './about/about.controller';
import SelectController from './select/select.controller';

import NavbarDirective from './components/navbar/navbar.directive';

import AudioPlayerService from './components/audioplayer/player.service';

import ModeFactory from './select/mode.factory.js';

var lodash = require('lodash');

angular.module('famousPlacesWeb', ['ngAnimate', 'ngCookies', 'ngTouch', 'ngSanitize', 'ngMessages', 'ngAria', 'angularScreenfull', 'ui.router', 'ui.bootstrap', 'toastr', 'ngAudio', 'ngStorage', 'btford.socket-io'])
  .constant('malarkey', malarkey)
  .constant('moment', moment)
  .constant('_', lodash)
  .constant('baseURLConfig', {
    'rootAPI': 'https://famous-places-api.herokuapp.com/api'
  })

  .constant('baseMusic', "https://dl.dropboxusercontent.com/u/13188176/Famous%20Places/Music/bensound-thejazzpiano.mp3")
  .constant('audioOn', 'volume_up')
  .constant('audioOff', 'volume_off')

  .config(config)
  .config(routerConfig)
  .run(runBlock)
  .controller('MainController', MainController)
  .controller('AboutController', AboutController)
  .controller('SelectController', SelectController)

  .directive('navbar', () => new NavbarDirective())

  .service('AudioPlayerService', AudioPlayerService)

  .factory('ModeFactory', ($http, baseURLConfig) => new ModeFactory($http, baseURLConfig));
<|endoftext|>


<commit_before>// ==UserScript==
// @name         涂鸦by SeLang
// @namespace    http://cmsv1.findmd5.com/
// @version      0.9
// @description  目标是聚合美女图片，省去翻页烦恼。已实现：蕾丝猫(lesmao.com)，优美(umei.cc)。有需要聚合的网址请反馈。 QQ群号：455809302,点击链接加入群【油猴脚本私人定制】：https://jq.qq.com/?_wv=1027&k=45p9bea
// @author       selang
// @include       /https?\:\/\/www\.lesmao\.com/
// @include       /https?\:\/\/www\.umei\.cc/
// @require       https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js
// @connect      *
// @grant        GM_download
// @grant        GM_openInTab
// @grant        GM_getTab
// @grant        GM_getTabs
// @grant        GM_saveTab
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    priorityLog('看到这里，你肯定是个老司机了。欢迎老司机进群：455809302交流。一起玩。\r\n如果不是老司机，只要有创意也欢迎加入。点击链接加入群【油猴脚本私人级别定制】：https://jq.qq.com/?_wv=1027&k=460soLy。');
    priorityLog('已实现：蕾丝猫(http://www.lesmao.com)，优美(http://www.umei.cc)');

    var currentPageUrl = window.location.href;
    var currentHostname = window.location.hostname;
    var currentPathname = window.location.pathname;
    var currentProtocol = window.location.protocol;
    if ('www.lesmao.com' === currentHostname) {
        var match = currentPathname.match(/^\/(thread-\d+-)(\d+)(-\d+\.html)$/im);
        var preUrl = currentProtocol + '//' + currentHostname + '/'
        var limitPage = 30;
        if (match !== null) {
            var partPreUrl = match[1];
            var currentPageNum = match[2];
            var subfixUrl = match[3];
            currentWindowImpl(preUrl + partPreUrl, limitPage, subfixUrl);
        } else {
            // Match attempt failed
            var mod = getUrlParam('mod');
            if ('viewthread' === mod) {
                var tid = getUrlParam('tid');
                var partPreUrl = '/forum.php?mod=viewthread&tid=' + tid + '&page=';
                var subfixUrl = '';
                currentWindowImpl(preUrl + partPreUrl, limitPage, subfixUrl);
            }
        }
    } else if ('www.umei.cc' === currentHostname) {
        var match = currentPathname.match(/^\/(\w+\/\w+\/)(\d+)(?:_\d+)?\.htm$/im);
        var preUrl = currentProtocol + '//' + currentHostname + '/'
        if (match !== null) {
            var partPreUrl = match[1];
            var pageId = match[2];
            var subfixUrl = '.htm';
            // log(preUrl + partPreUrl + pageId + subfixUrl);
            var pageStr = $('.NewPages li a').html();
            // log(pageStr);
            var myregexp = /共(\d+)页/m;
            var match2 = myregexp.exec(pageStr);
            var limitPage = 0;
            if (match2 != null) {
                limitPage = match2[1];
                currentWindowImpl(preUrl + partPreUrl + pageId + '_', limitPage, subfixUrl);
            }
        }
    }
})();

//获取参数
function getUrlParam(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var r = window.location.search.substr(1).match(reg);
    if (r != null) return decodeURI(r[2]);
    return null;
}

function currentWindowImpl(preUrl, limitPage, subfixUrl) {
    injectAggregationRef();
    switchAggregationBtn(preUrl, limitPage, subfixUrl);
    dependenceJQuery(window, bindBtn(window, function (e) {
        switchAggregationBtn(preUrl, limitPage, subfixUrl);
    }));
}

//按钮切换
function switchAggregationBtn(preUrl, limitPage, subfixUrl) {
    if ($('#injectaggregatBtn').val() === '聚合显示') {
        $('#injectaggregatBtn').val('聚合隐藏');
        collectPics(window, preUrl, limitPage, subfixUrl);
        $('#c_container').show();
        $('#thread-pic').hide();
        $('#thread-page').hide();

        $('.ImageBody').hide();// umei.cc
    } else {
        $('#injectaggregatBtn').val('聚合显示');
        $('#c_container').hide();
        $('#thread-pic').show();
        $('#thread-page').show();

        $('.ImageBody').show();// umei.cc
    }
}

//日志
function log(c) {
    if (true) {
        console.log(c);
    }
}

function priorityLog(c) {
    console.log(c);
}

//注入JS：jquery
function injectJs(e) {
    if (e.jQuery) {
        log('jquery available');
    } else {
        var ele = e.document.createElement('script');
        ele.src = "https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js";
        e.document.body.appendChild(ele);
        var id = e.setInterval(function () {
            if (e.jQuery) {
                e.clearInterval(id);
            }
        }, 100);
    }
}

//等待JQuery加载完毕
function dependenceJQuery(e, callback) {
    var id = e.setInterval(function () {
        if (e.jQuery) {
            e.clearInterval(id);
            callback;
        }
    }, 100);
}

//收集图片，回调
function collectPics(e, preUrl, limitPage, subfixUrl) {
    var id = e.setInterval(function () {
        if (e.$) {
            e.clearInterval(id);
            var breakPageLoop = false;
            for (var i = 1; i <= limitPage; i++) {
                //创建div去装各自
                e.$('#c_container').append('

');
                if (!breakPageLoop) {
                    var lock = true;
                    obtainHtml(preUrl + i + subfixUrl, function (html, i) {
                        // log(html);
                        var parser = new DOMParser();
                        var doc = parser.parseFromString(html, "text/html");
                        // log(preUrl + i + subfixUrl);
                        var imgObj;
                        imgObj = $(doc).find('ul > li > img');
                        if (imgObj.length==0) {
                            imgObj = $(doc).find('.ImageBody p img')
                        }
                        var status = query(e.$('#c_' + i), $(imgObj));
                        if ('end page' === status) {
                            breakPageLoop = true;
                        }
                        lock = false;
                    }, i);
                } else {
                    break;
                }
            }
        }
    }, 100);
}

//查询图片
function query(objContainer, jqObj) {
    jqObj.each(function (index) {
        // log(index + ": " + $(this).prop('outerHTML'));
        var imgSrc = $(this).attr('src');
        if (imgSrc.endsWith('/k/1178/')) {
            return 'end page';
        } else {
            $(this)[0].style = "width: 100%;height: 100%";
            objContainer.append('
' + $(this).prop('outerHTML') + '
');
        }
    });
}

//获取网页
function obtainHtml(url, sucess, i) {
    //GM_download('http://www.w3school.com.cn/jquery/test1.txt', "就好");
    GM_xmlhttpRequest({
        method: 'GET',
        headers: {
            "Accept": "application/*"
        },
        url: url,
        onload: function (response) {
            sucess(response.responseText, i);
        }
    });
}

function injectAggregationRef() {
    if ($('.thread-tr')) {//lesmao
        $('.thread-tr').after('<input type="button" id="injectaggregatBtn" value="聚合显示"/>');
    }
    if ($('#vt')) {//lesmao
        $('#vt').append('<input type="button" id="injectaggregatBtn" value="聚合显示"/>');
    }
    if ($('.hr10')) {//http://www.umei.cc/weimeitupian/oumeitupian/20043_2.htm
        $($('.hr10')[0]).after('<input type="button" id="injectaggregatBtn" value="聚合显示"/>');
        $('iframe').remove();//移除广告等无必要元素
    }
    $('#injectaggregatBtn').after('

');
}

function bindBtn(e, callback) {
    $('#injectaggregatBtn').bind('click', callback);
}
<commit_msg>关闭普通log输出<commit_after>// ==UserScript==
// @name         涂鸦by SeLang
// @namespace    http://cmsv1.findmd5.com/
// @version      1.0
// @description  目标是聚合美女图片，省去翻页烦恼。已实现：蕾丝猫(lesmao.com)，优美(umei.cc)。有需要聚合的网址请反馈。 QQ群号：455809302,点击链接加入群【油猴脚本私人定制】：https://jq.qq.com/?_wv=1027&k=45p9bea
// @author       selang
// @include       /https?\:\/\/www\.lesmao\.com/
// @include       /https?\:\/\/www\.umei\.cc/
// @require       https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js
// @connect      *
// @grant        GM_download
// @grant        GM_openInTab
// @grant        GM_getTab
// @grant        GM_getTabs
// @grant        GM_saveTab
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function () {
    'use strict';

    priorityLog('看到这里，你肯定是个老司机了。欢迎老司机进群：455809302交流。一起玩。\r\n如果不是老司机，只要有创意也欢迎加入。点击链接加入群【油猴脚本私人级别定制】：https://jq.qq.com/?_wv=1027&k=460soLy。');
    priorityLog('已实现：蕾丝猫(http://www.lesmao.com)，优美(http://www.umei.cc)');

    var currentPageUrl = window.location.href;
    var currentHostname = window.location.hostname;
    var currentPathname = window.location.pathname;
    var currentProtocol = window.location.protocol;
    if ('www.lesmao.com' === currentHostname) {
        var match = currentPathname.match(/^\/(thread-\d+-)(\d+)(-\d+\.html)$/im);
        var preUrl = currentProtocol + '//' + currentHostname + '/'
        var limitPage = 30;
        if (match !== null) {
            var partPreUrl = match[1];
            var currentPageNum = match[2];
            var subfixUrl = match[3];
            currentWindowImpl(preUrl + partPreUrl, limitPage, subfixUrl);
        } else {
            // Match attempt failed
            var mod = getUrlParam('mod');
            if ('viewthread' === mod) {
                var tid = getUrlParam('tid');
                var partPreUrl = '/forum.php?mod=viewthread&tid=' + tid + '&page=';
                var subfixUrl = '';
                currentWindowImpl(preUrl + partPreUrl, limitPage, subfixUrl);
            }
        }
    } else if ('www.umei.cc' === currentHostname) {
        var match = currentPathname.match(/^\/(\w+\/\w+\/)(\d+)(?:_\d+)?\.htm$/im);
        var preUrl = currentProtocol + '//' + currentHostname + '/'
        if (match !== null) {
            var partPreUrl = match[1];
            var pageId = match[2];
            var subfixUrl = '.htm';
            // log(preUrl + partPreUrl + pageId + subfixUrl);
            var pageStr = $('.NewPages li a').html();
            // log(pageStr);
            var myregexp = /共(\d+)页/m;
            var match2 = myregexp.exec(pageStr);
            var limitPage = 0;
            if (match2 != null) {
                limitPage = match2[1];
                currentWindowImpl(preUrl + partPreUrl + pageId + '_', limitPage, subfixUrl);
            }
        }
    }
})();

//获取参数
function getUrlParam(name) {
    var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)");
    var r = window.location.search.substr(1).match(reg);
    if (r != null) return decodeURI(r[2]);
    return null;
}

function currentWindowImpl(preUrl, limitPage, subfixUrl) {
    injectAggregationRef();
    switchAggregationBtn(preUrl, limitPage, subfixUrl);
    dependenceJQuery(window, bindBtn(window, function (e) {
        switchAggregationBtn(preUrl, limitPage, subfixUrl);
    }));
}

//按钮切换
function switchAggregationBtn(preUrl, limitPage, subfixUrl) {
    if ($('#injectaggregatBtn').val() === '聚合显示') {
        $('#injectaggregatBtn').val('聚合隐藏');
        collectPics(window, preUrl, limitPage, subfixUrl);
        $('#c_container').show();
        $('#thread-pic').hide();
        $('#thread-page').hide();

        $('.ImageBody').hide();// umei.cc
    } else {
        $('#injectaggregatBtn').val('聚合显示');
        $('#c_container').hide();
        $('#thread-pic').show();
        $('#thread-page').show();

        $('.ImageBody').show();// umei.cc
    }
}

//日志
function log(c) {
    if (false) {
        console.log(c);
    }
}

function priorityLog(c) {
    console.log(c);
}

//注入JS：jquery
function injectJs(e) {
    if (e.jQuery) {
        log('jquery available');
    } else {
        var ele = e.document.createElement('script');
        ele.src = "https://cdn.staticfile.org/jquery/1.12.4/jquery.min.js";
        e.document.body.appendChild(ele);
        var id = e.setInterval(function () {
            if (e.jQuery) {
                e.clearInterval(id);
            }
        }, 100);
    }
}

//等待JQuery加载完毕
function dependenceJQuery(e, callback) {
    var id = e.setInterval(function () {
        if (e.jQuery) {
            e.clearInterval(id);
            callback;
        }
    }, 100);
}

//收集图片，回调
function collectPics(e, preUrl, limitPage, subfixUrl) {
    var id = e.setInterval(function () {
        if (e.$) {
            e.clearInterval(id);
            var breakPageLoop = false;
            for (var i = 1; i <= limitPage; i++) {
                //创建div去装各自
                e.$('#c_container').append('

');
                if (!breakPageLoop) {
                    var lock = true;
                    obtainHtml(preUrl + i + subfixUrl, function (html, i) {
                        // log(html);
                        var parser = new DOMParser();
                        var doc = parser.parseFromString(html, "text/html");
                        // log(preUrl + i + subfixUrl);
                        var imgObj;
                        imgObj = $(doc).find('ul > li > img');
                        if (imgObj.length==0) {
                            imgObj = $(doc).find('.ImageBody p img')
                        }
                        var status = query(e.$('#c_' + i), $(imgObj));
                        if ('end page' === status) {
                            breakPageLoop = true;
                        }
                        lock = false;
                    }, i);
                } else {
                    break;
                }
            }
        }
    }, 100);
}

//查询图片
function query(objContainer, jqObj) {
    jqObj.each(function (index) {
        // log(index + ": " + $(this).prop('outerHTML'));
        var imgSrc = $(this).attr('src');
        if (imgSrc.endsWith('/k/1178/')) {
            return 'end page';
        } else {
            $(this)[0].style = "width: 100%;height: 100%";
            objContainer.append('
' + $(this).prop('outerHTML') + '
');
        }
    });
}

//获取网页
function obtainHtml(url, sucess, i) {
    //GM_download('http://www.w3school.com.cn/jquery/test1.txt', "就好");
    GM_xmlhttpRequest({
        method: 'GET',
        headers: {
            "Accept": "application/*"
        },
        url: url,
        onload: function (response) {
            sucess(response.responseText, i);
        }
    });
}

function injectAggregationRef() {
    if ($('.thread-tr')) {//lesmao
        $('.thread-tr').after('<input type="button" id="injectaggregatBtn" value="聚合显示"/>');
    }
    if ($('#vt')) {//lesmao
        $('#vt').append('<input type="button" id="injectaggregatBtn" value="聚合显示"/>');
    }
    if ($('.hr10')) {//http://www.umei.cc/weimeitupian/oumeitupian/20043_2.htm
        $($('.hr10')[0]).after('<input type="button" id="injectaggregatBtn" value="聚合显示"/>');
        $('iframe').remove();//移除广告等无必要元素
    }
    $('#injectaggregatBtn').after('

');
}

function bindBtn(e, callback) {
    $('#injectaggregatBtn').bind('click', callback);
}
<|endoftext|>


<commit_before>//       telegram.link
//
//       Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//       Released under the Simplified BSD License
//       http://telegram.link

// Export the classes
exports.PublicKey = require('./public-key');
exports.PQFinder = require('./pq-finder');

// Export functions
exports.createSHA1Hash = createSHA1Hash;
exports.createRandomBuffer = createRandomBuffer;
exports.createNonce = createNonce;
exports.rsaEncrypt = rsaEncrypt;
exports.aesDecrypt = aesDecrypt;
exports.aesEncrypt = aesEncrypt;
exports.modPow = modPow;
exports.createMessageId = createMessageId;
exports.timeSynchronization = timeSynchronization;

// Import dependencies
var crypto = require('crypto');
var util = require('util');
var BigInteger = require('jsbn');
var CryptoJS = require("node-cryptojs-aes").CryptoJS;
var getLogger = require('get-log');

// Create SHA1 hash starting from a buffer or an array of buffers
function createSHA1Hash(buffer) {
    var logger = getLogger('crypto-util.createSHA1Hash');
    var sha1sum = crypto.createHash('sha1');
    if (util.isArray(buffer)) {
        if (logger.isDebugEnabled()) logger.debug('It\'s an Array of buffers');
        for (var i = 0; i < buffer.length; i++) {
            sha1sum.update(buffer[i]);
        }
    } else {
        if (logger.isDebugEnabled()) logger.debug('It\'s only one buffer');
        sha1sum.update(buffer);
    }
    return sha1sum.digest();
}

// Create a random Buffer
function createRandomBuffer(bytesLength) {
    return new Buffer(crypto.randomBytes(bytesLength));
}

// Create a new nonce
function createNonce(bytesLength) {
    return '0x' + createRandomBuffer(bytesLength).toString('hex');
}

// RSA encrypt function, param: { key: publicKey, message: messageBuffer }
function rsaEncrypt(param) {
    var logger = getLogger('crypto-util.rsaEncrypt');

    var publicKey = param.key;
    var messageBuffer = param.message;
    var messageLength = messageBuffer.length;
    if (!messageBuffer || messageLength > 255) {
        logger.warn('Message is undefined or exceeds 255 bytes length.');
        return;
    }
    if (messageLength < 255) {
        // Add random bytes as padding
        var paddingLength = 255 - messageLength;
        messageBuffer = Buffer.concat([messageBuffer, createRandomBuffer(paddingLength)]);
    }
    if (logger.isDebugEnabled()) logger.debug('Message to be encrypt (%s) = %s',
        messageBuffer.length, messageBuffer.toString('hex'));
    // Encrypt the message
    var modulus = new BigInteger(publicKey.getModulus(), 16);
    var exponent = new BigInteger(publicKey.getExponent(), 16);
    var message = new BigInteger(messageBuffer);
    var encryptedMessage = new Buffer(message.modPowInt(exponent, modulus).toByteArray());
    if (encryptedMessage.length > 256) {
        encryptedMessage = encryptedMessage.slice(encryptedMessage.length - 256);
    }
    if (logger.isDebugEnabled()) logger.debug('Encrypted message(%s) = %s', encryptedMessage.length, encryptedMessage.toString('hex'));
    return encryptedMessage;
}

// AES decrypt function
function aesDecrypt(msg, key, iv) {
    var logger = getLogger('crypto-util.aesDecrypt');
    var encryptedMsg = buffer2WordArray(msg);
    var keyWordArray = buffer2WordArray(key);
    var ivWordArray = buffer2WordArray(iv);
    if (logger.isDebugEnabled()) {
        logger.debug('encryptedMsg = %s', JSON.stringify(encryptedMsg));
        logger.debug('keyWordArray = %s', JSON.stringify(keyWordArray));
        logger.debug('ivWordArray = %s', JSON.stringify(ivWordArray));
    }
    var decryptedWordArray = CryptoJS.AES.decrypt({ciphertext: encryptedMsg}, keyWordArray, {
        iv: ivWordArray,
        padding: CryptoJS.pad.NoPadding,
        mode: CryptoJS.mode.IGE
    });
    if (logger.isDebugEnabled()) logger.debug('decryptedWordArray = %s', JSON.stringify(decryptedWordArray));
    return wordArray2Buffer(decryptedWordArray);
}

// AES encrypt function
function aesEncrypt(msg, key, iv) {
    var logger = getLogger('crypto-util.aesEncrypt');
    // Check if padding is needed
    var padding = msg.length % 16;
    if (padding > 0) {
        paddingBuffer = createRandomBuffer(16 - padding);
        msg = Buffer.concat([msg, paddingBuffer]);
    }
    // Convert buffers to wordArrays
    var plainMsg = buffer2WordArray(msg);
    var keyWordArray = buffer2WordArray(key);
    var ivWordArray = buffer2WordArray(iv);
    if (logger.isDebugEnabled()) {
        logger.debug('plainMsg = %s', JSON.stringify(plainMsg));
        logger.debug('keyWordArray = %s', JSON.stringify(keyWordArray));
        logger.debug('ivWordArray = %s', JSON.stringify(ivWordArray));
    }
    // Encrypt plain message
    var encryptedWordArray = CryptoJS.AES.encrypt(plainMsg, keyWordArray, {
        iv: ivWordArray,
        padding: CryptoJS.pad.NoPadding,
        mode: CryptoJS.mode.IGE
    }).ciphertext;
    if (logger.isDebugEnabled()) logger.debug('encryptedWordArray = %s', JSON.stringify(encryptedWordArray));
    // Return the encrypted buffer
    return wordArray2Buffer(encryptedWordArray);
}

// Mod Pow
function modPow(x, e, m) {
    var logger = getLogger('crypto-util.aesDecrypt');
    var bigX = (typeof x == 'number') ? new BigInteger(x + '', 10) : new BigInteger(x.toString('hex'), 16);
    var bigE = (typeof e == 'number') ? new BigInteger(e + '', 10) : new BigInteger(e.toString('hex'), 16);
    var bigM = (typeof m == 'number') ? new BigInteger(m + '', 10) : new BigInteger(m.toString('hex'), 16);
    var bigResult = bigX.modPow(bigE, bigM);
    if (logger.isDebugEnabled()) logger.debug('X = %s, E = %s, M = %s, result = %s', bigX, bigE, bigM, bigResult);
    var result = new Buffer(bigResult.toByteArray());
    if (result.length > 256) {
        result = result.slice(result.length - 256);
    }
    return result;
}

function buffer2WordArray(buffer) {
    var length = buffer.length;
    var wordArray = [];
    for (var i = 0; i < length; i++) {
        wordArray[i >>> 2] |= buffer[i] << (24 - 8 * (i % 4));
    }
    return new CryptoJS.lib.WordArray.init(wordArray, length);
}

function wordArray2Buffer(wordArray) {
    var words = wordArray.words;
    var sigBytes = wordArray.sigBytes;
    var buffer = new Buffer(sigBytes);
    for (var i = 0; i < sigBytes; i++) {
        buffer.writeUInt8((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff, i);
    }
    return buffer;
}

// Time offset between local time and server time
var timeOffset = 0;
var lastResponseTime = Number.MAX_VALUE;

// Create a message ID starting from the local time
function createMessageId() {
    var logger = getLogger('crypto-util.createMessageId');
    var time = new BigInteger((new Date().getTime() + timeOffset).toString());
    if (logger.isDebugEnabled()) logger.debug('Create with time %s and offset %s', time, timeOffset);
    time = time.divide(new BigInteger((1000).toString()));
    return time.shiftLeft(32).toString();
}

// Synchronize the local time with the server time
function timeSynchronization(serverTime, requestDuration) {
    var logger = getLogger('crypto-util.timeSynchronization');
    var localTime = new Date().getTime();
    var responseTime = requestDuration / 2;
    if (lastResponseTime > responseTime) {
        lastResponseTime = responseTime;
        timeOffset = (serverTime + responseTime) - localTime;
        logger.info('(ServerTime %s + ServerResponseTime %s) - LocalTime %s = timeOffset %s ',
            serverTime, responseTime, localTime, timeOffset);
    } else {
        logger.info('Discarded: ServerResponseTime (%s) higher than previous one (%s) ',
            responseTime, lastResponseTime);
    }
}<commit_msg>Add fractional part of the time in `messageId`<commit_after>//       telegram.link
//
//       Copyright 2014 Enrico Stara 'enrico.stara@gmail.com'
//       Released under the Simplified BSD License
//       http://telegram.link

// Export the classes
exports.PublicKey = require('./public-key');
exports.PQFinder = require('./pq-finder');

// Export functions
exports.createSHA1Hash = createSHA1Hash;
exports.createRandomBuffer = createRandomBuffer;
exports.createNonce = createNonce;
exports.rsaEncrypt = rsaEncrypt;
exports.aesDecrypt = aesDecrypt;
exports.aesEncrypt = aesEncrypt;
exports.modPow = modPow;
exports.createMessageId = createMessageId;
exports.timeSynchronization = timeSynchronization;

// Import dependencies
var crypto = require('crypto');
var util = require('util');
var BigInteger = require('jsbn');
var CryptoJS = require("node-cryptojs-aes").CryptoJS;
var getLogger = require('get-log');

// Create SHA1 hash starting from a buffer or an array of buffers
function createSHA1Hash(buffer) {
    var logger = getLogger('crypto-util.createSHA1Hash');
    var sha1sum = crypto.createHash('sha1');
    if (util.isArray(buffer)) {
        if (logger.isDebugEnabled()) logger.debug('It\'s an Array of buffers');
        for (var i = 0; i < buffer.length; i++) {
            sha1sum.update(buffer[i]);
        }
    } else {
        if (logger.isDebugEnabled()) logger.debug('It\'s only one buffer');
        sha1sum.update(buffer);
    }
    return sha1sum.digest();
}

// Create a random Buffer
function createRandomBuffer(bytesLength) {
    return new Buffer(crypto.randomBytes(bytesLength));
}

// Create a new nonce
function createNonce(bytesLength) {
    return '0x' + createRandomBuffer(bytesLength).toString('hex');
}

// RSA encrypt function, param: { key: publicKey, message: messageBuffer }
function rsaEncrypt(param) {
    var logger = getLogger('crypto-util.rsaEncrypt');

    var publicKey = param.key;
    var messageBuffer = param.message;
    var messageLength = messageBuffer.length;
    if (!messageBuffer || messageLength > 255) {
        logger.warn('Message is undefined or exceeds 255 bytes length.');
        return;
    }
    if (messageLength < 255) {
        // Add random bytes as padding
        var paddingLength = 255 - messageLength;
        messageBuffer = Buffer.concat([messageBuffer, createRandomBuffer(paddingLength)]);
    }
    if (logger.isDebugEnabled()) logger.debug('Message to be encrypt (%s) = %s',
        messageBuffer.length, messageBuffer.toString('hex'));
    // Encrypt the message
    var modulus = new BigInteger(publicKey.getModulus(), 16);
    var exponent = new BigInteger(publicKey.getExponent(), 16);
    var message = new BigInteger(messageBuffer);
    var encryptedMessage = new Buffer(message.modPowInt(exponent, modulus).toByteArray());
    if (encryptedMessage.length > 256) {
        encryptedMessage = encryptedMessage.slice(encryptedMessage.length - 256);
    }
    if (logger.isDebugEnabled()) logger.debug('Encrypted message(%s) = %s', encryptedMessage.length, encryptedMessage.toString('hex'));
    return encryptedMessage;
}

// AES decrypt function
function aesDecrypt(msg, key, iv) {
    var logger = getLogger('crypto-util.aesDecrypt');
    var encryptedMsg = buffer2WordArray(msg);
    var keyWordArray = buffer2WordArray(key);
    var ivWordArray = buffer2WordArray(iv);
    if (logger.isDebugEnabled()) {
        logger.debug('encryptedMsg = %s', JSON.stringify(encryptedMsg));
        logger.debug('keyWordArray = %s', JSON.stringify(keyWordArray));
        logger.debug('ivWordArray = %s', JSON.stringify(ivWordArray));
    }
    var decryptedWordArray = CryptoJS.AES.decrypt({ciphertext: encryptedMsg}, keyWordArray, {
        iv: ivWordArray,
        padding: CryptoJS.pad.NoPadding,
        mode: CryptoJS.mode.IGE
    });
    if (logger.isDebugEnabled()) logger.debug('decryptedWordArray = %s', JSON.stringify(decryptedWordArray));
    return wordArray2Buffer(decryptedWordArray);
}

// AES encrypt function
function aesEncrypt(msg, key, iv) {
    var logger = getLogger('crypto-util.aesEncrypt');
    // Check if padding is needed
    var padding = msg.length % 16;
    if (padding > 0) {
        paddingBuffer = createRandomBuffer(16 - padding);
        msg = Buffer.concat([msg, paddingBuffer]);
    }
    // Convert buffers to wordArrays
    var plainMsg = buffer2WordArray(msg);
    var keyWordArray = buffer2WordArray(key);
    var ivWordArray = buffer2WordArray(iv);
    if (logger.isDebugEnabled()) {
        logger.debug('plainMsg = %s', JSON.stringify(plainMsg));
        logger.debug('keyWordArray = %s', JSON.stringify(keyWordArray));
        logger.debug('ivWordArray = %s', JSON.stringify(ivWordArray));
    }
    // Encrypt plain message
    var encryptedWordArray = CryptoJS.AES.encrypt(plainMsg, keyWordArray, {
        iv: ivWordArray,
        padding: CryptoJS.pad.NoPadding,
        mode: CryptoJS.mode.IGE
    }).ciphertext;
    if (logger.isDebugEnabled()) logger.debug('encryptedWordArray = %s', JSON.stringify(encryptedWordArray));
    // Return the encrypted buffer
    return wordArray2Buffer(encryptedWordArray);
}

// Mod Pow
function modPow(x, e, m) {
    var logger = getLogger('crypto-util.aesDecrypt');
    var bigX = (typeof x == 'number') ? new BigInteger(x + '', 10) : new BigInteger(x.toString('hex'), 16);
    var bigE = (typeof e == 'number') ? new BigInteger(e + '', 10) : new BigInteger(e.toString('hex'), 16);
    var bigM = (typeof m == 'number') ? new BigInteger(m + '', 10) : new BigInteger(m.toString('hex'), 16);
    var bigResult = bigX.modPow(bigE, bigM);
    if (logger.isDebugEnabled()) logger.debug('X = %s, E = %s, M = %s, result = %s', bigX, bigE, bigM, bigResult);
    var result = new Buffer(bigResult.toByteArray());
    if (result.length > 256) {
        result = result.slice(result.length - 256);
    }
    return result;
}

function buffer2WordArray(buffer) {
    var length = buffer.length;
    var wordArray = [];
    for (var i = 0; i < length; i++) {
        wordArray[i >>> 2] |= buffer[i] << (24 - 8 * (i % 4));
    }
    return new CryptoJS.lib.WordArray.init(wordArray, length);
}

function wordArray2Buffer(wordArray) {
    var words = wordArray.words;
    var sigBytes = wordArray.sigBytes;
    var buffer = new Buffer(sigBytes);
    for (var i = 0; i < sigBytes; i++) {
        buffer.writeUInt8((words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff, i);
    }
    return buffer;
}

// Time offset between local time and server time
var timeOffset = 0;
var lastResponseTime = Number.MAX_VALUE;

// Create a message ID starting from the local time
function createMessageId() {
    var logger = getLogger('crypto-util.createMessageId');
    // Constants
    var thousand = new BigInteger((1000).toString());
    var lowerMultiplier = new BigInteger((4294964).toString());
    // Take the time and sum the time-offset with the server clock
    var time = new BigInteger((new Date().getTime() + timeOffset).toString());
    // Divide the time by 1000 `result[0]` and take the fractional part `result[1]`
    var result = time.divideAndRemainder(thousand);
    // Prepare lower 32 bit using the fractional part of the time
    var lower = result[1].multiply(lowerMultiplier);
    // Create the message id
    var messageId = result[0].shiftLeft(32).add(lower);
    if (logger.isDebugEnabled()) {
        logger.debug('MessageId(%s) was created with time = %s and offset = %s, lower = %s,  messageID binary = %s',
            messageId.toString(), time, timeOffset,  lower,  messageId.toString(2));
    }
    return messageId.toString();
}

// Synchronize the local time with the server time
function timeSynchronization(serverTime, requestDuration) {
    var logger = getLogger('crypto-util.timeSynchronization');
    var localTime = new Date().getTime();
    var responseTime = requestDuration / 2;
    if (lastResponseTime > responseTime) {
        lastResponseTime = responseTime;
        timeOffset = (serverTime + responseTime) - localTime;
        logger.info('(ServerTime %s + ServerResponseTime %s) - LocalTime %s = timeOffset %s ',
            serverTime, responseTime, localTime, timeOffset);
    } else {
        logger.info('Discarded: ServerResponseTime (%s) higher than previous one (%s) ',
            responseTime, lastResponseTime);
    }
}<|endoftext|>


<commit_before>var	Q = require('q'),
	API = require('./api').API;

var MBTA = function() {};

MBTA.REDLINE = "Red";
MBTA.ORANGELINE = "Orange";
MBTA.BLUELINE = "Blue";

MBTA.prototype = new API({
	hostname: 'developer.mbta.com',
	format:   API.FORMAT.JSON
});

MBTA.prototype.predictions = function(forLine) {
	return this.call('RT_Archive/RealTimeHeavyRailKeys.csv', API.FORMAT.CSV_HEADER).then(function(predictions) {
            var prediction, i, line, predictionByLine = {};
            for (i=0;i<predictions.length;i++) {
                prediction = predictions[i];
                line = prediction["Line"];
                if (!predictionByLine[line])
                    predictionByLine[line] = [];
                predictionByLine[line].push(prediction);
            }
            if (forLine === undefined)
                return predictionByLine;
            else
                return predictionByLine[forLine];
        });
};

MBTA.prototype.subways = function() {
	var deferred = Q.defer();
	deferred.resolve([MBTA.REDLINE, MBTA.ORANGELINE, MBTA.BLUELINE])
	return deferred.promise;
}

MBTA.prototype.subway = function(line) {
	return this.call('Data/'+line+'.json');
};

exports.MBTA = MBTA;
<commit_msg>Refactor mbta adapter<commit_after>var	Q = require('q'),
	API = require('./api').API;

var MBTA = function() {};

MBTA.REDLINE = "Red";
MBTA.ORANGELINE = "Orange";
MBTA.BLUELINE = "Blue";

MBTA.prototype = new API({
	hostname: 'developer.mbta.com',
	format:   API.FORMAT.JSON
});

MBTA.prototype.subways = function(forLine) {
	return this.call('RT_Archive/RealTimeHeavyRailKeys.csv', API.FORMAT.CSV_HEADER).then(function(predictions) {
            var prediction, i, line, predictionByLine = {};
            for (i=0;i<predictions.length;i++) {
                prediction = predictions[i];
                line = prediction["Line"];
                if (!predictionByLine[line])
                    predictionByLine[line] = [];
                predictionByLine[line].push(prediction);
            }
            if (forLine === undefined)
                return predictionByLine;
            else
                return predictionByLine[forLine];
        });
};

MBTA.prototype.lines = function() {
	var deferred = Q.defer();
	deferred.resolve([MBTA.REDLINE, MBTA.ORANGELINE, MBTA.BLUELINE])
	return deferred.promise;
}

MBTA.prototype.predictions = function(line) {
	return this.call('Data/'+line+'.json');
};

exports.MBTA = MBTA;
<|endoftext|>


<commit_before>// The following is needed for Mocha < 1.9.0 when using PhantomJS:
//
//  /*global confirm, mocha */
//  mocha.globals(["confirm"]);
//

describe("App.Views.Note", function () {

  before(function () {
    // Regions for different views.
    $("#fixtures").append($(
      "

" +
      "

"
    ));

    // App.Views.Note fixture.
    this.$fixture = $(
      "
" +
        "

" +
      "
"
    );

    // Any model changes will trigger a `model.save()`, which
    // won't work in the tests, so we have to fake the method.
    //
    // Stub the model prototype *once* for all our tests.
    sinon.stub(App.Models.Note.prototype, "save");
  });

  beforeEach(function () {
    this.routerSpy = sinon.spy();
    this.$fixture.appendTo($("#fixtures"));

    // Creation calls `render()`, so in tests we have an
    // *already rendered* view.
    this.view = new App.Views.Note({
      el: this.$fixture,
      model: new App.Models.Note()
    }, {
      // Pass an empty view and manually mock router.
      // We are essentially "faux" mocking the components.
      nav: new Backbone.View(),
      router: {
        navigate: this.routerSpy
      }
    });
  });

  afterEach(function () {
    this.$fixture.empty();
    if (this.view) { this.view.model.destroy(); }
  });

  after(function () {
    $("#fixtures").empty();
    App.Models.Note.prototype.save.restore();
  });

  describe("view modes and actions", function () {
    // `NoteView` first goes to `#note/:id/view`
    it("navigates / displays 'view' by default", function () {
      expect(this.routerSpy).to.be.calledWithMatch(/view$/);

      // Check CSS visibility directly. Not necessarily a best
      // practice as it uses internal knowledge of the DOM, but
      // gets us a quick check on what should be the visible
      // view pane.
      expect($("#note-pane-view")
        .css("display")).to.not.equal("none");
      expect($("#note-pane-edit")
        .css("display")).to.equal("none");
    });

    // Edit event triggers navigation to `#note/:id/edit`
    it("navigates / displays 'edit' on event", function () {
      this.view.trigger("update:edit");
      expect(this.routerSpy).to.be.calledWithMatch(/edit$/);

      expect($("#note-pane-edit")
        .css("display")).to.not.equal("none");
      expect($("#note-pane-view")
        .css("display")).to.equal("none");
    });

    it("confirms note on delete", sinon.test(function () {
      this.stub(window, "confirm").returns(false);
      this.view.deleteNote();
      expect(window.confirm)
        .to.have.been.calledOnce.and
        .to.have.been.calledWith("Delete note?");
    }));
  });

  describe("model interaction", function () {
    afterEach(function () {
      // Wipe out to prevent any further use.
      this.view = null;
    });

    // It is a good habit to check that views are actually
    // disposed of when expected. Here, we bind view removal to
    // the destruction of a model.
    it("is removed on destroyed model", sinon.test(function () {
      this.spy(this.view, "remove");
      this.spy(this.view.noteView, "remove");

      this.view.model.trigger("destroy");

      expect(this.view.remove).to.be.calledOnce;
      expect(this.view.noteView.remove).to.be.calledOnce;
    }));
  });

  describe("note rendering", function () {

    it("can render a note", function () {
      // Don't explicitly call `render()` because
      // `initialize()` already called it.
      expect($(".region-note")
        .css("display")).to.not.equal("none");
      expect($(".region-notes")
        .css("display")).to.equal("none");
    });

    // Borrows a `NoteView` spec verbatim to make sure that the
    // overall view code renders correctly.
    // -- Omitted in Book. --
    it("can render a default note view", function () {
      var $title = $("#pane-title"),
        $text = $("#pane-text");

      // Default to empty title in `h2` tag.
      expect($title.text()).to.equal("");
      expect($title.prop("tagName")).to.match(/h2/i);

      // Have simple default message.
      expect($text.text()).to.equal("Edit your note!");
      expect($text.html())
        .to.equal("
Edit your note!
");
    });

    it("calls render on model events", sinon.test(function () {
      // Spy on `render` and check call/return value.
      this.spy(this.view, "render");

      this.view.model.trigger("change");

      expect(this.view.render)
        .to.be.calledOnce.and
        .to.have.returned(this.view);
    }));

    it("calls render on changed data", sinon.test(function () {
      this.spy(this.view, "render");

      // Replace form value and blur to force changes.
      $("#input-text").val("# A Heading!");
      $("#note-form-edit").blur();

      // `Note` view should have rendered.
      expect(this.view.render)
        .to.be.calledOnce.and
        .to.have.returned(this.view);

      // Check the `NoteView` view rendered the new markdown.
      expect($("#pane-text").html())
        .to.match(/
A Heading!<\/h1>/);
    }));
  });
});
<commit_msg>Removed from note.spec.js unnecessary # on id<commit_after>// The following is needed for Mocha < 1.9.0 when using PhantomJS:
//
//  /*global confirm, mocha */
//  mocha.globals(["confirm"]);
//

describe("App.Views.Note", function () {

  before(function () {
    // Regions for different views.
    $("#fixtures").append($(
      "

" +
      "

"
    ));

    // App.Views.Note fixture.
    this.$fixture = $(
      "
" +
        "

" +
      "
"
    );

    // Any model changes will trigger a `model.save()`, which
    // won't work in the tests, so we have to fake the method.
    //
    // Stub the model prototype *once* for all our tests.
    sinon.stub(App.Models.Note.prototype, "save");
  });

  beforeEach(function () {
    this.routerSpy = sinon.spy();
    this.$fixture.appendTo($("#fixtures"));

    // Creation calls `render()`, so in tests we have an
    // *already rendered* view.
    this.view = new App.Views.Note({
      el: this.$fixture,
      model: new App.Models.Note()
    }, {
      // Pass an empty view and manually mock router.
      // We are essentially "faux" mocking the components.
      nav: new Backbone.View(),
      router: {
        navigate: this.routerSpy
      }
    });
  });

  afterEach(function () {
    this.$fixture.empty();
    if (this.view) { this.view.model.destroy(); }
  });

  after(function () {
    $("#fixtures").empty();
    App.Models.Note.prototype.save.restore();
  });

  describe("view modes and actions", function () {
    // `NoteView` first goes to `#note/:id/view`
    it("navigates / displays 'view' by default", function () {
      expect(this.routerSpy).to.be.calledWithMatch(/view$/);

      // Check CSS visibility directly. Not necessarily a best
      // practice as it uses internal knowledge of the DOM, but
      // gets us a quick check on what should be the visible
      // view pane.
      expect($("#note-pane-view")
        .css("display")).to.not.equal("none");
      expect($("#note-pane-edit")
        .css("display")).to.equal("none");
    });

    // Edit event triggers navigation to `#note/:id/edit`
    it("navigates / displays 'edit' on event", function () {
      this.view.trigger("update:edit");
      expect(this.routerSpy).to.be.calledWithMatch(/edit$/);

      expect($("#note-pane-edit")
        .css("display")).to.not.equal("none");
      expect($("#note-pane-view")
        .css("display")).to.equal("none");
    });

    it("confirms note on delete", sinon.test(function () {
      this.stub(window, "confirm").returns(false);
      this.view.deleteNote();
      expect(window.confirm)
        .to.have.been.calledOnce.and
        .to.have.been.calledWith("Delete note?");
    }));
  });

  describe("model interaction", function () {
    afterEach(function () {
      // Wipe out to prevent any further use.
      this.view = null;
    });

    // It is a good habit to check that views are actually
    // disposed of when expected. Here, we bind view removal to
    // the destruction of a model.
    it("is removed on destroyed model", sinon.test(function () {
      this.spy(this.view, "remove");
      this.spy(this.view.noteView, "remove");

      this.view.model.trigger("destroy");

      expect(this.view.remove).to.be.calledOnce;
      expect(this.view.noteView.remove).to.be.calledOnce;
    }));
  });

  describe("note rendering", function () {

    it("can render a note", function () {
      // Don't explicitly call `render()` because
      // `initialize()` already called it.
      expect($(".region-note")
        .css("display")).to.not.equal("none");
      expect($(".region-notes")
        .css("display")).to.equal("none");
    });

    // Borrows a `NoteView` spec verbatim to make sure that the
    // overall view code renders correctly.
    // -- Omitted in Book. --
    it("can render a default note view", function () {
      var $title = $("#pane-title"),
        $text = $("#pane-text");

      // Default to empty title in `h2` tag.
      expect($title.text()).to.equal("");
      expect($title.prop("tagName")).to.match(/h2/i);

      // Have simple default message.
      expect($text.text()).to.equal("Edit your note!");
      expect($text.html())
        .to.equal("
Edit your note!
");
    });

    it("calls render on model events", sinon.test(function () {
      // Spy on `render` and check call/return value.
      this.spy(this.view, "render");

      this.view.model.trigger("change");

      expect(this.view.render)
        .to.be.calledOnce.and
        .to.have.returned(this.view);
    }));

    it("calls render on changed data", sinon.test(function () {
      this.spy(this.view, "render");

      // Replace form value and blur to force changes.
      $("#input-text").val("# A Heading!");
      $("#note-form-edit").blur();

      // `Note` view should have rendered.
      expect(this.view.render)
        .to.be.calledOnce.and
        .to.have.returned(this.view);

      // Check the `NoteView` view rendered the new markdown.
      expect($("#pane-text").html())
        .to.match(/
A Heading!<\/h1>/);
    }));
  });
});
<|endoftext|>


<commit_before>var commands = []

// var checker = require('../../Utils/access_checker')
// var logger = require('../../Utils/error_loggers')
var config = require('../../config.js')
var analytics = require('../../Utils/orwell.js')
var bugsnag = require('bugsnag')
const Dash = require('rethinkdbdash')
const r = new Dash()
const roles = require('../../roles')

bugsnag.register(config.discord.bugsnag)

commands.ping = {
  phantom: true,
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg) {
    msg.channel.sendMessage('Pong!').then(successmsg => {
      setTimeout(() => bot.Messages.deleteMessages([msg, successmsg]), config.timeouts.messageDelete)
    })
  }
}

commands.help = {
  adminOnly: false,
  modOnly: false,
  fn: function (bot, msg) {
    msg.channel.sendMessage(`Hey ${msg.author.mention}! You can find all the info you need about the bot over at <#268812893087203338>!`).then(successmsg => {
      setTimeout(() => bot.Messages.deleteMessages([msg, successmsg]), config.timeouts.errorMessageDelete)
    })
  }
}

commands.stats = {
  adminOnly: false,
  modOnly: false,
  fn: function (bot, msg) {
    let moment = require('moment') // forgive me father for i have sinned
    analytics.getPoints(msg.member.id).then(data => {
      if (data === null) return msg.reply("you don't have any stats registered right now.")
      let now = new Date()
      let today = new Date(now.getFullYear(), now.getUTCMonth(), now.getUTCDate()).getTime()
      let dataObj = {}
      if (data.messages) Object.entries(data.messages).forEach(m => dataObj[m[0]] ? Object.assign(dataObj[m[0]], { msgs: m[1] }) : dataObj[m[0]] = { msgs: m[1] })
      if (data.commands) Object.entries(data.commands).forEach(c => dataObj[c[0]] ? Object.assign(dataObj[c[0]], { cmds: c[1] }) : dataObj[c[0]] = { cmds: c[1] })
      let dataArr = Object.entries(dataObj).sort()
      let field = []

      for (let i in dataArr) {
        let date = dataArr[i][0]
        if (today - parseInt(date) <= 172800000) {
          let parsed = moment(parseInt(date)).format("MMM Do")
          if (data.messages && dataArr[i][1].msgs) field.push({
            name: `Messages on ${parsed}`,
            value: data.messages[date],
            inline: dataArr[i] ? (dataArr[i][1].cmds ? true : false) : false
          })
          if (data.commands && dataArr[i][1].cmds) field.push({
            name: `Commands on ${parsed}`,
            value: data.commands[date],
            inline: dataArr[i] ? (dataArr[i][1].msgs ? true : false) : false
          })
        }
      }

      let sortRoles = Object.entries(roles).sort((a, b) => a[1].threshold - b[1].threshold)
      let nextRole = sortRoles.find(r => r[1].threshold > data.consecutive.length)
      field.push(
        {
          name: `Consecutive active days`,
          value: data.consecutive.length,
          inline: true
        },
        {
          name: `Days needed for next rank`,
          value: (nextRole) ? nextRole[1].threshold - data.consecutive.length : 'N/A',
          inline: true
        }
      )
      msg.author.openDM().then((e) => {
        e.sendMessage('', false, {
          color: 0x59f442,
          title: `${msg.author.username} - Statistics`,
          thumbnail: {
            url: msg.author.staticAvatarURL
          },
          fields: field
        }).catch(bugsnag.notify) // Send Message to DM error
      }).then(msg.delete()).catch(bugsnag.notify) // Error opening DM channel
    }).catch(e => {
      msg.reply('an unexpected error occured while getting your stats, try again later.')
      console.error(e)
    })
  }
}

commands['stats-reset'] = {
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg, suffix) {
    r.db('DFB').table('analytics').get(suffix).then(data => {
      if (data !== null) {
        msg.reply(`you're about to reset the stats for ${suffix}, are you sure?`).then(() => {
          wait(bot, msg).then(resp => {
            if (resp === null) {
              msg.channel.sendMessage('No answer given in time, operation aborted.')
            }
            if (resp === false) {
              msg.channel.sendMessage('Operation aborted.')
            }
            if (resp === true) {
              msg.channel.sendMessage(`Are you **ABSOLUTELY** sure? This can not be undone!`).then(() => {
                wait(bot, msg).then(resp2 => {
                  if (resp2 === null) {
                    msg.channel.sendMessage('No answer given in time, operation aborted.')
                  }
                  if (resp2 === false) {
                    msg.channel.sendMessage('Operation aborted.')
                  }
                  if (resp2 === true) {
                    r.db('DFB').table('analytics').get(suffix).delete().run().then(() => {
                      msg.channel.sendMessage(`Stats for ${suffix} are deleted.`)
                    }).catch(e => {
                      bugsnag.notify(e)
                      msg.channel.sendMessage(`Failed to delete stats for ${suffix}`)
                    })
                  }
                })
              })
            }
          })
        })
      } else {
        msg.reply(`no data for ${suffix} found.`)
      }
    })
  }
}

commands.lookup = {
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg, suffix) {
    let moment = require('moment') // forgive me father for i have sinned
    analytics.getPoints((msg.mentions.length !== 0) ? msg.mentions[0].id : suffix).then(data => {
      if (data === null) return msg.reply("couldn't find data on this user.")
      let member = msg.guild.members.find(member => member.id === data.id)
      let now = new Date()
      let today = new Date(now.getFullYear(), now.getUTCMonth(), now.getUTCDate()).getTime()
      let dataObj = {}
      if (data.messages) Object.entries(data.messages).forEach(m => dataObj[m[0]] ? Object.assign(dataObj[m[0]], { msgs: m[1] }) : dataObj[m[0]] = { msgs: m[1] })
      if (data.commands) Object.entries(data.commands).forEach(c => dataObj[c[0]] ? Object.assign(dataObj[c[0]], { cmds: c[1] }) : dataObj[c[0]] = { cmds: c[1] })
      let dataArr = Object.entries(dataObj).sort()
      let field = []

      for (let i in dataArr) {
        let date = dataArr[i][0]
        if (today - parseInt(date) <= 172800000) {
          let parsed = moment(parseInt(date)).format("MMM Do")
          if (data.messages && dataArr[i][1].msgs) field.push({
            name: `Messages on ${parsed}`,
            value: data.messages[date],
            inline: dataArr[i] ? (dataArr[i][1].cmds ? true : false) : false
          })
          if (data.commands && dataArr[i][1].cmds) field.push({
            name: `Commands on ${parsed}`,
            value: data.commands[date],
            inline: dataArr[i] ? (dataArr[i][1].msgs ? true : false) : false
          })
        }
        // if (field.length === 3) break; <- Pretty sure this line is useless because of the if statement, right?
      }

      let sortRoles = Object.entries(roles).sort((a, b) => a[1].threshold - b[1].threshold)
      let nextRole = sortRoles.find(r => r[1].threshold > data.consecutive.length)
      field.push(
        {
          name: `Consecutive active days`,
          value: data.consecutive.length,
          inline: true
        },
        {
          name: `Days needed for next rank`,
          value: (nextRole) ? nextRole[1].threshold - data.consecutive.length : 'N/A',
          inline: true
        }
      )
      msg.channel.sendMessage('', false, {
        color: 0x59f442,
        title: `Statistics for ${member.username}#${member.discriminator}`,
        thumbnail: {
          url: member.staticAvatarURL
        },
        fields: field
      })
    }).catch(e => {
      msg.reply('an unexpected error occured while getting your stats, try again later.')
      console.error(e)
    })
  }
}

commands.fetch = {
  phantom: true,
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg) {
    msg.channel.fetchMessages().then(g => {
      msg.reply(`fetched ${g.messages.length} messages in this channel.`).then(f => {
        setTimeout(() => bot.Messages.deleteMessages([msg, f]), config.timeouts.messageDelete)
      })
    })
  }
}

commands.shutdown = {
  phantom: true,
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg) {
    msg.reply('shutting down...').then(() => {
      process.exit(0)
    })
  }
}

function wait (bot, msg) {
  let yn = /^y(es)?$|^n(o)?$/i
  return new Promise((resolve, reject) => {
    bot.Dispatcher.on('MESSAGE_CREATE', function doStuff (c) {
      var time = setTimeout(() => {
        resolve(null)
        bot.Dispatcher.removeListener('MESSAGE_CREATE', doStuff)
      }, config.timeouts.duplicateConfirm) // We won't wait forever for the person to anwser
      if (c.message.channel.id !== msg.channel.id) return
      if (c.message.author.id !== msg.author.id) return
      if (c.message.content.match(yn) === null) return
      else {
        resolve((c.message.content.match(/^y(es)?/i) !== null))
        bot.Dispatcher.removeListener('MESSAGE_CREATE', doStuff)
        clearTimeout(time)
      }
    })
  })
}

exports.Commands = commands
<commit_msg>Update way to get next rank in stats command (#95)<commit_after>var commands = []

// var checker = require('../../Utils/access_checker')
// var logger = require('../../Utils/error_loggers')
var config = require('../../config.js')
var analytics = require('../../Utils/orwell.js')
var bugsnag = require('bugsnag')
const Dash = require('rethinkdbdash')
const r = new Dash()
const roles = require('../../roles')

bugsnag.register(config.discord.bugsnag)

commands.ping = {
  phantom: true,
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg) {
    msg.channel.sendMessage('Pong!').then(successmsg => {
      setTimeout(() => bot.Messages.deleteMessages([msg, successmsg]), config.timeouts.messageDelete)
    })
  }
}

commands.help = {
  adminOnly: false,
  modOnly: false,
  fn: function (bot, msg) {
    msg.channel.sendMessage(`Hey ${msg.author.mention}! You can find all the info you need about the bot over at <#268812893087203338>!`).then(successmsg => {
      setTimeout(() => bot.Messages.deleteMessages([msg, successmsg]), config.timeouts.errorMessageDelete)
    })
  }
}

commands.stats = {
  adminOnly: false,
  modOnly: false,
  fn: function (bot, msg) {
    let moment = require('moment') // forgive me father for i have sinned
    analytics.getPoints(msg.member.id).then(data => {
      if (data === null) return msg.reply("you don't have any stats registered right now.")
      let now = new Date()
      let today = new Date(now.getFullYear(), now.getUTCMonth(), now.getUTCDate()).getTime()
      let dataObj = {}
      if (data.messages) Object.entries(data.messages).forEach(m => dataObj[m[0]] ? Object.assign(dataObj[m[0]], { msgs: m[1] }) : dataObj[m[0]] = { msgs: m[1] })
      if (data.commands) Object.entries(data.commands).forEach(c => dataObj[c[0]] ? Object.assign(dataObj[c[0]], { cmds: c[1] }) : dataObj[c[0]] = { cmds: c[1] })
      let dataArr = Object.entries(dataObj).sort()
      let field = []

      for (let i in dataArr) {
        let date = dataArr[i][0]
        if (today - parseInt(date) <= 172800000) {
          let parsed = moment(parseInt(date)).format("MMM Do")
          if (data.messages && dataArr[i][1].msgs) field.push({
            name: `Messages on ${parsed}`,
            value: data.messages[date],
            inline: dataArr[i] ? (dataArr[i][1].cmds ? true : false) : false
          })
          if (data.commands && dataArr[i][1].cmds) field.push({
            name: `Commands on ${parsed}`,
            value: data.commands[date],
            inline: dataArr[i] ? (dataArr[i][1].msgs ? true : false) : false
          })
        }
      }

      let sortedRoles = Object.entries(roles).sort((a, b) => a[1].rank - b[1].rank)
      let nextRankByThres = sortedRoles.find(r => r[1].threshold > data.consecutive.length)
      let rolesID = sortedRoles.map(r => r[0])
      let ownedRoles = msg.member.roles.map(r => rolesID.includes(r.id) ? r.id : '')
      let highestRole = sortedRoles.findIndex(r => r[0] === ownedRoles[ownedRoles.length - 1])
      let nextRankByOwned = highestRole != -1 ? sortedRoles[highestRole + 1] : ''
      let nextRank = (nextRankByThres && nextRankByThres) ? ((ownedRoles.includes(nextRankByThres[0])) ? nextRankByOwned[1].threshold : nextRankByThres[1].threshold) - data.consecutive.length : 'N/A'

      field.push(
        {
          name: `Consecutive active days`,
          value: data.consecutive.length,
          inline: true
        },
        {
          name: `Days needed for next rank`,
          value: nextRank,
          inline: true
        }
      )
      msg.author.openDM().then((e) => {
        e.sendMessage('', false, {
          color: 0x59f442,
          title: `${msg.author.username} - Statistics`,
          thumbnail: {
            url: msg.author.staticAvatarURL
          },
          fields: field
        }).catch(bugsnag.notify) // Send Message to DM error
      }).then(msg.delete()).catch(bugsnag.notify) // Error opening DM channel
    }).catch(e => {
      msg.reply('an unexpected error occured while getting your stats, try again later.')
      console.error(e)
    })
  }
}

commands['stats-reset'] = {
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg, suffix) {
    r.db('DFB').table('analytics').get(suffix).then(data => {
      if (data !== null) {
        msg.reply(`you're about to reset the stats for ${suffix}, are you sure?`).then(() => {
          wait(bot, msg).then(resp => {
            if (resp === null) {
              msg.channel.sendMessage('No answer given in time, operation aborted.')
            }
            if (resp === false) {
              msg.channel.sendMessage('Operation aborted.')
            }
            if (resp === true) {
              msg.channel.sendMessage(`Are you **ABSOLUTELY** sure? This can not be undone!`).then(() => {
                wait(bot, msg).then(resp2 => {
                  if (resp2 === null) {
                    msg.channel.sendMessage('No answer given in time, operation aborted.')
                  }
                  if (resp2 === false) {
                    msg.channel.sendMessage('Operation aborted.')
                  }
                  if (resp2 === true) {
                    r.db('DFB').table('analytics').get(suffix).delete().run().then(() => {
                      msg.channel.sendMessage(`Stats for ${suffix} are deleted.`)
                    }).catch(e => {
                      bugsnag.notify(e)
                      msg.channel.sendMessage(`Failed to delete stats for ${suffix}`)
                    })
                  }
                })
              })
            }
          })
        })
      } else {
        msg.reply(`no data for ${suffix} found.`)
      }
    })
  }
}

commands.lookup = {
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg, suffix) {
    let moment = require('moment') // forgive me father for i have sinned
    analytics.getPoints((msg.mentions.length !== 0) ? msg.mentions[0].id : suffix).then(data => {
      if (data === null) return msg.reply("couldn't find data on this user.")
      let member = msg.guild.members.find(member => member.id === data.id)
      let now = new Date()
      let today = new Date(now.getFullYear(), now.getUTCMonth(), now.getUTCDate()).getTime()
      let dataObj = {}
      if (data.messages) Object.entries(data.messages).forEach(m => dataObj[m[0]] ? Object.assign(dataObj[m[0]], { msgs: m[1] }) : dataObj[m[0]] = { msgs: m[1] })
      if (data.commands) Object.entries(data.commands).forEach(c => dataObj[c[0]] ? Object.assign(dataObj[c[0]], { cmds: c[1] }) : dataObj[c[0]] = { cmds: c[1] })
      let dataArr = Object.entries(dataObj).sort()
      let field = []

      for (let i in dataArr) {
        let date = dataArr[i][0]
        if (today - parseInt(date) <= 172800000) {
          let parsed = moment(parseInt(date)).format("MMM Do")
          if (data.messages && dataArr[i][1].msgs) field.push({
            name: `Messages on ${parsed}`,
            value: data.messages[date],
            inline: dataArr[i] ? (dataArr[i][1].cmds ? true : false) : false
          })
          if (data.commands && dataArr[i][1].cmds) field.push({
            name: `Commands on ${parsed}`,
            value: data.commands[date],
            inline: dataArr[i] ? (dataArr[i][1].msgs ? true : false) : false
          })
        }
      }

      let sortedRoles = Object.entries(roles).sort((a, b) => a[1].rank - b[1].rank)
      let nextRankByThres = sortedRoles.find(r => r[1].threshold > data.consecutive.length)
      let rolesID = sortedRoles.map(r => r[0])
      let ownedRoles = member.roles.map(r => rolesID.includes(r.id) ? r.id : '')
      let highestRole = sortedRoles.findIndex(r => r[0] === ownedRoles[ownedRoles.length - 1])
      let nextRankByOwned = highestRole != -1 ? sortedRoles[highestRole + 1] : ''
      let nextRank = (nextRankByThres && nextRankByThres) ? ((ownedRoles.includes(nextRankByThres[0])) ? nextRankByOwned[1].threshold : nextRankByThres[1].threshold) - data.consecutive.length : 'N/A'

      field.push(
        {
          name: `Consecutive active days`,
          value: data.consecutive.length,
          inline: true
        },
        {
          name: `Days needed for next rank`,
          value: nextRank,
          inline: true
        }
      )
      msg.channel.sendMessage('', false, {
        color: 0x59f442,
        title: `Statistics for ${member.username}#${member.discriminator}`,
        thumbnail: {
          url: member.staticAvatarURL
        },
        fields: field
      })
    }).catch(e => {
      msg.reply('an unexpected error occured while getting your stats, try again later.')
      console.error(e)
    })
  }
}

commands.fetch = {
  phantom: true,
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg) {
    msg.channel.fetchMessages().then(g => {
      msg.reply(`fetched ${g.messages.length} messages in this channel.`).then(f => {
        setTimeout(() => bot.Messages.deleteMessages([msg, f]), config.timeouts.messageDelete)
      })
    })
  }
}

commands.shutdown = {
  phantom: true,
  adminOnly: true,
  modOnly: false,
  fn: function (bot, msg) {
    msg.reply('shutting down...').then(() => {
      process.exit(0)
    })
  }
}

function wait (bot, msg) {
  let yn = /^y(es)?$|^n(o)?$/i
  return new Promise((resolve, reject) => {
    bot.Dispatcher.on('MESSAGE_CREATE', function doStuff (c) {
      var time = setTimeout(() => {
        resolve(null)
        bot.Dispatcher.removeListener('MESSAGE_CREATE', doStuff)
      }, config.timeouts.duplicateConfirm) // We won't wait forever for the person to anwser
      if (c.message.channel.id !== msg.channel.id) return
      if (c.message.author.id !== msg.author.id) return
      if (c.message.content.match(yn) === null) return
      else {
        resolve((c.message.content.match(/^y(es)?/i) !== null))
        bot.Dispatcher.removeListener('MESSAGE_CREATE', doStuff)
        clearTimeout(time)
      }
    })
  })
}

exports.Commands = commands
<|endoftext|>


<commit_before>import {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLEnumType,
  GraphQLUnionType,
  GraphQLList,
  getNullableType,
  getNamedType,
} from 'graphql/type';
import casual from 'casual-browserify';
import { graphql } from 'graphql';
import uuid from 'node-uuid';
import { forEachField, buildSchemaFromTypeDefinitions } from './schemaGenerator';

// This function wraps addMockFunctionsToSchema for more convenience
function mockServer(schema, mocks = {}, preserveResolvers = false) {
  let mySchema = schema;
  if (!(schema instanceof GraphQLSchema)) {
    // TODO: provide useful error messages here if this fails
    mySchema = buildSchemaFromTypeDefinitions(schema);
  }
  addMockFunctionsToSchema({ schema: mySchema, mocks, preserveResolvers });

  return { query: (query, vars) => graphql(mySchema, query, {}, {}, vars) };
}

// TODO allow providing a seed such that lengths of list could be deterministic
// this could be done by using casual to get a random list length if the casual
// object is global.
function addMockFunctionsToSchema({ schema, mocks = {}, preserveResolvers = false } = {}) {
  function isObject(thing) {
    return thing === Object(thing) && !Array.isArray(thing);
  }
  if (!schema) {
    // XXX should we check that schema is an instance of GraphQLSchema?
    throw new Error('Must provide schema to mock');
  }
  if (!isObject(mocks)) {
    throw new Error('mocks must be of type Object');
  }

  // use Map internally, because that API is nicer.
  const mockFunctionMap = new Map();
  Object.keys(mocks).forEach((typeName) => {
    mockFunctionMap.set(typeName, mocks[typeName]);
  });

  mockFunctionMap.forEach((mockFunction, mockTypeName) => {
    if (typeof mockFunction !== 'function') {
      throw new Error(`mockFunctionMap[${mockTypeName}] must be a function`);
    }
  });

  const defaultMockMap = new Map();
  defaultMockMap.set('Int', () => casual.integer());
  defaultMockMap.set('Float', () => casual.double());
  defaultMockMap.set('String', () => casual.words(2));
  defaultMockMap.set('Boolean', () => casual.coin_flip);
  defaultMockMap.set('ID', () => uuid.v4());

  function mergeObjects(a, b) {
    return Object.assign(a, b);
  }

  // takes either an object or a (possibly nested) array
  // and completes the customMock object with any fields
  // defined on genericMock
  // only merges objects or arrays. Scalars are returned as is
  function mergeMocks(genericMockFunction, customMock) {
    if (Array.isArray(customMock)) {
      return customMock.map((el) => mergeMocks(genericMockFunction, el));
    }
    if (isObject(customMock)) {
      return mergeObjects(genericMockFunction(), customMock);
    }
    return customMock;
  }

  const mockType = function mockType(type, typeName, fieldName) {
    // order of precendence for mocking:
    // 1. if the object passed in already has fieldName, just use that
    // --> if it's a function, that becomes your resolver
    // --> if it's a value, the mock resolver will return that
    // 2. if the nullableType is a list, recurse
    // 2. if there's a mock defined for this typeName, that will be used
    // 3. if there's no mock defined, use the default mocks for this type
    return (o, a, c, r) => {
      // nullability doesn't matter for the purpose of mocking.
      const fieldType = getNullableType(type);
      const namedFieldType = getNamedType(fieldType);

      if (o && typeof o[fieldName] !== 'undefined') {
        let result;
        // if we're here, the field is already defined
        if (typeof o[fieldName] === 'function') {
          result = o[fieldName](o, a, c, r);
          if (result instanceof MockList) {
            result = result.mock(o, a, c, r, fieldType, mockType);
          }
        } else {
          result = o[fieldName];
        }

        // Now we merge the result with the default mock for this type.
        // This allows overriding defaults while writing very little code.
        if (mockFunctionMap.has(namedFieldType.name)) {
          result = mergeMocks(
            mockFunctionMap.get(namedFieldType.name).bind(null, o, a, c, r), result
          );
        }
        return result;
      }

      if (fieldType instanceof GraphQLList) {
        return [mockType(type.ofType)(o, a, c, r), mockType(type.ofType)(o, a, c, r)];
      }
      if (mockFunctionMap.has(fieldType.name)) {
        // the object passed doesn't have this field, so we apply the default mock
        return mockFunctionMap.get(fieldType.name)(o, a, c, r);
      }
      if (fieldType instanceof GraphQLObjectType) {
        // objects don't return actual data, we only need to mock scalars!
        return {};
      }
      // TODO mocking Interface and Union types will require determining the
      // resolve type before passing it on.
      // XXX we recommend a generic way for resolve type here, which is defining
      // typename on the object.
      if (fieldType instanceof GraphQLUnionType) {
        return { typename: casual.random_element(fieldType.getTypes()) };
      }
      if (fieldType instanceof GraphQLEnumType) {
        return casual.random_element(fieldType.getValues()).value;
      }
      if (defaultMockMap.has(fieldType.name)) {
        return defaultMockMap.get(fieldType.name)(o, a, c, r);
      }
      // if we get to here, we don't have a value, and we don't have a mock for this type,
      // we could return undefined, but that would be hard to debug, so we throw instead.
      throw new Error(`No mock defined for type "${fieldType.name}"`);
    };
  };

  forEachField(schema, (field, typeName, fieldName) => {
    if (preserveResolvers && field.resolve) {
      return;
    }

    // we have to handle the root mutation and root query types differently,
    // because no resolver is called at the root.
    const isOnQueryType = typeName === (schema.getQueryType() || {}).name;
    const isOnMutationType = typeName === (schema.getMutationType() || {}).name;
    if (isOnQueryType || isOnMutationType) {
      if (mockFunctionMap.has(typeName)) {
        const rootMock = mockFunctionMap.get(typeName);
        if (rootMock()[fieldName]) {
          // TODO: assert that it's a function
          // eslint-disable-next-line no-param-reassign
          field.resolve = (o, a, c, r) => {
            const u = o || {}; // TODO: should we clone instead?
            u[fieldName] = rootMock()[fieldName];
            // XXX this is a bit of a hack to still use mockType, which
            // lets you mock lists etc. as well
            // otherwise we could just set field.resolve to rootMock()[fieldName]
            // it's like pretending there was a resolve function that ran before
            // the root resolve function.
            return mockType(field.type, typeName, fieldName)(u, a, c, r);
          };
          return;
        }
      }
    }
    // eslint-disable-next-line no-param-reassign
    field.resolve = mockType(field.type, typeName, fieldName);
  });
}

class MockList {
  // wrappedFunction can return another MockList or a value
  constructor(len, wrappedFunction) {
    this.len = len;
    if (typeof wrappedFunction !== 'undefined') {
      if (typeof wrappedFunction !== 'function') {
        throw new Error('Second argument to MockList must be a function or undefined');
      }
      this.wrappedFunction = wrappedFunction;
    }
  }

  mock(o, a, c, r, fieldType, mockTypeFunc) {
    function randint(low, high) {
      return Math.floor(Math.random() * (high - low + 1) + low);
    }
    let arr;
    if (Array.isArray(this.len)) {
      arr = new Array(randint(this.len[0], this.len[1]));
    } else {
      arr = new Array(this.len);
    }
    for (let i = 0; i < arr.length; i++) {
      if (typeof this.wrappedFunction === 'function') {
        const res = this.wrappedFunction(o, a, c, r);
        if (res instanceof MockList) {
          arr[i] = res.mock(o, a, c, r, getNullableType(fieldType.ofType), mockTypeFunc);
        } else {
          arr[i] = res;
        }
      } else {
        arr[i] = mockTypeFunc(fieldType.ofType)(o, a, c, r);
      }
    }
    return arr;
  }
}

export {
  addMockFunctionsToSchema,
  MockList,
  mockServer,
};
<commit_msg>wrote hint for even more convenient mocking of Union types<commit_after>import {
  GraphQLSchema,
  GraphQLObjectType,
  GraphQLEnumType,
  GraphQLUnionType,
  GraphQLList,
  getNullableType,
  getNamedType,
} from 'graphql/type';
import casual from 'casual-browserify';
import { graphql } from 'graphql';
import uuid from 'node-uuid';
import { forEachField, buildSchemaFromTypeDefinitions } from './schemaGenerator';

// This function wraps addMockFunctionsToSchema for more convenience
function mockServer(schema, mocks = {}, preserveResolvers = false) {
  let mySchema = schema;
  if (!(schema instanceof GraphQLSchema)) {
    // TODO: provide useful error messages here if this fails
    mySchema = buildSchemaFromTypeDefinitions(schema);
  }
  addMockFunctionsToSchema({ schema: mySchema, mocks, preserveResolvers });

  return { query: (query, vars) => graphql(mySchema, query, {}, {}, vars) };
}

// TODO allow providing a seed such that lengths of list could be deterministic
// this could be done by using casual to get a random list length if the casual
// object is global.
function addMockFunctionsToSchema({ schema, mocks = {}, preserveResolvers = false } = {}) {
  function isObject(thing) {
    return thing === Object(thing) && !Array.isArray(thing);
  }
  if (!schema) {
    // XXX should we check that schema is an instance of GraphQLSchema?
    throw new Error('Must provide schema to mock');
  }
  if (!isObject(mocks)) {
    throw new Error('mocks must be of type Object');
  }

  // use Map internally, because that API is nicer.
  const mockFunctionMap = new Map();
  Object.keys(mocks).forEach((typeName) => {
    mockFunctionMap.set(typeName, mocks[typeName]);
  });

  mockFunctionMap.forEach((mockFunction, mockTypeName) => {
    if (typeof mockFunction !== 'function') {
      throw new Error(`mockFunctionMap[${mockTypeName}] must be a function`);
    }
  });

  const defaultMockMap = new Map();
  defaultMockMap.set('Int', () => casual.integer());
  defaultMockMap.set('Float', () => casual.double());
  defaultMockMap.set('String', () => casual.words(2));
  defaultMockMap.set('Boolean', () => casual.coin_flip);
  defaultMockMap.set('ID', () => uuid.v4());

  function mergeObjects(a, b) {
    return Object.assign(a, b);
  }

  // takes either an object or a (possibly nested) array
  // and completes the customMock object with any fields
  // defined on genericMock
  // only merges objects or arrays. Scalars are returned as is
  function mergeMocks(genericMockFunction, customMock) {
    if (Array.isArray(customMock)) {
      return customMock.map((el) => mergeMocks(genericMockFunction, el));
    }
    if (isObject(customMock)) {
      return mergeObjects(genericMockFunction(), customMock);
    }
    return customMock;
  }

  const mockType = function mockType(type, typeName, fieldName) {
    // order of precendence for mocking:
    // 1. if the object passed in already has fieldName, just use that
    // --> if it's a function, that becomes your resolver
    // --> if it's a value, the mock resolver will return that
    // 2. if the nullableType is a list, recurse
    // 2. if there's a mock defined for this typeName, that will be used
    // 3. if there's no mock defined, use the default mocks for this type
    return (o, a, c, r) => {
      // nullability doesn't matter for the purpose of mocking.
      const fieldType = getNullableType(type);
      const namedFieldType = getNamedType(fieldType);

      if (o && typeof o[fieldName] !== 'undefined') {
        let result;
        // if we're here, the field is already defined
        if (typeof o[fieldName] === 'function') {
          result = o[fieldName](o, a, c, r);
          if (result instanceof MockList) {
            result = result.mock(o, a, c, r, fieldType, mockType);
          }
        } else {
          result = o[fieldName];
        }

        // Now we merge the result with the default mock for this type.
        // This allows overriding defaults while writing very little code.
        if (mockFunctionMap.has(namedFieldType.name)) {
          result = mergeMocks(
            mockFunctionMap.get(namedFieldType.name).bind(null, o, a, c, r), result
          );
        }
        return result;
      }

      if (fieldType instanceof GraphQLList) {
        return [mockType(type.ofType)(o, a, c, r), mockType(type.ofType)(o, a, c, r)];
      }
      if (mockFunctionMap.has(fieldType.name)) {
        // the object passed doesn't have this field, so we apply the default mock
        return mockFunctionMap.get(fieldType.name)(o, a, c, r);
      }
      if (fieldType instanceof GraphQLObjectType) {
        // objects don't return actual data, we only need to mock scalars!
        return {};
      }
      // TODO mocking Interface and Union types will require determining the
      // resolve type before passing it on.
      // XXX we recommend a generic way for resolve type here, which is defining
      // typename on the object.
      if (fieldType instanceof GraphQLUnionType) {
        // TODO: if a union type doesn't implement resolveType, we could overwrite
        // it with a default that works with what's below.
        return { typename: casual.random_element(fieldType.getTypes()) };
      }
      if (fieldType instanceof GraphQLEnumType) {
        return casual.random_element(fieldType.getValues()).value;
      }
      if (defaultMockMap.has(fieldType.name)) {
        return defaultMockMap.get(fieldType.name)(o, a, c, r);
      }
      // if we get to here, we don't have a value, and we don't have a mock for this type,
      // we could return undefined, but that would be hard to debug, so we throw instead.
      throw new Error(`No mock defined for type "${fieldType.name}"`);
    };
  };

  forEachField(schema, (field, typeName, fieldName) => {
    if (preserveResolvers && field.resolve) {
      return;
    }

    // we have to handle the root mutation and root query types differently,
    // because no resolver is called at the root.
    const isOnQueryType = typeName === (schema.getQueryType() || {}).name;
    const isOnMutationType = typeName === (schema.getMutationType() || {}).name;
    if (isOnQueryType || isOnMutationType) {
      if (mockFunctionMap.has(typeName)) {
        const rootMock = mockFunctionMap.get(typeName);
        if (rootMock()[fieldName]) {
          // TODO: assert that it's a function
          // eslint-disable-next-line no-param-reassign
          field.resolve = (o, a, c, r) => {
            const u = o || {}; // TODO: should we clone instead?
            u[fieldName] = rootMock()[fieldName];
            // XXX this is a bit of a hack to still use mockType, which
            // lets you mock lists etc. as well
            // otherwise we could just set field.resolve to rootMock()[fieldName]
            // it's like pretending there was a resolve function that ran before
            // the root resolve function.
            return mockType(field.type, typeName, fieldName)(u, a, c, r);
          };
          return;
        }
      }
    }
    // eslint-disable-next-line no-param-reassign
    field.resolve = mockType(field.type, typeName, fieldName);
  });
}

class MockList {
  // wrappedFunction can return another MockList or a value
  constructor(len, wrappedFunction) {
    this.len = len;
    if (typeof wrappedFunction !== 'undefined') {
      if (typeof wrappedFunction !== 'function') {
        throw new Error('Second argument to MockList must be a function or undefined');
      }
      this.wrappedFunction = wrappedFunction;
    }
  }

  mock(o, a, c, r, fieldType, mockTypeFunc) {
    function randint(low, high) {
      return Math.floor(Math.random() * (high - low + 1) + low);
    }
    let arr;
    if (Array.isArray(this.len)) {
      arr = new Array(randint(this.len[0], this.len[1]));
    } else {
      arr = new Array(this.len);
    }
    for (let i = 0; i < arr.length; i++) {
      if (typeof this.wrappedFunction === 'function') {
        const res = this.wrappedFunction(o, a, c, r);
        if (res instanceof MockList) {
          arr[i] = res.mock(o, a, c, r, getNullableType(fieldType.ofType), mockTypeFunc);
        } else {
          arr[i] = res;
        }
      } else {
        arr[i] = mockTypeFunc(fieldType.ofType)(o, a, c, r);
      }
    }
    return arr;
  }
}

export {
  addMockFunctionsToSchema,
  MockList,
  mockServer,
};
<|endoftext|>