text:
local ts_utils = require('nvim-treesitter.ts_utils')
local utils = require('tabout.utils')
local config = require('tabout.config')
local logger = require('tabout.logger')

---@class TaboutNode
local M = {}

-- should be get_one_line and get_multi_line

---returns Node if node has a valid parent, else nil
---@param node Node
---@param multi boolean
---@return integer
---@return integer
local get_parent_node = function(node, multi)
    local parent = node:parent()

    --[[ iterate over parent nodes until no more nodes, no node on the same line
                or a wrapped node is encountered ]]
    while (parent and not M.is_wrapped(parent)) do
        if not multi and not M.is_one_line(parent) then break end
        parent = parent:parent()
    end

    if parent and M.is_wrapped(parent) then
        if not multi and not M.is_one_line(parent) then return nil end
        return parent
    else
        return nil
    end
end

---returns line,col or nil,nil
---@param dir string | "'forward'" | "'backward'"
---@return integer
---@return integer
M.get_node_at_cursor = function(dir)
    local cursor = vim.api.nvim_win_get_cursor(0)
    local cursor_range = {cursor[1] - 1, cursor[2]}
    if dir == 'backward' and cursor[2] then
        cursor_range = {cursor[1] - 1, cursor[2] - 1}
    end
    local root =
        ts_utils.get_root_for_position(cursor_range[1], cursor_range[2])

    if not root then
        logger.debug(
            "get_node_at_cursor: no root found at " .. cursor_range[1] .. ":" ..
                cursor_range[2])
        return
    end

    logger.debug(
        "get_node_at_cursor: root found at " .. cursor_range[1] .. ":" ..
            cursor_range[2] .. ", " ..
            root:named_descendant_for_range(cursor_range[1], cursor_range[2],
                                            cursor_range[1], cursor_range[2])
                :type())

    return root:named_descendant_for_range(cursor_range[1], cursor_range[2],
                                           cursor_range[1], cursor_range[2])
end

---returns line,col or nil,nil
---@param node Node
---@param dir string | "'forward'" | "'backward'"
---@param multi boolean
---@return integer
---@return integer
M.get_tabout_position = function(node, dir, multi)
    if type(node) ~= 'userdata' or node == 'nil' then
        logger.debug("get_tabout_position: no node supplied")
        return nil, nil
    end

    if M.is_wrapped(node) then
        if multi or M.is_one_line(node) then
            logger.debug("is wrapped node")
            if dir == 'backward' then return node:start() end
            return node:end_()
        else
            if dir == 'backward' and M.starts_on_same_line(node) then
                return node:start()
            end
        end
    end

    if not config.options.ignore_beginning then
        logger.debug("ignoring beginning")
        return nil, nil
    end

    -- if dir == 'backward' then
    --     local text = vim.treesitter.get_node_text(node)
    --     logger.debug(text[1] .. ', ' .. tostring(node:end_()) .. ', ' ..
    --                      tostring(node:end_()) .. ', ' .. node:type() .. ', ' ..
    --                      text[#text])
    -- end

    -- if cursor is at the beginning of the node look for wrapped parents
    if dir == 'backward' and utils.is_cursor_at_position(node:end_()) or
        utils.is_cursor_at_position(node:start()) then
        local parent = get_parent_node(node, multi)

        if parent then
            if dir == 'forward' then return parent:end_() end

            return parent:start()
        end
    end

    -- nothing parseable was found, try scanning the text on current line
    if M.is_one_line(node) then
        return M.scan_text(node, dir)
    end

    return nil, nil
end

---@return boolean
---@param node Node
M.is_wrapped = function(node)
    local text = vim.split(vim.treesitter.get_node_text(node, 0), '\n')
    if type(next(text)) ~= 'nil' then
        local first = string.sub(text[1], 1, 1)
        local last = string.sub(text[#text], -1)

        logger.debug('wrapped with: ' .. first .. last)
        return config.tabouts[first] == last
    end

    return false
end

---@return boolean
---@param node Node
M.starts_on_same_line = function(node)
    local start_line, _, _, _ = node:range()
    local current_line = vim.api.nvim_win_get_cursor(0)[1] - 1

    return start_line == current_line
end

---@return boolean
---@param node Node
M.is_one_line = function(node)
    local start_line, _, end_line, _ = node:range()

    return start_line == end_line
end

---Scann a node`s text for combos
---@param node Node
---@return integer
---@return integer
M.scan_text = function(node, dir)
    -- just scan on current line
    local parent = node:parent()
    if not M.is_one_line(parent) then
        parent = node
    end
    while (parent:parent() and M.is_one_line(parent:parent())) do
        parent = parent:parent()
    end
    logger.debug('scanning text inside ' .. parent:type() .. ' node')
    text = vim.treesitter.get_node_text(parent, 0)

    if (utils.str_is_empty(text)) then
        return nil, nil
    end

    local cursor = vim.api.nvim_win_get_cursor(0)
    local current_cursor_line = cursor[1]
    local current_cursor_row = cursor[2]
    local _, node_start = parent:start()
    local _, node_end = parent:end_()

    if dir == 'backward' then
        current_cursor_row = current_cursor_row-1
    end

    if dir == 'backward' then
        text = string.sub(text, 1, current_cursor_row-node_end)
    else
        text = string.sub(text, current_cursor_row-node_start)
    end

    logger.debug("substring: " .. text)

    local iter = 0
    local line = nil
    local col = nil

    text:gsub(".", function(c)

        for _, combo in pairs(config.options.tabouts) do
            if dir == 'backward' then
                if combo.open == c then
                    line = current_cursor_line - 1
                    col = node_start + iter
                    return
                end
            else
                if combo.close == c then
                    line = current_cursor_line - 1
                    col = current_cursor_row + iter
                    return
                end
            end
        end
        iter = iter + 1
    end)

    return line, col
end

return M


text:
local api = vim.api

---@class MapargDict
---@field lhs string The {lhs} of the mapping.
---@field rhs string The {rhs} of the mapping as typed.
---@field silent number 1 for a |:map-silent| mapping, else 0.
---@field noremap number 1 if the {rhs} of the mapping is not remappable.
---@field expr number 1 for an expression mapping (|:map-<expr>|).
---@field buffer number 1 for a buffer local mapping (|:map-local|).
---@field mode string | "'n'" | "'v'" | "'x'" | "'s'" | "'o'" | "'!'" | "'i'" | "'l'" | "'c'" | "'t'" | "''" | "' '" | "'!'"  " " Normal, Visual and Operator-pending, "!" Insert and Commandline mode (|mapmode-ic|)
---@field sid number The script local ID, used for <sid> mappings (|<SID>|).

---@class Utils
local M = {}

---check if cursor is at a certain position
---@param line number
---@param col number
---@return boolean
M.is_cursor_at_position = function(line, col)
    local cursor = api.nvim_win_get_cursor(0)

    return line == cursor[1] - 1 and col == cursor[2]
end

---registers a command!
---@param command string
---@param fn_string string
M.register_command = function(command, fn_string)
    api.nvim_command('command! ' .. command .. ' ' .. fn_string)
end

---escape a string
---@param str string
M.replace = function(str)
    return api.nvim_replace_termcodes(str, true, true, true)
end

---string is nil or blank
---@param str string
---@return boolean
M.str_is_empty = function (str)
    return str == nil or str == ''
end

---map a key in mode
---@param mode string | "'n'" | "'v'" | "'x'" | "'s'" | "'o'" | "'!'" | "'i'" | "'l'" | "'c'" | "'t'" | "''"
---@param lhs string
---@param rhs string
---@param opts? {silent: boolean, expr: boolean}
M.map = function(mode, lhs, rhs, opts)
    local options = {noremap = true}
    if opts then options = vim.tbl_extend('force', options, opts) end
    api.nvim_set_keymap(mode, lhs, rhs, options)
end

---unmap a key in mode
---@param mode string | "'n'" | "'v'" | "'x'" | "'s'" | "'o'" | "'!'" | "'i'" | "'l'" | "'c'" | "'t'" | "''"
---@param lhs string
M.unmap = function(mode, lhs) api.nvim_del_keymap(mode, lhs) end

--[[ ---get mapargs dict for a binding
---@param mapping string
---@param mode string | "'n'" | "'v'" | "'x'" | "'s'" | "'o'" | "'!'" | "'i'" | "'l'" | "'c'" | "'t'" | "''"
---@return MapargDict
M.get_mapargs = function(mapping, mode) return
    vim.fn.maparg(mapping, mode, 0, 1) end

---determine if a maparg is valid by its dict
---@param dict MapargDict
---@return boolean
M.is_valid_mapping = function(dict) return next(dict) and dict.rhs ~= '' end ]]

---get the global rhs for a lhs
---@param lhs string
---@param mode string | "'n'" | "'v'" | "'x'" | "'s'" | "'o'" | "'!'" | "'i'" | "'l'" | "'c'" | "'t'" | "''"
---@return string
M.get_rhs = function(lhs, mode)
    local rhs = ''

    for _, mapping in ipairs(vim.api.nvim_get_keymap(mode)) do
        if mapping.lhs == lhs then
            if mapping.rhs ~= '' then rhs = mapping.rhs end
            break
        end
    end

    return rhs
end

return M

repo_language: Lua

mime_type: text/plain


text:
local config = require('tabout.config')

---@class Logger
local M = {}

--- @param msg string
--- @param hl? string
M.log = function(msg, hl)
    vim.api.nvim_echo({{config.name .. ': ', hl}, {msg}}, true, {})
end

--- @param msg string
M.warn = function(msg) M.log(msg, 'WarningMsg') end

--- @param msg string
M.debug = function(msg) if config.debug then M.log(msg, 'Todo') end end

return M


text:
local api = vim.api
local config = require('tabout.config')
local node = require('tabout.node')
local utils = require('tabout.utils')
local logger = require('tabout.logger')

local M = {}

local debug_node = function(line, col, node)
    if not node then logger.warn("No node at " .. line .. ":" .. col) end
    local text = vim.split(vim.treesitter.get_node_text(node, 0), '\n')
    logger.warn(text[1] .. ', ' .. tostring(line) .. ', ' .. tostring(col) ..
                    ', ' .. node:type() .. ', ' .. text[#text])

    local parent = node:parent()
    if parent then
        local text = vim.split(vim.treesitter.get_node_text(parent, 0), '\n')
        logger.warn(
            text[1] .. ', ' .. tostring(line) .. ', ' .. tostring(col) .. ', ' ..
                parent:type() .. ', ' .. text[#text])
    end
end

-- TODO: rework, could work with smth like 'am i in a node? is my position at the beginning or end of it?'
---@param dir string | "'forward'" | "'backward'"
local get_char_at_cursor_position = function(dir)
    local col = vim.api.nvim_win_get_cursor(0)[2]
    if col == 1 then return string.sub(vim.api.nvim_get_current_line(), 1, 1) end
    if dir == 'backward' then col = col - 1 end
    -- the hell do I know why thats not workig, althoug its the line, if col is greater 1 its nil
    -- local char = string.sub(vim.api.nvim_get_current_line(), col, 1)
    -- local char = vim.api.getline('.')[col]
    local line = vim.fn.getline('.')
    local substr = vim.fn.strpart(line, -1, col + 2)
    -- local substr = vim.fn.trim(substr)
    local char = string.sub(substr, -1)
    logger.debug("char is " .. char .. ", " .. type(char) .. ", " .. string.len(char))
    return char
end

local can_tabout = function()
    -- TODO: check filetype on buffenter and enable/disable there
    if vim.tbl_contains(config.options.exclude, vim.bo.filetype) then
        return false
    end

    return api.nvim_win_is_valid(api.nvim_get_current_win())
end

local forward_tab = function()
    logger.debug("tabbing forward")
    if config.options.act_as_tab then
        if (prev_char == '' or prev_char == ' ') then
            api.nvim_command('cal feedkeys("' .. utils.replace(config.options.default_tab) .. '", "n" )')
        else
            api.nvim_command('cal feedkeys("' .. utils.replace('<Tab>') .. '", "n" )')
        end
    end
end

local backward_tab = function()
    logger.debug("tabbing backward " .. tostring(config.options.act_as_shift_tab))

    local prev_char = get_char_at_cursor_position('backward')
    if config.options.act_as_shift_tab then
        api.nvim_command('cal feedkeys("' .. utils.replace(config.options.default_shift_tab) .. '", "n" )')
    end
end

---@param dir string | "'forward'" | "'backward'"
---@param enabled boolean
---@param multi boolean
M.tabout = function(dir, enabled, multi)
    local tab_action

    if dir == 'forward' then
        tab_action = forward_tab
    else
        tab_action = backward_tab
    end

    logger.debug(dir)
    if not enabled or not can_tabout() then return tab_action() end
    logger.debug(dir .. " allowed")

    local n = node.get_node_at_cursor(dir)
    -- no need to tabout if we are on root level
    -- includes comments :(
    if not n or not n:parent() then return tab_action() end

    local line, col = node.get_tabout_position(n, dir, multi)

    -- just trigger the tab action if there is no target for a tabout
    if not line then
        if config.debug then
            local node_line, node_col = nil, nil
            if dir == 'forward' then
                node_line, node_col = n:end_()
            else
                node_line, node_col = n:start()
            end
            logger.debug(dir .. " error")
            debug_node(node_line, node_col, n)
        end

        return tab_action()
    end

    if api.nvim_get_mode().mode == 'ic' then
        -- stop ins-completion without side-effects
        api.nvim_feedkeys(utils.replace('<C-G><C-G>'), 'ni', true)
    end
    return api.nvim_win_set_cursor(0, {line + 1, col})
end

return M


text:
if !has('nvim-0.5')
  echohl WarningMsg
  echom "tabout.nvim needs Neovim >= 0.5"
  echohl None
  finish
endif

if exists('g:loaded_tabout') | finish | endif " prevent loading file twice

let g:loaded_tabout = 1


text:
# Awesome Backbone.js [![Awesome](https://cdn.rawgit.com/sindresorhus/awesome/d7305f38d29fed78fa85652e3a63e154dd8e8829/media/badge.svg)](https://github.com/sindresorhus/awesome)

> A list of resources for backbone.js

## General

- [Backbone.js](http://backbonejs.org/)
- [Github repository](https://github.com/jashkenas/backbone)
- [Annotated source](http://backbonejs.org/docs/backbone.html)
- [Backbone plugins](http://backplug.io/)
- [Backbone Index](https://backboneindex.com/) - Discover new Backbone plugins and resources
- [BackboneConf](http://backboneconf.com/) - Conference about building applications on the web, with a focus on the Backbone.js library
- [StackOverflow](http://stackoverflow.com/questions/tagged/backbone.js) - Tagged questions
- [Yeoman generator for Backbone.js](https://github.com/yeoman/generator-backbone)

## Tutorials and articles

- [A pragmatic guide to Backbone.js apps](http://pragmatic-backbone.com/) by Julio Cesar Ody
- [Getting Started with Backbone](http://www.korenlc.com/backbone-js-tutorial-getting-started-with-backbone/) by Koren Leslie Cohen
- [A Complete Guide for Learning Backbone.js](http://www.codebeerstartups.com/2012/12/a-complete-guide-for-learning-backbone-js/) by Mohit Jain
- [Hello Backbone.js](http://jasongiedymin.github.io/hello-backbonejs/) by Artur Adib
- Backbone.js Wine Cellar Tutorial by Christophe Coenraets - [Part 1](http://coenraets.org/blog/2011/12/backbone-js-wine-cellar-tutorial-part-1-getting-started/), [Part 2](http://coenraets.org/blog/2011/12/backbone-js-wine-cellar-tutorial-part-2-crud/) and [Part 3](http://coenraets.org/blog/2011/12/backbone-js-wine-cellar-tutorial-part-3-deep-linking-and-application-states/)
- [Backbone patterns](http://ricostacruz.com/backbone-patterns/) by Rico Sta. Cruz
- [Complex Single Page Application Architecture with Backbone.js](http://blog.soom.la/2013/10/complex-single-page-application.html) by Gur Dotan
- [Building large Backbone applications](http://victorsavkin.com/post/59496656297/building-large-backbone-applications) by Victor Savkin
- [A Guide to Writing Backbone Apps at Coursera](http://blog.pamelafox.org/2013/07/a-guide-to-writing-backbone-apps-at.html) by Pamela Fox
- CloudEdit: A Backbone.js Tutorial with Rails by James Yu - [Part 1](http://www.jamesyu.org/2011/01/27/cloudedit-a-backbone-js-tutorial-by-example), [Part 2](http://www.jamesyu.org/2011/02/09/backbone.js-tutorial-with-rails-part-2) and [Part 3](http://www.jamesyu.org/2012/05/20/converting-cloudedit-from-backbone-to-parse/)
- [Our First Node.js App: Backbone on the Client and Server](http://nerds.airbnb.com/weve-launched-our-first-nodejs-app-to-product/) by Spike Brehm
- [Using Backbone.js With Require.js](http://gregfranko.com/blog/using-backbone-dot-js-with-require-dot-js/) by Greg Franko
- [Backbone Model Unit Testing with Jasmine](http://blog.katworksgames.com/2013/03/30/bb_model_test_jasmine/) by Ken Tabor
- [Backbone.js and Socket.io](http://developer.teradata.com/blog/jasonstrimpel/2011/11/backbone-js-and-socket-io) by Jason Strimpel
- [Presenter First in Backbone.js](http://spin.atomicobject.com/2012/01/03/presenter-first-in-backbone-js/) by Shawn Andersom
- [Websockets for Backbone](http://www.artandlogic.com/blog/2014/06/websockets-for-backbone/) by Christopher Keefer
- Fun with CoffeeScript and Backbone.js - [Part 1](http://www.artandlogic.com/blog/2012/06/fun-with-coffeescript-and-backbone-js-part-1/), [Part 2](http://www.artandlogic.com/blog/2012/06/fun-with-coffeescript-and-backbone-js-part-2/) and [Part 3](http://www.artandlogic.com/blog/2012/06/fun-with-coffeescript-and-backbone-js-part-3/)
- [Components with Backbone.js and Marionette.js](http://blog.jeremyfairbank.com/javascript/components-with-backbone-js-and-marionette-js/) by Jeremy Fairbank
- [Boilerplate web app using Backbone.js, Express.js, Node.js, MongoDB](http://kroltech.com/2013/12/boilerplate-web-app-using-backbone-js-expressjs-node-js-mongodb/) by jkat98
- [Backbone.js Tips And Patterns](http://www.smashingmagazine.com/2013/08/09/backbone-js-tips-patterns/) by Philip Whisenhunt
- [Leveraging Deferreds in Backbone.js](http://quickleft.com/blog/leveraging-deferreds-in-backbonejs) by Sam Breed
- [Backbone.js for large scale applications - UI architecture](http://orizens.com/wp/topics/backbone-js-for-large-scale-applications-ui-architecture/) by Oren Farhi
- [Decorating A Backbone.Model With Features, For A UI Component](http://derickbailey.com/2014/09/09/decorating-a-backbone-model-with-features-for-a-ui-component/) by Derick Bailey
- [Backbone.fwd: Forward Events From One Backbone Object, Through Another](http://derickbailey.com/2014/05/13/backbone-fwd-forward-events-from-one-backbone-object-through-another/) by Derick Bailey
- [Backbone.View Patterns - Rendering A Collection](http://orizens.com/wp/topics/backbone-view-patterns-rendering-a-collection/) by Oren Fahri
- [Backbone.View Patterns - How & Why Use Subviews](http://orizens.com/wp/topics/backbone-view-patterns-how-why-to-use-subviews/) by Oren Fahri
- [Backbone.View Patterns - The "Render" Method](http://orizens.com/wp/topics/backbone-view-patterns-the-render-method/) by Oren Fahri
- [Building progressively enhanced websites using bulletproof Backbone & CSS transforms](http://azumbrunnen.me/blog/building-progressively-enhanced-websites-with-bulletproof-backbone/) by Adrian Zumbrunnen
- [Decoupling Backbone Apps From WebSockets](http://lostechies.com/derickbailey/2012/04/19/decoupling-backbone-apps-from-websockets/) by Derick Bailey
- [Backbone.js And JavaScript Garbage Collection](http://lostechies.com/derickbailey/2012/03/19/backbone-js-and-javascript-garbage-collection/) by Derick Bailey
- [Getting familiar with Backbone.js](https://blog.safaribooksonline.com/2013/11/16/getting-familiar-with-backbone-js/) by Pasha Craydon
- [Backbone Antipatterns](http://blog.shinetech.com/2013/11/26/backbone-antipatterns/) by Ben Teese
- Backbone.js for Absolute Beginners by Adrian Mejia - [Part 1](http://adrianmejia.com/blog/2012/09/11/backbone-dot-js-for-absolute-beginners-getting-started/), [Part 2](http://adrianmejia.com/blog/2012/09/13/backbone-js-for-absolute-beginners-getting-started-part-2/), [Part 3](http://adrianmejia.com/blog/2012/09/13/backbonejs-for-absolute-beginners-getting-started-part-3/) and [Part 4](http://adrianmejia.com/blog/2012/09/13/backbone-js-for-absolute-beginners-getting-started-part-4/)
- Building an App with Express and Backbone by Jack Franklin - [Part 1](http://javascriptplayground.com/blog/2013/02/express-backbone-application-part1), [Part 2](http://javascriptplayground.com/blog/2013/02/building-an-app-with-express-and-backbone-part-2), [Part 3](http://javascriptplayground.com/blog/2013/04/building-an-app-with-express-and-backbone-part-3), [Part 4](http://javascriptplayground.com/blog/2013/07/building-an-app-with-express-and-backbone-part-4), [Part 5](http://javascriptplayground.com/blog/2013/08/express-backbone-part5), [Part 6](http://javascriptplayground.com/blog/2013/09/express-backbone-part6), [Part 7](http://javascriptplayground.com/blog/2013/09/express-backbone-part7) and [Part 8](http://javascriptplayground.com/blog/2013/10/express-backbone-part8)
- [Backbone with ES6](http://mikefowler.me/2014/06/11/backbone-with-es6/) by Mike Fowler
- [How to share Backbone.js models with Node.js](http://amirmalik.net/2010/11/27/how-to-share-backbonejs-models-with-nodejs)
- [Backbone.js tricks or treats](http://tiagorg.github.io/talk-backbone-tricks-or-treats-html5devconf) by Tiago Garcia
- Backbone.js organizational patterns by Ben Eddy - [Part 1](http://www.foraker.com/backbone-js-organizational-patterns/), [Part 2](http://www.foraker.com/backbone-js-organizational-patterns-part-ii/)
- [Backbone Cleanup](http://www.webdeveasy.com/backbone-cleanup/) by Naor Yehodaey
- [Lazy loading Backbone collections with Promises](http://tech.adroll.com/blog/web/2013/11/12/lazyloading-backbone-collection-with-promises.html) by Timothée Boucher
- [5 Ways to Level-Up Your Backbone.js Code](https://blog.engineyard.com/2015/5-ways-to-level-up-your-backbone-code) by David Aragon
- [Why Backbone.js and ES6 Classes Don't Mix](http://benmccormick.org/2015/04/07/es6-classes-and-backbone-js/) by Ben McCormick
- [Top 8 Common Backbone.js Developer Mistakes](http://www.toptal.com/backbone-js/top-8-common-backbone-js-developer-mistakes) by Mahmud Ridwan
- [8 Great Backbone.js Interview Questions](http://www.toptal.com/backbone-js/interview-questions)
- [Integrating React With Backbone](https://quickleft.com/blog/integrating-react-with-backbone/) by Ben Lewis
- [Using BackboneJS to improve your JavaScript code organization over a Ruby on Rails app](https://medium.com/@ezekielriva/using-backbonejs-to-improve-your-javascript-code-organization-over-a-ruby-on-rails-app-11784575a4f2) by Ezequiel Rivadeneira Lichardi
- JavaScript For The Impatient: BackboneJS - [Part 1](https://medium.com/@coreyhowell/javascript-for-the-impatient-backbonejs-part-1-7f4dba8fcea), [Part 2](https://medium.com/@coreyhowell/javascript-for-the-impatient-backbonejs-part-2-a139df9bbbf0) by Corey Howell
- [Building Backbone apps using Flux](https://medium.com/@victordoss/building-backbone-apps-using-flux-f656fd8a873a) by Victor Doss
- [Bringing Meteor’s reactive collections to Backbone](https://mixmax.com/blog/meteor-and-backbone) by Jeff Wear

## Books

- [Developing Backbone.js Applications](http://addyosmani.github.io/backbone-fundamentals/) by Addy Osmani
- [Backbone Tutorials](https://leanpub.com/backbonetutorials) by Thomas Davis
- [Building Backbone Plugins](https://leanpub.com/building-backbone-plugins) by Derick Bailey and Jerome Gravel-Niquet
- [Mastering Backbone.js](http://www.amazon.com/dp/1783288493) by Abiee Echamea
- [Backbone.js Testing](http://backbone-testing.com/) by Ryan Roemer
- [Backbone.js Patterns and Best Practices](http://www.amazon.com/dp/1783283572) by Swarnendu De
- [Backbone.js Cookbook](http://www.amazon.com/dp/1782162720/) by Vadim Mirgorod
- [Instant Backbone.js Application Development](http://www.amazon.com/dp/1782165665) by Thomas Hunter II
- [Beginning Backbone.js](http://www.amazon.com/dp/1430263342/) by James Sugrue
- [Backbone.js Blueprints](http://www.amazon.com/dp/1783286997/) by Andrew Burgess
- [Recipes with Backbone](http://recipeswithbackbone.com/) by Nick Gauthier and Chris Strom
- [Full Stack Web Development with Backbone.js](http://shop.oreilly.com/product/0636920030799.do) by Patrick Mulder
- [Developing Single Page Apps with Backbone.js](https://singlepagebook.supportbee.com/) by Prateek Dayal - work in progress

## Videos

- [BackboneConf 2013](https://www.youtube.com/playlist?list=PLlgxAbM67lYLJm9_Ub3gStXa2vjb51aUX) - Youtube playlist
- [BackboneConf 2014](https://www.youtube.com/playlist?list=PLlgxAbM67lYIGw8DnANC7VgREbzJRQged) - Youtube playlist
- [Introduction to Backbone.js](https://www.youtube.com/playlist?list=PLCE344BDBD8FAC282) - Youtube playlist by Joseph Zimmerman
- [Backbone App Walkthrough](https://www.youtube.com/playlist?list=PL0C57F698BD4766B1) - Youtube playlist by Joseph Zimmerman
- [Backbone.js Tutorial](http://www.youtube.com/playlist?list=PLTjRvDozrdlwn9IsHWEs9IQv3HQob4bH3) - Youtube playlist by Mosh Hamedani
- [Backbone-Require-Boilerplate Explained](http://gregfranko.com/blog/backbone-require-boilerplate-explained/) by Greg Franko
- [BackboneRails Screencasts](http://www.backbonerails.com/) - Learn to Build Web Apps Using Backbone + Rails
- [Backbone.js with Derick Bailey](https://www.youtube.com/watch?v=VERQEr-bVTs) by Derick Bailey
- [JavaScript Application Architecture with Backbone.js](http://www.youtube.com/watch?v=PGgZ7qRvoGE) by Mathias Schäfer
- [An Introduction to Backbone.js](http://www.youtube.com/watch?v=PcTVQyrWSSs) by Sid Maestre
- [Put Some Backbone.js in your Apps](http://www.youtube.com/watch?v=jM8KE_Fa6JI) by Sid Maestre
- [Evolving Your JavaScript with Backbone.js](http://wordpress.tv/2013/09/05/k-adam-white-evolving-your-javascript-with-backbone-js/) by K Adam White

## Examples and boilerplates

- [Backbone Require Boilerplate](https://github.com/BoilerplateMVC/Backbone-Require-Boilerplate) - A Rad Backbone.js and Require.js Boilerplate Project
- [Wine Cellar Sample Application](https://github.com/ccoenraets/backbone-cellar) - "Backbone Cellar" is a sample application built with Backbone.js
- [Employee Directory](https://github.com/ccoenraets/directory-backbone-bootstrap) - Employee Directory sample application built with Backbone.js and Twitter Bootstrap
- [Backbone Contact Manager](https://github.com/dmytroyarmak/backbone-contact-manager) - Simple Backbone.js example application
- [Backbone Boilerplates](https://github.com/addyosmani/backbone-boilerplates) - Backbone.js stack boilerplates demonstrating integration with Express, Ruby, PHP, Grails and more
- [Backbone TodoMVC example](http://todomvc.com/examples/backbone/) - Simple todo app built using Backbone and Backbone.localStorage.

## Courses

- [Anatomy of Backbone.js](https://www.codeschool.com/courses/anatomy-of-backbone-js)
- [Advanced Backbone Patterns and Techniques](http://code.tutsplus.com/courses/advanced-backbone-patterns-and-techniques)
- [Connected to the Backbone](http://code.tutsplus.com/courses/connected-to-the-backbone)
- [Structuring Your Web Apps via Backbone.js](https://github.com/hegdeashwin/Backbone)
- [Backbone.JS In-Depth and Introduction to Testing with Mocha and Sinon](https://frontendmasters.com/courses/backbone-js-in-depth-testing-mocha-sinon/)
- [Up and Running with Backbone.js](http://www.lynda.com/Backbonejs-tutorials/Up-Running-Backbonejs/163089-2.html)
- [Application Building Patterns with Backbone.js](http://www.pluralsight.com/courses/playing-with-backbonejs)
- [Backbone.js Fundamentals](http://www.pluralsight.com/courses/backbone-fundamentals)

## Libraries

- [Backbone.Relational](https://github.com/PaulUithol/Backbone-relational)
- [Backbone-forms](https://github.com/powmedia/backbone-forms)
- [Backgrid.js](https://github.com/wyuenho/backgrid)
- [Backbone.localStorage](https://github.com/jeromegn/Backbone.localStorage)
- [Backbone.stickit](https://github.com/NYTimes/backbone.stickit)
- [Backbone.paginator](https://github.com/backbone-paginator/backbone.paginator)
- [Backbone.validation](https://github.com/thedersen/backbone.validation)
- [Exoskeleton](https://github.com/paulmillr/exoskeleton)
- [Backbone.dualstorage](https://github.com/nilbus/Backbone.dualStorage)
- [Backbone.iobind](https://github.com/noveogroup/backbone.iobind)
- [Backbone-associations](https://github.com/dhruvaray/backbone-associations)
- [Backbone-tastypie](https://github.com/PaulUithol/backbone-tastypie)
- [Backbone.Radio](https://github.com/marionettejs/backbone.radio)
- [Backbone.BabySitter](https://github.com/marionettejs/backbone.babysitter)
- [Backbone.History.Polyfill](https://github.com/FidelityInternational/BackboneHistoryPolyfill)

repo_name: sadcitizen/awesome-backbone


text:
The input is a list of names in random order. We need a function that chains elements
of the list in such a way that the last character of an name is the same as the first
one of the next name.

For instance:
- Input: [“Raymond”, “Nora”, “Daniel”, “Louie”, “Peter”, “Esteban”]
- Output: [“Peter”, “Raymond”, “Daniel”, “Louie”, “Esteban”, “Nora”]

There should be only one solution, so the first element is unique.


text:
## Problem
Given a list of numbers, e.g.:

```
25626 25757 24367 24267 16 100 2 7277
```

Output a delta encoding for the sequence. In a delta encoding, the first
element is reproduced as is. Each subsequent element is represented as the
numeric difference from the element before it. E.g. for the sequence above,
the delta encoding would be:

```
25626 131 -1390 -100 -24251 84 -98 7275
```

However, if a difference value does not fit in a single signed byte,
i.e. -127 <= x <= 127, then, instead of the difference, we would like
to use an escape token, printing it.

This will denote that the value following the escape token is a full
four-byte difference value, rather than a one-byte different value.

For this exercise, we'll declare -128 as the escape token.

Following the same example above, the final result would be:
```
25626 -128 131 -128 -1390 -100 -128 -24251 84 -98 -128 7275
```
## Solution
```php
<?php

const ESCAPE_TOKEN = -128;

$input = file_get_contents("php://stdin");
$values = explode(" ", $input);

$result = array();
$prev = $values[0];
$result[] = $prev;
$num_vals = count($values);
for ($i=1; $i<$num_vals; $i++) {
    $diff = $values[$i] - $prev;
    if (abs($diff) > 127) {
        $result[] = ESCAPE_TOKEN;
    }
    $result[] = $diff;
    $prev = $values[$i];

}
fwrite(STDOUT, implode(" ", $result));
```


text:
## Problem:
An anagram is a word that can be written as a permutation of the characters
of another word, like "dirty room" and "dormitory" (ignore spaces). However,
"the" and "thee" are not anagrams, since "the" only has a single "e" whereas
"thee" has two "e" characters (spaces can occur zero, or multiple times, however.)

Given a list of words as strings, you should return another list of strings,
each containing words that are mutual anagrams.

Each string of the output should be a single group of anagarms joined with commas.

Within an output string, the expression should be sorted lexicographically.
If a group contains only a single element, output that one-element group
as a single string. And the string should be also output in lexicographical
order.  Given e.g.:
```
pear
amleth
dormitory
tinsel
dirty room
hamlet
listen
silnet
```
... the output would be:
```
amleth,hamlet
dirty room,dormitory
listen,silnet,tinsel
pear
```

## Solution
```php
<?php

$input = file_get_contents("php://stdin");

$lines = explode("\n", $input);

function getAnagramValue($word) {
	$word = str_replace(' ', '', $word);
	$word = strtolower($word);
	return count_chars($word, 1);
}

function getAnagrams($word, array $wordlist) {
	$retval = array();
	foreach ($wordlist as $candidate) {
		if (getAnagramValue($word) == getAnagramValue($candidate)) {
			$retval[] = $candidate;
		}
	}
	return $retval;
}

$anagrams = array();
foreach ($lines as $word) {
    if (empty($word)) {
        continue;
    }
	$anagrams[] = getAnagrams($word, $lines);
}

$sorted_anagrams = array();
foreach ($anagrams as $word => $candidates) {
	sort($candidates);
	$sorted_anagrams[] = implode(",", $candidates);
}

$sorted_anagrams = array_unique($sorted_anagrams);
sort($sorted_anagrams);

foreach ($sorted_anagrams as $words) {
    fwrite(STDOUT, $words . "\n");
}
```


text:
## Problem
Identify whether there exists a pair of numbers in an array such that
their sum is equal to **N**.

Input:
The first line contains one integer *N*, which is the sum we are trying to find.
The second line contains one integer *M*, which is the length of the array.
This is followed by M lines each containing one element of the array.

Output:
Output 1 if there exists a pair of numbers in the array such that their
sum equals N. If such a pair does not exist, output 0.

Sample Input:
```
66
10
18
11
21
28
31
38
40
55
60
62
```
Sample Output:
```
1
```

## Solution

```php
<?php

$input = file_get_contents("php://stdin");

$lines = explode("\n", $input);
$lines = array_map('intval', $lines);

$sum = array_shift($lines);
$len = array_shift($lines);

$found = false;
foreach ($lines as $num) {
    $diff = $sum - $num;
    if (in_array($diff, $lines)) {
        $found = true;
        break;
    }
}

fwrite(STDOUT, intval($found));
```


text:
## Problem
Identify whether four sides (given by four integers) can form a square,
a rectangle, or neither.

Input:
You will receive an list of strings, each containing four space-separated
integers, which represent the length of the sides of a polygon. The input
lines will follow the 'A B C D' order as in the following representation:

```
|-----A-----|
|           |
|           |
D           B
|           |
|           |
|-----C-----|
```

Output:
A single line which contains 3 space-separated integers; representing
the number of squares, number of rectangles, and number of other polygons
with 4 sides.  Note that squares shouldn't be counted as rectangles.
Invalid polygons should also be counted as 'other polygons'.

Constraints:
The four integers representing the sides will be such that: -2000 <=X <= 2000
(Where X represents the integer)

Sample Input:
```
36 30 36 30
15 15 15 15
46 96 90 100
86 86 86 86
100 200 100 200
-100 200 -100 200
```

Sample Output:
```2 2 2```

## Solution

```php
<?php

// define some constants to save mem
const SQUARE = 1;
const RECTANGLE = 2;
const OTHER = 3;

// identifies the shape-type based on given sides
function getShape($a, $b, $c, $d) {
    if (0 >= min($a, $b, $c, $d)) {
        return OTHER;
    }
    elseif (($a == $b) && ($b == $c) && ($c == $d)) {
        return SQUARE;
    }
    elseif (($a == $c) && ($b == $d)) {
        return RECTANGLE;
    }
    else {
        return OTHER;
    }
}

// reset the statistics
$result = array(
  SQUARE => 0,
  RECTANGLE => 0,
  OTHER => 0,
);

$input = file_get_contents("php://stdin");
$lines = explode("\n", $input);

foreach ($lines as $line) {
    $sides = explode(" ", $line);
    list($a, $b, $c, $d) = array_map('intval', $sides);
    $result[getShape($a, $b, $c, $d)]++;
}

fwrite(STDOUT,
       $result[SQUARE] . ' ' . $result[RECTANGLE] . ' ' . $result[OTHER]);
```


text:
# Booking.com

This repository contains my solutions to [Booking.com](http://www.booking.com/)'s Coding assignment for the [Software Developer](https://workingatbooking.com/) position. I am sharing this for educational-purposes only. Use at your own risk.

The online coding assignment consists of four problems:
- [Polygon Identifier](./polygon.md)
- [Delta Encoding](./delta-encoding.md)
- [Mutual Anagrams Detector](./anagram.md)
- [Array Analyzer](./sum-array.md)


text:
# Awesome-Hackernews

[![Awesome](https://awesome.re/badge.svg)](https://awesome.re)
![lint workflow](https://github.com/bminusl/awesome-hackernews/actions/workflows/lint.yml/badge.svg)

**[Hacker News](https://news.ycombinator.com/)** is a social news website focusing on computer technologies, hacking and startups. It promotes any content likely to "gratify the intellectual curiosity" of readers.

This is a curated list of [FOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software) tools that aim to improve the general Hacker News experience, in any shape or form.

See [Contributing](.github/contributing.md).

Inspired by [awesome-selfhosted](https://github.com/awesome-selfhosted/awesome-selfhosted).

You may also want to visit [awesome-hacker-news](https://github.com/cheeaun/awesome-hacker-news), a complementary list which is not focused on FOSS.

---

- List
  - [Clients](#clients)
    - [CLI & TUI](#cli--tui)
    - [Desktop](#desktop)
    - [Emacs](#emacs)
    - [Smartphone Apps](#smartphone-apps)
      - [Android](#android)
      - [iOS](#ios)
    - [SQL](#sql)
    - [Vim](#vim)
    - [Web](#web)
  - [E-readers](#e-readers)
    - [Kindle](#kindle)
    - [ReMarkable](#remarkable)
  - [Embeddables](#embeddables)
  - [Feeds](#feeds)
  - [Libraries](#libraries)
    - [Python](#python)
  - [In Web Browsers](#in-web-browsers)
    - [User Scripts](#user-scripts)
    - [Web Extensions](#web-extensions)
      - [Chrome](#chrome)
      - [Firefox](#firefox)
  - [Uncategorized](#uncategorized)
- [Contributing](#contributing)
- [License](#license)

---

## Clients

### CLI & TUI

**[`↑ back to top ↑`](#)**

- [CHN](https://github.com/ghosthamlet/CHN) - HN on Console with auto classifer and recommender in reactjs style code. `MIT`
- [circumflex](https://github.com/bensadeh/circumflex) - It's HN in your terminal. `AGPL-3.0`
- [CLHN](https://github.com/nichochar/CLHN) - See HN stories. `MIT`
- [hackernews cli](https://github.com/mtharrison/hackernews) - Print out the latest posts to your terminal. `no license`
- [hackernews-TUI](https://github.com/aome510/hackernews-TUI) - A Terminal UI to browse HN. `MIT`
- [haxor-news](https://github.com/donnemartin/haxor-news) - Browse HN like a haxor. `Apache-2.0`
- [hn-cli](https://github.com/rafaelrinaldi/hn-cli) - CLI to browse HN. `MIT`
- [HNTerm](https://hnterm.ggerganov.com/) - HN in the terminal. ([Source Code](https://github.com/ggerganov/hnterm)) `MIT`
- [lurker](https://github.com/wcarhart/lurker) - HN terminal client. `MIT`
- [pyhn](https://github.com/toxinu/pyhn) - HN command line client. `MIT`
- [rich-hn](https://github.com/zaataylor/rich-hn) - A CLI reader app with rich formatting and good UX. `MIT`

### Desktop

**[`↑ back to top ↑`](#)**

- [Hacker Menu](https://hackermenu.io) - Read HN from your macOS menubar. ([Source Code](https://github.com/owenthereal/hacker-menu)) `MIT`
- [HackerTray](https://github.com/captn3m0/hackertray) - Read HN from your system tray in Linux. `MIT`
- [HackUp](https://github.com/mdh34/hackup) - Read HN from the desktop. `GPL-3.0`
- [HNReader](https://github.com/mattrighetti/HNReaderApp) - HN client for macOS. `Apache-2.0/MIT`
- [toucHNews](https://github.com/mrmekon/toucHNews) - HN news feed from the Mac Touch Bar. `Apache-2.0`

### Emacs

**[`↑ back to top ↑`](#)**

- [Emacs hnreader](https://github.com/thanhvg/emacs-hnreader) - Read HN inside Emacs. `GPL-3.0`

### Smartphone Apps

#### Android

**[`↑ back to top ↑`](#)**

- [HackerWeb](https://play.google.com/store/apps/details?id=cheeaun.hackerweb) - A read-only client. ([Source Code](https://github.com/cheeaun/hackerweb)) `MIT`
- [Materialistic](https://play.google.com/store/apps/details?id=io.github.hidroh.materialistic) - A material-design reader. ([Source Code](https://github.com/hidroh/materialistic)) `Apache-2.0`

#### iOS

**[`↑ back to top ↑`](#)**

- [HackerWeb](https://apps.apple.com/app/id1084209377) - A read-only client. ([Source Code](https://github.com/cheeaun/hackerweb)) `MIT`

### SQL

**[`↑ back to top ↑`](#)**

- [Steampipe](https://hub.steampipe.io/plugins/turbot/hackernews) - Query HN using SQL. ([Source Code](https://github.com/turbot/steampipe-plugin-hackernews)) `MPL-2.0`

### Vim

**[`↑ back to top ↑`](#)**

- [vim-hackernews](https://www.vim.org/scripts/script.php?script_id=5108) - Browse HN in Vim. ([Source Code](https://github.com/dansomething/vim-hackernews)) `MIT`

### Web

**[`↑ back to top ↑`](#)**

- [Hacker Search](https://hiring.ben.church/) - Search through "Who is Hiring?" threads. ([Source Code](https://github.com/bechurch/hacker-hiring-hunt)) `no license`
- [Hackernews Redesign](https://hn-redesign.vercel.app/) - A minimal client built with Next.js and TailwindCSS. ([Source Code](https://github.com/RocktimSaikia/hackernews-redesign)) `MIT`
- [Paper HN](https://www.wolfgangfaust.com/project/paper-hn/) - HN front page in the style of a print newspaper. ([Source Code](https://github.com/wolfgang42/paper-hn)) `MIT`
- [readhn](https://www.read.hn/) - A simple and fast HN Top Stories reader – using Instapaper. ([Source Code](https://github.com/kaelig/readhn)) `no license`

## E-readers

### Kindle

**[`↑ back to top ↑`](#)**

- [hn-kindle](https://github.com/sprusr/hn-kindle) - Create an ebook of the day's HN articles and send it to your Kindle. `no license`

### ReMarkable

**[`↑ back to top ↑`](#)**

- [hackernews2remarkable](https://github.com/khanhas/hackernews2remarkable) - Fetch top articles, pack as EPUB right in your reMarkable device. `MIT`

## Embeddables

**[`↑ back to top ↑`](#)**

- [HackerBadge](https://github.com/AnandChowdhary/hackerbadge) - Embeddable badges for HN posts/launches. `MIT`
- [YComments](https://ycomments.benwinding.com/) - Automatic links to discussions. ([Source Code](https://github.com/benwinding/ycomments)) `MIT`

## Feeds

**[`↑ back to top ↑`](#)**

- [Hacker News RSS](https://hnrss.github.io/) - Custom, realtime RSS feeds. ([Source Code](https://github.com/hnrss/hnrss)) `no license`
- [hnapp](http://hnapp.com/) - Faceted search engine, RSS & JSON feeds. ([Source Code](https://github.com/raquo/hnapp)) `MIT`

## Libraries

### Python

**[`↑ back to top ↑`](#)**

- [asyncio-hn](https://github.com/itielshwartz/asyncio-hn) - Python (asyncio) wrapper for the HN api. `MIT`
- [haxor](https://github.com/avinassh/haxor) - Unofficial Python wrapper for official HN API. `MIT`
- [py-search-hn](https://github.com/nicolashahn/py-search-hn) - Search HN with Python. `MIT`

## In Web Browsers

### User Scripts

**[`↑ back to top ↑`](#)**

- [already-seen](https://github.com/ryanberckmans/already-seen) - Auto-hide HN or Reddit links after you've seen them once. `MIT`
- [hackahackernews](https://github.com/raszpl/hackahackernews) - HN comment/viewed improvements. `GPL-3.0`

### Web Extensions

#### Chrome

**[`↑ back to top ↑`](#)**

- [Hacker News - Show Links](https://chrome.google.com/webstore/detail/hacker-news-show-links/phckhodapplnokiepnaneoglleghgpfi) - Show comments that have links, and more. ([Source Code](https://github.com/boomeasy/hnlinks)) `no license`
- [Hacker News Comment Highlighter](https://chrome.google.com/webstore/detail/hacker-news-comment-highl/acgehogfllndeafpieloojhdmpffbjnb) - Highlight unread comments in threads. ([Source Code](https://github.com/jbergknoff/hn-comment-highlighter)) `MIT`
- [Hacker News Comments Notifier Plugin](https://chrome.google.com/webstore/detail/hacker-news-comments-noti/fcdogcdfbjpnhcnflhdpcdfjmmiedebo) - Get notifications on articles subscribed. ([Source Code](https://github.com/binomads/hn-notification-plugin)) `MIT`
- [Hacker News Shortcut](https://chrome.google.com/webstore/detail/hacker-news-shortcut/dmiimkldokblocpmleogaeohkbffnobo) - Full keyboard navigation. ([Source Code](https://github.com/AsyncMoksha/hackercut)) `no license`
- [Hacker Reads for Goodreads](https://chrome.google.com/webstore/detail/hacker-reads-for-goodread/ohkekgnmihdgcfflheadklfihdaibdcb) - Add HN comments to books on Goodreads. ([Source Code](https://github.com/spookyuser/hacker-reads)) `MIT`
- [hacker-news-reading-time](https://github.com/bencevans/hacker-news-reading-time) - Add a reading time metric to each HN story. `no license`
- [HackerTagger](https://chrome.google.com/webstore/detail/hackertagger/geiililcplncieampkfliiilkajhdkkp) - Tag users on HN with useful metadata. ([Source Code](https://github.com/guzba/hackertagger)) `MIT`
- [HN Favourite Users](https://chrome.google.com/webstore/detail/hn-favourite-users/hleiemhnepoghplnnhkhpnjeiaifnobo) - Highlights your favourite users. ([Source Code](https://github.com/janhancic/hn-favourite-users)) `MIT`
- [HN Keyword Filter](https://chrome.google.com/webstore/detail/hn-keyword-filter/ooablmjjcdbdjhhjkaffpbjnanonjgnm) - Filter stories using a comma separated list. ([Source Code](https://github.com/ShamariFeaster/chrome-extension-hn-filter/tree/master)) `GPL-3.0`
- [HN Special](https://chrome.google.com/webstore/detail/hn-special-an-addition-to/cchaceegbflphbdpfocjalgjhjoahiia) - A completely new visual style and a few new features. ([Source Code](https://github.com/gabrielecirulli/hn-special)) `MIT`
- [HN Stories](https://github.com/bigyank/HN-Stories) - Sneak peak at HN. `no license`
- [HN Unread Comments](https://chrome.google.com/webstore/detail/hn-unread-comments/fpndmkcfggkffpablcooicmihgcgalil) - See unread comments. ([Source Code](https://github.com/janhancic/hn-unread-comments)) `MIT`
- [Newsit](https://newsit.benwinding.com/) - Find the top voted Hackernews or Reddit discussion around the page you're looking at. ([Source Code](https://github.com/benwinding/newsit)) `MIT`
- [Refined Hacker News](https://chrome.google.com/webstore/detail/refined-hacker-news/gcibdgjaladjjloeocimnijdgopejkfk) - Interface tweaks and features to make the HN experience better. ([Source Code](https://github.com/plibither8/refined-hacker-news)) `MIT`
- [What Hacker News Says](https://chrome.google.com/webstore/detail/what-hacker-news-says/khgegkjchclhgpglloficdmdannlpmoi) - Easily find discussions about the page you're currently browsing. ([Source Code](https://github.com/pinoceniccola/what-hn-says-webext)) `MIT`

#### Firefox

**[`↑ back to top ↑`](#)**

- [Hacker News Discussion Button](https://addons.mozilla.org/en-US/firefox/addon/hacker-news-discussion-button/) - Privacy-preserving extension linking to HN discussion. ([Source Code](https://github.com/jstrieb/hackernews-button)) `GPL-3.0`
- [Hacker Reads for Goodreads](https://addons.mozilla.org/en-GB/firefox/addon/hacker-reads-for-goodreads/) - Add HN comments to books on Goodreads. ([Source Code](https://github.com/spookyuser/hacker-reads)) `MIT`
- [hacker-news-reading-time](https://github.com/bencevans/hacker-news-reading-time) - Add a reading time metric to each HN story. `no license`
- [HN Stories](https://addons.mozilla.org/en-US/firefox/addon/hn-stories/) - Sneak peak at HN. ([Source Code](https://github.com/bigyank/HN-Stories)) `no license`
- [hnlinks](https://addons.mozilla.org/en-US/firefox/addon/hnlinks/) - Adds a sidebar to threads which collects all the external links from comments and shows if a link has been previously posted in HN. ([Source Code](https://github.com/dit7ya/hnlinks)) `GPL-3.0`
- [Newsit](https://newsit.benwinding.com/) - Find the top voted Hackernews or Reddit discussion around the page you're looking at. ([Source Code](https://github.com/benwinding/newsit)) `MIT`
- [Refined Hacker News](https://addons.mozilla.org/en-US/firefox/addon/refined-hacker-news/) - Interface tweaks and features to make the HN experience better. ([Source Code](https://github.com/plibither8/refined-hacker-news)) `MIT`
- [What Hacker News Says](https://addons.mozilla.org/en-US/firefox/addon/what-hacker-news-says/) - Easily find discussions about the page you're currently browsing. ([Source Code](https://github.com/pinoceniccola/what-hn-says-webext)) `MIT`

## Uncategorized

**[`↑ back to top ↑`](#)**

- [Comments.hn](https://comments.hn/) - Prepend `comments.hn?q=` to any website to see its HN comments. ([Source Code](https://github.com/amaurym/comments.hn)) `MIT`

---

## Contributing

Contributing guidelines can be found in [.github/contributing.md](.github/contributing.md).

## License

[![CC0](https://mirrors.creativecommons.org/presskit/buttons/88x31/svg/cc-zero.svg)](https://creativecommons.org/publicdomain/zero/1.0/)

repo_name: lebriton/awesome-hackernews

repo_language: Python


text: * @bminusl


text:
# Contributing

## Adding a new project to the list

All instructions for adding a new project to the list are enumerated in [.github/pull_request_template.md](pull_request_template.md).

Anyway, here is the general procedure to follow:

1. [Edit the README.md file](https://github.com/bminusl/awesome-hackernews/edit/main/README.md) in your favorite text editor.
2. Insert the new entry in the right place and following the correct format. [`scripts/format_entry.sh`](../scripts/format_entry.sh) can be invoked to help you.
3. Save and commit.
4. Open a pull request.
5. Fill out the pull request template.
6. Submit it.
7. Wait for review and approval.
8. Celebrate. 🎉

Repeat these steps for each project to add.

**Be rigorous and responsive for a smooth review process.**

## Other

In general, any help is welcome.

Have a look at issues labeled with [`help wanted`](https://github.com/bminusl/awesome-hackernews/issues?q=is%3Aissue+is%3Aopen+label%3A%22help+wanted%22).


text:
Thank you for your contribution to the awesome-hackernews repository. :thumbsup:

Before submitting this PR, please **make sure the following conditions are met** (**please tick the relevant boxes**):

- [ ] ⅰ. The pull request concerns only one project1.
- [ ] ⅱ. This project is classified as [FOSS](https://en.wikipedia.org/wiki/Free_and_open-source_software).
- [ ] ⅲ. The pull request title is "Add <xxx> to <yyy>", which means adding project <xxx> to section <yyy>.2

  Examples:
  - "Add My Project to Embeddables"
  - "Add My Project to Libraries/Python", for nested sections
- [ ] ⅳ. Your addition satisfies one of the following formats3:
  - If the project has a homepage (different from where the source code is hosted):
    ```
    - [Name](http://homepage/) - Short description. ([Source Code](http://url.of/source/code)) `License`
    ```
  - Else:
    ```
    - [Name](http://url.of/source/code) - Short description. `License`
    ```

  Guidelines about the description:
  - Avoid redundancy
  - Keep it short
  - End with '.'

- [ ] ⅴ. Your addition is inserted in alphabetical order in the list (case-insensitive).
- [ ] ⅵ. You have searched the repository for any relevant issues or PRs, including closed ones.

**If you have any questions or would like to provide more information regarding your PR, please use the comments section below.**

---

1: If you want to submit multiple projects, please open one PR per project.

2: If the project does not fit into an already existing section, i.e. a new one must be created, do not hesitate to create it. In this case, don't forget to update the TOC and put the "`back to top`" link.

3: [`scripts/format_entry.sh`](https://github.com/bminusl/awesome-hackernews/blob/main/scripts/format_entry.sh) can be invoked to help you.

---

A [custom linter](https://github.com/bminusl/awesome-hackernews/blob/main/scripts/lint.py) is automatically invoked through GitHub actions.
You are responsible of correcting any errors that are detected.

NB: If you are a first time contributor, you will have to wait for a maintainer to approve GitHub actions (a.k.a. the linter) to run.


text:
name: Lint the awesome list

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]
    types:
      - opened # pull request is created
      - synchronize # commit(s) pushed to the pull request

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
    - uses: actions/checkout@v2
    - uses: actions/setup-python@v2
      with:
        python-version: '3.x'
    - run: python scripts/lint.py


text:
#!/bin/bash

read -p "Does the project have a homepage (different from where the source code is hosted)? [y/N] " has_homepage
[[ "$has_homepage" == [Yy]* ]] && has_homepage=true || has_homepage=false
$has_homepage && echo "You entered 'Yes'" || echo "You entered 'No'"

read -p "Project name: " name

$has_homepage && message="Homepage URL: " || message="Source code URL: "
read -p "$message" main_url

read -p "Description (avoid redundancy, keep it short, end with '.'): " description
$has_homepage && read -p "Source code URL: " source_code_url
read -p "License: " license

echo ""
echo "Copy this entry to your clipboard, paste it in the appropriate place in the README.md:"
if $has_homepage; then
    echo "- [$name]($main_url) - $description ([Source Code]($source_code_url)) \`$license\`"
else
    echo "- [$name]($main_url) - $description \`$license\`"
fi


text:
import re, sys
from collections import defaultdict
from urllib.parse import urlparse

_anything_regex = r"(.+)"
format_1_regex = re.compile(
    r"^- \[%s\]\(%s\) - %s\. \(\[Source Code\]\(%s\)\) `%s`$" % ((_anything_regex,) * 5)
)
# Note: a 4th "fake" group is added to match the groups of the previous regex. This helps to simplify the code.
format_2_regex = re.compile(
    r"^- \[%s\]\(%s\) - %s\. ()`%s`$" % ((_anything_regex,) * 4)
)

def parse_readme():
    data = defaultdict(list)
    with open("README.md") as f:
        # The list is between the 2nd and 3rd horizontal rules (hr).
        hr_counter = 0

        for line_number, line in enumerate(f, start=1):
            line = line[:-1]

            if line == "---":
                hr_counter += 1
                if hr_counter > 2:
                    break
                continue
            if hr_counter < 2:
                continue

            if line.startswith("#"):
                current_section = (line_number, line.lstrip("#")[1:])
            elif line.startswith("-"):
                data[current_section].append((line_number, line))
    return dict(data)

def lint_entry(entry):
    line_number, line = entry

    # BBB: walrus operator
    m = format_1_regex.match(line) or format_2_regex.match(line)
    if m:
        # TODO: lint name, description, license
        name, url, description, code_url, license = m.groups()

        if hasattr(lint_entry, "previous_line"):
            if line.lower() < lint_entry.previous_line.lower():
                yield "l%s: The entry is not inserted correctly in case-insensitive alphabetical order (compared to l%s)" % (
                    line_number,
                    line_number - 1,
                )
        # XXX: using this is a bit hacky (anti-pattern?)
        lint_entry.previous_line = line

        if urlparse(url) == urlparse(code_url):
            yield "l%s: The Source Code URL is a duplicate of the main URL" % line_number
    else:
        yield "l%s: The entry is not formatted correctly" % line_number

def lint():
    errors = []

    for section_name, sublist in parse_readme().items():
        for entry in sublist:
            errors.extend(lint_entry(entry))
        del lint_entry.previous_line

    if errors:
        for e in errors:
            print(e)
        print("\n%s errors" % len(errors))
        sys.exit(1)
    print("0 error")

if __name__ == "__main__":
    lint()


text:
# Home Assistant configuration.yaml

################
# command_line #
################
command_line:
  - switch:
      name: fan_on
      unique_id: fan_on
# ssh is only required if HA and rpitx run on different machines
      command_on: "ssh -i /config/id_rsa -o StrictHostKeyChecking=no -q pi@<YOUR.PI.IP.ADDRESS> sudo ./rpitx/sendiq -s 250000 -f 868.0000e6 -t u8 -i ./rpitx/fan-all-on.iq"
      command_off: off
# HA doesn't get feedback if the device is on or off.  This returns the switch always back to the off-state.
      command_state: off

################
# emulated_hue #
################
emulated_hue:
  listen_port: 80
  expose_by_default: false
  entities:
    switch.fan_on:
      name: "fan on"
      hidden: false

##############
# automation #
##############
automation:
  - alias: webhook_fan_on
    trigger:
    - platform: webhook
      webhook_id: <PICK-A-RANDOM-WEBHOOK-ID>
    condition: []
    action:
    - data: {}
      entity_id: switch.fan_on
      service: switch.turn_on
    mode: single


text:
# record and replay Radio Frequency (RF) remote signal
Record any replay any RF signal. (optional) Integrate with Home Assistant. Trigger replay with Alexa or Google Home.
![](docs/pics/project-drawing.png)
Common use cases: airconditioners, remote power plugs, fans, shades and kitchen hoods.  This won't work with rolling codes often used for more secure communication to open doors for example.

## required hardware

* Raspberry Pi
* radio receiver USB dongle (RTL-SDR with RTL2832U chipset)

## record and replay RF signal

on your Pi
1. Install [Raspberry Pi OS](https://www.raspberrypi.org/downloads/raspberry-pi-os/).
2. Install https://github.com/F5OEO/rpitx
3. Connect the RTL-SDR dongle to your Pi.
4. Record remote signal.
> To find the frequency, your can install [SDR](https://www.rtl-sdr.com/big-list-rtl-sdr-supported-software/) software.  If the remote has an FCC id, you can look that up [here](https://fccid.io).  Common frequencies are: 433.92, 868.3, 315, 288, 300, 303, 306, 310, 318, 330, 390, 403.55 and 418 MHz.
```bash
cd rpitx
# this records on 868.00 MHz frequency and writes it to a fan-on-button.iq file
rtl_sdr -s 250000 -g 35 -f 868.0000e6 fan-on-button.iq
```
      *above can also be done through the [rtlmenu.sh GUI](https://github.com/defcon24bit/record-and-replay-RF-remote/tree/master/docs/record-RF-signal-screenshots.md)*

5. CTRL + C to stop recording.
> You can remove the RTL-SDR dongle when you're done recoding.  It's not required for sending.
6. Add electrical wire to GPIO pin#7 (4th pin down, left row) - see [picture](https://github.com/defcon24bit/record-and-replay-RF-remote/tree/master/docs/pi-gpio-pins-picture.md).
7. Replay recording.
```bash
sudo ./rpitx/sendiq -s 250000 -f 868.0000e6 -t u8 -i ./rpitx/fan-on-button.iq
```
<!--4. Launch the rpitx menu.
```bash
cd rpitx
./rtlmenu.sh
```
5. Set frequency, record and test replay - see [screenshot examples](https://github.com/defcon24bit/record-and-replay-RF-remote/tree/master/docs/record-RF-signal-screenshots.md).
6. Rename the /rpitx/record.iq file.
> The menu always uses the same file, so to avoid overwriting rename the file
```bash
cp record.iq on-button.iq
```
7. Go back to step 4. and repeat until you've recorded all buttons on your remote.
-->

## integrate with Home Assistant

If you don't have HA, start [here](https://github.com/defcon24bit/record-and-replay-RF-remote/tree/master/docs/install-hassio.md).

### add as switch

Create a command line switch for everyone recording you want to replay.

```yaml
# configuration.yaml
command_line:
  - switch:
      name: fan_on
      unique_id: fan_on
# ssh is only required if HA and rpitx run on different machines
      command_on: "ssh -i /config/id_rsa -o StrictHostKeyChecking=no -q pi@<YOUR.PI.IP.ADDRESS> sudo ./rpitx/sendiq -s 250000 -f 868.0000e6 -t u8 -i ./rpitx/fan-all-on.iq"
      command_off: off
# HA doesn't get feedback if the device is on or off.  This returns the switch always back to the off-state.
      command_state: off
```

### expose switch to 'emulated hue' component

This will allow Alexa to see the switch as a Philips Hue light.

```yaml
# configuration.yaml
emulated_hue:
  listen_port: 80
  expose_by_default: false
  entities:
    switch.fan_on:
      name: "fan on"
      hidden: false
```

Alexa gets confused if you create multiple devices with simular names.
To work around this, create Alexa Routines with a unique trigger sentences and avoid the words 'on' or 'off'.
I used *start*- and *stop cooking* - see [example](https://github.com/defcon24bit/record-and-replay-RF-remote/tree/master/docs/alexa-routine-picture.md).

### create automation that exposes switch as a webhook

This will allow you to remotely trigger the switch via an HTTP POST request.
Can be used with Google Home for example.

```yaml
# configuration.yaml
automation:
  - alias: webhook_fan_on
    trigger:
    - platform: webhook
      webhook_id: <PICK-A-RANDOM-WEBHOOK-ID>
    condition: []
    action:
    - data: {}
      entity_id: switch.fan_on
      service: switch.turn_on
    mode: single
```
Test the webhook
```bash
curl -X POST https://<YOUR-HA>.duckdns.org/api/webhook/<YOUR-WEBHOOK-ID>
```

### create certificate on HA

Only required if HA and rpitx run on different machines.
The certificate allows to remote execute ssh commands on another machine without a password prompt.

create certificate
```bash
ssh-keygen -t rsa -b 4096
```
send certificate to your pi (command still run on HA)
```bash
ssh-copy-id pi@<YOUR.PI.IP.ADDRESS>
```
> Initially,  HA gave errors when running the remote ssh commands.  I can't remember the exact fix.  It was either the HA user context not having access to the key files or the file and folder permissions for the certificate keys were not set correctly.  I think below two things fixed it.  Let me know if this works for you.

on HA
copy the id_rsa to the HA /config folder

on your Pi
```bash
chmod 700 ~/.ssh/
chmod 600 ~/.ssh/*
```

## create webhooks without HA

If you don't have HA, you can try creating webhooks with https://github.com/ncarlier/webhookd.
Protecting this setup is beyond the scope of this repository. It's not a question if you get hacked, but when.
Consider using an [nginx proxy](https://nginx.org/en/), [Let's Encrypt](https://letsencrypt.org) and [Duck DNS](https://www.duckdns.org).

<!--
## create IFTTT Applets
1. Create a [new IFTTT Applet](https://ifttt.com/create).
2. Select '+This' -> 'Amazon Alexa' or 'Google Assistant' -> 'Say a specific/simple phrase'.
3. Select '+That' -> 'Webhooks' -> 'Make a web request'.
URL ```https://<YOUR-HA>/api/webhook/<YOUR-WEBHOOK-ID>```
Method: ```POST```.
-->

## credits

All rights of the original authors reserved.

[Creative Commons Zero v1.0 Universal license](https://github.com/defcon24bit/record-and-replay-RF-remote/tree/master/LICENSE)


text:
![](pics/alexa-routine.png)
[<- back to Read Me](https://github.com/defcon24bit/record-and-replay-RF-remote#expose-switch-to-emulated-hue-component)


text:
![](pics/pi-elect-wire-on-pin-7.png)
[<- back to Read Me](https://github.com/defcon24bit/record-and-replay-RF-remote#record-and-replay-rf-signal)


text:
[<- back to Read Me](https://github.com/defcon24bit/record-and-replay-RF-remote#record-and-replay-rf-signal)
![](pics/record-RF-menu-00001.png)
![](pics/record-RF-menu-00002.png)
![](pics/record-RF-menu-00003.png)
![](pics/record-RF-menu-00004.png)
![](pics/record-RF-menu-00005.png)
![](pics/record-RF-menu-00006.png)
![](pics/record-RF-menu-00007.png)
![](pics/record-RF-menu-00008.png)
[<- back to Read Me](https://github.com/defcon24bit/record-and-replay-RF-remote#record-and-replay-rf-signal)


text:
1. Install [Home Assistant](https://www.home-assistant.io/getting-started/).
2. Add DuckDNS add-on.  See add-on store under Supervisor.
This is only required if you want to use webhooks.
This add-on incudes Let's Encrypt.

Tip: block IPs that try to brute force logins.
```yaml
# configuration.yaml
http:
  ssl_certificate: /ssl/fullchain.pem
  ssl_key: /ssl/privkey.pem
  ip_ban_enabled: true
  login_attempts_threshold: 3
```

[<- back to Read Me](https://github.com/defcon24bit/record-and-replay-RF-remote#integrate-with-home-assistant)


text:
[bdist_wheel]
# This flag says that the code is written to work on both Python 2 and Python
# 3. If at all possible, it is good practice to do this. If you cannot, you
# will need to generate wheels for each Python version that you support.
universal=1


text:
# Include the license file
include LICENSE

# Include the data files
recursive-include data *

# If using Python 2.6 or less, then have to include package data, even though
# it's already declared in setup.py
# include sample/*.dat


text:
# this file is *not* meant to cover or endorse the use of tox or pytest or
# testing in general,
#
#  It's meant to show the use of:
#
#  - check-manifest
#     confirm items checked into vcs are in your sdist
#  - python setup.py check (using the readme_renderer extension)
#     confirms your long_description will render correctly on pypi
#
#  and also to help confirm pull requests to this project.

[tox]
envlist = py{35}

[testenv]
basepython =
    py34: python3.5
deps =
    check-manifest
    {py35}: readme_renderer
    flake8
    pytest
commands =
    check-manifest --ignore tox.ini,tests*
    # py26 doesn't have "setup.py check"
    {py35}: python setup.py check -m -r -s
    flake8 .
    py.test tests
[flake8]
exclude = .tox,*.egg,build,data
select = E,W,F


text:
# PRCDNS
这是一个Google DNS代理
不同之处在于CDN友好，根据你的IP返回最优的解析结果

### 原理
[DNS-over-HTTPS API](https://developers.google.com/speed/public-dns/docs/dns-over-https)
它支持edns_client_subnet，把你的IP作为参数提交，它会返回最优的解析结果，所以说它
是我见过的最完美的DNS解决方案。

### 注意事项：PRCDNS前面一定要放pdnsd或者unbound
1. PRCDNS**只支持TCP查询**
2. PRCDNS**没有缓存**

很多二级运营商为了节省成本，减少外网之间的带宽结算费用，对DNS查询做了重点照顾，
防止用户使用114、百度、阿里的公共DNS，强制用户将某些流量大的域名指向它的缓存服务器，
于是UDP成了重灾区，目前TCP没事，114已经支持TCP，百度、阿里还不行。PRCDNS前面放
pdnsd、unbound，即解决了缓存问题，又解决了UDP的问题

### 安装
基于Python 3.5
```bash
sudo pip3 install PRCDNS
```

### 使用

1. 如果你把PRCDNS部署到VPS上，和SS做邻居，这样在家、公司都能用
```python
PRCDNS
```
2. 如果把PRCDNS部署到自己本地的机器或者路由器上，请将SS通过polipo转为http类型，以便于PRCDNS可以访问https://developers.google.com
```python
PRCDNS -r http://127.0.0.1:8123
```

### 参数
```bash
--debug 调试模式 选填 默认false
-l 监听IP 选填 默认0.0.0.0
-p 监听端口 选填 默认3535
-r http_proxy 如果PRCDNS可以访问https://developers.google.com就不用填写
```
欢迎通过Issue讨论、提问和给予指导


text: PRCDNS -l $HOST -p $PORT


text:
"""A setuptools based setup module.
"""

# Always prefer setuptools over distutils
from codecs import open
from os import path

from setuptools import setup, find_packages

here = path.abspath(path.dirname(__file__))

# Get the long description from the README file
with open(path.join(here, 'README'), encoding='utf-8') as f:
    long_description = f.read()

setup(
    name='PRCDNS',

    # Versions should comply with PEP440.  For a discussion on single-sourcing
    # the version across setup.py and the project code, see
    # https://packaging.python.org/en/latest/single_source_version.html
    version='0.0.9',

    description='准确、CDN友好的DNS软件，使用DNS-Over-HTTP',
    long_description=long_description,

    # The project's main homepage.
    url='https://github.com/lbp0200/PRCDNS',

    # Author details
    author='lbp0200',
    author_email='lbp0408@gmail.com',

    # Choose your license
    license='unlicense',

    # See https://pypi.python.org/pypi?%3Aaction=list_classifiers
    classifiers=[
        # How mature is this project? Common values are
        #   3 - Alpha
        #   4 - Beta
        #   5 - Production/Stable
        'Development Status :: 4 - Beta',

        # Indicate who your project is intended for
        'Intended Audience :: Developers',
        'Topic :: Software Development :: Build Tools',

        # Pick your license as you wish (should match "license" above)
        'License :: OSI Approved :: Python Software Foundation License',

        # Specify the Python versions you support here. In particular, ensure
        # that you indicate whether you support Python 2, Python 3 or both.
        'Programming Language :: Python :: 3.5',
        'Programming Language :: Python :: 3.6',
    ],

    # What does your project relate to?
    keywords='DNS',

    # You can just specify the packages manually here if your project is
    # simple. Or you can use find_packages().
    packages=find_packages(exclude=['contrib', 'docs', 'tests']),

    # Alternatively, if you want to distribute just a my_module.py, uncomment
    # this:
    #   py_modules=["my_module"],

    # List run-time dependencies here.  These will be installed by pip when
    # your project is installed. For an analysis of "install_requires" vs pip's
    # requirements files see:
    # https://packaging.python.org/en/latest/requirements.html
    install_requires=['asyncio', 'aiohttp', 'aiodns', 'cchardet', 'dnslib', 'argparse', 'IPy'],

    # List additional groups of dependencies here (e.g. development
    # dependencies). You can install these using the following syntax,
    # for example:
    # $ pip install -e .[dev,test]
    extras_require={
        'dev': ['check-manifest'],
        'test': ['coverage'],
    },

    # If there are data files included in your packages that need to be
    # installed, specify them here.  If using Python 2.6 or less, then these
    # have to be included in MANIFEST.in as well.
    package_data={
        'prcdns': ['domain_data.dat'],
    },

    # Although 'package_data' is the preferred approach, in some case you may
    # need to place data files outside of your packages. See:
    # http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
    # In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
    data_files=[],

    # To provide executable scripts, use entry points in preference to the
    # "scripts" keyword. Entry points provide cross-platform support and allow
    # pip to create the appropriate form of executable for the target platform.
    entry_points={
        'console_scripts': [
            'PRCDNS=PRCDNS.server:main',
        ],
    },
)


text:
FROM alpine:3.9

ENV HOST "0.0.0.0"
ENV PORT "3535"

RUN apk update && \
    apk add python3 alpine-sdk python3-dev libffi-dev curl && \
    pip3 install 'aiohttp<3.0' PRCDNS && \
    apk del alpine-sdk python3-dev libffi-dev

ADD ./run.sh /
WORKDIR /

CMD ["sh", "-x", "/run.sh"]


text:
# PRCDNS
这是一个Google DNS代理
不同之处在于CDN友好，根据你的IP返回最优的解析结果

基于Python2.7另外开发了一个[prc-dns](https://github.com/lbp0200/prc-dns)，新增IPV6、脱离必须使用代理的限制，支持UDP和TCP。

### 原理
[DNS-over-HTTPS API](https://developers.google.com/speed/public-dns/docs/dns-over-https)
它支持edns_client_subnet，把你的IP作为参数提交，它会返回最优的解析结果，所以说它
是我见过的最完美的DNS解决方案。

### 测试
```bash
#本人北京联通，对比OPENDNS进行测试，证明PRCDNS是CDN友好的
#23.106.151.177:3535 是我搭建的测试地址，遇到攻击可能会关闭
#seaof-153-125-234-242.jp-tokyo-12.arukascloud.io:31910 部署在日本樱花docker
#208.67.222.222:443  OPENDNS
dig @23.106.151.177 +tcp -p 3535 google.com.hk
dig @208.67.222.222 +tcp -p 443 google.com.hk

dig @23.106.151.177 +tcp -p 3535 img.alicdn.com #123.125.18.108北京联通
dig @seaof-153-125-234-242.jp-tokyo-12.arukascloud.io +tcp -p 31910 img.alicdn.com #123.125.18.108北京联通
dig @208.67.222.222 +tcp -p 443 img.alicdn.com  #69.192.12.15香港
```

### 注意事项：PRCDNS前面一定要放pdnsd或者unbound
1. PRCDNS**只支持TCP查询**
2. PRCDNS**没有缓存**

很多二级运营商为了节省成本，减少外网之间的带宽结算费用，对DNS查询做了重点照顾，
防止用户使用114、百度、阿里的公共DNS，强制用户将某些流量大的域名指向它的缓存服务器，
于是UDP成了重灾区，目前TCP没事，114已经支持TCP，百度、阿里还不行。PRCDNS前面放
pdnsd、unbound，即解决了缓存问题，又解决了UDP的问题

### 安装
基于Python 3.5
```bash
sudo pip3 install PRCDNS
```

### 使用

如果你把PRCDNS部署到VPS上，和SS做邻居，这样在家、公司都能用
```bash
PRCDNS
```
如果把PRCDNS部署到自己本地的机器或者路由器上，请将SS通过polipo转为http类型，以便于PRCDNS可以访问https://developers.google.com
```bash
PRCDNS -r http://127.0.0.1:8123
```
请使用Supervisor保证PRCDNS一直运行

### 参数
```bash
--debug 调试模式 选填 默认false
-l 监听IP 选填 默认0.0.0.0
-p 监听端口 选填 默认3535
-r http_proxy 如果PRCDNS可以访问https://developers.google.com就不用填写
```
欢迎通过Issue讨论、提问和给予指导

### 更多文档
[wiki](https://github.com/lbp0200/PRCDNS/wiki)

### docker 方式运行
1. build： `docker build -t prcdns:latest .`
2. run:  `docker run -p 3535:3535 -d --name prcdns prcdns:latest`


text:
import argparse
import asyncio
import functools
import json

from dnslib import *

from PRCDNS.proxy_client import ProxyClient

class DNSServerProtocol(asyncio.Protocol):
    args = None
    gFuncs = globals()
    peername = None
    loop = None
    request = None

    def __init__(self, args, loop):
        self.args = args
        self.loop = loop

    def get_data(self, data):
        l = len(data)
        if l == 2:
            return
        sz = struct.unpack(">H", data[:2])[0]
        if sz < l - 2:
            raise Exception("Wrong size of TCP packet")
        elif sz > l - 2:
            return data
            # raise Exception("Too big TCP packet")
        return data[2:]

    def connection_made(self, transport):
        self.peername = transport.get_extra_info('peername')
        if self.args.debug:
            print('Connection from {}'.format(self.peername))
        self.transport = transport

    def data_received(self, data):
        data = self.get_data(data)
        if data is None:
            self.transport.close()
            return
        self.request = DNSRecord.parse(data)
        if self.args.debug:
            print('Data received: {!r}'.format(self.request))

        from IPy import IP
        ip = IP(self.peername[0])
        client_ip = self.peername[0]
        if ip.iptype() == 'PRIVATE':
            client_ip = self.args.myip

        url = 'https://dns.google.com/resolve?name={}&edns_client_subnet={}/24'.format(str(self.request.q.qname),
                                                                                client_ip)
        # client = ProxyClient()
        # google_dns_resp = client.query_domain(url, self.args.proxy)
        try:
            from asyncio import ensure_future
        except ImportError:
            from asyncio import async as ensure_future

        asyncio.ensure_future(ProxyClient.query_domain(url, self.args.proxy), loop=self.loop).add_done_callback(
            functools.partial(self.send_resp))

    def send_resp(self, fut):
        google_dns_resp = fut.result()
        # google_dns_resp = '{"Status": 0,"TC": false,"RD": true,"RA": true,"AD": false,"CD": false,"Question":[ {"name": "img.alicdn.com.","type": 1}],"Answer":[ {"name": "img.alicdn.com.","type": 5,"TTL": 21557,"data": "img.alicdn.com.danuoyi.alicdn.com."},{"name": "img.alicdn.com.danuoyi.alicdn.com.","type": 1,"TTL": 59,"data": "111.32.130.109"},{"name": "img.alicdn.com.danuoyi.alicdn.com.","type": 1,"TTL": 59,"data": "111.32.130.108"}],"Additional":[],"edns_client_subnet": "223.72.90.0/24","Comment": "Response from danuoyinewns1.gds.alicdn.com.(121.43.18.33)"}'
        if self.args.debug:
            print('from: {};response: {}'.format(self.peername[0], google_dns_resp))
        resp = json.loads(google_dns_resp)
        a = self.request.reply()
        if resp['Status'] == 0 and 'Answer' in resp:
            for answer in resp['Answer']:
                qTypeFunc = QTYPE[answer['type']]
                a.add_answer(RR(answer['name'], answer['type'], rdata=self.gFuncs[qTypeFunc](answer['data']),
                                ttl=answer['TTL']))
        # elif resp['Status'] == 3 and 'Authority' in resp:
        #     for answer in resp['Authority']:
        #         qTypeFunc = QTYPE[answer['type']]
        #         a.add_answer(RR(answer['name'], answer['type'], rdata=self.gFuncs[qTypeFunc](answer['data']),
        #                         ttl=answer['TTL']))
        else:
            self.transport.close()
            return
        if self.args.debug:
            print('Send: {!r}'.format(a))
        b_resp = a.pack()
        b_resp = struct.pack(">H", b_resp.__len__()) + b_resp
        self.transport.write(b_resp)
        self.transport.close()

def get_arg():
    """解析参数"""
    parser = argparse.ArgumentParser(prog='prcdns', description='google dns proxy.')
    parser.add_argument('--debug', help='debug model,default NO', default=False)
    parser.add_argument('-l', '--listen', help='listening IP,default 0.0.0.0', default='0.0.0.0')
    parser.add_argument('-p', '--port', help='listening Port,default 3535', default=3535)
    parser.add_argument('-r', '--proxy', help='Used For Query Google DNS,default direct', default=None)
    parser.add_argument('-ip', '--myip', help='IP location', default=None)

    return parser.parse_args()

def main():
    args = get_arg()
    if args.proxy is None:  # 无代理，在PRC局域网外
        myip = ProxyClient.get_url('http://ipinfo.io/json')
        myip = json.loads(myip)
        args.myip = myip['ip']
    else:  # 用代理，在PRC局域网内
        if args.myip is None:
            myip = ProxyClient.get_url('http://ip.taobao.com/service/getIpInfo.php?ip=myip')
            print(myip)
            myip = json.loads(myip)
            myip = myip['data']['ip']
            args.myip = myip

    loop = asyncio.get_event_loop()
    loop.set_debug(args.debug)

    # Each client connection will create a new protocol instance
    coro = loop.create_server(lambda: DNSServerProtocol(args, loop), args.listen, args.port)
    server = loop.run_until_complete(coro)

    try:
        print("public ip is {}".format(args.myip))
        print("listen on {0}:{1}".format(args.listen, args.port))
        loop.run_forever()
    except KeyboardInterrupt:
        pass

    print('Close Server')
    server.close()
    loop.run_until_complete(server.wait_closed())
    loop.close()

if __name__ == "__main__":
    main()


text:
import asyncio
import aiohttp

class ProxyClient:
    """Google DNS Client"""
    proxy = None

    # def __init__(self, proxy=None):
    #     self.proxy = proxy

    @staticmethod
    async def fetch(session, url, proxy=None):
        with aiohttp.Timeout(10):
            # http://127.0.0.1:8123
            async with session.get(url, proxy=proxy) as response:
                return await response.text()

    @staticmethod
    async def query_domain(url, proxy=None):
        async with aiohttp.ClientSession() as session:
            return await ProxyClient.fetch(session, url, proxy)

    @staticmethod
    async def get(loop, url):
        async with aiohttp.ClientSession(loop=loop) as session:
            return await ProxyClient.fetch(session, url)

    @staticmethod
    def get_url(url, proxy=None):
        loop = asyncio.get_event_loop()
        return loop.run_until_complete(ProxyClient.get(loop, url))


text:
class DomainCache:
    def read(self):
        pass


text:
# the inclusion of the tests module is not meant to offer best practices for
# testing in general, but rather to support the `find_packages` example in
# setup.py that excludes installing the "tests" package


text:
# the inclusion of the tests module is not meant to offer best practices for
# testing in general, but rather to support the `find_packages` example in
# setup.py that excludes installing the "tests" package
import asyncio
import unittest

from dnslib import *

class TestDict(unittest.TestCase):
    # def test_init(self):
    #     self.assertEqual(1, 1)
    # def test_demo(self):

    def test_dig(self):
        loop = asyncio.get_event_loop()

        tasks = [
            asyncio.ensure_future(self.tcp_echo_client(loop)),
            asyncio.ensure_future(self.tcp_echo_client(loop)),
            asyncio.ensure_future(self.tcp_echo_client(loop)),
            asyncio.ensure_future(self.tcp_echo_client(loop)),
            asyncio.ensure_future(self.tcp_echo_client(loop)),
        ]

        loop.run_until_complete(asyncio.gather(*tasks))
        loop.close()

    @asyncio.coroutine
    def tcp_echo_client(self, loop):
        reader, writer = yield from asyncio.open_connection(
            '127.0.0.1', 3535,
            # '114.114.114.114', 53,
            loop=loop)

        d = DNSRecord.question("img.alicdn.com")
        q = d.pack()
        b_req = struct.pack(">H", q.__len__()) + q
        writer.write(b_req)

        data = yield from reader.read()
        resp = DNSRecord.parse(data[2:])
        print('Received: %r' % resp)

        print('Close the socket')
        writer.close()

if __name__ == '__main__':
    unittest.main()