<commit_before>$(document).ready(function () {

    get_data = {
        project: READTHEDOCS_DATA['project'],
        version: READTHEDOCS_DATA['version'],
        page: READTHEDOCS_DATA['page'],
        theme: READTHEDOCS_DATA['theme'],
        format: "jsonp",
    }

    if ("docroot" in READTHEDOCS_DATA) {
      get_data['docroot'] = READTHEDOCS_DATA['docroot']
    }

    if (window.location.pathname.indexOf('/projects/') == 0) {
      get_data['subproject'] = true
    }

    // Theme popout code
    $.ajax({
      url: "https://readthedocs.org/api/v2/footer_html/",
      //url: "http://localhost:8000/api/v2/footer_html/",
      crossDomain: true,
      xhrFields: {
        withCredentials: true,
      },
      dataType: "jsonp",
      data: get_data,
      success: function (data) {
            if (READTHEDOCS_DATA['theme'] != "sphinx_rtd_theme") {
              $("body").append(data['html'])
            } else {
              $("div.rst-other-versions").html(data['html'])
            }
      },
      error: function () {
          console.log('Error loading footer')
      }
    });

    // Shift nav in mobile when clicking the menu.
    $(document).on('click', "[data-toggle='wy-nav-top']", function() {
      $("[data-toggle='wy-nav-shift']").toggleClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    // Close menu when you click a link.
    $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
      $("[data-toggle='wy-nav-shift']").removeClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    $(document).on('click', "[data-toggle='rst-current-version']", function() {
      $("[data-toggle='rst-versions']").toggleClass("shift-up");
    });
    // Make tables responsive
    $("table.docutils:not(.field-list)").wrap("

");

    // Add Grok the Docs Client
    $.ajax({
        url: "https://api.grokthedocs.com/static/javascript/bundle-client.js",
        crossDomain: true,
        dataType: "script",
    });

    if (READTHEDOCS_DATA['theme'] == 'sphinx_rtd_theme') {
      searchLanding()
    }

    // Search

    $(document).on({
      mouseenter: function(ev) {
          var tooltip = $(ev.target).next()
          tooltip.show()
      },
      mouseleave: function(ev) {
          var tooltip = $(ev.target).next()
          tooltip.hide()
      }
    }, '.result-count')

    $(document).on('submit', '#rtd-search-form', function (ev) {
      //ev.preventDefault();
      clearSearch()
      var query = $("#rtd-search-form input[name='q']").val()
      getSearch(query, true)
    })

    $(document).on('click', '.search-result', function (ev) {
      ev.preventDefault();
      console.log(ev.target)
      html = $(ev.target).next().html()
      displayContent(html);
    })

    function searchLanding() {
      // Highlight based on highlight GET arg
      var params = $.getQueryParameters();
      var query = (params.q) ? params.q[0].split(/\s+/) : [];
      var clear = true
      /* Don't "search" on highlight phrases
      if (!query.length) {
        // Only clear on q
        clear = false
        var query = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
      }
      */
      if (query.length) {
        query = query.join(" ")
        console.log("Searching based on GET arg for: " + query)
        $("#rtd-search-form input[name='q'").val(query)
        getSearch(query, clear)
      }
    }

    function getSearch(query, clear) {
      var get_data = {
        project: READTHEDOCS_DATA['project'],
        version: READTHEDOCS_DATA['version'],
        format: "jsonp",
        q: query
      }

      // Search results
      $.ajax({
        url: "https://readthedocs.org/api/v2/search/section/",
        //url: "http://localhost:8000/api/v2/search/section/",
        crossDomain: true,
        xhrFields: {
          withCredentials: true,
        },
        dataType: "jsonp",
        data: get_data,
        success: function (data) {
          clearSearch(clear)
          hits = data.results.hits.hits
          if (!hits.length) {
            resetState()
          } else {
            displaySearch(hits, query)
          }
        },
        error: function () {
            console.log('Error searching')
        }
      });
    }

    function displayContent(html) {
        var content = $('.rst-content')
        content.html(html)
    }

    function displaySearch(hits, query) {
      FIRSTRUN = {}
      current = $(".toctree-l1.current > a")
      for (index in hits) {
        var hit = hits[index]
        var path = hit.fields.path
        var pageId = hit.fields.page_id
        var title = hit.fields.title
        var content = hit.fields.content
        var highlight = hit.highlight.content
        var score = hit._score

        var li = $(".toctree-l1 > a[href^='" + path + "']")

        /*
        // This doesn't work :)
        if (!li.length && $(current.next().children()[0]).text() == title) {
            li = current
            console.log("Current page: " + title)
        } else {
          console.log("Not: " + title)
        }
        */

        var ul = li.next()

        console.log(path)

        // Display content for first result
        if (index == 0) {
          // Don't display content for now, so we show sphinx results
          //displayContent(content)
        }

        // Clear out subheading with result content
        if (!FIRSTRUN[path]) {
          li.show()
          li.attr("href", li.attr('href') + "?highlight=" + query)
          li.parent().addClass("current")
          li.append("")
          ul.empty()
          FIRSTRUN[path] = true
        }

        // Dedupe
        if (!FIRSTRUN[path+title]) {
          ul.append('
' + '' + title + '' + '' + content + '' + '
')
          if (score > 1) {
            $(".toctree-l2 ")
            inserted = $('.toctree-l2 > [pageId="' + pageId + '"]')
            inserted.append("")
          }
          FIRSTRUN[path+title] = true
        }
      }
      // Hide non-showing bits
      $.each($(".toctree-l1 > a"), function (index, el) {
          hide = true
          if ($(el).attr('href') == "") {
              // Current page
              hide = false
          }
          for (key in FIRSTRUN) {
              if ($(el).attr('href').indexOf(key) == 0) {
                hide = false
              }
          }
          if (hide) {
            console.log("Hiding " + el)
            $(el).hide()
          }

      })

    }

    function resetState() {
      $.each($(".toctree-l1 > a"), function (index, el) {
        var el = $(el)
        el.show()
        el.parent().show()
      })

    }
    function clearSearch(empty) {
      $('.result-icon').remove()
      $.each($(".toctree-l1 > a"), function (index, el) {
        var el = $(el)
        if (empty) {
          console.log('Clearing ' + el.next())
          el.parent().removeClass('current')
          el.next().empty()
        }
      })
    }

})
<commit_msg>Fix it both places<commit_after>$(document).ready(function () {

    get_data = {
        project: READTHEDOCS_DATA['project'],
        version: READTHEDOCS_DATA['version'],
        page: READTHEDOCS_DATA['page'],
        theme: READTHEDOCS_DATA['theme'],
        format: "jsonp",
    }

    if ("docroot" in READTHEDOCS_DATA) {
      get_data['docroot'] = READTHEDOCS_DATA['docroot']
    }

    if (window.location.pathname.indexOf('/projects/') == 0) {
      get_data['subproject'] = true
    }

    // Theme popout code
    $.ajax({
      url: "https://readthedocs.org/api/v2/footer_html/",
      //url: "http://localhost:8000/api/v2/footer_html/",
      crossDomain: true,
      xhrFields: {
        withCredentials: true,
      },
      dataType: "jsonp",
      data: get_data,
      success: function (data) {
            if (READTHEDOCS_DATA['theme'] != "sphinx_rtd_theme") {
              $("body").append(data['html'])
            } else {
              $("div.rst-other-versions").html(data['html'])
            }
      },
      error: function () {
          console.log('Error loading footer')
      }
    });

    // Shift nav in mobile when clicking the menu.
    $(document).on('click', "[data-toggle='wy-nav-top']", function() {
      $("[data-toggle='wy-nav-shift']").toggleClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    // Close menu when you click a link.
    $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
      $("[data-toggle='wy-nav-shift']").removeClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    $(document).on('click', "[data-toggle='rst-current-version']", function() {
      $("[data-toggle='rst-versions']").toggleClass("shift-up");
    });
    // Make tables responsive
    $("table.docutils:not(.field-list)").wrap("

");

    // Add Grok the Docs Client
    $.ajax({
        url: "https://api.grokthedocs.com/static/javascript/bundle-client.js",
        crossDomain: true,
        dataType: "script",
    });

    if (READTHEDOCS_DATA['theme'] == 'sphinx_rtd_theme') {
      searchLanding()
    }

    // Search

    $(document).on({
      mouseenter: function(ev) {
          var tooltip = $(ev.target).next()
          tooltip.show()
      },
      mouseleave: function(ev) {
          var tooltip = $(ev.target).next()
          tooltip.hide()
      }
    }, '.result-count')

    $(document).on('submit', '#rtd-search-form', function (ev) {
      //ev.preventDefault();
      clearSearch()
      var query = $("#rtd-search-form input[name='q']").val()
      getSearch(query, true)
    })

    $(document).on('click', '.search-result', function (ev) {
      ev.preventDefault();
      console.log(ev.target)
      html = $(ev.target).next().html()
      displayContent(html);
    })

    function searchLanding() {
      // Highlight based on highlight GET arg
      var params = $.getQueryParameters();
      var query = (params.q) ? params.q[0].split(/\s+/) : [];
      var clear = true
      /* Don't "search" on highlight phrases
      if (!query.length) {
        // Only clear on q
        clear = false
        var query = (params.highlight) ? params.highlight[0].split(/\s+/) : [];
      }
      */
      if (query.length) {
        query = query.join(" ")
        console.log("Searching based on GET arg for: " + query)
        $("#rtd-search-form input[name='q']").val(query)
        getSearch(query, clear)
      }
    }

    function getSearch(query, clear) {
      var get_data = {
        project: READTHEDOCS_DATA['project'],
        version: READTHEDOCS_DATA['version'],
        format: "jsonp",
        q: query
      }

      // Search results
      $.ajax({
        url: "https://readthedocs.org/api/v2/search/section/",
        //url: "http://localhost:8000/api/v2/search/section/",
        crossDomain: true,
        xhrFields: {
          withCredentials: true,
        },
        dataType: "jsonp",
        data: get_data,
        success: function (data) {
          clearSearch(clear)
          hits = data.results.hits.hits
          if (!hits.length) {
            resetState()
          } else {
            displaySearch(hits, query)
          }
        },
        error: function () {
            console.log('Error searching')
        }
      });
    }

    function displayContent(html) {
        var content = $('.rst-content')
        content.html(html)
    }

    function displaySearch(hits, query) {
      FIRSTRUN = {}
      current = $(".toctree-l1.current > a")
      for (index in hits) {
        var hit = hits[index]
        var path = hit.fields.path
        var pageId = hit.fields.page_id
        var title = hit.fields.title
        var content = hit.fields.content
        var highlight = hit.highlight.content
        var score = hit._score

        var li = $(".toctree-l1 > a[href^='" + path + "']")

        /*
        // This doesn't work :)
        if (!li.length && $(current.next().children()[0]).text() == title) {
            li = current
            console.log("Current page: " + title)
        } else {
          console.log("Not: " + title)
        }
        */

        var ul = li.next()

        console.log(path)

        // Display content for first result
        if (index == 0) {
          // Don't display content for now, so we show sphinx results
          //displayContent(content)
        }

        // Clear out subheading with result content
        if (!FIRSTRUN[path]) {
          li.show()
          li.attr("href", li.attr('href') + "?highlight=" + query)
          li.parent().addClass("current")
          li.append("")
          ul.empty()
          FIRSTRUN[path] = true
        }

        // Dedupe
        if (!FIRSTRUN[path+title]) {
          ul.append('
' + '' + title + '' + '' + content + '' + '
')
          if (score > 1) {
            $(".toctree-l2 ")
            inserted = $('.toctree-l2 > [pageId="' + pageId + '"]')
            inserted.append("")
          }
          FIRSTRUN[path+title] = true
        }
      }
      // Hide non-showing bits
      $.each($(".toctree-l1 > a"), function (index, el) {
          hide = true
          if ($(el).attr('href') == "") {
              // Current page
              hide = false
          }
          for (key in FIRSTRUN) {
              if ($(el).attr('href').indexOf(key) == 0) {
                hide = false
              }
          }
          if (hide) {
            console.log("Hiding " + el)
            $(el).hide()
          }

      })

    }

    function resetState() {
      $.each($(".toctree-l1 > a"), function (index, el) {
        var el = $(el)
        el.show()
        el.parent().show()
      })

    }
    function clearSearch(empty) {
      $('.result-icon').remove()
      $.each($(".toctree-l1 > a"), function (index, el) {
        var el = $(el)
        if (empty) {
          console.log('Clearing ' + el.next())
          el.parent().removeClass('current')
          el.next().empty()
        }
      })
    }

})
<|endoftext|>


<commit_before>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal2 from 'terminal.js-wcwidth';

import key from '../../utils/keyboard';
import {
  getWidth,
  indexOfWidth,
  substrWidth,
} from '../../utils/char';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  static forwardEvents = [
    'connect',
    'disconnect',
    'message',
    'error',
  ];
  constructor(config) {
    super();
    config = {...defaultConfig, ...config};

    this._parser = config.parser;
    this._term = new Terminal2(config.terminal);
    this._state = { ...Bot.initialState };
    this._term.state.setMode('stringWidth', 'dbcs');

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.connect();

    Bot.forwardEvents.forEach(e => {
      socket.on(e, this.emit.bind(this, e));
    });
    socket.on('message', (msg) => {
      this._term.write(msg);
      this.emit('redraw', this._term.toString());
    });
    this._socket = socket;
  }

  get state() {
    return {...this._state};
  }

  getLine = (n) => {
    return this._term.state.getLine(n);
  };

  async send(msg) {
    return new Promise(resolve => {
      this._socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  async login(username, password) {
    if (this._state.login) return;
    await this.send(`${username}${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
      this.emit('stateChange', this.state);
    }
    return ret;
  }

  async _checkLogin() {
    const { getLine } = this;
    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(0).str.includes("主功能表")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`$$${offset}${key.Enter}`);
    }
    const { getLine } = this;
    let articles = [];
    for(let i=3; i<=22; i++) {
      let line = getLine(i).str;
      articles.push({
        sn:     substrWidth('dbcs', line, 0,   7).trim(),
        push:   substrWidth('dbcs', line, 9,   2).trim(),
        date:   substrWidth('dbcs', line, 11,  5).trim(),
        author: substrWidth('dbcs', line, 17, 12).trim(),
        status: substrWidth('dbcs', line, 30,  2).trim(),
        title:  substrWidth('dbcs', line, 32    ).trim(),
      });
    }
    return articles;
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const { getLine } = this;

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author:    substrWidth('dbcs', getLine(0).str, 7, 50).trim(),
      title:     substrWidth('dbcs', getLine(1).str, 7    ).trim(),
      timestamp: substrWidth('dbcs', getLine(2).str, 7    ).trim(),
      lines: [],
    };

    article.lines.push(getLine(0).str);

    do {
      for(let i=1; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    } while (!getLine(23).str.includes("100%"));

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.send(key.ArrowLeft);

    return article;
  }

  async getFavorite() {
    await this.enterFavorite();
    const { getLine } = this;

    const favorites = [];

    for(let i=3; i<23; i++) {
      let line = getLine(i).str;
      if (line.trim() === '') break;
      let favorite = {
        bn:        substrWidth('dbcs', line,  3,  4).trim() | 0,
        read:      substrWidth('dbcs', line,  8,  2).trim() === '',
        boardname: substrWidth('dbcs', line, 10, 12).trim(),
        category:  substrWidth('dbcs', line, 23,  4).trim(),
        title:     substrWidth('dbcs', line, 30, 31),
        users:     substrWidth('dbcs', line, 62,  5).trim(),
        admin:     substrWidth('dbcs', line, 67    ).trim(),
        folder:    false,
        divider:   false,
      };
      switch (favorite.boardname) {
        case 'MyFavFolder':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            folder: true,
          };
          break;
        case '------------':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            divider: true,
          };
          break;
        default:
          break;
      }
      favorites.push(favorite);
    }
    return favorites;
  }

  async enter() {
    await this.send(`${key.ArrowLeft.repeat(10)}`);
    return true;
  }

  async enterBoard(boardname) {
    await this.enter();
    await this.send(`s${boardname}${key.Enter} ${key.Home}${key.End}`);
    boardname = boardname.toLowerCase();
    const { getLine } = this;

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      this.emit('stateChange', this.state);
      return true;
    }
    return false;
  }

  async enterFavorite() {
    await this.enter();
    await this.send(`F${key.Enter}`);
    return true;
  }
}

export default Bot;
<commit_msg>modify code logic to make it more reasonable<commit_after>import EventEmitter from 'eventemitter3';
import sleep from 'sleep-promise';
import Terminal2 from 'terminal.js-wcwidth';

import key from '../../utils/keyboard';
import {
  getWidth,
  indexOfWidth,
  substrWidth,
} from '../../utils/char';

import defaultConfig from './config';

const setIntevalUntil = (async (_func, _validate, _inteval) => {
  await sleep(_inteval);
  let ret = await _func();
  if (_validate(ret)) return ret;
  else return setIntevalUntil(_func, _validate, _inteval);
});

class Bot extends EventEmitter {
  static initialState = {
    login: false,
  };
  static forwardEvents = [
    'connect',
    'disconnect',
    'message',
    'error',
  ];
  constructor(config) {
    super();
    config = {...defaultConfig, ...config};

    this._parser = config.parser;
    this._term = new Terminal2(config.terminal);
    this._state = { ...Bot.initialState };
    this._term.state.setMode('stringWidth', 'dbcs');

    let Socket;
    switch (config.protocol.toLowerCase()) {
      case 'websocket':
      case 'ws':
      case 'wss':
        Socket = require("../../core/socket").default;
        break;
      case 'telnet':
      case 'ssh':
      default:
        Socket = null;
    }

    if (Socket === null) {
      throw `Invalid protocol: ${config.protocol}`;
    }

    const socket = new Socket(config);
    socket.connect();

    Bot.forwardEvents.forEach(e => {
      socket.on(e, this.emit.bind(this, e));
    });
    socket.on('message', (msg) => {
      this._term.write(msg);
      this.emit('redraw', this._term.toString());
    });
    this._socket = socket;
  }

  get state() {
    return {...this._state};
  }

  getLine = (n) => {
    return this._term.state.getLine(n);
  };

  async send(msg) {
    return new Promise(resolve => {
      this._socket.send(msg);
      this.once('message', msg => {
        resolve(msg);
      });
    });
  }

  async login(username, password) {
    if (this._state.login) return;
    await this.send(`${username}${key.Enter}${password}${key.Enter}`);
    let ret;
    while ((ret = await this._checkLogin()) === null) {
      await sleep(400);
    }
    if (ret) {
      const { _state: state } = this;
      state.login = true;
      state.position = {
        boardname: "",
      };
      this.emit('stateChange', this.state);
    }
    return ret;
  }

  async _checkLogin() {
    const { getLine } = this;
    if (getLine(21).str.includes("密碼不對或無此帳號")) {
      this.emit('login.failed');
      return false;
    } else if (getLine(22).str.includes("您想刪除其他重複登入的連線嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    } else if (getLine(23).str.includes("您要刪除以上錯誤嘗試的記錄嗎")) {
      await this.send(`y${key.Enter}`);
    } else if (getLine(0).str.includes("主功能表")) {
      this.emit('login.success');
      return true;
    } else {
      await this.send(`q`);
    }
    return null;
  }

  async getArticles(boardname, offset=0) {
    await this.enterBoard(boardname);
    offset |= 0;
    if (offset > 0) {
      offset = Math.max(offset-9, 1);
      await this.send(`$$${offset}${key.Enter}`);
    }
    const { getLine } = this;
    let articles = [];
    for(let i=3; i<=22; i++) {
      let line = getLine(i).str;
      articles.push({
        sn:     substrWidth('dbcs', line, 0,   7).trim(),
        push:   substrWidth('dbcs', line, 9,   2).trim(),
        date:   substrWidth('dbcs', line, 11,  5).trim(),
        author: substrWidth('dbcs', line, 17, 12).trim(),
        status: substrWidth('dbcs', line, 30,  2).trim(),
        title:  substrWidth('dbcs', line, 32    ).trim(),
      });
    }
    return articles;
  }

  async getArticle(boardname, sn) {
    await this.enterBoard(boardname);
    const { getLine } = this;

    await this.send(`${sn}${key.Enter}${key.Enter}`);

    let article = {
      sn,
      author:    substrWidth('dbcs', getLine(0).str, 7, 50).trim(),
      title:     substrWidth('dbcs', getLine(1).str, 7    ).trim(),
      timestamp: substrWidth('dbcs', getLine(2).str, 7    ).trim(),
      lines: [],
    };

    article.lines.push(getLine(0).str);

    while (!getLine(23).str.includes("100%")) {
      for(let i=1; i<23; i++) {
        article.lines.push(getLine(i).str);
      }
      await this.send(key.PgDown);
    }

    const lastLine = article.lines[article.lines.length-1];
    for(let i=0; i<23; i++) {
      if (getLine(i).str == lastLine) {
        for(let j=i+1; j<23; j++) {
          article.lines.push(getLine(j).str);
        }
        break;
      }
    }

    await this.send(key.ArrowLeft);

    return article;
  }

  async getFavorite() {
    await this.enterFavorite();
    const { getLine } = this;

    const favorites = [];

    for(let i=3; i<23; i++) {
      let line = getLine(i).str;
      if (line.trim() === '') break;
      let favorite = {
        bn:        substrWidth('dbcs', line,  3,  4).trim() | 0,
        read:      substrWidth('dbcs', line,  8,  2).trim() === '',
        boardname: substrWidth('dbcs', line, 10, 12).trim(),
        category:  substrWidth('dbcs', line, 23,  4).trim(),
        title:     substrWidth('dbcs', line, 30, 31),
        users:     substrWidth('dbcs', line, 62,  5).trim(),
        admin:     substrWidth('dbcs', line, 67    ).trim(),
        folder:    false,
        divider:   false,
      };
      switch (favorite.boardname) {
        case 'MyFavFolder':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            folder: true,
          };
          break;
        case '------------':
          favorite = {
            ...favorite,
            title:  substrWidth('dbcs', line, 30),
            users: '',
            admin: '',
            divider: true,
          };
          break;
        default:
          break;
      }
      favorites.push(favorite);
    }
    return favorites;
  }

  async enter() {
    await this.send(`${key.ArrowLeft.repeat(10)}`);
    return true;
  }

  async enterBoard(boardname) {
    await this.enter();
    await this.send(`s${boardname}${key.Enter} ${key.Home}${key.End}`);
    boardname = boardname.toLowerCase();
    const { getLine } = this;

    if (getLine(23).str.includes("按任意鍵繼續")) {
      await this.send(` `);
    }
    if (getLine(0).str.toLowerCase().includes(`${boardname}`)) {
      this._state.position.boardname = boardname;
      this.emit('stateChange', this.state);
      return true;
    }
    return false;
  }

  async enterFavorite() {
    await this.enter();
    await this.send(`F${key.Enter}`);
    return true;
  }
}

export default Bot;
<|endoftext|>


<commit_before>$(document).ready(function () {

    // Theme popout code
    $.ajax({
      url: "https://readthedocs.org/api/v2/footer_html/",
      //url: "http://localhost:8000/api/v2/footer_html/",
      crossDomain: true,
      xhrFields: {
        withCredentials: true,
      },
      dataType: "jsonp",
      data: {
        project: READTHEDOCS_DATA['project'],
        version: READTHEDOCS_DATA['version'],
        page: READTHEDOCS_DATA['page'],
        theme: READTHEDOCS_DATA['theme'],
        docroot: READTHEDOCS_DATA['docroot']
        format: "jsonp",
      },
      success: function (data) {
            if (READTHEDOCS_DATA['theme'] != "sphinx_rtd_theme") {
              $("body").append(data['html'])
            } else {
              $("div.rst-other-versions").html(data['html'])
            }
      },
      error: function () {
          console.log('Error loading footer')
      }
    });

    // Shift nav in mobile when clicking the menu.
    $(document).on('click', "[data-toggle='wy-nav-top']", function() {
      $("[data-toggle='wy-nav-shift']").toggleClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    // Close menu when you click a link.
    $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
      $("[data-toggle='wy-nav-shift']").removeClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    $(document).on('click', "[data-toggle='rst-current-version']", function() {
      $("[data-toggle='rst-versions']").toggleClass("shift-up");
    });

    // Add Grok the Docs Client
    $.ajax({
        url: "https://api.grokthedocs.com/static/javascript/bundle-client.js",
        crossDomain: true,
        dataType: "script",
    });

})
<commit_msg>Fix JS with optional docroot.<commit_after>$(document).ready(function () {

    get_data = {
        project: READTHEDOCS_DATA['project'],
        version: READTHEDOCS_DATA['version'],
        page: READTHEDOCS_DATA['page'],
        theme: READTHEDOCS_DATA['theme'],
        format: "jsonp",
    }

    if (typeof docroot !== 'undefined') {
      get_data['docroot'] = READTHEDOCS_DATA['docroot']
    }

    // Theme popout code
    $.ajax({
      //url: "https://readthedocs.org/api/v2/footer_html/",
      url: "http://localhost:8000/api/v2/footer_html/",
      crossDomain: true,
      xhrFields: {
        withCredentials: true,
      },
      dataType: "jsonp",
      data: {
      },
      success: function (data) {
            if (READTHEDOCS_DATA['theme'] != "sphinx_rtd_theme") {
              $("body").append(data['html'])
            } else {
              $("div.rst-other-versions").html(data['html'])
            }
      },
      error: function () {
          console.log('Error loading footer')
      }
    });

    // Shift nav in mobile when clicking the menu.
    $(document).on('click', "[data-toggle='wy-nav-top']", function() {
      $("[data-toggle='wy-nav-shift']").toggleClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    // Close menu when you click a link.
    $(document).on('click', ".wy-menu-vertical .current ul li a", function() {
      $("[data-toggle='wy-nav-shift']").removeClass("shift");
      $("[data-toggle='rst-versions']").toggleClass("shift");
    });
    $(document).on('click', "[data-toggle='rst-current-version']", function() {
      $("[data-toggle='rst-versions']").toggleClass("shift-up");
    });

    // Add Grok the Docs Client
    $.ajax({
        url: "https://api.grokthedocs.com/static/javascript/bundle-client.js",
        crossDomain: true,
        dataType: "script",
    });

})
<|endoftext|>


<commit_before>var pk = pk || {};
/**
Create a new slider control

HTML:

	<input id='slider' />

Javascript:

	pk.slider({
		element: document.getElementById('slider'),
		units: 'mm',
		min: 30,
		max: 980,
		value: 133
	});

@class pk.slider
@constructor
@param options {Object}
@param options.element {Object} DOM element to convert to control
@param [options.value=0] {Number} Initial value, defaults to the attribute value set on the passed element, or `0`
@param [options.axis=x] {String} Either `x` (horizontal) or `y` (vertical), ignored if slider is circle
@param [options.min=0] {Number} Minimum value
@param [options.max=100] {Number} Maximum value
@param [options.decimals=0] {Number} Number of decimal places
@param [options.name=pk-slider-RandInt] {String} Name of underlying input control, defaults to the attribute value set on the passed element, or `pk-slider-RandInt`
@param [options.tabindex=0] {Number} Tabindex of control, defaults to the attribute value set on the passed element, or `0`
@param [options.disabled=false] {Boolean} Disabled state of control, defaults to the attribute value set on the passed element, or `false`
@param [options.listeners] {Object} Object array of event listeners to bind to underlying input(s)
@param [options.circle=false] {Object} Object array of properties to define circular slider
@param [options.circle.stroke=20] {Number} Stroke width of slider circle
@return Object {Object} Consisting of original DOM element (item `0`) and class methods (see below)
@chainable
*/
(function(pk) {
    pk.slider = function(opt) {
        var el = opt.element,
            units = opt.units === undefined ? '' : opt.units,
            listeners = opt.listeners === undefined ? {} : opt.listeners,
            min = opt.min || 0,
            max = opt.max || 100,
            decimals = opt.decimals || 0,
            axis = opt.axis || "x",
            range = Math.abs(max - min),
            inputValue = opt.value || el.getAttribute('value') || 0,
            inputDisabled = (opt.disabled || el.getAttribute('disabled')) ? 'disabled' : '',
            inputName = opt.name || el.getAttribute('name') || 'pk-slider-' + pk.getRand(1, 999),
            inputTabIndex = opt.tabindex || el.getAttribute('tabindex') || 0,
			circle = opt.circle || false;
			// circle=false;
			if(circle){
				circle={
					stroke:circle.stroke || 20
				}
			}

        if (!axis || !(axis.indexOf("x") < 0 || axis.indexOf("y") < 0)) {
            axis = "x";
        }

        var tpl = "
\
            <input type='hidden' name='" + inputName + "' " + inputDisabled + " value='" + inputValue + "'/>\

";

		/*
		var tpl = "
\
            <input type='hidden' name='" + inputName + "' " + inputDisabled + " value='" + inputValue + "'/>\

";
		*/
        el = pk.replaceEl(el, tpl);
		var l=pk.layout(el);
		var d = l.height > l.width ? l.height : l.width;
		function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
		  var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
		  return {
			x: centerX + (radius * Math.cos(angleInRadians)),
			y: centerY + (radius * Math.sin(angleInRadians))
		  };
		}

		function describeArc(x, y, radius, startAngle, endAngle){
			endAngle = endAngle == 360 || endAngle > 360 ? 359.9 : endAngle ;
			var start = polarToCartesian(x, y, radius, endAngle);
			var end = polarToCartesian(x, y, radius, startAngle);

			var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

			var d = [
				"M", start.x, start.y,
				"A", radius, radius, 0, arcSweep, 0, end.x, end.y
			].join(" ");

			return d;
		}
		var pathEl='';
		var innerTpl='';
		if(circle){
			innerTpl="<svg height='"+d+"' width='"+d+"' xmlns='http://www.w3.org/2000/svg' version='1.1'>\
			   <circle class='pk-slider-circle' cx='"+d/2+"' cy='"+d/2+"' r='"+((d-circle.stroke)/2)+"' stroke='black' stroke-width='"+circle.stroke+"' fill='none' />\
			   <path x='"+d/2+"' y='"+d/2+"' fill='none' stroke='red' d='' stroke-width='"+circle.stroke+"'/>\
			</svg>";
			el.appendChild(pk.createEl(innerTpl));
			el.appendChild(pk.createEl(""));

			pathEl = el.children[1].childNodes[3];
			pk.attribute(pathEl, 'd', describeArc(d/2, d/2, (d-circle.stroke)/2, 0, 360));
		}else{
			// get biggest dimension
			innerTpl="
\
                \

";
			el.appendChild(pk.createEl(innerTpl));
		}

        var indicatorEl = circle ? el.children[1].childNodes[1] : el.children[1],
            inputEl = el.children[0],
            valueEl = circle ? el.children[2].children[0] : indicatorEl.children[0],
            unitsEl = circle ? el.children[2].children[1] : indicatorEl.children[1];

        /**
        Fired on slide event starting
        @event slidestart
        @param element {Object} Element event fired on
        @param event {Object} Event object
        */

        /**
        Fired on during slide event
        @event sliding
        @param element {Object} Element event fired on
        @param event {Object} Event object
        */

        /**
        Fired on slide event ending
        @event slideend
        @param element {Object} Element event fired on
        @param event {Object} Event object
        */
        pk.bindListeners(listeners, inputEl);
        pk.drag({
            element: el,
            contain: {
                element: el
            },
            move: false,
            listeners: {
                dragging: function(el, e) {
                    if (obj.disabled()) {
                        return false;
                    }
					var perc=0;
				    var p =pk.layout(el,e);
					if(circle){
						var origin={
							x:p.left+(p.width/2),
							y:p.top+(p.height/2)
						}
						if(e.pageX >= origin.x){
							perc = 90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI;
						}else{
							perc = 180+(90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI);
						}
						perc=perc/360;
					}else{
						perc = axis === "x" ? (e.dragDist.x + e.dragOffset.x) / pk.layout(el).width : 1 - (e.dragDist.y + e.dragOffset.y) / pk.layout(el).height;
						perc = perc < 0 ? 0 : perc;
						perc = perc > 1 ? 1 : perc;

					}
					obj.val(min + Math.round(perc * range));
                    if (listeners && listeners.sliding) {
                        listeners.sliding(el, e);
                    }
                },
                dragstart: function(el, e) {

                    if (obj.disabled()) {
                        return false;
                    }
                    if (listeners && listeners.slidestart) {
                        listeners.slidestart(el, e);
                    }
                    pk.removeClass(indicatorEl, 'pk-animated');
                },
                dragend: function(el, e) {
                    if (obj.disabled()) {
                        return false;
                    }
                    if (listeners && listeners.slideend) {
                        listeners.slideend(el, e);
                    }
                    pk.addClass(indicatorEl, 'pk-animated');
                }
            }
        });

        pk.bindEvent('click', el, function(e) {
            if (obj.disabled()) {
                return false;
            }
			var perc=0;
			if(circle){
				var p =pk.layout(el,e);
				var origin={
					x:p.left+(p.width/2),
					y:p.top+(p.height/2)
				}
				if(e.pageX >= origin.x){
					perc = 90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI;
				}else{
					perc = 180+(90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI);
				}
				perc=perc/360;
			}else{
				perc = axis === "x" ? ((e.clientX - el.getBoundingClientRect().left) / pk.layout(el).width) : 1 - ((e.clientY - el.getBoundingClientRect().top) / pk.layout(el).height);
			}
            obj.val(min + Math.round(perc * range));
        });
        pk.bindEvent("mousewheel", el, function(e) {
            pk.preventBubble(e);
            if (obj.disabled()) {
                return false;
            }
            var offset = 0.1;
            if (e.wheelDelta > 0 || e.detail < 0) {
                offset = offset * -1;
            }
            obj.val(range * offset + parseInt(obj.val(), 0));
        });
        /**
        Gets or sets control value
        @method val
        @param [value] {Number} Value to set
        @return {Number} Returns current value
        */

        /**
        Gets or sets control disabled state
        @method disabled
        @param [boolean] {Boolean} Disabled state
        @return {Boolean} Returns disabled state
        */
        var obj = {
            0: el,
            val: function(val, force) {
                if (val === undefined || (obj.disabled() && !force)) {
                    return inputEl.value;
                }
                val = val < min ? min : val;
                val = val > max ? max : val;
                inputEl.value = val;
				if(circle){
					pk.attribute(pathEl, 'd', describeArc(d/2, d/2, (d-circle.stroke)/2, 0, (val - min) *360 / range));
				}else{
					if (axis === "x") {
						indicatorEl.style.width = (val - min) * 100 / range + '%';
					} else {
						indicatorEl.style.height = (val - min) * 100 / range + '%';
					}
				}
                valueEl.innerHTML = val.toFixed(decimals);
                unitsEl.innerHTML = units;
            },
            disabled: function(val) {
                if (val !== undefined) {
                    pk.toggleClass(el, 'pk-disabled', val);
                    pk.attribute(inputEl, 'disabled', val);
                }
                return pk.attribute(inputEl, 'disabled');
            }
        };
        obj.val(inputValue, true);
        return obj;
    };
    return pk;
})(pk);
<commit_msg>Update slider.js<commit_after>var pk = pk || {};
/**
Create a new slider control

HTML:

	<input id='slider' />

Javascript:

	pk.slider({
		element: document.getElementById('slider'),
		units: 'mm',
		min: 30,
		max: 980,
		value: 133
	});

@class pk.slider
@constructor
@param options {Object}
@param options.element {Object} DOM element to convert to control
@param [options.value=0] {Number} Initial value, defaults to the attribute value set on the passed element, or `0`
@param [options.axis=x] {String} Either `x` (horizontal) or `y` (vertical), ignored if slider is circle
@param [options.min=0] {Number} Minimum value
@param [options.max=100] {Number} Maximum value
@param [options.decimals=0] {Number} Number of decimal places
@param [options.name=pk-slider-RandInt] {String} Name of underlying input control, defaults to the attribute value set on the passed element, or `pk-slider-RandInt`
@param [options.tabindex=0] {Number} Tabindex of control, defaults to the attribute value set on the passed element, or `0`
@param [options.disabled=false] {Boolean} Disabled state of control, defaults to the attribute value set on the passed element, or `false`
@param [options.listeners] {Object} Object array of event listeners to bind to underlying input(s)
@param [options.circle=false] {Object} Object array of properties to define circular slider
@param [options.circle.stroke=20] {Number} Stroke width of slider circle
@return Object {Object} Consisting of original DOM element (item `0`) and class methods (see below)
@chainable
*/
(function(pk) {
    pk.slider = function(opt) {
        var el = opt.element,
            units = opt.units === undefined ? '' : opt.units,
            listeners = opt.listeners === undefined ? {} : opt.listeners,
            min = opt.min || 0,
            max = opt.max || 100,
            decimals = opt.decimals || 0,
            axis = opt.axis || "x",
            range = Math.abs(max - min),
            inputValue = opt.value || el.getAttribute('value') || 0,
            inputDisabled = (opt.disabled || el.getAttribute('disabled')) ? 'disabled' : '',
            inputName = opt.name || el.getAttribute('name') || 'pk-slider-' + pk.getRand(1, 999),
            inputTabIndex = opt.tabindex || el.getAttribute('tabindex') || 0,
			circle = opt.circle || false;
			// circle=false;
			if(circle){
				circle={
					stroke:circle.stroke || 20,
					start:circle.start || 0,
					length:!circle.length || circle.length===360 ? null : circle.length
				}
			}

        if (!axis || !(axis.indexOf("x") < 0 || axis.indexOf("y") < 0)) {
            axis = "x";
        }

        var tpl = "
\
            <input type='hidden' name='" + inputName + "' " + inputDisabled + " value='" + inputValue + "'/>\

";

		/*
		var tpl = "
\
            <input type='hidden' name='" + inputName + "' " + inputDisabled + " value='" + inputValue + "'/>\

";
		*/
        el = pk.replaceEl(el, tpl);
		var l=pk.layout(el);
		var d = l.height > l.width ? l.height : l.width;
		function polarToCartesian(centerX, centerY, radius, angleInDegrees) {
		  var angleInRadians = (angleInDegrees-90) * Math.PI / 180.0;
		  return {
			x: centerX + (radius * Math.cos(angleInRadians)),
			y: centerY + (radius * Math.sin(angleInRadians))
		  };
		}

		function describeArc(x, y, radius, startAngle, endAngle){
			endAngle = endAngle == 360 || endAngle > 360 ? 359.9 : endAngle ;

			if(circle.length){
				startAngle=endAngle-(circle.length / 2);
				endAngle=endAngle+(circle.length / 2);
			}

			var start = polarToCartesian(x, y, radius, endAngle);
			var end = polarToCartesian(x, y, radius, startAngle);

			var arcSweep = endAngle - startAngle <= 180 ? "0" : "1";

			var d = [
				"M", start.x, start.y,
				"A", radius, radius, 0, arcSweep, 0, end.x, end.y
			].join(" ");

			return d;
		}
		var pathEl='';
		var innerTpl='';
		if(circle){
			innerTpl="<svg height='"+d+"' width='"+d+"' xmlns='http://www.w3.org/2000/svg' version='1.1'>\
			   <circle class='pk-slider-circle' cx='"+d/2+"' cy='"+d/2+"' r='"+((d-circle.stroke)/2)+"' stroke='black' stroke-width='"+circle.stroke+"' fill='none' />\
			   <path x='"+d/2+"' y='"+d/2+"' fill='none' stroke='red' d='' stroke-width='"+circle.stroke+"'/>\
			</svg>";
			el.appendChild(pk.createEl(innerTpl));
			el.appendChild(pk.createEl(""));

			pathEl = el.children[1].childNodes[3];
			pk.attribute(pathEl, 'd', describeArc(d/2, d/2, (d-circle.stroke)/2, 0, 360));
		}else{
			// get biggest dimension
			innerTpl="
\
                \

";
			el.appendChild(pk.createEl(innerTpl));
		}

        var indicatorEl = circle ? el.children[1].childNodes[1] : el.children[1],
            inputEl = el.children[0],
            valueEl = circle ? el.children[2].children[0] : indicatorEl.children[0],
            unitsEl = circle ? el.children[2].children[1] : indicatorEl.children[1];

        /**
        Fired on slide event starting
        @event slidestart
        @param element {Object} Element event fired on
        @param event {Object} Event object
        */

        /**
        Fired on during slide event
        @event sliding
        @param element {Object} Element event fired on
        @param event {Object} Event object
        */

        /**
        Fired on slide event ending
        @event slideend
        @param element {Object} Element event fired on
        @param event {Object} Event object
        */
        pk.bindListeners(listeners, inputEl);
        pk.drag({
            element: el,
            contain: {
                element: el
            },
            move: false,
            listeners: {
                dragging: function(el, e) {
                    if (obj.disabled()) {
                        return false;
                    }
					var perc=0;
				    var p =pk.layout(el,e);
					if(circle){
						var origin={
							x:p.left+(p.width/2),
							y:p.top+(p.height/2)
						}
						if(e.pageX >= origin.x){
							perc = 90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI;
						}else{
							perc = 180+(90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI);
						}
						perc=perc/360;
					}else{
						perc = axis === "x" ? (e.dragDist.x + e.dragOffset.x) / pk.layout(el).width : 1 - (e.dragDist.y + e.dragOffset.y) / pk.layout(el).height;
						perc = perc < 0 ? 0 : perc;
						perc = perc > 1 ? 1 : perc;

					}
					obj.val(min + Math.round(perc * range));
                    if (listeners && listeners.sliding) {
                        listeners.sliding(el, e);
                    }
                },
                dragstart: function(el, e) {

                    if (obj.disabled()) {
                        return false;
                    }
                    if (listeners && listeners.slidestart) {
                        listeners.slidestart(el, e);
                    }
                    pk.removeClass(indicatorEl, 'pk-animated');
                },
                dragend: function(el, e) {
                    if (obj.disabled()) {
                        return false;
                    }
                    if (listeners && listeners.slideend) {
                        listeners.slideend(el, e);
                    }
                    pk.addClass(indicatorEl, 'pk-animated');
                }
            }
        });

        pk.bindEvent('click', el, function(e) {
            if (obj.disabled()) {
                return false;
            }
			var perc=0;
			if(circle){
				var p =pk.layout(el,e);
				var origin={
					x:p.left+(p.width/2),
					y:p.top+(p.height/2)
				}
				if(e.pageX >= origin.x){
					perc = 90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI;
				}else{
					perc = 180+(90-Math.atan((origin.y-e.pageY)/(e.pageX-origin.x))*180/Math.PI);
				}
				perc=perc/360;
			}else{
				perc = axis === "x" ? ((e.clientX - el.getBoundingClientRect().left) / pk.layout(el).width) : 1 - ((e.clientY - el.getBoundingClientRect().top) / pk.layout(el).height);
			}
            obj.val(min + Math.round(perc * range));
        });
        pk.bindEvent("mousewheel", el, function(e) {
            pk.preventBubble(e);
            if (obj.disabled()) {
                return false;
            }
            var offset = 0.1;
            if (e.wheelDelta > 0 || e.detail < 0) {
                offset = offset * -1;
            }
            obj.val(range * offset + parseInt(obj.val(), 0));
        });
        /**
        Gets or sets control value
        @method val
        @param [value] {Number} Value to set
        @return {Number} Returns current value
        */

        /**
        Gets or sets control disabled state
        @method disabled
        @param [boolean] {Boolean} Disabled state
        @return {Boolean} Returns disabled state
        */
        var obj = {
            0: el,
            val: function(val, force) {
                if (val === undefined || (obj.disabled() && !force)) {
                    return inputEl.value;
                }
                val = val < min ? min : val;
                val = val > max ? max : val;
                inputEl.value = val;
				if(circle){
					pk.attribute(pathEl, 'd', describeArc(d/2, d/2, (d-circle.stroke)/2, 0, (val - min) *360 / range));
				}else{
					if (axis === "x") {
						indicatorEl.style.width = (val - min) * 100 / range + '%';
					} else {
						indicatorEl.style.height = (val - min) * 100 / range + '%';
					}
				}
                valueEl.innerHTML = val.toFixed(decimals);
                unitsEl.innerHTML = units;
            },
            disabled: function(val) {
                if (val !== undefined) {
                    pk.toggleClass(el, 'pk-disabled', val);
                    pk.attribute(inputEl, 'disabled', val);
                }
                return pk.attribute(inputEl, 'disabled');
            }
        };
        obj.val(inputValue, true);
        return obj;
    };
    return pk;
})(pk);
<|endoftext|>


<commit_before>/*

Socket!

Thanks to ging for getting this working, https://github.com/pantlesswonder/robojardeuce

PANTS!

*/

(function (parent) {
    'use strict';

    var url    = require('url'),
        http   = require('http'),
        https  = require('https'),
        uuid   = require('node-uuid'),
        events = require('events'),
        util;

    function InvalidURL (parsedURL) {
        this.parsedURL = parsedURL;
    }
    InvalidURL.prototype = {
        prototype: Error.prototype,
        toString: function () { return "Invalid URL: " + this.parsedURL.href; }
    };

    function InvalidState (extra) {
        this.extra = extra;
    }
    InvalidState.prototype = {
        prototype: Error.prototype,
        toString: function () { return "Invalid State " + this.extra; }
    };

    util = (function () {
        var empty = {};
        return {
            hasOwnProperty: function (obj, field) {
                return empty.hasOwnProperty.call(obj, field);
            },

            shallowCopy: function (src, dest) {
                var keys = Object.keys(src),
                    i;
                for (i = 0; i < keys.length; i += 1) {
                    dest[keys[i]] = src[keys[i]];
                }
            },

            liftFunctions: function (src, dest, fields) {
                var i, field;
                for (i = 0; i < fields.length; i += 1) {
                    field = fields[i];
                    if (undefined !== src[field] &&
                        undefined !== src[field].call) {
                        dest[field] = src[field].bind(src);
                    }
                }
            }
        };
    }());

    function SockJSClient (server) {
        var parsed, serverId, sessionId;

        parsed = url.parse(server);

        if ('http:' === parsed.protocol) {
            this.client = http;
        } else if ('https:' === parsed.protocol) {
            this.client = https;
        } else {
            throw new InvalidURL(parsed);
        }

        if (parsed.pathname === '/') {
            parsed.pathname = '';
        }

        serverId = Math.round(Math.random() * 999);
        sessionId = uuid();

        this.server = url.parse(
            parsed.protocol + "//" + parsed.host + parsed.pathname +
                "/" + serverId + "/" + sessionId);

        this.error = Object.getPrototypeOf(this).error.bind(this);
        this.connection = Object.getPrototypeOf(this).connection.bind(this);
        this.closed = Object.getPrototypeOf(this).closed.bind(this);

        this.emitter = new events.EventEmitter();
        util.liftFunctions(
            this.emitter, this,
            ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);

        this.writeBuffer = [];
    }

    SockJSClient.prototype = {
        isReady:   false,
        isClosing: false,
        isClosed:  false,

        connect: function () {
            if (this.isReady || this.isClosing || this.isClosed) {
                return;
            }
            var transport = new XHRStreaming(this);
            transport.on('error', this.error);
            transport.on('connection', this.connection);
            transport.on('close', this.closed);
            (new StateMachine(transport)).invoke();
        },

        connection: function (transport) {
            if (this.isClosing) {
                transport.close();
            } else if (! (this.isReady || this.isClosed)) {
                this.isReady = true;
                this.transport = transport;
                this.emit('connection');
                if (0 !== this.writeBuffer.length) {
                    transport.write(this.writeBuffer);
                    this.writeBuffer = [];
                }
            }
        },

        error: function () {
            this.isReady = false;
            var args = Array.prototype.slice.call(arguments, 0);
            args.unshift('error');
            this.emit.apply(this, args);
            if (this.isClosing) {
                this.closed();
            }
        },

        write: function (message) {
            if (this.isClosed || this.isClosing) {
                return;
            } else if (this.isReady) {
                return this.transport.write([message]);
            } else {
                this.writeBuffer.push(message);
            }
        },

        close: function () {
            if (! (this.isClosing || this.isClosed)) {
                this.isClosing = true;
                if (this.isReady) {
                    this.isReady = false;
                    this.transport.close();
                }
            }
        },

        closed: function () {
            if (! this.isClosed) {
                var args = Array.prototype.slice.call(arguments, 0);
                args.unshift('close');
                this.emit.apply(this, args);
            }
            this.isClosed  = true;
            this.isClosing = false;
            this.isReady   = false;
        }
    };

    function XHRStreaming (sjs) {
        this.sjs = sjs;
        this.emitter = new events.EventEmitter();
        util.liftFunctions(
            this.emitter, this,
            ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);
        this.error = Object.getPrototypeOf(this).error.bind(this);
        this.initialPayloadRemaining = this.initialPayloadLength;
        this.partialChunk = "";
    }
    XHRStreaming.prototype = {
        fsm: {'start':          'connected',
              'connected':      'dataInitial',
              'dataInitial':    'dataOpen',
              'dataOpen':       'running',
              'running':        'running',
              'errored':        'errored'
             },

        initialPayloadLength: 2049,

        start: function (sm) {
            var request = {method: 'POST',
                           headers: {'Content-Length': 0}},
                clientRequest;
            util.shallowCopy(this.sjs.server, request);
            request.path += '/xhr_streaming';
            clientRequest = this.sjs.client.request(request, sm.stepper());
            clientRequest.on('error', this.error.bind(this, sm));
            clientRequest.end();
        },

        write: function (message) {
            var data = JSON.stringify(message),
                request = {method: 'POST',
                           headers: {
                               'Content-Type': 'application/json',
                               'Content-Length': Buffer.byteLength(data,'utf8')}},
                clientRequest;
            util.shallowCopy(this.sjs.server, request);
            request.path += '/xhr_send';
            clientRequest = this.sjs.client.request(request);
            clientRequest.write(data);
            clientRequest.end();
        },

        close: function () {
            if (undefined !== this.response) {
                this.response.removeAllListeners();
                this.response.destroy();
            }
            this.emit('close');
        },

        connected: function (sm, result) {
            this.response = result;
            if (200 !== result.statusCode) {
                this.error(sm, result.statusCode);
            } else {
                result.setEncoding('utf8');
                result.on('data', sm.stepper());
                result.on('end', this.reopen.bind(this, sm));
            }
        },

        dataInitial: function (sm, chunk) {
            var remaining = this.initialPayloadRemaining - chunk.length;
            if (remaining > 0) {
                this.initialPayloadRemaining = remaining;
                sm.switchTo('dataInitial');
            } else {
                this.initialPayloadRemaining = this.initialPayloadLength;
                if (remaining < 0) {
                    (sm.stepper())(chunk.slice(this.initialPayloadRemaining));
                }
            }
        },

        dataOpen: function (sm, chunk) {
            var fsm;
            if (typeof chunk == 'object') {
                throw new Error ('what the fuck is this shit?');
            }
            chunk = this.partialChunk.concat(chunk);
            while (chunk[0] === '\n')
                chunk = chunk.substr(1);
            if (chunk.length < 2) {
                this.partialChunk = chunk;
                sm.switchTo('dataOpen');
            } else {
                this.partialChunk = "";
                if ('o\n' === chunk.slice(0, 2)) {
                    fsm = {};
                    util.shallowCopy(this.fsm, fsm);
                    this.fsm = fsm;
                    fsm['dataInitial'] = 'running'; // from here on, another 'o\n' is an error
                    this.emit('connection', this);
                    if (2 < chunk.length) {
                        (sm.stepper())(sm, chunk.slice(2));
                    }
                } else {
                    console.log("Ignoring strange header chunk", chunk.trim());
                    sm.switchTo('dataOpen');
                    //this.error(sm, chunk);
                }
            }
        },

        running: function (sm, chunk) {
            var type;
            chunk = this.partialChunk.concat(chunk);
            if (1 < chunk.length) {
                if ('\n' == chunk.charAt(0))
                {
                    chunk = chunk.substring(1);
                }
                type = chunk.charAt(0);
                switch (type) {
                case 'h': // heartbeat
                    this.partialChunk = chunk.slice(2);
                    break;
                case 'a': // data
                    this.emitData(chunk, this.partialChunk.length);
                    break;
                case 'c': // close frame
                    this.close();
                    break;
                case 'o':
                    console.log('Received an o frame midway through connection. What does that do?')
                    break;
                default:
                    this.error(sm, "Unexpected frame type", type, chunk);
                    break;
                }
            } else {
                this.partialChunk = chunk;
            }
        },

        emitData: function (chunk, searchStart) {
            var index = chunk.indexOf('\n', searchStart),
                array, i;
            if (-1 === index) {
                this.partialChunk = chunk;
            } else {
                index += 1;
                if (index === chunk.length) {
                    this.partialChunk = "";
                } else {
                    this.partialChunk = chunk.slice(index);
                }
                array = JSON.parse(chunk.slice(1, index));
                for (i = 0; i < array.length; i += 1) {
                    this.sjs.emit('data', array[i]);
                }
            }
        },

        reopen: function (sm) {
            (sm.stepper('start'))();
        },

        error: function () {
            if (undefined !== this.response) {
                this.response.removeAllListeners();
                this.response.destroy();
            }
            var args = Array.prototype.slice.call(arguments, 0),
                sm;
            sm = args.shift();
            sm.switchTo('errored');
            this.emit('error', args);
        },

        errored: function () {}
    }

    function StateMachine (callbacks) {
        this.callbacks = callbacks;
        this.stepper = Object.getPrototypeOf(this).stepper.bind(this);
        this.fun = this.stepper();
    }
    StateMachine.prototype = {
        invoke: function () {
            if (undefined === this.fun) {
                throw new InvalidState(this);
            }
            var args = Array.prototype.slice.call(arguments, 0);
            args.unshift(this);
            return this.fun.apply(this.callbacks, args);
        },

        nextStateName: function () {
            if (util.hasOwnProperty(this, 'switchedTo')) {
                return this.switchedTo;
            } else if (util.hasOwnProperty(this, 'stateName')) {
                return this.callbacks.fsm[this.stateName];
            } else {
                return 'start';
            }
        },

        switchTo: function (name) {
            if (undefined === name) {
                delete this.switchedTo;
            } else {
                this.switchedTo = name;
            }
        },

        stepper: function (name) {
            return (function () {
                if (undefined !== name) {
                    this.switchTo(name);
                }
                this.stateName = this.nextStateName();
                this.switchTo();
                this.fun = this.callbacks[this.stateName];
                this.invoke.apply(this, arguments);
            }).bind(this);
        }
    };

    exports.create = function (url) {
        var sjsc = new SockJSClient(url);
        sjsc.connect();
        return sjsc;
    };
    exports.InvalidURL = InvalidURL;
    exports.InvalidState = InvalidState;

}(this));
<commit_msg>attempts to further stablize the bot. disconnection needs to be handlede better<commit_after>/*

Socket!

Thanks to ging for getting this working, https://github.com/pantlesswonder/robojardeuce

PANTS!

*/

(function (parent) {
    'use strict';

    var url    = require('url'),
        http   = require('http'),
        https  = require('https'),
        uuid   = require('node-uuid'),
        events = require('events'),
        util;

    function InvalidURL (parsedURL) {
        this.parsedURL = parsedURL;
    }
    InvalidURL.prototype = {
        prototype: Error.prototype,
        toString: function () { return "Invalid URL: " + this.parsedURL.href; }
    };

    function InvalidState (extra) {
        this.extra = extra;
    }
    InvalidState.prototype = {
        prototype: Error.prototype,
        toString: function () { return "Invalid State " + this.extra; }
    };

    util = (function () {
        var empty = {};
        return {
            hasOwnProperty: function (obj, field) {
                return empty.hasOwnProperty.call(obj, field);
            },

            shallowCopy: function (src, dest) {
                var keys = Object.keys(src),
                    i;
                for (i = 0; i < keys.length; i += 1) {
                    dest[keys[i]] = src[keys[i]];
                }
            },

            liftFunctions: function (src, dest, fields) {
                var i, field;
                for (i = 0; i < fields.length; i += 1) {
                    field = fields[i];
                    if (undefined !== src[field] &&
                        undefined !== src[field].call) {
                        dest[field] = src[field].bind(src);
                    }
                }
            }
        };
    }());

    function SockJSClient (server) {
        var parsed, serverId, sessionId;

        parsed = url.parse(server);

        if ('http:' === parsed.protocol) {
            this.client = http;
        } else if ('https:' === parsed.protocol) {
            this.client = https;
        } else {
            throw new InvalidURL(parsed);
        }

        if (parsed.pathname === '/') {
            parsed.pathname = '';
        }

        serverId = Math.round(Math.random() * 999);
        sessionId = uuid();

        this.server = url.parse(
            parsed.protocol + "//" + parsed.host + parsed.pathname +
                "/" + serverId + "/" + sessionId);

        this.error = Object.getPrototypeOf(this).error.bind(this);
        this.connection = Object.getPrototypeOf(this).connection.bind(this);
        this.closed = Object.getPrototypeOf(this).closed.bind(this);

        this.emitter = new events.EventEmitter();
        util.liftFunctions(
            this.emitter, this,
            ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);

        this.writeBuffer = [];
    }

    SockJSClient.prototype = {
        isReady:   false,
        isClosing: false,
        isClosed:  false,

        connect: function () {
            if (this.isReady || this.isClosing || this.isClosed) {
                return;
            }
            var transport = new XHRStreaming(this);
            transport.on('error', this.error);
            transport.on('connection', this.connection);
            transport.on('close', this.closed);
            (new StateMachine(transport)).invoke();
        },

        connection: function (transport) {
            if (this.isClosing) {
                transport.close();
            } else if (! (this.isReady || this.isClosed)) {
                this.isReady = true;
                this.transport = transport;
                this.emit('connection');
                if (0 !== this.writeBuffer.length) {
                    transport.write(this.writeBuffer);
                    this.writeBuffer = [];
                }
            }
        },

        error: function () {
            this.isReady = false;
            var args = Array.prototype.slice.call(arguments, 0);
            args.unshift('error');
            this.emit.apply(this, args);
            if (this.isClosing) {
                this.closed();
            }
        },

        write: function (message) {
            if (this.isClosed || this.isClosing) {
                return;
            } else if (this.isReady) {
                return this.transport.write([message]);
            } else {
                this.writeBuffer.push(message);
            }
        },

        close: function () {
            if (! (this.isClosing || this.isClosed)) {
                this.isClosing = true;
                if (this.isReady) {
                    this.isReady = false;
                    this.transport.close();
                }
            }
        },

        closed: function () {
            if (! this.isClosed) {
                var args = Array.prototype.slice.call(arguments, 0);
                args.unshift('close');
                this.emit.apply(this, args);
            }
            this.isClosed  = true;
            this.isClosing = false;
            this.isReady   = false;
        }
    };

    function XHRStreaming (sjs) {
        this.sjs = sjs;
        this.emitter = new events.EventEmitter();
        util.liftFunctions(
            this.emitter, this,
            ['on', 'once', 'removeListener', 'removeAllListeners', 'emit']);
        this.error = Object.getPrototypeOf(this).error.bind(this);
        this.initialPayloadRemaining = this.initialPayloadLength;
        this.partialChunk = "";
    }
    XHRStreaming.prototype = {
        fsm: {'start':          'connected',
              'connected':      'dataInitial',
              'dataInitial':    'dataOpen',
              'dataOpen':       'running',
              'running':        'running',
              'errored':        'errored'
             },

        initialPayloadLength: 2049,

        start: function (sm) {
            var request = {method: 'POST',
                           headers: {'Content-Length': 0}},
                clientRequest;
            util.shallowCopy(this.sjs.server, request);
            request.path += '/xhr_streaming';
            clientRequest = this.sjs.client.request(request, sm.stepper());
            clientRequest.on('error', this.error.bind(this, sm));
            clientRequest.end();
        },

        write: function (message) {
            var data = JSON.stringify(message),
                request = {method: 'POST',
                           headers: {
                               'Content-Type': 'application/json',
                               'Content-Length': Buffer.byteLength(data,'utf8')}},
                clientRequest;
            util.shallowCopy(this.sjs.server, request);
            request.path += '/xhr_send';
            clientRequest = this.sjs.client.request(request);
            clientRequest.write(data);
            clientRequest.end();
        },

        close: function () {
            if (undefined !== this.response) {
                this.response.removeAllListeners();
                this.response.destroy();
            }
            this.emit('close');
        },

        connected: function (sm, result) {
            this.response = result;
            if (200 !== result.statusCode) {
                this.error(sm, result.statusCode);
            } else {
                result.setEncoding('utf8');
                result.on('data', sm.stepper());
                result.on('end', this.reopen.bind(this, sm));
            }
        },

        dataInitial: function (sm, chunk) {
            var remaining = this.initialPayloadRemaining - chunk.length;
            if (remaining > 0) {
                this.initialPayloadRemaining = remaining;
                sm.switchTo('dataInitial');
            } else {
                this.initialPayloadRemaining = this.initialPayloadLength;
                if (remaining < 0) {
                    (sm.stepper())(chunk.slice(this.initialPayloadRemaining));
                }
            }
        },

        dataOpen: function (sm, chunk) {
            var fsm;
            if (typeof chunk == 'object') {
                throw new Error ('what the fuck is this shit?');
            }
            chunk = this.partialChunk.concat(chunk);
            while (chunk[0] === '\n')
                chunk = chunk.substr(1);
            if (chunk.length < 2) {
                this.partialChunk = chunk;
                sm.switchTo('dataOpen');
            } else {
                this.partialChunk = "";
                if ('o\n' === chunk.slice(0, 2)) {
                    fsm = {};
                    util.shallowCopy(this.fsm, fsm);
                    this.fsm = fsm;
                    fsm['dataInitial'] = 'running'; // from here on, another 'o\n' is an error
                    this.emit('connection', this);
                    if (2 < chunk.length) {
                        (sm.stepper())(sm, chunk.slice(2));
                    }
                } else {
                    console.log("Ignoring strange header chunk", chunk.trim());
                    sm.switchTo('dataOpen');
                    //this.error(sm, chunk);
                }
            }
        },

        running: function (sm, chunk) {
            var type;
            chunk = this.partialChunk.concat(chunk);
            while (chunk.length > 0 && '\n' == chunk.charAt(0))
                chunk = chunk.substring(1);
            if (1 < chunk.length) {
                type = chunk.charAt(0);
                switch (type) {
                case 'h': // heartbeat
                    this.partialChunk = chunk.slice(2);
                    break;
                case 'a': // data
                    this.emitData(chunk, this.partialChunk.length);
                    break;
                case 'o': // when we get o in this frame, it counts as a close
                case 'c': // close frame
                    console.log('Am i closing now?', chunk)
                    this.close();
                    break;
                default:
                    this.error(sm, "Unexpected frame type", type, chunk);
                    break;
                }
            } else {
                this.partialChunk = chunk;
            }
        },

        emitData: function (chunk, searchStart) {
            var index = chunk.indexOf('\n', searchStart),
                array, i;
            if (-1 === index) {
                this.partialChunk = chunk;
            } else {
                index += 1;
                if (index === chunk.length) {
                    this.partialChunk = "";
                } else {
                    this.partialChunk = chunk.slice(index);
                }
                array = JSON.parse(chunk.slice(1, index));
                for (i = 0; i < array.length; i += 1) {
                    this.sjs.emit('data', array[i]);
                }
            }
        },

        reopen: function (sm) {
            (sm.stepper('start'))();
        },

        error: function () {
            if (undefined !== this.response) {
                this.response.removeAllListeners();
                this.response.destroy();
            }
            var args = Array.prototype.slice.call(arguments, 0),
                sm;
            sm = args.shift();
            sm.switchTo('errored');
            this.emit('error', args);
        },

        errored: function () {}
    }

    function StateMachine (callbacks) {
        this.callbacks = callbacks;
        this.stepper = Object.getPrototypeOf(this).stepper.bind(this);
        this.fun = this.stepper();
    }
    StateMachine.prototype = {
        invoke: function () {
            if (undefined === this.fun) {
                throw new InvalidState(this);
            }
            var args = Array.prototype.slice.call(arguments, 0);
            args.unshift(this);
            return this.fun.apply(this.callbacks, args);
        },

        nextStateName: function () {
            if (util.hasOwnProperty(this, 'switchedTo')) {
                return this.switchedTo;
            } else if (util.hasOwnProperty(this, 'stateName')) {
                return this.callbacks.fsm[this.stateName];
            } else {
                return 'start';
            }
        },

        switchTo: function (name) {
            if (undefined === name) {
                delete this.switchedTo;
            } else {
                this.switchedTo = name;
            }
        },

        stepper: function (name) {
            return (function () {
                if (undefined !== name) {
                    this.switchTo(name);
                }
                this.stateName = this.nextStateName();
                this.switchTo();
                this.fun = this.callbacks[this.stateName];
                this.invoke.apply(this, arguments);
            }).bind(this);
        }
    };

    exports.create = function (url) {
        var sjsc = new SockJSClient(url);
        sjsc.connect();
        return sjsc;
    };
    exports.InvalidURL = InvalidURL;
    exports.InvalidState = InvalidState;

}(this));
<|endoftext|>


<commit_before><commit_msg>:zap: Optimize output<commit_after><|endoftext|>


<commit_before>(function () {
  const $ = window.Bliss
  const Mavo = window.Mavo
  const firebase = window.firebase

  Mavo.Backend.register($.Class({
    extends: Mavo.Backend,
    id: 'Firebase',
    constructor: function (databaseUrl) {
      let id = this.mavo.id || 'mavo'
      let config = {}

      let initUsingAttributes = /^https:\/\/.*\.firebaseio\.com$/.test(databaseUrl)

      // Init Firebase using attributes
      if (initUsingAttributes) {
        config = {
          apiKey: this.mavo.element.getAttribute('mv-firebase-api-key'),
          authDomain: this.mavo.element.getAttribute('mv-firebase-auth-domain'),
          databaseURL: databaseUrl,
          storageBucket: this.mavo.element.getAttribute('mv-firebase-storage-bucket')
        }

        if (!config.apiKey) {
          return this.mavo.error('Firebase: mv-firebase-api-key attribute missing')
        }

        // Support using multiple apps on the same page
        if (!firebase.apps.length) {
          this.app = firebase.initializeApp(config)
        } else {
          this.app = firebase.initializeApp(config, `app${firebase.apps.length}`)
        }
      } else {
        // Init firebase using script
        config = firebase.default.app().options

        if (!config.apiKey) {
          return this.mavo.error('Firebase: apiKey missing from config')
        }

        this.app = firebase.default
      }

      this.statusChangesCallbacks = []

      // PERMISSIONS
      let unauthenticatedPermissionsAttr = this.mavo.element.getAttribute('mv-unauthenticated-permissions')
      let authenticatedPermissionsAttr = this.mavo.element.getAttribute('mv-authenticated-permissions')

      let authenticatedPermissions = getPermissions(authenticatedPermissionsAttr) || ['read', 'edit', 'add', 'delete', 'save', 'logout']

      // Use default permissions if unauthenticated-permissions isn't specified,
      // attribute 'firebase-auth-domain' has to be set if permission 'login' is used
      let unauthenticatedPermissions = getPermissions(unauthenticatedPermissionsAttr)
      if (unauthenticatedPermissions) {
        if (!config.authDomain && unauthenticatedPermissions.includes('login')) {
          if (initUsingAttributes) {
            return this.mavo.error('Firebase: authDomain missing from config (needed if permission \'login\' is specified)')
          } else {
            return this.mavo.error('Firebase: firebase-auth-domain attribute missing (needed if permission \'login\' is specified)')
          }
        }
      } else {
        if (config.authDomain) {
          unauthenticatedPermissions = ['read', 'login']
        } else {
          unauthenticatedPermissions = ['read']
        }
      }

      this.defaultPermissions = {
        authenticated: authenticatedPermissions,
        unauthenticated: unauthenticatedPermissions
      }

      this.permissions.on(this.defaultPermissions.unauthenticated)

      this.db = this.app.database().ref(id)

      // STORAGE

      // Only allow file uploading if storageBucket is defined
      if (config.storageBucket) {
        this.storage = this.app.storage().ref(id)

        this.upload = function (file) {
          let ref = this.storage.child(`${file.name}-${Date.now()}`)

          return ref.put(file).then(() => {
            return ref.getDownloadURL()
          })
        }
      }

      // Firebase auth changes
      this.app.auth().onAuthStateChanged(user => {
        if (!user) {
          return
        }

        this.permissions.off(this.defaultPermissions.unauthenticated).on(this.defaultPermissions.authenticated)
        this.user = {
          username: user.email,
          name: user.displayName,
          avatar: user.photoURL
        }
      }, error => {
        this.mavo.error('Firebase: ' + error.message)
      })

      // Enable pushing data from server
      let serverPushAttr = this.mavo.element.getAttribute('mv-server-push')
      if (serverPushAttr !== null && serverPushAttr !== 'false') {
        this.setListenForChanges(true)
      }

      // HELPER FUNCTIONS

      function getPermissions (attr) {
        if (attr) {
          return attr.split(/\s+/)
        } else if (attr === '') {
          return []
        }
      }
    },

    onStatusChange: function (callback) {
      this.statusChangesCallbacks.push(callback)

      if (this.listeningOnStatus) {
        return
      }
      this.listeningOnStatus = true

      this.app.database().ref('.info/connected').on('value', snap => {
        this.statusChangesCallbacks.forEach(callback => callback(snap.val()))
      })
    },

    setListenForChanges: function (bool) {
      if (bool) {
        if (!this.listeningForChanges) {
          this.db.on('value', snapshot => {
            let doc = snapshot.val()

            // Ignore if data is old
            if (this.compareDocRevs({
              _rev: this.rev
            }, doc) !== 1) {
              return
            }

            this.rev = doc._rev

            this.onNewData(doc)
          })
        }
      } else {
        this.db.off()
      }

      this.listeningForChanges = bool
    },

    onNewData: function (data) {
      return this.mavo.render(data)
    },

    load: function () {
      return this.db.once('value').then(snapshot => {
        let data = snapshot.val()

        if (!data) {
          this.rev = 1
          return {}
        }

        this.rev = data._rev || 1
        return data
      })
    },

    store: function (data) {
      // Needed to make this.mavo.unsavedChanges work correctly
      return Promise.resolve().then(() => {
        this.storeData = data

        // this.mavo.unsavedChanges needed because of https://github.com/mavoweb/mavo/issues/256
        if (!this.mavo.unsavedChanges || this.storing) {
          return
        }

        this.storing = true

        return this.put().then(() => {
          this.storing = false
        })
      })
    },

    put: function (data) {
      data = this.storeData || data
      delete this.storeData

      // Overrides server with local data
      return this.db.transaction(currentData => {
        if (currentData) {
          this.rev = Number.isInteger(currentData._rev) ? currentData._rev + 1 : 1
        } else {
          this.rev = 1
        }

        return Object.assign(data, {
          _rev: this.rev
        })
      }).then(() => {
        if (this.storeData) {
          return this.put()
        }
      })
    },

    login: function () {
      return this.ready.then(() => {
        if (this.user) {
          return
        }

        let provider = new this.app.auth.GoogleAuthProvider()

        return this.app.auth().signInWithPopup(provider).catch(error => {
          this.mavo.error('Firebase: ' + error.message)
          return Promise.reject(error)
        })
      })
    },

    logout: function () {
      return this.app.auth().signOut().then(() => {
        // Sign-out successful.
        this.permissions.off(this.defaultPermissions.authenticated).on(this.defaultPermissions.unauthenticated)
      }).catch((error) => {
        this.mavo.error('Firebase: ' + error.message)
      })
    },

    compareDocRevs: function (docA, docB) {
      // If b is newer return 1

      if (!docA || !Number.isInteger(docA._rev)) {
        return 1
      }

      if (!docB || !Number.isInteger(docB._rev)) {
        return -1
      }

      if (docA._rev === docB._rev) {
        return 0
      }

      return docA._rev < docB._rev ? 1 : -1
    },

    static: {
      test: url => /^(firebase|https:\/\/.*\.firebaseio\.com)$/.test(url)
    }
  }))
})()
<commit_msg>Fix authentication bug<commit_after>(function () {
  const $ = window.Bliss
  const Mavo = window.Mavo
  const firebase = window.firebase

  Mavo.Backend.register($.Class({
    extends: Mavo.Backend,
    id: 'Firebase',
    constructor: function (databaseUrl) {
      let id = this.mavo.id || 'mavo'
      let config = {}

      let initUsingAttributes = /^https:\/\/.*\.firebaseio\.com$/.test(databaseUrl)

      // Init Firebase using attributes
      if (initUsingAttributes) {
        config = {
          apiKey: this.mavo.element.getAttribute('mv-firebase-api-key'),
          authDomain: this.mavo.element.getAttribute('mv-firebase-auth-domain'),
          databaseURL: databaseUrl,
          storageBucket: this.mavo.element.getAttribute('mv-firebase-storage-bucket')
        }

        if (!config.apiKey) {
          return this.mavo.error('Firebase: mv-firebase-api-key attribute missing')
        }

        // Support using multiple apps on the same page
        if (!firebase.apps.length) {
          this.app = firebase.initializeApp(config)
        } else {
          this.app = firebase.initializeApp(config, `app${firebase.apps.length}`)
        }
      } else {
        // Init firebase using script
        config = firebase.default.app().options

        if (!config.apiKey) {
          return this.mavo.error('Firebase: apiKey missing from config')
        }

        this.app = firebase.default
      }

      this.statusChangesCallbacks = []

      // PERMISSIONS
      let unauthenticatedPermissionsAttr = this.mavo.element.getAttribute('mv-unauthenticated-permissions')
      let authenticatedPermissionsAttr = this.mavo.element.getAttribute('mv-authenticated-permissions')

      let authenticatedPermissions = getPermissions(authenticatedPermissionsAttr) || ['read', 'edit', 'add', 'delete', 'save', 'logout']

      // Use default permissions if unauthenticated-permissions isn't specified,
      // attribute 'firebase-auth-domain' has to be set if permission 'login' is used
      let unauthenticatedPermissions = getPermissions(unauthenticatedPermissionsAttr)
      if (unauthenticatedPermissions) {
        if (!config.authDomain && unauthenticatedPermissions.includes('login')) {
          if (initUsingAttributes) {
            return this.mavo.error('Firebase: authDomain missing from config (needed if permission \'login\' is specified)')
          } else {
            return this.mavo.error('Firebase: firebase-auth-domain attribute missing (needed if permission \'login\' is specified)')
          }
        }
      } else {
        if (config.authDomain) {
          unauthenticatedPermissions = ['read', 'login']
        } else {
          unauthenticatedPermissions = ['read']
        }
      }

      this.defaultPermissions = {
        authenticated: authenticatedPermissions,
        unauthenticated: unauthenticatedPermissions
      }

      this.permissions.on(this.defaultPermissions.unauthenticated)

      this.db = this.app.database().ref(id)

      // STORAGE

      // Only allow file uploading if storageBucket is defined
      if (config.storageBucket) {
        this.storage = this.app.storage().ref(id)

        this.upload = function (file) {
          let ref = this.storage.child(`${file.name}-${Date.now()}`)

          return ref.put(file).then(() => {
            return ref.getDownloadURL()
          })
        }
      }

      // Firebase auth changes
      this.app.auth().onAuthStateChanged(user => {
        if (!user) {
          return
        }

        this.permissions.off(this.defaultPermissions.unauthenticated).on(this.defaultPermissions.authenticated)
        this.user = {
          username: user.email,
          name: user.displayName,
          avatar: user.photoURL
        }
      }, error => {
        this.mavo.error('Firebase: ' + error.message)
      })

      // Enable pushing data from server
      let serverPushAttr = this.mavo.element.getAttribute('mv-server-push')
      if (serverPushAttr !== null && serverPushAttr !== 'false') {
        this.setListenForChanges(true)
      }

      // HELPER FUNCTIONS

      function getPermissions (attr) {
        if (attr) {
          return attr.split(/\s+/)
        } else if (attr === '') {
          return []
        }
      }
    },

    onStatusChange: function (callback) {
      this.statusChangesCallbacks.push(callback)

      if (this.listeningOnStatus) {
        return
      }
      this.listeningOnStatus = true

      this.app.database().ref('.info/connected').on('value', snap => {
        this.statusChangesCallbacks.forEach(callback => callback(snap.val()))
      })
    },

    setListenForChanges: function (bool) {
      if (bool) {
        if (!this.listeningForChanges) {
          this.db.on('value', snapshot => {
            let doc = snapshot.val()

            // Ignore if data is old
            if (this.compareDocRevs({
              _rev: this.rev
            }, doc) !== 1) {
              return
            }

            this.rev = doc._rev

            this.onNewData(doc)
          })
        }
      } else {
        this.db.off()
      }

      this.listeningForChanges = bool
    },

    onNewData: function (data) {
      return this.mavo.render(data)
    },

    load: function () {
      return this.db.once('value').then(snapshot => {
        let data = snapshot.val()

        if (!data) {
          this.rev = 1
          return {}
        }

        this.rev = data._rev || 1
        return data
      })
    },

    store: function (data) {
      // Needed to make this.mavo.unsavedChanges work correctly
      return Promise.resolve().then(() => {
        this.storeData = data

        // this.mavo.unsavedChanges needed because of https://github.com/mavoweb/mavo/issues/256
        if (!this.mavo.unsavedChanges || this.storing) {
          return
        }

        this.storing = true

        return this.put().then(() => {
          this.storing = false
        })
      })
    },

    put: function (data) {
      data = this.storeData || data
      delete this.storeData

      // Overrides server with local data
      return this.db.transaction(currentData => {
        if (currentData) {
          this.rev = Number.isInteger(currentData._rev) ? currentData._rev + 1 : 1
        } else {
          this.rev = 1
        }

        return Object.assign(data, {
          _rev: this.rev
        })
      }).then(() => {
        if (this.storeData) {
          return this.put()
        }
      })
    },

    login: function () {
      return this.ready.then(() => {
        if (this.user) {
          return
        }

        let provider = new firebase.auth.GoogleAuthProvider()

        return this.app.auth().signInWithPopup(provider).catch(error => {
          this.mavo.error('Firebase: ' + error.message)
          return Promise.reject(error)
        })
      })
    },

    logout: function () {
      return this.app.auth().signOut().then(() => {
        // Sign-out successful.
        this.permissions.off(this.defaultPermissions.authenticated).on(this.defaultPermissions.unauthenticated)
      }).catch((error) => {
        this.mavo.error('Firebase: ' + error.message)
      })
    },

    compareDocRevs: function (docA, docB) {
      // If b is newer return 1

      if (!docA || !Number.isInteger(docA._rev)) {
        return 1
      }

      if (!docB || !Number.isInteger(docB._rev)) {
        return -1
      }

      if (docA._rev === docB._rev) {
        return 0
      }

      return docA._rev < docB._rev ? 1 : -1
    },

    static: {
      test: url => /^(firebase|https:\/\/.*\.firebaseio\.com)$/.test(url)
    }
  }))
})()
<|endoftext|>


<commit_before>var c = require('constants');

function countRoles(room, role) {
  return _.filter(room.find(FIND_MY_CREEPS), (creep) => creep.memory.role == role).length;
}

function getRole(room) {
  if (countRoles(room, 'harvester') < 2) {
    return 'harvester';
  }
  // TODO: check if we are under attack
  return 'builder';
}

function getCost(body) {
  var cost = 0;
  for (piece in body) {
    cost += c.costs[body[piece]];
  }
  return cost;
}

var adds = [
  [WORK, CARRY, MOVE, MOVE],
  [WORK, MOVE],
  [CARRY, MOVE],
  [MOVE]
];

function getBody(role, energy) {
  var base = [WORK, CARRY, MOVE];
  var cost = getCost(base);

  while (cost < energy) {
    var remaining = energy - cost;
    var added = false;

    // add the largest chunk we can, bail if there isn't enough energy left
    for (var i in adds) {
      var toAdd = adds[i];
      if (getCost(toAdd) < remaining) {
        base = base.concat(toAdd);
        cost = getCost(base);
        added = true;
        break;
      }
    }

    if (!added) {
      break;
    }
  }
  return base;
}

function spawnCreep(spawn, energy) {
  var role = getRole(spawn.room);
  var body = getBody(role, energy);
  console.log('Spawning a ' + role + ' using ' + energy + ' energy ' + body);
  spawn.createCreep(body, undefined, {role: role});
}

function run() {
  for (var name in Game.spawns) {
    var spawn = Game.spawns[name];
    var room = spawn.room;
    var noCreeps = room.find(FIND_MY_CREEPS).length === 0;
    // TODO: handle energy in multiple spawns?
    if (room.energyAvailable === room.energyCapacityAvailable || noCreeps) {
      spawnCreep(spawn, room.energyAvailable);
    }
  }
}

module.exports = {
  run: run
};
<commit_msg>more harvesters<commit_after>var c = require('constants');

function countRoles(room, role) {
  return _.filter(room.find(FIND_MY_CREEPS), (creep) => creep.memory.role == role).length;
}

function getRole(room) {
  if (countRoles(room, 'harvester') < 3) {
    return 'harvester';
  }
  // TODO: check if we are under attack
  return 'builder';
}

function getCost(body) {
  var cost = 0;
  for (piece in body) {
    cost += c.costs[body[piece]];
  }
  return cost;
}

var adds = [
  [WORK, CARRY, MOVE, MOVE],
  [WORK, MOVE],
  [CARRY, MOVE],
  [MOVE]
];

function getBody(role, energy) {
  var base = [WORK, CARRY, MOVE];
  var cost = getCost(base);

  while (cost < energy) {
    var remaining = energy - cost;
    var added = false;

    // add the largest chunk we can, bail if there isn't enough energy left
    for (var i in adds) {
      var toAdd = adds[i];
      if (getCost(toAdd) < remaining) {
        base = base.concat(toAdd);
        cost = getCost(base);
        added = true;
        break;
      }
    }

    if (!added) {
      break;
    }
  }
  return base;
}

function spawnCreep(spawn, energy) {
  var role = getRole(spawn.room);
  var body = getBody(role, energy);
  console.log('Spawning a ' + role + ' using ' + energy + ' energy ' + body);
  spawn.createCreep(body, undefined, {role: role});
}

function run() {
  for (var name in Game.spawns) {
    var spawn = Game.spawns[name];
    var room = spawn.room;
    var noCreeps = room.find(FIND_MY_CREEPS).length === 0;
    // TODO: handle energy in multiple spawns?
    if (room.energyAvailable === room.energyCapacityAvailable || noCreeps) {
      spawnCreep(spawn, room.energyAvailable);
    }
  }
}

module.exports = {
  run: run
};
<|endoftext|>