Search DuckDuckGo from the command-line

search-pane 5.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. #!/usr/bin/python3
  2. from subprocess import call, check_output
  3. from threading import Thread
  4. import os
  5. import sys
  6. import readline
  7. import tty
  8. import termios
  9. from GoogleScraper import scrape_with_config, GoogleSearchError
  10. from tabulate import tabulate
  11. HOME = os.path.expanduser("~")
  12. HISTFILE = os.path.join(HOME, ".search-pane/history")
  13. CONFIG = {
  14. 'SCRAPING': {
  15. 'use_own_ip': True,
  16. 'keyword': None,
  17. 'search_engines': 'google',
  18. 'num_pages_for_keyword': 1,
  19. 'scrape_method': 'http',
  20. 'verbosity': 0
  21. }
  22. }
  23. BROWSER_CMD = 'w3m'
  24. def clear_screen():
  25. os.system('cls' if os.name == 'nt' else 'clear') # clear the terminal
  26. def write_other_hosts(query):
  27. # write to history files on other registered hosts
  28. with open(os.devnull, 'w') as FNULL:
  29. with open(os.path.join(HOME, ".search-pane/other-hosts"), "r") as f:
  30. for line in f:
  31. line = line.strip().split()
  32. host = line[0]
  33. path = line[1]
  34. port = None
  35. if (len(line) > 2): # port is optional
  36. port = line[2]
  37. if host.startswith('"') and host.endswith('"'):
  38. host = host[1:-1]
  39. # make sure we don't write to local file again
  40. client_names = check_output(['hostname']).split()
  41. if (host.split('@')[-1] not in client_names):
  42. if port:
  43. call(['ssh', host, '-p', port, 'echo', '"' + query +
  44. '"', '>>', path], stderr=FNULL)
  45. else:
  46. call(['ssh', host, 'echo', '"' + query + '"', '>>',
  47. path], stderr=FNULL)
  48. def get_rows_and_columns():
  49. rows, columns = os.popen('stty size', 'r').read().split()
  50. return int(rows), int(columns)
  51. def getch():
  52. """getch() -> key character
  53. Read a single keypress from stdin and return the resulting character.
  54. Nothing is echoed to the console. This call will block if a keypress
  55. is not already available, but will not wait for Enter to be pressed.
  56. If the pressed key was a modifier key, nothing will be detected; if
  57. it were a special function key, it may return the first character of
  58. of an escape sequence, leaving additional characters in the buffer.
  59. """
  60. fd = sys.stdin.fileno()
  61. old_settings = termios.tcgetattr(fd)
  62. try:
  63. tty.setraw(fd)
  64. ch = sys.stdin.read(1)
  65. finally:
  66. termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
  67. return ch
  68. def get_query():
  69. try:
  70. query = input('Search: ') # get user's search
  71. write_to_history(query)
  72. return query
  73. except KeyboardInterrupt:
  74. sys.exit(0)
  75. def write_to_history(query, add_query=False):
  76. if add_query:
  77. readline.add_history(query) # add query to history buffer
  78. readline.write_history_file(HISTFILE) # write search to history file
  79. # Spin off another thread for sshing so user doesn't have to wait for
  80. # connection to complete before viewing w3m.
  81. try:
  82. Thread(target=write_other_hosts, args=(query,)).start()
  83. except Exception as errtxt:
  84. print(errtxt)
  85. def do_search(query):
  86. try:
  87. CONFIG['SCRAPING']['keyword'] = query
  88. return scrape_with_config(CONFIG)
  89. except GoogleSearchError as e:
  90. print(e)
  91. def print_results(search, start, end, columns):
  92. actual_cols = columns - 9
  93. for serp in search.serps:
  94. table = []
  95. for index, link in list(enumerate(serp.links))[start:end]:
  96. title = link.title
  97. domain = link.domain
  98. snippet = link.snippet
  99. if title:
  100. title = title[:actual_cols]
  101. if domain:
  102. domain = domain[:actual_cols]
  103. if snippet:
  104. snippet = snippet.replace('\n', ' ')
  105. snippet = snippet[:actual_cols]
  106. table.extend([[(index+1), color.BOLD + title + color.END],
  107. ["", color.UNDERLINE + domain + color.END],
  108. ["", snippet],
  109. ["", ""]])
  110. print(tabulate(table, tablefmt="pipe"))
  111. class color:
  112. PURPLE = '\033[95m'
  113. CYAN = '\033[96m'
  114. DARKCYAN = '\033[36m'
  115. BLUE = '\033[94m'
  116. GREEN = '\033[92m'
  117. YELLOW = '\033[93m'
  118. RED = '\033[91m'
  119. BOLD = '\033[1m'
  120. UNDERLINE = '\033[4m'
  121. END = '\033[0m'
  122. readline.parse_and_bind("tab: complete")
  123. # load history
  124. try:
  125. readline.read_history_file(HISTFILE)
  126. except IOError:
  127. open(HISTFILE, 'a').close()
  128. readline.read_history_file(HISTFILE)
  129. clear_screen()
  130. if len(sys.argv) > 1:
  131. query = ' '.join(sys.argv[1:])
  132. write_to_history(query, add_query=True)
  133. else:
  134. query = get_query()
  135. search = do_search(query)
  136. rows, columns = get_rows_and_columns()
  137. clear_screen()
  138. result_i = rows // 4
  139. print_results(search, 0, result_i, columns)
  140. try:
  141. while True:
  142. key = getch()
  143. rows, columns = get_rows_and_columns()
  144. if key == '\x03' or key == 'q':
  145. raise KeyboardInterrupt
  146. elif key == ' ':
  147. if result_i < search.serps[0].num_results:
  148. end_i = result_i + (rows // 4)
  149. print_results(search, result_i, end_i, columns)
  150. result_i = end_i
  151. elif key == 'r':
  152. clear_screen()
  153. search = do_search(get_query())
  154. clear_screen()
  155. result_i = rows // 4
  156. print_results(search, 0, result_i, columns)
  157. continue
  158. elif key == 'h' or key == '?':
  159. print('Help:\n\t1-9\t\topen result N in browser\n\t' +
  160. '<space>\t\tscroll down one page of results\n\t' +
  161. 'r\t\trestart search\n\t' +
  162. 'h\t\tshow this help\n\t' +
  163. 'q\t\tquit')
  164. try:
  165. key = int(key)
  166. except ValueError:
  167. continue
  168. call([BROWSER_CMD, search.serps[0].links[(key-1)].link])
  169. except KeyboardInterrupt:
  170. sys.exit(0)