<commit_before>'use strict';

importScripts('/serviceworker-cache-polyfill.js');

var CACHE_NAME = 'bafrontend';
var CACHE_VERSION = '0.0.9';

var urlsToCache = [
  '/',
  '/src/css/styles.min.css',
  '/assets/baf-logo.png',
  '/assets/city.png',
  '/assets/community.jpeg',
  '/assets/frontenders.png',
  '/assets/sponsors/auth0.jpeg',
  '/assets/sponsors/aerolab.jpeg',
  '/assets/sponsors/davinci.jpeg',
  '/assets/sponsors/flowics.jpeg',
  '/assets/sponsors/mango.png',
  '/assets/sponsors/mercadolibre.jpeg',
  '/assets/sponsors/px2html.jpeg',
  '/assets/icons/twitter.svg',
  '/assets/icons/github.svg',
  '/assets/icons/linkedin.svg',
  '/assets/Sun/Sun-ExtraLight.ttf',
  '/assets/favicons/favicon.ico'
];

/**
 * Install
 */
this.addEventListener('install', function(eve) {
  var urls = urlsToCache.map(function(url) {
    return new Request(url, {credentials: 'include'});
  });
  eve.waitUntil(
    caches.open(CACHE_NAME + '-v' + CACHE_VERSION)
      .then(function(cache) {
        return cache.addAll(urls);
      })
  );
});

/**
 * Active / Remove old caches
 */
this.addEventListener('active', function(eve) {
  var currentCacheName = CACHE_NAME + '-v' + CACHE_VERSION;
  caches.keys().then(function(cacheNames) {
    return Promise.all(
      cacheNames.map(function(cacheName) {
        if (cacheName != currentCacheName) {
          return caches.delete(cacheName);
        }
      })
    );
  });
});

/**
 * Fetch
 */
this.addEventListener('fetch', function(eve) {
  var request = eve.request;
  var url = new URL(request.url);
  eve.respondWith(
    caches.match(request).then(function(response) {
      return response || fetch(request);
    })
  );
});
<commit_msg>Fix activate event.<commit_after>'use strict';

importScripts('/serviceworker-cache-polyfill.js');

var CACHE_NAME = 'bafrontend';
var CACHE_VERSION = '0.0.10';

var urlsToCache = [
  '/',
  '/src/css/styles.min.css',
  '/assets/baf-logo.png',
  '/assets/city.png',
  '/assets/community.jpeg',
  '/assets/frontenders.png',
  '/assets/sponsors/auth0.jpeg',
  '/assets/sponsors/aerolab.jpeg',
  '/assets/sponsors/davinci.jpeg',
  '/assets/sponsors/flowics.jpeg',
  '/assets/sponsors/mango.png',
  '/assets/sponsors/mercadolibre.jpeg',
  '/assets/sponsors/px2html.jpeg',
  '/assets/icons/twitter.svg',
  '/assets/icons/github.svg',
  '/assets/icons/linkedin.svg',
  '/assets/Sun/Sun-ExtraLight.ttf',
  '/assets/favicons/favicon.ico'
];

/**
 * Install
 */
this.addEventListener('install', function(eve) {
  var urls = urlsToCache.map(function(url) {
    return new Request(url, {credentials: 'include'});
  });
  eve.waitUntil(
    caches.open(CACHE_NAME + '-v' + CACHE_VERSION)
      .then(function(cache) {
        return cache.addAll(urls);
      })
  );
});

/**
 * Active / Remove old caches
 */
this.addEventListener('activate', function(eve) {
  var currentCacheName = CACHE_NAME + '-v' + CACHE_VERSION;
  caches.keys().then(function(cacheNames) {
    return Promise.all(
      cacheNames.map(function(cacheName) {
        if (cacheName != currentCacheName) {
          return caches.delete(cacheName);
        }
      })
    );
  });
});

/**
 * Fetch
 */
this.addEventListener('fetch', function(eve) {
  var request = eve.request;
  var url = new URL(request.url);
  eve.respondWith(
    caches.match(request).then(function(response) {
      return response || fetch(request);
    })
  );
});
<|endoftext|>


<commit_before>var VERSION = '0.0.2';

this.addEventListener('install', function(e) {
  e.waitUntil(caches.open(VERSION).then(cache => {
    return cache.addAll([
      '/',
      '/index.html',
      '/css/base.css',
      '/css/mobile.css',
      '/css/pygments.css',
      '/manifest.json',
      '/images/favicon.png',
      '/images/avatar.jpeg',
      '/elements/emoji-rain.html',
      '/bower_components/webcomponentsjs/webcomponents-lite.js',
    ]).then(_ => this.skipWaiting());
}))});

this.addEventListener('fetch', function(e) {
  e.respondWith(caches.match(e.request).then((res) => {
    // If there is no match in the cache, we get undefined back,
    // in that case go to the network!
    return res ? res : fetch(e.request);
  }));
});

this.addEventListener('activate', function(e) {
  e.waitUntil(caches.keys().then((keys) => {
    return Promise.all(keys.map(k => {
      if (k !== VERSION) {
        return caches.delete(k);
      }
    })).then(_ => {
      return this.clients.claim()
    });
}))});

// fetch from network
// and put into our cache
function handleNoCacheMatch(e) {
  return fetch(e.request).then(res => {
    var origin = location.origin;
    var weCare =
        res.url.indexOf(origin + '/images') !== -1 ||
        res.url.indexOf(origin + '/fonts') !== -1 ||
        res.url.indexOf(origin + '/about') !== -1 ||
        res.url.indexOf(origin + '/talks') !== -1 ||
        res.url.indexOf(origin + '/posts') !== -1;

    if (!weCare)
      return res;

    return caches.open(VERSION).then(cache => {
      cache.put(e.request, res.clone());
      return res;
    });
  });
}
<commit_msg>maybe new cache?<commit_after>var VERSION = '0.0.3';

this.addEventListener('install', function(e) {
  e.waitUntil(caches.open(VERSION).then(cache => {
    return cache.addAll([
      '/',
      '/index.html',
      '/css/base.css',
      '/css/mobile.css',
      '/css/pygments.css',
      '/manifest.json',
      '/images/favicon.png',
      '/images/avatar.jpeg',
      '/elements/emoji-rain.html',
      '/bower_components/webcomponentsjs/webcomponents-lite.js',
    ]).then(_ => this.skipWaiting());
}))});

this.addEventListener('fetch', function(e) {
  e.respondWith(caches.match(e.request).then((res) => {
    // If there is no match in the cache, we get undefined back,
    // in that case go to the network!
    return res ? res : fetch(e.request);
  }));
});

this.addEventListener('activate', function(e) {
  e.waitUntil(caches.keys().then((keys) => {
    return Promise.all(keys.map(k => {
      if (k !== VERSION) {
        return caches.delete(k);
      }
    })).then(_ => {
      return this.clients.claim()
    });
}))});

// fetch from network
// and put into our cache
function handleNoCacheMatch(e) {
  return fetch(e.request).then(res => {
    var origin = location.origin;
    var weCare =
        res.url.indexOf(origin + '/images') !== -1 ||
        res.url.indexOf(origin + '/fonts') !== -1 ||
        res.url.indexOf(origin + '/about') !== -1 ||
        res.url.indexOf(origin + '/talks') !== -1 ||
        res.url.indexOf(origin + '/posts') !== -1;

    if (!weCare)
      return res;

    return caches.open(VERSION).then(cache => {
      cache.put(e.request, res.clone());
      return res;
    });
  });
}
<|endoftext|>


<commit_before>importScripts('/assets/sw/sw-toolbox.js');

const config = {
  offlinePage: '/404.html'
};

config.filesToCache = [
    '/',
    config.offlinePage,
    '/index.html',
    '/blog/index.html',
    '/assets/images/hero_image.jpg',
    '/sw.js',
    '/about/',
    '/assets/favicon/manifest.json'
];

/**
 * Generates a placeholder SVG image of the given size.
 */
function offlineImage(name, width, height) {
  return `<?xml version="1.0"?>
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <g fill="none" fill-rule="evenodd"><path fill="#F8BBD0" d="M0 0h${width}v${height}H0z"/></g>
  <text text-anchor="middle" x="${Math.floor(width / 2)}" y="${Math.floor(height / 2)}">image offline (${name})</text>
<style><![CDATA[
text{
  font: 48px Roboto,Verdana, Helvetica, Arial, sans-serif;
}
]]></style>
</svg>`;
}
/**
 * Returns true if the Accept header contains the given content type string.
 */
function requestAccepts(request, contentType) {
  return request.headers.get('Accept').indexOf(contentType) != -1;
}

/**
 * ampbyexample.com fetch handler:
 *
 * - one-behind caching
 * - shows offline page
 * - generates placeholder image for unavailable images
 */
function ampByExampleHandler(request, values) {
  // for samples show offline page if offline and samples are not cached
  if (requestAccepts(request, 'text/html')) {
    // never use cached version for AMP CORS requests (e.g. amp-live-list)
    if (request.url.indexOf("__amp_source_origin") != -1) {
      return toolbox.networkOnly(request, values);
    }
    // cache or network - whatever is fastest
    return toolbox.fastest(request, values).catch(function() {
        return toolbox.cacheOnly(new Request(config.offlinePage), values);
    });
  }
  // always try to load images from the cache first
  // fallback to placeholder SVG image if offline and image not available
  if (requestAccepts(request, 'image/')) {
    return toolbox.cacheFirst(request, values).catch(function() {
      const url = request.url;
      const fileName = url.substring(url.lastIndexOf('/') + 1);
      // TODO use correct image dimensions
      return new Response(offlineImage(fileName, 1080, 610),
          { headers: { 'Content-Type': 'image/svg+xml' } }
      );
    });
  } else {
    // cache all other requests
    return toolbox.fastest(request, values);
  }
}

toolbox.options.debug = false;
toolbox.router.default = toolbox.networkOnly;
toolbox.router.get('/(.*)', ampByExampleHandler, {origin: self.location.origin});
// cache first google fonts
toolbox.router.get('/(.+)', toolbox.cacheFirst, {origin: /https?:\/\/fonts.+/});

toolbox.precache(config.filesToCache);<commit_msg>offline page<commit_after>importScripts('/assets/sw/sw-toolbox.js');

const config = {
  offlinePage: '/offline.html'
};

config.filesToCache = [
    '/',
    config.offlinePage,
    '/offline.html',
    '/404.html',
    '/index.html',
    '/blog/index.html',
    '/assets/images/hero_image.jpg',
    '/sw.js',
    '/about/',
    '/assets/favicon/manifest.json'
];

/**
 * Generates a placeholder SVG image of the given size.
 */
function offlineImage(name, width, height) {
  return `<?xml version="1.0"?>
<svg width="${width}" height="${height}" viewBox="0 0 ${width} ${height}" xmlns="http://www.w3.org/2000/svg" version="1.1">
  <g fill="none" fill-rule="evenodd"><path fill="#F8BBD0" d="M0 0h${width}v${height}H0z"/></g>
  <text text-anchor="middle" x="${Math.floor(width / 2)}" y="${Math.floor(height / 2)}">image offline (${name})</text>
<style><![CDATA[
text{
  font: 48px Roboto,Verdana, Helvetica, Arial, sans-serif;
}
]]></style>
</svg>`;
}
/**
 * Returns true if the Accept header contains the given content type string.
 */
function requestAccepts(request, contentType) {
  return request.headers.get('Accept').indexOf(contentType) != -1;
}

/**
 * ampbyexample.com fetch handler:
 *
 * - one-behind caching
 * - shows offline page
 * - generates placeholder image for unavailable images
 */
function ampByExampleHandler(request, values) {
  // for samples show offline page if offline and samples are not cached
  if (requestAccepts(request, 'text/html')) {
    // never use cached version for AMP CORS requests (e.g. amp-live-list)
    if (request.url.indexOf("__amp_source_origin") != -1) {
      return toolbox.networkOnly(request, values);
    }
    // cache or network - whatever is fastest
    return toolbox.fastest(request, values).catch(function() {
        return toolbox.cacheOnly(new Request(config.offlinePage), values);
    });
  }
  // always try to load images from the cache first
  // fallback to placeholder SVG image if offline and image not available
  if (requestAccepts(request, 'image/')) {
    return toolbox.cacheFirst(request, values).catch(function() {
      const url = request.url;
      const fileName = url.substring(url.lastIndexOf('/') + 1);
      // TODO use correct image dimensions
      return new Response(offlineImage(fileName, 1080, 610),
          { headers: { 'Content-Type': 'image/svg+xml' } }
      );
    });
  } else {
    // cache all other requests
    return toolbox.fastest(request, values);
  }
}

toolbox.options.debug = false;
toolbox.router.default = toolbox.networkOnly;
toolbox.router.get('/(.*)', ampByExampleHandler, {origin: self.location.origin});
// cache first google fonts
toolbox.router.get('/(.+)', toolbox.cacheFirst, {origin: /https?:\/\/fonts.+/});

toolbox.precache(config.filesToCache);<|endoftext|>


<commit_before>var VERSION = '16';

this.addEventListener('install', function(e) {
  e.waitUntil(caches.open(VERSION).then(cache => {
    return cache.addAll([
      '/',
      '/index.html',
      '/sw.js',
      '/stylesheets/default.css',
      '/javascripts/default.js'
    ]);
  }))
});

this.addEventListener('fetch', function(e) {
  var tryInCachesFirst = caches.open(VERSION).then(cache => {
    return cache.match(e.request).then(response => {
      if (!response) {
        return handleNoCacheMatch(e);
      }
      // Update cache record in the background
      fetchFromNetworkAndCache(e);
      // Reply with stale data
      return response
    });
  });
  e.respondWith(tryInCachesFirst);
});

this.addEventListener('activate', function(e) {
  e.waitUntil(caches.keys().then(keys => {
    return Promise.all(keys.map(key => {
      if (key !== VERSION)
        return caches.delete(key);
    }));
  }));
});

function fetchFromNetworkAndCache(e) {
  return fetch(e.request).then(res => {
    // don't cache ANY foreign requests right now.
    if (new URL(res.url).origin !== location.origin) return res;

    return caches.open(VERSION).then(cache => {
      // TODO: figure out if the content is new and therefore the page needs a reload.
      cache.put(e.request, res.clone());
      return res;
    });
  }).catch(err => console.error(e.request.url, err));
}

function handleNoCacheMatch(e) {
  return fetchFromNetworkAndCache(e);
}<commit_msg>bump sw version.<commit_after>var VERSION = '17';

this.addEventListener('install', function(e) {
  e.waitUntil(caches.open(VERSION).then(cache => {
    return cache.addAll([
      '/',
      '/index.html',
      '/sw.js',
      '/stylesheets/default.css',
      '/javascripts/default.js'
    ]);
  }))
});

this.addEventListener('fetch', function(e) {
  var tryInCachesFirst = caches.open(VERSION).then(cache => {
    return cache.match(e.request).then(response => {
      if (!response) {
        return handleNoCacheMatch(e);
      }
      // Update cache record in the background
      fetchFromNetworkAndCache(e);
      // Reply with stale data
      return response
    });
  });
  e.respondWith(tryInCachesFirst);
});

this.addEventListener('activate', function(e) {
  e.waitUntil(caches.keys().then(keys => {
    return Promise.all(keys.map(key => {
      if (key !== VERSION)
        return caches.delete(key);
    }));
  }));
});

function fetchFromNetworkAndCache(e) {
  return fetch(e.request).then(res => {
    // don't cache ANY foreign requests right now.
    if (new URL(res.url).origin !== location.origin) return res;

    return caches.open(VERSION).then(cache => {
      // TODO: figure out if the content is new and therefore the page needs a reload.
      cache.put(e.request, res.clone());
      return res;
    });
  }).catch(err => console.error(e.request.url, err));
}

function handleNoCacheMatch(e) {
  return fetchFromNetworkAndCache(e);
}<|endoftext|>


<commit_before>var Telegram = require('node-telegram-bot-api');
var fs = require('fs');
var path = require('path');
var irc = require('./irc');
var nodeStatic = require('node-static');
var mkdirp = require('mkdirp');
var crypto = require('crypto');
var exec = require('child_process');
var dwebp = require('dwebp-bin');

// tries to read chat ids from a file
var readChatIds = function(arr) {
    console.log('\n');
    console.log('NOTE!');
    console.log('=====');

    var idMissing = false;
    try {
        var json = JSON.parse(fs.readFileSync(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/chat_ids'));
        for (var i = 0; i < arr.length; i++) {
            var key = arr[i].tgGroup;
            if (key in json) {
                arr[i].tgChatId = json[key];
                console.log('id found for:', key, ':', json[key]);
            } else {
                console.log('id not found:', key);
                idMissing = true;
            }
        }
    } catch (e) {
        console.log('~/.teleirc/chat_ids file not found!');
        idMissing = true;
    }

    if (idMissing) {
        console.log(
            '\nPlease add your Telegram bot to a Telegram group and have' +
            '\nsomeone send a message to that group.' +
            '\nteleirc will then automatically store your group chat_id.');
    }

    console.log('\n');
};

var writeChatIds = function(config) {
    var json = {};
    for (var i = 0; i < config.channels.length; i++) {
        if (config.channels[i].tgChatId) {
            json[config.channels[i].tgGroup] = config.channels[i].tgChatId;
        }
    }
    json = JSON.stringify(json);
    fs.writeFile(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/chat_ids', json, function(err) {
        if (err) {
            console.log('error while storing chat ID:');
            console.log(err);
        } else {
            console.log('successfully stored chat ID in ~/.teleirc/chat_ids');
        }
    });
};

var getName = function(user, config) {
    var name = config.nameFormat;

    if (user.username) {
        name = name.replace('%username%', '@' + user.username, 'g');
    } else {
        // if user lacks username, use fallback format string instead
        name = name.replace('%username%', config.usernameFallbackFormat, 'g');
    }

    name = name.replace('%firstName%', user.first_name || '', 'g');
    name = name.replace('%lastName%', user.last_name || '', 'g');

    // get rid of leading and trailing whitespace
    name = name.replace(/(^\s*)|(\s*$)/g, '');

    return name;
};

function randomValueBase64(len) {
    return crypto.randomBytes(Math.ceil(len * 3 / 4))
        .toString('base64')
        .slice(0, len)
        .replace(/\+/g, '0')
        .replace(/\//g, '0');
}

var serveFile = function(fileId, config, tg, callback) {
    var randomString = randomValueBase64(config.mediaRandomLenght);
    mkdirp(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files/' + randomString);
    tg.downloadFile(fileId, process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files/' + randomString).then(function(filePath) {
        if (path.extname(filePath) == '.webp') {
            var newPath = path.dirname(filePath) + '/' + path.basename(filePath, '.webp') + '.png';
            exec.execFile(dwebp.path, [filePath, '-o', newPath], function (error) {
                if (!error) {
                    filePath = newPath;
                    callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
                } else {
                    console.error('Convert webp failed: ' + error);
                    var cloudconvert = new (require('cloudconvert'))(config.cloudConvertKey);
                    // create the process. see https://cloudconvert.com/apidoc#create
                    cloudconvert.createProcess({inputformat: 'webp', outputformat: 'png'}, function(err, process) {
                        if(err) {
                            console.error('CloudConvert Process creation failed: ' + err);
                        } else {
                            // start the process. see https://cloudconvert.com/apidoc#create
                            process.start({
                                outputformat: 'png',
                                input: 'upload'
                            }, function (err, process) {
                                if (err) {
                                    console.error('CloudConvert Process start failed: ' + err);
                                } else {
                                    // upload the input file. see https://cloudconvert.com/apidoc#upload
                                    process.upload(fs.createReadStream(filePath), null, function (err, process) {
                                        if (err) {
                                            console.error('CloudConvert Process upload failed: ' + err);
                                        } else {
                                            // wait until the process is finished (or completed with an error)
                                            process.wait(function (err, process) {
                                                if (err) {
                                                    console.error('CloudConvert Process failed: ' + err);
                                                } else {
                                                    // download it
                                                    process.download(fs.createWriteStream(newPath), null, function (err, process) {
                                                        if (err) {
                                                            console.error('CloudConvert Process download failed: ' + err);
                                                        } else {
                                                            filePath = newPath;
                                                            callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
                                                        }
                                                    });
                                                }
                                            });
                                        }
                                    });
                                }
                            });
                        }
                    });
                }
            });
        } else {
            callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
        }
    });
};

module.exports = function(config, sendTo) {
    // start HTTP server for media files if configured to do so
    if (config.showMedia) {
        var fileServer = new nodeStatic.Server(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files');
        mkdirp(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files');

        require('http').createServer(function(req, res) {
            req.addListener('end', function() {
                fileServer.serve(req, res);
            }).resume();
        }).listen(config.httpLocalBindPort, config.httpLocalBindIP);
    }

    var tg = new Telegram(config.tgToken, {polling: true});

    readChatIds(config.channels);

    tg.on('message', function(msg) {
        var channel = config.channels.filter(function(channel) {
            return channel.tgGroup === msg.chat.title;
        })[0];

        if (!channel) {
            return;
        }

        if (!channel.tgChatId) {
            console.log('storing chat ID: ' + msg.chat.id);
            channel.tgChatId = msg.chat.id;
            writeChatIds(config);
        }

        if (msg.text && !msg.text.indexOf('/names')) {
            var names = sendTo.ircNames(channel);
            names.sort();
            names = 'Users on ' + (channel.chanAlias || channel.ircChan) + ':\n\n' +
                names.join(', ');

            return tg.sendMessage(channel.tgChatId, names);
        }

        // skip posts containing media if it's configured off
        if ((msg.audio || msg.document || msg.photo || msg.sticker || msg.video ||
            msg.voice || msg.contact || msg.location) && !config.showMedia) {
            return;
        }
        var text;

        var caption = '';
        if (msg.caption) {
            caption = msg.caption + ' ';
        }

        var forward = '';
        if (msg.forward_from) {
            forward = 'Fwd: ';
            var forwardName = getName(msg.forward_from, config);
            if (forwardName != '@' + config.tgBotName) {
                forward = forward + '<' + forwardName + '> ';
            }
        }

        var reply = '';
        if (msg.reply_to_message) {
            var replyName = getName(msg.reply_to_message.from, config);
            if (replyName == '@' + config.tgBotName) {
                replyName = '';
                var matches = msg.reply_to_message.text.match(/^<(.*?)>/);
                if (matches) {
                    replyName = matches[1];
                } else {
                    matches = msg.reply_to_message.text.match(/^\*(.*?) /);
                    if (matches) {
                        replyName = matches[1];
                    }
                }
                var lastMessageId = 'lastMessageId' + channel.tgChatId + replyName;
                // console.log('Comparing saved ID and current in TG group ' + channel.tgChatId + ': ' + process.env['lastMessageId' + channel.tgChatId] + ' | ' + msg.message_id);
                if (lastMessageId in process.env && process.env[lastMessageId] != msg.reply_to_message.message_id) {
                    reply = '"' + msg.reply_to_message.text + '"';
                    sendTo.irc(channel.ircChan, '-> ' + reply);
                    reply = '';
                } else {
                    reply = replyName + ': ';
                }
            } else {
                var lastMessageId = 'lastMessageId' + channel.tgChatId + getName(msg.reply_to_message.from, config);
                // console.log('Comparing saved ID and current in TG group ' + channel.tgChatId + ': ' + process.env['lastMessageId' + channel.tgChatId] + ' | ' + msg.message_id);
                if (msg.reply_to_message.text && (lastMessageId in process.env && process.env[lastMessageId] != msg.reply_to_message.message_id)) {
                    reply = '"<' + replyName + '> ' + msg.reply_to_message.text + '"';
                    sendTo.irc(channel.ircChan, '-> ' + reply);
                    reply = '';
                } else {
                    reply = replyName + ': ';
                }
            }
        }

        if (msg.audio) {
            serveFile(msg.audio.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(Audio) ' + url);
            });
        } else if (msg.document) {
            serveFile(msg.document.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(File) ' + url);
            });
        } else if (msg.photo) {
            // pick the highest quality photo
            var photo = msg.photo[msg.photo.length - 1];

            serveFile(photo.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    caption + '(Photo, ' + photo.width + 'x' + photo.height + ') ' + url);
            });
        } else if (msg.new_chat_photo) {
            // pick the highest quality photo
            var chatPhoto = msg.new_chat_photo[msg.new_chat_photo.length - 1];

            serveFile(chatPhoto.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' +
                    '(New chat photo, ' + chatPhoto.width + 'x' + chatPhoto.height + ') ' + url);
            });
        } else if (msg.sticker) {
            serveFile(msg.sticker.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(Sticker, ' + msg.sticker.width + 'x' + msg.sticker.height + ') ' + url);
            });
        } else if (msg.video) {
            serveFile(msg.video.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    caption + '(Video, ' + msg.video.duration + 's) ' + url);
            });
        } else if (msg.voice) {
            serveFile(msg.voice.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(Voice, ' + msg.audio.duration + 's) ' + url);
            });
        } else if (msg.contact) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                '(Contact, ' + '"' + msg.contact.first_name + ' ' +
                msg.contact.last_name + '", ' +
                msg.contact.phone_number + ')');
        } else if (msg.location) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                '(Location, ' + 'lon: ' + msg.location.longitude +
                              ', lat: ' + msg.location.latitude + ')');
        } else if (msg.new_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.new_chat_participant, config) +
                ' was added by ' + getName(msg.from, config));
        } else if (msg.left_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.left_chat_participant, config) +
                ' was removed by ' + getName(msg.from, config));
        } else {
            text = msg.text.replace(/\n/g , '\n<' + getName(msg.from, config) + '> ');
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply + text);
        }

        process.env['lastMessageId' + channel.tgChatId + getName(msg.from, config)] = msg.message_id;
        //console.log('Saved message ID recived in TG group ' + channel.tgChatId + ': ' + process.env['lastMessageId' + channel.tgChatId]);

    });

    sendTo.tg = function(channel, msg) {
        console.log('  >> relaying to TG: ' + msg);

        if (!channel.tgChatId) {
            var err = 'ERROR: No chat_id set! Add me to a Telegram group ' +
                      'and say hi so I can find your group\'s chat_id!';
            sendTo.irc(channel.ircChan, err);
            console.error(err);
            return;
        }

        tg.sendMessage(channel.tgChatId, msg).then(function (sended) {
            var chatId = sended.chat_id;
            var messageId = sended.message_id;

            var replyName = '';
            var matches = sended.text.match(/^<(.*?)>/);
            if (matches) {
                replyName = matches[1];
            } else {
                matches = sended.text.match(/^\*(.*?) /);
                if (matches) {
                    replyName = matches[1];
                }
            }

            process.env['lastMessageId' + chatId + replyName] = messageId;
            //console.log('Saved message ID sent to TG group ' + chatId + ': ' + process.env['lastMessageId' + chatId]);
        });
    };
};
<commit_msg>Logging messages from IRC to TG.<commit_after>var Telegram = require('node-telegram-bot-api');
var fs = require('fs');
var path = require('path');
var irc = require('./irc');
var nodeStatic = require('node-static');
var mkdirp = require('mkdirp');
var crypto = require('crypto');
var exec = require('child_process');
var dwebp = require('dwebp-bin');

// tries to read chat ids from a file
var readChatIds = function(arr) {
    console.log('\n');
    console.log('NOTE!');
    console.log('=====');

    var idMissing = false;
    try {
        var json = JSON.parse(fs.readFileSync(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/chat_ids'));
        for (var i = 0; i < arr.length; i++) {
            var key = arr[i].tgGroup;
            if (key in json) {
                arr[i].tgChatId = json[key];
                console.log('id found for:', key, ':', json[key]);
            } else {
                console.log('id not found:', key);
                idMissing = true;
            }
        }
    } catch (e) {
        console.log('~/.teleirc/chat_ids file not found!');
        idMissing = true;
    }

    if (idMissing) {
        console.log(
            '\nPlease add your Telegram bot to a Telegram group and have' +
            '\nsomeone send a message to that group.' +
            '\nteleirc will then automatically store your group chat_id.');
    }

    console.log('\n');
};

var writeChatIds = function(config) {
    var json = {};
    for (var i = 0; i < config.channels.length; i++) {
        if (config.channels[i].tgChatId) {
            json[config.channels[i].tgGroup] = config.channels[i].tgChatId;
        }
    }
    json = JSON.stringify(json);
    fs.writeFile(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/chat_ids', json, function(err) {
        if (err) {
            console.log('error while storing chat ID:');
            console.log(err);
        } else {
            console.log('successfully stored chat ID in ~/.teleirc/chat_ids');
        }
    });
};

var getName = function(user, config) {
    var name = config.nameFormat;

    if (user.username) {
        name = name.replace('%username%', '@' + user.username, 'g');
    } else {
        // if user lacks username, use fallback format string instead
        name = name.replace('%username%', config.usernameFallbackFormat, 'g');
    }

    name = name.replace('%firstName%', user.first_name || '', 'g');
    name = name.replace('%lastName%', user.last_name || '', 'g');

    // get rid of leading and trailing whitespace
    name = name.replace(/(^\s*)|(\s*$)/g, '');

    return name;
};

function randomValueBase64(len) {
    return crypto.randomBytes(Math.ceil(len * 3 / 4))
        .toString('base64')
        .slice(0, len)
        .replace(/\+/g, '0')
        .replace(/\//g, '0');
}

var serveFile = function(fileId, config, tg, callback) {
    var randomString = randomValueBase64(config.mediaRandomLenght);
    mkdirp(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files/' + randomString);
    tg.downloadFile(fileId, process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files/' + randomString).then(function(filePath) {
        if (path.extname(filePath) == '.webp') {
            var newPath = path.dirname(filePath) + '/' + path.basename(filePath, '.webp') + '.png';
            exec.execFile(dwebp.path, [filePath, '-o', newPath], function (error) {
                if (!error) {
                    filePath = newPath;
                    callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
                } else {
                    console.error('Convert webp failed: ' + error);
                    var cloudconvert = new (require('cloudconvert'))(config.cloudConvertKey);
                    // create the process. see https://cloudconvert.com/apidoc#create
                    cloudconvert.createProcess({inputformat: 'webp', outputformat: 'png'}, function(err, process) {
                        if(err) {
                            console.error('CloudConvert Process creation failed: ' + err);
                        } else {
                            // start the process. see https://cloudconvert.com/apidoc#create
                            process.start({
                                outputformat: 'png',
                                input: 'upload'
                            }, function (err, process) {
                                if (err) {
                                    console.error('CloudConvert Process start failed: ' + err);
                                } else {
                                    // upload the input file. see https://cloudconvert.com/apidoc#upload
                                    process.upload(fs.createReadStream(filePath), null, function (err, process) {
                                        if (err) {
                                            console.error('CloudConvert Process upload failed: ' + err);
                                        } else {
                                            // wait until the process is finished (or completed with an error)
                                            process.wait(function (err, process) {
                                                if (err) {
                                                    console.error('CloudConvert Process failed: ' + err);
                                                } else {
                                                    // download it
                                                    process.download(fs.createWriteStream(newPath), null, function (err, process) {
                                                        if (err) {
                                                            console.error('CloudConvert Process download failed: ' + err);
                                                        } else {
                                                            filePath = newPath;
                                                            callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
                                                        }
                                                    });
                                                }
                                            });
                                        }
                                    });
                                }
                            });
                        }
                    });
                }
            });
        } else {
            callback(config.httpLocation + '/' + randomString + '/' + path.basename(filePath));
        }
    });
};

module.exports = function(config, sendTo) {
    // start HTTP server for media files if configured to do so
    if (config.showMedia) {
        var fileServer = new nodeStatic.Server(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files');
        mkdirp(process.env.OPENSHIFT_DATA_DIR + '/.teleirc/files');

        require('http').createServer(function(req, res) {
            req.addListener('end', function() {
                fileServer.serve(req, res);
            }).resume();
        }).listen(config.httpLocalBindPort, config.httpLocalBindIP);
    }

    var tg = new Telegram(config.tgToken, {polling: true});

    readChatIds(config.channels);

    tg.on('message', function(msg) {
        var channel = config.channels.filter(function(channel) {
            return channel.tgGroup === msg.chat.title;
        })[0];

        if (!channel) {
            return;
        }

        if (!channel.tgChatId) {
            console.log('storing chat ID: ' + msg.chat.id);
            channel.tgChatId = msg.chat.id;
            writeChatIds(config);
        }

        if (msg.text && !msg.text.indexOf('/names')) {
            var names = sendTo.ircNames(channel);
            names.sort();
            names = 'Users on ' + (channel.chanAlias || channel.ircChan) + ':\n\n' +
                names.join(', ');

            return tg.sendMessage(channel.tgChatId, names);
        }

        // skip posts containing media if it's configured off
        if ((msg.audio || msg.document || msg.photo || msg.sticker || msg.video ||
            msg.voice || msg.contact || msg.location) && !config.showMedia) {
            return;
        }
        var text;

        var caption = '';
        if (msg.caption) {
            caption = msg.caption + ' ';
        }

        var forward = '';
        if (msg.forward_from) {
            forward = 'Fwd: ';
            var forwardName = getName(msg.forward_from, config);
            if (forwardName != '@' + config.tgBotName) {
                forward = forward + '<' + forwardName + '> ';
            }
        }

        var reply = '';
        if (msg.reply_to_message) {
            var replyName = getName(msg.reply_to_message.from, config);
            if (replyName == '@' + config.tgBotName) {
                replyName = '';
                var matches = msg.reply_to_message.text.match(/^<(.*?)>/);
                if (matches) {
                    replyName = matches[1];
                } else {
                    matches = msg.reply_to_message.text.match(/^\*(.*?) /);
                    if (matches) {
                        replyName = matches[1];
                    }
                }
                var lastMessageId = 'lastMessageId' + channel.tgChatId + replyName;
                console.log('Comparing saved ID and current in TG group ' + channel.tgChatId + ' from user ' + replyName + ': ' + process.env[lastMessageId] + ' | ' + msg.reply_to_message.message_id);
                if (lastMessageId in process.env && process.env[lastMessageId] != msg.reply_to_message.message_id) {
                    reply = '"' + msg.reply_to_message.text + '"';
                    sendTo.irc(channel.ircChan, '-> ' + reply);
                    reply = '';
                } else {
                    reply = replyName + ': ';
                }
            } else {
                var lastMessageId = 'lastMessageId' + channel.tgChatId + getName(msg.reply_to_message.from, config);
                // console.log('Comparing saved ID and current in TG group ' + channel.tgChatId + ': ' + process.env['lastMessageId' + channel.tgChatId] + ' | ' + msg.message_id);
                if (msg.reply_to_message.text && (lastMessageId in process.env && process.env[lastMessageId] != msg.reply_to_message.message_id)) {
                    reply = '"<' + replyName + '> ' + msg.reply_to_message.text + '"';
                    sendTo.irc(channel.ircChan, '-> ' + reply);
                    reply = '';
                } else {
                    reply = replyName + ': ';
                }
            }
        }

        if (msg.audio) {
            serveFile(msg.audio.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(Audio) ' + url);
            });
        } else if (msg.document) {
            serveFile(msg.document.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(File) ' + url);
            });
        } else if (msg.photo) {
            // pick the highest quality photo
            var photo = msg.photo[msg.photo.length - 1];

            serveFile(photo.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    caption + '(Photo, ' + photo.width + 'x' + photo.height + ') ' + url);
            });
        } else if (msg.new_chat_photo) {
            // pick the highest quality photo
            var chatPhoto = msg.new_chat_photo[msg.new_chat_photo.length - 1];

            serveFile(chatPhoto.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' +
                    '(New chat photo, ' + chatPhoto.width + 'x' + chatPhoto.height + ') ' + url);
            });
        } else if (msg.sticker) {
            serveFile(msg.sticker.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(Sticker, ' + msg.sticker.width + 'x' + msg.sticker.height + ') ' + url);
            });
        } else if (msg.video) {
            serveFile(msg.video.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    caption + '(Video, ' + msg.video.duration + 's) ' + url);
            });
        } else if (msg.voice) {
            serveFile(msg.voice.file_id, config, tg, function(url) {
                sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                    '(Voice, ' + msg.audio.duration + 's) ' + url);
            });
        } else if (msg.contact) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                '(Contact, ' + '"' + msg.contact.first_name + ' ' +
                msg.contact.last_name + '", ' +
                msg.contact.phone_number + ')');
        } else if (msg.location) {
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply +
                '(Location, ' + 'lon: ' + msg.location.longitude +
                              ', lat: ' + msg.location.latitude + ')');
        } else if (msg.new_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.new_chat_participant, config) +
                ' was added by ' + getName(msg.from, config));
        } else if (msg.left_chat_participant) {
            sendTo.irc(channel.ircChan, getName(msg.left_chat_participant, config) +
                ' was removed by ' + getName(msg.from, config));
        } else {
            text = msg.text.replace(/\n/g , '\n<' + getName(msg.from, config) + '> ');
            sendTo.irc(channel.ircChan, '<' + getName(msg.from, config) + '> ' + forward + reply + text);
        }

        process.env['lastMessageId' + channel.tgChatId + getName(msg.from, config)] = msg.message_id;
        //console.log('Saved message ID recived in TG group ' + channel.tgChatId + ': ' + process.env['lastMessageId' + channel.tgChatId]);

    });

    sendTo.tg = function(channel, msg) {
        console.log('  >> relaying to TG: ' + msg);

        if (!channel.tgChatId) {
            var err = 'ERROR: No chat_id set! Add me to a Telegram group ' +
                      'and say hi so I can find your group\'s chat_id!';
            sendTo.irc(channel.ircChan, err);
            console.error(err);
            return;
        }

        tg.sendMessage(channel.tgChatId, msg).then(function (sended) {
            var chatId = sended.chat_id;
            var messageId = sended.message_id;

            var replyName = '';
            var matches = sended.text.match(/^<(.*?)>/);
            if (matches) {
                replyName = matches[1];
            } else {
                matches = sended.text.match(/^\*(.*?) /);
                if (matches) {
                    replyName = matches[1];
                }
            }
            process.env['lastMessageId' + chatId + replyName] = messageId;
            console.log('Saved message ID sent to TG group ' + chatId + ' from user ' + replyName + ': ' + process.env['lastMessageId' + chatId]);
        });
    };
};
<|endoftext|>


<commit_before>function edit_text($parent, $text, $editBtn, text_change_fn) {
    var $input = $('<input>');
    $input.val($text.text());

    function submit_sensor_name() {
        var new_name = $input.val();
        $text.text(new_name);
        $input.remove();
        $text.show();
        $editBtn.show();
        text_change_fn(new_name);
    }

    $input.focusout(submit_sensor_name);
    $input.keypress(function (evt) {
        evt = (evt) ? evt : (window.event) ? event : null;
        if (evt) {
            var charCode =
                evt.charCode ? evt.charCode :
                    (evt.keyCode ? evt.keyCode :
                        (evt.which ? evt.which : 0));
            if (charCode == 13)
                submit_sensor_name();
        }
    });
    $text.hide();
    $editBtn.hide();
    $input.appendTo($parent);
}

function add_sensor_name(uuid, name, sensor_change_evt) {
    var $parent = $("#" + uuid + ' .sensor-name-cell');

    $('')
        .addClass("sensor-name")
        .text(name)
        .appendTo($parent);

    var $btn =
        $("<button></button>")
            .addClass("edit-btn btn-s")
            .click(function () {
                var $editBtn = $('#' + uuid + " .edit-btn");
                var $text = $('#' + uuid + " .sensor-name");
                var $parent = $("#" + uuid + ' .sensor-name-cell');
                edit_text($parent, $text, $editBtn, sensor_change_evt)
            });
    $btn.appendTo($parent);
}

function deactivate_row(uuid) {
    $('#sensors').find('tr#' + uuid).addClass('inactive');
}

function activate_row(uuid) {
    $('#sensors').find('tr#' + uuid).removeClass('inactive');
}

function delete_row(uuid) {
    $row = $('#sensors').find('tr#' + uuid);
    $row.fadeOut(400, function(){
        $row.remove();
    });
}

function set_status_active(uuid, is_active) {
    var $icon = $("#" + uuid + " .activate-btn i");
    if (is_active) {
        $icon.attr('class', 'fa fa-check-circle-o');
    } else {
        $icon.attr('class', 'fa fa-circle-o');
    }
    $("#" + uuid + " .activate-btn").attr('data-active', '' + is_active);
}

function add_sensor_row(uuid, sensor_name) {
    var row = $("

", {
        id: uuid,
        class: ['sensor']
    });
    row.append($(""));

    var $acquire_btn =
        $('<button/>')
            .addClass("activate-btn")
            .addClass("btn btn-sm btn-primary")
            .attr("data-active", 'false')
            .append($(''))
            .click(function () {
                var is_acquiring = $acquire_btn.attr("data-active") == 'true';
                var start_acquiring = !is_acquiring;
                start_stop_acquire(uuid, start_acquiring);
                set_status_active(uuid, !is_acquiring);
            });

    var $eject_btn = $("<button class='btn btn-sm btn-warning'/>")
        .attr('title', 'put sensor in low power without starting acquisition.')
        .append($(""))
        .click(function () {
            eject_sensor(uuid);
            deactivate_row(uuid);
        });

    var $del_sensor_btn = $("<button class='btn btn-sm btn-delsensor'/>")
        .attr('title', 'delete sensor from table.')
        .append($(""))
        .click(function (event) {
            event.preventDefault();
            delete_row(uuid);
            eject_sensor(uuid);
        });

    row.append($("")
        .append($acquire_btn)
        .append($eject_btn)
        .append($del_sensor_btn)
    );

    var $plot_btn = $("<button class='btn btn-sm btn-primary plot-btn' />")
        .append($(""))
        .click(function () {
            $.ajax('/devices/' + uuid + '/samples/json',
                { success: function (data, error, xhr) {
                    filtered = [];
                    for (i in data) {
                        if (data[i].sensor == 1)
                            filtered.push(data[i]);
                    }
                    curve_set_data(filtered);
                    curve_set_mini_preview(uuid, $("#preview-chart-"+uuid), filtered)
                }
                }
            )
        });
    row.append($("")
        .append($("unknown"))
        .append($plot_btn)
        .append($("<button class='btn btn-sm btn-primary download-btn'/>")
            .append($(""))
            .click(function () {
                location.href = "/devices/" + uuid + "/samples/csv";
            })
        )

        .append($("<button class='btn btn-sm btn-danger delete-btn'/>")
            .click(function (event) {
                $.ajax("/devices/"+uuid+"/erase", {
                    type: "POST",
                    success: function (data, status, xhr) {
                        $("#"+uuid+" span.sample-count").text = 0;
                    }
                });
            })
            .append($(""))
        )

    );
    id: uuid,
    row.append($("	"))
        .append($("

",
        {
            id: "preview-chart-"+uuid,
            backgroundColor:"#337744",
            width: 200,
            height: 50
        }));

    row.append($("")
        .append($("hmm"))
        .append($("<button>Configure</button>")
                .addClass('btn btn-primary btn-sm configure-btn')
                .click(function (event) {
                    var t = $("#sample-interval").val() * 60;
                    $.ajax("/devices/"+uuid+"/sample-period", {
                        type: "POST",
                        data: {value: t},
                        success: function (data, status, xhr) {
                            $("#"+uuid+" .configuration-state").text = "Configured";
                        }
                    });
                })
               )
    );

    $("#sensors").find("tbody").append(row);
    add_sensor_name(uuid, sensor_name, function (name) {
        sensor_name_change(uuid, name);
    });

}
<commit_msg>Fix sample interval<commit_after>function edit_text($parent, $text, $editBtn, text_change_fn) {
    var $input = $('<input>');
    $input.val($text.text());

    function submit_sensor_name() {
        var new_name = $input.val();
        $text.text(new_name);
        $input.remove();
        $text.show();
        $editBtn.show();
        text_change_fn(new_name);
    }

    $input.focusout(submit_sensor_name);
    $input.keypress(function (evt) {
        evt = (evt) ? evt : (window.event) ? event : null;
        if (evt) {
            var charCode =
                evt.charCode ? evt.charCode :
                    (evt.keyCode ? evt.keyCode :
                        (evt.which ? evt.which : 0));
            if (charCode == 13)
                submit_sensor_name();
        }
    });
    $text.hide();
    $editBtn.hide();
    $input.appendTo($parent);
}

function add_sensor_name(uuid, name, sensor_change_evt) {
    var $parent = $("#" + uuid + ' .sensor-name-cell');

    $('')
        .addClass("sensor-name")
        .text(name)
        .appendTo($parent);

    var $btn =
        $("<button></button>")
            .addClass("edit-btn btn-s")
            .click(function () {
                var $editBtn = $('#' + uuid + " .edit-btn");
                var $text = $('#' + uuid + " .sensor-name");
                var $parent = $("#" + uuid + ' .sensor-name-cell');
                edit_text($parent, $text, $editBtn, sensor_change_evt)
            });
    $btn.appendTo($parent);
}

function deactivate_row(uuid) {
    $('#sensors').find('tr#' + uuid).addClass('inactive');
}

function activate_row(uuid) {
    $('#sensors').find('tr#' + uuid).removeClass('inactive');
}

function delete_row(uuid) {
    $row = $('#sensors').find('tr#' + uuid);
    $row.fadeOut(400, function(){
        $row.remove();
    });
}

function set_status_active(uuid, is_active) {
    var $icon = $("#" + uuid + " .activate-btn i");
    if (is_active) {
        $icon.attr('class', 'fa fa-check-circle-o');
    } else {
        $icon.attr('class', 'fa fa-circle-o');
    }
    $("#" + uuid + " .activate-btn").attr('data-active', '' + is_active);
}

function add_sensor_row(uuid, sensor_name) {
    var row = $("

", {
        id: uuid,
        class: ['sensor']
    });
    row.append($(""));

    var $acquire_btn =
        $('<button/>')
            .addClass("activate-btn")
            .addClass("btn btn-sm btn-primary")
            .attr("data-active", 'false')
            .append($(''))
            .click(function () {
                var is_acquiring = $acquire_btn.attr("data-active") == 'true';
                var start_acquiring = !is_acquiring;
                start_stop_acquire(uuid, start_acquiring);
                set_status_active(uuid, !is_acquiring);
            });

    var $eject_btn = $("<button class='btn btn-sm btn-warning'/>")
        .attr('title', 'put sensor in low power without starting acquisition.')
        .append($(""))
        .click(function () {
            eject_sensor(uuid);
            deactivate_row(uuid);
        });

    var $del_sensor_btn = $("<button class='btn btn-sm btn-delsensor'/>")
        .attr('title', 'delete sensor from table.')
        .append($(""))
        .click(function (event) {
            event.preventDefault();
            delete_row(uuid);
            eject_sensor(uuid);
        });

    row.append($("")
        .append($acquire_btn)
        .append($eject_btn)
        .append($del_sensor_btn)
    );

    var $plot_btn = $("<button class='btn btn-sm btn-primary plot-btn' />")
        .append($(""))
        .click(function () {
            $.ajax('/devices/' + uuid + '/samples/json',
                { success: function (data, error, xhr) {
                    filtered = [];
                    for (i in data) {
                        if (data[i].sensor == 1)
                            filtered.push(data[i]);
                    }
                    curve_set_data(filtered);
                    curve_set_mini_preview(uuid, $("#preview-chart-"+uuid), filtered)
                }
                }
            )
        });
    row.append($("")
        .append($("unknown"))
        .append($plot_btn)
        .append($("<button class='btn btn-sm btn-primary download-btn'/>")
            .append($(""))
            .click(function () {
                location.href = "/devices/" + uuid + "/samples/csv";
            })
        )

        .append($("<button class='btn btn-sm btn-danger delete-btn'/>")
            .click(function (event) {
                $.ajax("/devices/"+uuid+"/erase", {
                    type: "POST",
                    success: function (data, status, xhr) {
                        $("#"+uuid+" span.sample-count").text = 0;
                    }
                });
            })
            .append($(""))
        )

    );
    id: uuid,
    row.append($("	"))
        .append($("

",
        {
            id: "preview-chart-"+uuid,
            backgroundColor:"#337744",
            width: 200,
            height: 50
        }));

    row.append($("")
        .append($("hmm"))
        .append($("<button>Configure</button>")
                .addClass('btn btn-primary btn-sm configure-btn')
                .click(function (event) {
                    var t = parseInt($("#sample-interval").val()) * 60;
                    $.ajax("/devices/"+uuid+"/sample-period", {
                        type: "POST",
                        data: {value: t},
                        success: function (data, status, xhr) {
                            $("#"+uuid+" .configuration-state").html("Configured");
                        }
                    });
                })
               )
    );

    $("#sensors").find("tbody").append(row);
    add_sensor_name(uuid, sensor_name, function (name) {
        sensor_name_change(uuid, name);
    });

}
<|endoftext|>


<commit_before>const request = require('requestretry').defaults({json: true});
const config = require('./config');

const graphQL_endpoint = 'https://graphql.anilist.co/';
const db_store = config.db_store;
const db_name = 'anilist2'; // elasticsearch index name

const q = {};
q.query = `
query ($page: Int = 1, $perPage: Int = 1, $id: Int, $type: MediaType = ANIME) {
  Page(page: $page, perPage: $perPage) {
    pageInfo {
      total
      perPage
      currentPage
      lastPage
      hasNextPage
    }
    media(id: $id, type: $type) {
      id
      idMal
      title {
        native
        romaji
        english
      }
      type
      format
      status
      description
      startDate {
        year
        month
        day
      }
      endDate {
        year
        month
        day
      }
      season
      episodes
      duration
      source
      hashtag
      youtubeId
      updatedAt
      coverImage {
        large
        medium
      }
      bannerImage
      genres
      synonyms
      averageScore
      meanScore
      popularity
      tags {
        id
        name
        description
        category
        rank
        isGeneralSpoiler
        isMediaSpoiler
        isAdult
      }
      relations {
        edges {
          node {
            id
            title {
              native
            }
          }
          relationType
        }
      }
      characters {
        edges {
          role
          node {
            id
            name {
              first
              last
              native
              alternative
            }
            image {
              large
              medium
            }
            siteUrl
          }
          voiceActors(language: JAPANESE) {
            id
            name {
              first
              last
              native
            }
            language
            image {
              large
              medium
            }
            siteUrl
          }
        }
      }
      staff {
        edges {
          role
          node {
            id
            name {
              first
              last
              native
            }
            language
            image {
              large
              medium
            }
            description
            siteUrl
          }
        }
      }
      studios {
        edges {
          isMain
          node {
            id
            name
            siteUrl
          }
        }
      }
      isAdult
      externalLinks {
        id
        url
        site
      }
      rankings {
        id
        rank
        type
        format
        year
        season
        allTime
        context
      }
      stats {
        scoreDistribution
        userDistribution
      }
      siteUrl
    }
  }
}
`;
q.variables = {};

const submitQuery = (variables) => new Promise((resolve, reject) => {
  if(variables.id){
    console.log(`Crawling anime ${variables.id}`);
  }
  else if(variables.page){
    console.log(`Crawling page ${variables.page}`);
  }
  q.variables = variables;
  request({
    url: graphQL_endpoint,
    body: q,
    method: 'POST',
    maxAttempts: 5,
    retryDelay: 5000,
    retryStrategy: request.RetryStrategies.HTTPOrNetworkError
  })
  .then(function (response) {
    // console.log(response.statusCode);
    resolve(response.body.data);
  })
  .catch(function(error) {
    console.log(error);
    reject(error);
  })
});

let storeData = (id, data) => new Promise((resolve, reject) => {
  // console.log(`Storing ${type} ${data.id}`);
  let dataPath = `${db_store}${db_name}/anime/${id}`;
  request({
    method: 'PUT',
    url: dataPath,
    json: data
  }, (error, response, body) => {
    if (!error && response.statusCode < 400) {
      resolve(data);
      // console.log(`Stored ${type} ${data.id}`);
    } else {
      console.log(error);
      console.log(response);
      reject(Error(error));
    }
  });
});

const maxPerPage = 50;

const fetchAnime = (animeID) => submitQuery({id: animeID})
  .then(data => data.Page.media[0])
  .then(anime => storeData(anime.id, anime)
    .then(() => {console.log(`Completed anime ${anime.id} (${anime.title.native})`);})
  )
  .catch(error => {console.log(error)});

const fetchPage = (pageNumber) => submitQuery({page: pageNumber, perPage: maxPerPage})
  .then(data => data.Page.media)
  .then(anime_list => anime_list.map(anime => storeData(anime.id, anime)
    .then(() => {console.log(`Completed anime ${anime.id} (${anime.title.native})`);})
  ))
  .then(list => Promise.all(list))
  .catch(error => {console.log(error)});

const getLastPage = () => submitQuery({page: 1, perPage: maxPerPage})
  .then(data => data.Page.pageInfo.lastPage)
  .catch(error => {console.log(error)});

const args = process.argv.slice(2);

args.forEach((param, index) => {
  const value = args[index + 1];
  if (param === '--anime') {
    fetchAnime(parseInt(value, 10));
  }

  if (param === '--page') {
    const format = /^(\d+)(-)?(\d+)?$/;
    const startPage = parseInt(value.match(format)[1]);
    const fetchToEnd = value.match(format)[2] === '-';
    const endPage = fetchToEnd ? parseInt(value.match(format)[3]) : startPage;

    getLastPage()
      .then(last_page => {
        console.log(`The last page is ${last_page}`);
        return last_page;
      })
      .then(last_page => (endPage < last_page ? endPage : last_page))
      .then(last_page => Array.from(new Array(last_page + 1), (val, index) => index)
        .slice(startPage, last_page + 1)
      )
      .then(pages =>
        pages
        .reduce((result, page) => result.then(() => fetchPage(page)), Promise.resolve())
      );
  }
  /*
  if (param === '--cleanup') {
    let startPage = 1;

    getLastPage(260)
      .then(last_page => Array.from(new Array(last_page + 1), (val, index) => index)
        .slice(startPage, last_page + 1)
      )
      .then(pages =>
        pages
        .reduce((result, page) => result.then((allAnimeIDs) => fetchPage(page)
          .then(ids => allAnimeIDs.concat(ids))
        ), Promise.resolve([]))
      )
      .then(remoteAnimeIDs => {
        getAnimeIDs(0,100000).then(localIDs => {
          localIDs.forEach(id => {
            if(remoteAnimeIDs.indexOf(id) === -1){
              console.log(id, 'is not found on anilist');
            }
          });
        });
      });
  }
  */
});

<commit_msg>Improve error handling<commit_after>const request = require('requestretry').defaults({json: true});
const config = require('./config');

const graphQL_endpoint = 'https://graphql.anilist.co/';
const db_store = config.db_store;
const db_name = 'anilist2'; // elasticsearch index name

const q = {};
q.query = `
query ($page: Int = 1, $perPage: Int = 1, $id: Int, $type: MediaType = ANIME) {
  Page(page: $page, perPage: $perPage) {
    pageInfo {
      total
      perPage
      currentPage
      lastPage
      hasNextPage
    }
    media(id: $id, type: $type) {
      id
      idMal
      title {
        native
        romaji
        english
      }
      type
      format
      status
      description
      startDate {
        year
        month
        day
      }
      endDate {
        year
        month
        day
      }
      season
      episodes
      duration
      source
      hashtag
      youtubeId
      updatedAt
      coverImage {
        large
        medium
      }
      bannerImage
      genres
      synonyms
      averageScore
      meanScore
      popularity
      tags {
        id
        name
        description
        category
        rank
        isGeneralSpoiler
        isMediaSpoiler
        isAdult
      }
      relations {
        edges {
          node {
            id
            title {
              native
            }
          }
          relationType
        }
      }
      characters {
        edges {
          role
          node {
            id
            name {
              first
              last
              native
              alternative
            }
            image {
              large
              medium
            }
            siteUrl
          }
          voiceActors(language: JAPANESE) {
            id
            name {
              first
              last
              native
            }
            language
            image {
              large
              medium
            }
            siteUrl
          }
        }
      }
      staff {
        edges {
          role
          node {
            id
            name {
              first
              last
              native
            }
            language
            image {
              large
              medium
            }
            description
            siteUrl
          }
        }
      }
      studios {
        edges {
          isMain
          node {
            id
            name
            siteUrl
          }
        }
      }
      isAdult
      externalLinks {
        id
        url
        site
      }
      rankings {
        id
        rank
        type
        format
        year
        season
        allTime
        context
      }
      stats {
        scoreDistribution
        userDistribution
      }
      siteUrl
    }
  }
}
`;
q.variables = {};

const submitQuery = (variables) => new Promise((resolve, reject) => {
  if(variables.id){
    console.log(`Crawling anime ${variables.id}`);
  }
  else if(variables.page){
    console.log(`Crawling page ${variables.page}`);
  }
  q.variables = variables;
  request({
    url: graphQL_endpoint,
    body: q,
    method: 'POST',
    maxAttempts: 5,
    retryDelay: 5000,
    retryStrategy: request.RetryStrategies.HTTPOrNetworkError
  })
  .then(function (response) {
    // console.log(response.statusCode);
    if(response.body.data !== null) {
      resolve(response.body.data);
    }
    else{
      reject(response.body.errors);
    }
  })
  .catch(function(error) {
    console.log(error);
    reject(error);
  })
});

let storeData = (id, data) => new Promise((resolve, reject) => {
  // console.log(`Storing ${type} ${data.id}`);
  let dataPath = `${db_store}${db_name}/anime/${id}`;
  request({
    method: 'PUT',
    url: dataPath,
    json: data
  }, (error, response, body) => {
    if (!error && response.statusCode < 400) {
      resolve(data);
      // console.log(`Stored ${type} ${data.id}`);
    } else {
      console.log(error);
      console.log(response);
      reject(Error(error));
    }
  });
});

const maxPerPage = 50;

const fetchAnime = (animeID) => submitQuery({id: animeID})
  .then(data => data.Page.media[0])
  .then(anime => storeData(anime.id, anime)
    .then(() => {console.log(`Completed anime ${anime.id} (${anime.title.native})`);})
  )
  .catch(error => {console.log(error)});

const fetchPage = (pageNumber) => submitQuery({page: pageNumber, perPage: maxPerPage})
  .then(data => data.Page.media)
  .then(anime_list => anime_list.map(anime => storeData(anime.id, anime)
    .then(() => {console.log(`Completed anime ${anime.id} (${anime.title.native})`);})
  ))
  .then(list => Promise.all(list))
  .catch(error => {console.log(error)});

const getLastPage = () => submitQuery({page: 1, perPage: maxPerPage})
  .then(data => data.Page.pageInfo.lastPage)
  .catch(error => {console.log(error)});

const args = process.argv.slice(2);

args.forEach((param, index) => {
  const value = args[index + 1];
  if (param === '--anime') {
    fetchAnime(parseInt(value, 10));
  }

  if (param === '--page') {
    const format = /^(\d+)(-)?(\d+)?$/;
    const startPage = parseInt(value.match(format)[1]);
    const fetchToEnd = value.match(format)[2] === '-';
    const endPage = fetchToEnd ? parseInt(value.match(format)[3]) : startPage;

    getLastPage()
      .then(last_page => {
        console.log(`The last page is ${last_page}`);
        return last_page;
      })
      .then(last_page => (endPage < last_page ? endPage : last_page))
      .then(last_page => Array.from(new Array(last_page + 1), (val, index) => index)
        .slice(startPage, last_page + 1)
      )
      .then(pages =>
        pages
        .reduce((result, page) => result.then(() => fetchPage(page)), Promise.resolve())
      );
  }
  /*
  if (param === '--cleanup') {
    let startPage = 1;

    getLastPage(260)
      .then(last_page => Array.from(new Array(last_page + 1), (val, index) => index)
        .slice(startPage, last_page + 1)
      )
      .then(pages =>
        pages
        .reduce((result, page) => result.then((allAnimeIDs) => fetchPage(page)
          .then(ids => allAnimeIDs.concat(ids))
        ), Promise.resolve([]))
      )
      .then(remoteAnimeIDs => {
        getAnimeIDs(0,100000).then(localIDs => {
          localIDs.forEach(id => {
            if(remoteAnimeIDs.indexOf(id) === -1){
              console.log(id, 'is not found on anilist');
            }
          });
        });
      });
  }
  */
});

<|endoftext|>


<commit_before>'use strict';
/**
 * A WP REST API client for Node.js
 *
 * @example
 *     var wp = new WP({ endpoint: 'http://src.wordpress-develop.dev/wp-json' });
 *     wp.posts().then(function( posts ) {
 *         console.log( posts );
 *     }).catch(function( err ) {
 *         console.error( err );
 *     });
 *
 * @module WP
 * @main WP
 * @beta
 })
 */
var extend = require( 'node.extend' );

var defaults = {
	username: '',
	password: ''
};

// Pull in request module constructors
var MediaRequest = require( './lib/media' );
var PagesRequest = require( './lib/pages' );
var PostsRequest = require( './lib/posts' );
var TaxonomiesRequest = require( './lib/taxonomies' );
var TypesRequest = require( './lib/types' );
var UsersRequest = require( './lib/users' );
var CollectionRequest = require( './lib/shared/collection-request' );
var WPRequest = require( './lib/shared/wp-request' );

/**
 * The base constructor for the WP API service
 *
 * @class WP
 * @constructor
 * @uses PostsRequest
 * @uses TaxonomiesRequest
 * @uses UsersRequest
 * @param {Object} options An options hash to configure the instance
 * @param {String} options.endpoint The URI for a WP-API endpoint
 * @param {String} [options.username] A WP-API Basic Auth username
 * @param {String} [options.password] A WP-API Basic Auth password
 */
function WP( options ) {

	// Enforce `new`
	if ( this instanceof WP === false ) {
		return new WP( options );
	}

	this._options = extend( {}, defaults, options );

	if ( ! this._options.endpoint ) {
		throw new Error( 'options hash must contain an API endpoint URL string' );
	}

	// Ensure trailing slash on endpoint URI
	this._options.endpoint = this._options.endpoint.replace( /\/?$/, '/' );

	return this;
}

/**
 * Convenience method for making a new WP instance
 *
 * @example
 * These are equivalent:
 *
 *     var wp = new WP({ endpoint: 'http://my.blog.url/wp-json' });
 *     var wp = WP.site( 'http://my.blog.url/wp-json' );
 *
 * @method site
 * @static
 * @param {String} endpoint The URI for a WP-API endpoint
 * @return {WP} A new WP instance, bound to the provided endpoint
 */
WP.site = function( endpoint ) {
	return new WP({ endpoint: endpoint });
};

/**
 * Start a request against the `/media` endpoint
 *
 * @method media
 * @param {Object} [options] An options hash for a new MediaRequest
 * @return {MediaRequest} A MediaRequest instance
 */
WP.prototype.media = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new MediaRequest( options );
};

/**
 * Start a request against the `/pages` endpoint
 *
 * @method pages
 * @param {Object} [options] An options hash for a new PagesRequest
 * @return {PagesRequest} A PagesRequest instance
 */
WP.prototype.pages = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new PagesRequest( options );
};

/**
 * Start a request against the `/posts` endpoint
 *
 * @method posts
 * @param {Object} [options] An options hash for a new PostsRequest
 * @return {PostsRequest} A PostsRequest instance
 */
WP.prototype.posts = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new PostsRequest( options );
};

/**
 * Start a request against the `taxonomies` endpoint
 *
 * @method taxonomies
 * @param {Object} [options] An options hash for a new TaxonomiesRequest
 * @return {TaxonomiesRequest} A TaxonomiesRequest instance
 */
WP.prototype.taxonomies = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new TaxonomiesRequest( options );
};

/**
 * Define a method to handle specific taxonomies.
 *
 * It is repetitive to have to type `.taxonomies().taxonomy()` whenever you want to request
 * a taxonomy object or list of terms for a taxonomy. This convenience method lets you
 * create a `TaxonomiesRequest` object that is bound to the provided taxonomy name.
 *
 * @example
 * If your site uses two custom taxonomies, book_genre and book_publisher, before you would
 * have had to request these terms using the verbose form:
 *
 *     wp.taxonomies().taxonomy( 'book_genre' ).terms()...
 *     wp.taxonomies().taxonomy( 'book_publisher' )...
 *
 * Using `.taxonomy()`, the same query can be achieved much more succinctly:
 *
 *     wp.taxonomy( 'book_genre' ).terms()...
 *     wp.taxonomy( 'book_publisher' )...
 *
 * @method taxonomy
 * @param {String} taxonomyName The name of the taxonomy to request
 * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the value of taxonomyName
 */
WP.prototype.taxonomy = function( taxonomyName ) {
	var options = extend( {}, this._options );
	return new TaxonomiesRequest( options ).taxonomy( taxonomyName );
};

/**
 * Start a request against the `/posts/types` endpoint
 *
 * @method types
 * @param {Object} [options] An options hash for a new TypesRequest
 * @return {TypesRequest} A TypesRequest instance
 */
WP.prototype.types = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new TypesRequest( options );
};

/**
 * Start a request against the `/users` endpoint
 *
 * @method users
 * @param {Object} [options] An options hash for a new UsersRequest
 * @return {UsersRequest} A UsersRequest instance
 */
WP.prototype.users = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new UsersRequest( options );
};

/**
 * Define a method to handle specific custom post types.
 *
 * @example
 * If your site had an events custom type with name `event_cpt`, you could create a convenience
 * method for querying events and store it on the WP instance.
 *
 * Create the WP instance, define the custom endpoint handler, and save it to `wp.events`:
 *
 *     var wp = new WP({ endpoint: 'http://some-website.com/wp-json' });
 *     wp.events = wp.registerType( 'event_cpt' );
 *
 * You can now call `wp.events()` to trigger event post requests
 *
 *     wp.events().get()... // equivalent to wp.posts().type( 'event_cpt' ).get()...
 *
 * `registerType()` just returns a function, so there's no requirement to store it as a property
 * on the WP instance; however, following the above pattern is likely to be the most useful.
 *
 * @method registerType
 * @param {String|Array} type A string or array of post type names
 * @return {Function} A function to create PostsRequests pre-bound to the provided types
 */
WP.prototype.registerType = function( type ) {
	var options = extend( {}, this._options );
	return function() {
		return new PostsRequest( options ).type( type );
	};
};

/**
 * Generate a request against a completely arbitrary endpoint, with no assumptions about
 * or mutation of path, filtering, or query parameters. This request is not restricted to
 * the endpoint specified during WP object instantiation.
 *
 * @example
 * Generate a request to the explicit URL "http://your.website.com/wp-json/some/custom/path" (yeah, we wish ;)
 *
 *     wp.url( 'http://your.website.com/wp-json/some/custom/path' ).get()...
 *
 * @method url
 * @param {String} url The URL to request
 * @return {WPRequest} A WPRequest object bound to the provided URL
 */
WP.prototype.url = function( url ) {
	var options = extend( {}, this._options, {
		endpoint: url
	});
	return new WPRequest( options );
};

/**
 * Generate a query against an arbitrary path on the current endpoint. This is useful for
 * requesting resources at custom WP-API endpoints, such as WooCommerce's `/products`.
 *
 * @method root
 * @param {String} [relativePath] An endpoint-relative path to which to bind the request
 * @param {Boolean} [collection] Whether to return a CollectionRequest or a vanilla WPRequest
 * @return {CollectionRequest|WPRequest} A request object
 */
WP.prototype.root = function( relativePath, collection ) {
	relativePath = relativePath || '';
	collection = collection || false;
	var options = extend( {}, this._options );
	// Request should be
	var request = collection ? new CollectionRequest( options ) : new WPRequest( options );

	// Set the path template to the string passed in
	request._template = relativePath;

	return request;
};

module.exports = WP;
<commit_msg>Implement wp.categories() and wp.tags() (see #63, #64)<commit_after>'use strict';
/**
 * A WP REST API client for Node.js
 *
 * @example
 *     var wp = new WP({ endpoint: 'http://src.wordpress-develop.dev/wp-json' });
 *     wp.posts().then(function( posts ) {
 *         console.log( posts );
 *     }).catch(function( err ) {
 *         console.error( err );
 *     });
 *
 * @module WP
 * @main WP
 * @beta
 })
 */
var extend = require( 'node.extend' );

var defaults = {
	username: '',
	password: ''
};

// Pull in request module constructors
var MediaRequest = require( './lib/media' );
var PagesRequest = require( './lib/pages' );
var PostsRequest = require( './lib/posts' );
var TaxonomiesRequest = require( './lib/taxonomies' );
var TypesRequest = require( './lib/types' );
var UsersRequest = require( './lib/users' );
var CollectionRequest = require( './lib/shared/collection-request' );
var WPRequest = require( './lib/shared/wp-request' );

/**
 * The base constructor for the WP API service
 *
 * @class WP
 * @constructor
 * @uses PostsRequest
 * @uses TaxonomiesRequest
 * @uses UsersRequest
 * @param {Object} options An options hash to configure the instance
 * @param {String} options.endpoint The URI for a WP-API endpoint
 * @param {String} [options.username] A WP-API Basic Auth username
 * @param {String} [options.password] A WP-API Basic Auth password
 */
function WP( options ) {

	// Enforce `new`
	if ( this instanceof WP === false ) {
		return new WP( options );
	}

	this._options = extend( {}, defaults, options );

	if ( ! this._options.endpoint ) {
		throw new Error( 'options hash must contain an API endpoint URL string' );
	}

	// Ensure trailing slash on endpoint URI
	this._options.endpoint = this._options.endpoint.replace( /\/?$/, '/' );

	return this;
}

/**
 * Convenience method for making a new WP instance
 *
 * @example
 * These are equivalent:
 *
 *     var wp = new WP({ endpoint: 'http://my.blog.url/wp-json' });
 *     var wp = WP.site( 'http://my.blog.url/wp-json' );
 *
 * @method site
 * @static
 * @param {String} endpoint The URI for a WP-API endpoint
 * @return {WP} A new WP instance, bound to the provided endpoint
 */
WP.site = function( endpoint ) {
	return new WP({ endpoint: endpoint });
};

/**
 * Start a request against the `/media` endpoint
 *
 * @method media
 * @param {Object} [options] An options hash for a new MediaRequest
 * @return {MediaRequest} A MediaRequest instance
 */
WP.prototype.media = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new MediaRequest( options );
};

/**
 * Start a request against the `/pages` endpoint
 *
 * @method pages
 * @param {Object} [options] An options hash for a new PagesRequest
 * @return {PagesRequest} A PagesRequest instance
 */
WP.prototype.pages = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new PagesRequest( options );
};

/**
 * Start a request against the `/posts` endpoint
 *
 * @method posts
 * @param {Object} [options] An options hash for a new PostsRequest
 * @return {PostsRequest} A PostsRequest instance
 */
WP.prototype.posts = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new PostsRequest( options );
};

/**
 * Start a request against the `taxonomies` endpoint
 *
 * @method taxonomies
 * @param {Object} [options] An options hash for a new TaxonomiesRequest
 * @return {TaxonomiesRequest} A TaxonomiesRequest instance
 */
WP.prototype.taxonomies = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new TaxonomiesRequest( options );
};

/**
 * Start a request for a specific taxonomy object
 *
 * It is repetitive to have to type `.taxonomies().taxonomy()` whenever you want to request
 * a taxonomy object or list of terms for a taxonomy. This convenience method lets you
 * create a `TaxonomiesRequest` object that is bound to the provided taxonomy name.
 *
 * @example
 * If your site uses two custom taxonomies, book_genre and book_publisher, before you would
 * have had to request these terms using the verbose form:
 *
 *     wp.taxonomies().taxonomy( 'book_genre' ).terms()...
 *     wp.taxonomies().taxonomy( 'book_publisher' )...
 *
 * Using `.taxonomy()`, the same query can be achieved much more succinctly:
 *
 *     wp.taxonomy( 'book_genre' ).terms()...
 *     wp.taxonomy( 'book_publisher' )...
 *
 * @method taxonomy
 * @param {String} taxonomyName The name of the taxonomy to request
 * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the value of taxonomyName
 */
WP.prototype.taxonomy = function( taxonomyName ) {
	var options = extend( {}, this._options );
	return new TaxonomiesRequest( options ).taxonomy( taxonomyName );
};

/**
 * Request a list of category terms
 *
 * This is a shortcut method to retrieve the terms for the "category" taxonomy
 *
 * @example
 * These are equivalent:
 *     wp.taxonomies().taxonomy( 'category' ).terms()
 *     wp.categories()
 *
 * @method categories
 * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the terms for "category"
 */
WP.prototype.categories = function() {
	var options = extend( {}, this._options );
	return new TaxonomiesRequest( options ).taxonomy( 'category' ).terms();
};

/**
 * Request a list of post_tag terms
 *
 * This is a shortcut method to retrieve the terms for the "post_tag" taxonomy
 *
 * @example
 * These are equivalent:
 *     wp.taxonomies().taxonomy( 'post_tag' ).terms()
 *     wp.tags()
 *
 * @method tags
 * @return {TaxonomiesRequest} A TaxonomiesRequest object bound to the terms for "post_tag"
 */
WP.prototype.tags = function() {
	var options = extend( {}, this._options );
	return new TaxonomiesRequest( options ).taxonomy( 'post_tag' ).terms();
};

/**
 * Start a request against the `/posts/types` endpoint
 *
 * @method types
 * @param {Object} [options] An options hash for a new TypesRequest
 * @return {TypesRequest} A TypesRequest instance
 */
WP.prototype.types = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new TypesRequest( options );
};

/**
 * Start a request against the `/users` endpoint
 *
 * @method users
 * @param {Object} [options] An options hash for a new UsersRequest
 * @return {UsersRequest} A UsersRequest instance
 */
WP.prototype.users = function( options ) {
	options = options || {};
	options = extend( options, this._options );
	return new UsersRequest( options );
};

/**
 * Define a method to handle specific custom post types.
 *
 * @example
 * If your site had an events custom type with name `event_cpt`, you could create a convenience
 * method for querying events and store it on the WP instance.
 *
 * Create the WP instance, define the custom endpoint handler, and save it to `wp.events`:
 *
 *     var wp = new WP({ endpoint: 'http://some-website.com/wp-json' });
 *     wp.events = wp.registerType( 'event_cpt' );
 *
 * You can now call `wp.events()` to trigger event post requests
 *
 *     wp.events().get()... // equivalent to wp.posts().type( 'event_cpt' ).get()...
 *
 * `registerType()` just returns a function, so there's no requirement to store it as a property
 * on the WP instance; however, following the above pattern is likely to be the most useful.
 *
 * @method registerType
 * @param {String|Array} type A string or array of post type names
 * @return {Function} A function to create PostsRequests pre-bound to the provided types
 */
WP.prototype.registerType = function( type ) {
	var options = extend( {}, this._options );
	return function() {
		return new PostsRequest( options ).type( type );
	};
};

/**
 * Generate a request against a completely arbitrary endpoint, with no assumptions about
 * or mutation of path, filtering, or query parameters. This request is not restricted to
 * the endpoint specified during WP object instantiation.
 *
 * @example
 * Generate a request to the explicit URL "http://your.website.com/wp-json/some/custom/path" (yeah, we wish ;)
 *
 *     wp.url( 'http://your.website.com/wp-json/some/custom/path' ).get()...
 *
 * @method url
 * @param {String} url The URL to request
 * @return {WPRequest} A WPRequest object bound to the provided URL
 */
WP.prototype.url = function( url ) {
	var options = extend( {}, this._options, {
		endpoint: url
	});
	return new WPRequest( options );
};

/**
 * Generate a query against an arbitrary path on the current endpoint. This is useful for
 * requesting resources at custom WP-API endpoints, such as WooCommerce's `/products`.
 *
 * @method root
 * @param {String} [relativePath] An endpoint-relative path to which to bind the request
 * @param {Boolean} [collection] Whether to return a CollectionRequest or a vanilla WPRequest
 * @return {CollectionRequest|WPRequest} A request object
 */
WP.prototype.root = function( relativePath, collection ) {
	relativePath = relativePath || '';
	collection = collection || false;
	var options = extend( {}, this._options );
	// Request should be
	var request = collection ? new CollectionRequest( options ) : new WPRequest( options );

	// Set the path template to the string passed in
	request._template = relativePath;

	return request;
};

module.exports = WP;
<|endoftext|>