how2
Version:
AI for your terminal. Uses Google and Stackoverflow to find how to do things on a Unix command line.
173 lines (148 loc) • 5.13 kB
JavaScript
const _ = require('lodash')
const spinner = require('simple-spinner')
const colors = require('colors/safe')
const googleIt = require('google-it')
const stackexchange = require('./stackexchange')
const log = require('./log')
const utils = require('./utils')
const ui = require('./ui')
const context = new stackexchange({ version: 2.2 })
async function fetchQuestionAnswers (parsedLink) {
const questionCriteria = {
filter: '!-*f(6s6U8Q9b' // body_markdown and link
}
questionCriteria.site = parsedLink.hostname
try {
const results = await context.questions.answers(questionCriteria, [parsedLink.questionId])
const answers = _.sortBy(results.items, (answer) => -answer.score)
return answers
} catch (err) {
ui.stop()
log.error('Cannot fetch answers from Stackoverflow.')
log.error(err)
return null
}
}
function onErrorSearch (err) {
ui.stop()
const msg = err.toString()
if (msg.indexOf('have detected unusual traffic') !== -1) {
log.error('You are doing too many requests to Google. You need to wait a bit before trying again.')
} else if (msg.indexOf('ENOTFOUND') !== -1) {
log.error('Cannot connect to Google. Make sure you are connected.')
log.error(err)
} else {
log.error('Cannot connect to Google.')
log.error(err)
log.error(err.stack)
}
process.exit(1)
}
const shellSearchDomains = { 'superuser.com': 1, 'askubuntu.com': 1, 'stackoverflow.com': 1 }
function domainFilter (el) {
const url = new URL(el.link)
return url.hostname.endsWith('stackexchange.com') || url.hostname in shellSearchDomains
}
async function googleSearch (text, lang) {
if (!text) return new Promise((resolve) => resolve([]))
// const site = lang ? `site:stackoverflow.com ${lang}` : 'site:superuser.com'
// const searchQuery = `${site} ${text}`
const searchQuery = text
return googleIt({ query: searchQuery, limit: 20, disableConsole: true }).then(res => res.filter(domainFilter))
}
function selectAnswer ({ title, text, answers, lang, index, remember, askInteractive }) {
spinner.stop()
if (answers.length === 0) {
log.error('Cannot find any reasonable answer for your query.')
// if (!lang) {
// console.log(`To get the best answers, make sure you specify the language with ${colors.blue('-l:')}`)
// console.log(`example: $ how2 ${colors.blue('-l python')} permutations list`)
// console.log(`example: $ how2 ${colors.blue('-l ruby')} permutations list`)
// }
process.exit(1)
}
const markdown = utils.toEscapedMarkdown(answers[index].body_markdown)
console.log(colors.underline.green(`${title}\n`))
console.log(markdown)
console.log(`(${colors.underline.blue(answers[index].link)})\n`)
if (askInteractive) {
console.log('Press SPACE for more choices, any other key to quit.')
require('keypress')(process.stdin)
if (process.stdin.setRawMode) process.stdin.setRawMode(true)
process.stdin.on('keypress', onKeypress)
function onKeypress (_, key) {
if (key) {
if (key.name === 'space') {
process.stdin.removeListener('keypress', onKeypress)
return interactiveMain(text, lang, remember)
}
process.exit(0)
}
}
}
}
function onAfterInteractiveSearch (searchResults) {
spinner.stop()
if (searchResults.length === 0) {
log.error("Sorry, can't find answers for your query.")
process.exit(1)
}
ui.showGoogleList(searchResults, async (index) => {
const parsedLink = utils.parseStackoverflowQuestionId(
searchResults[index].link
)
try {
const answers = await fetchQuestionAnswers(parsedLink)
showAnswers(answers)
} catch (err) {
onErrorSearch(err)
}
})
}
function showAnswers (answers) {
ui.showAnswers(answers, (index) => {
const selected = answers[index]
if (selected) {
ui.showAnswer(selected)
}
})
}
async function main (text, lang, askInteractive) {
console.log('Searching StackOverflow...')
spinner.start({ doNotBlock: true })
try {
const searchResults = await googleSearch(text, lang)
if (searchResults.length === 0) {
log.error('Sorry, I cannot find any reasonable answer for your query.')
process.exit(1)
}
const index = 0
const parsedLink = utils.parseStackoverflowQuestionId(searchResults[index].link)
const { title } = searchResults[index]
const remember = searchResults
try {
const answers = await fetchQuestionAnswers(parsedLink)
selectAnswer({ title, text, answers, lang, index, remember, askInteractive })
} catch (err) {
log.error(err)
process.exit(1)
}
} catch (err) {
onErrorSearch(err)
}
}
async function interactiveMain (text, lang, remember) {
ui.start()
spinner.start({ doNotBlock: true })
if (!remember) {
try {
const searchResults = await googleSearch(text, lang)
onAfterInteractiveSearch(searchResults)
} catch (err) {
onErrorSearch(err)
}
} else {
onAfterInteractiveSearch(remember)
}
}
module.exports = { main, interactiveMain, googleSearch, fetchQuestionAnswers }