├── Screens ├── 34820.png ├── screen1.png ├── screenshot1.png ├── screenshot2.png └── screenshot3.png ├── requirements.txt ├── Data ├── settings.json └── questions.json ├── LICENSE.md ├── README.md └── answer_bot.py /Screens/34820.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sushant10/HQ_Bot/HEAD/Screens/34820.png -------------------------------------------------------------------------------- /Screens/screen1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sushant10/HQ_Bot/HEAD/Screens/screen1.png -------------------------------------------------------------------------------- /Screens/screenshot1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sushant10/HQ_Bot/HEAD/Screens/screenshot1.png -------------------------------------------------------------------------------- /Screens/screenshot2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sushant10/HQ_Bot/HEAD/Screens/screenshot2.png -------------------------------------------------------------------------------- /Screens/screenshot3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sushant10/HQ_Bot/HEAD/Screens/screenshot3.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | unidecode 2 | future 3 | selenium 4 | simplejson 5 | Pillow 6 | pytesseract 7 | beautifulsoup4 8 | pyscreenshot 9 | lxml 10 | wikipedia 11 | wxPython 12 | git+https://github.com/abenassi/Google-Search-API/ 13 | halo 14 | -------------------------------------------------------------------------------- /Data/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "remove_words": [ 3 | "who", 4 | "what", 5 | "where", 6 | "when", 7 | "of", 8 | "and", 9 | "that", 10 | "have", 11 | "for", 12 | "the", 13 | "why", 14 | "the", 15 | "on", 16 | "with", 17 | "as", 18 | "this", 19 | "by", 20 | "from", 21 | "they", 22 | "a", 23 | "an", 24 | "and", 25 | "my", 26 | "are", 27 | "in", 28 | "to", 29 | "these", 30 | "is", 31 | "does", 32 | "which", 33 | "his", 34 | "her", 35 | "also", 36 | "have", 37 | "it", 38 | "not", 39 | "we", 40 | "means", 41 | "you", 42 | "comes", 43 | "came", 44 | "come", 45 | "about", 46 | "if", 47 | "by", 48 | "from", 49 | "go", 50 | "?", 51 | ",", 52 | "!", 53 | "'", 54 | "has", 55 | "\"" 56 | ], 57 | "negative_words": [ 58 | "not", 59 | "isn\"t", 60 | "except", 61 | "don\"t", 62 | "doesn\"t", 63 | "wasn\"t", 64 | "wouldn\"t", 65 | "can\"t" 66 | ] 67 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Sushant Rao 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Data/questions.json: -------------------------------------------------------------------------------- 1 | { 2 | "Which baseball player is NOT mentioned in Billy Joel’s “We Didn’t Start the Fire”?":[ 3 | "Minky Mantel", 4 | "Jackie Robinson", 5 | "Roy Campanella" 6 | ], 7 | "Which part of the newspaper would you post an item for sale?":[ 8 | "Sports section", 9 | "Weather page", 10 | "Classifieds" 11 | ], 12 | "Which NBA player is known as \"The Greek Freak\"":[ 13 | "LeBron James", 14 | "Stephen Curry", 15 | "Giannis Antetokounmpo" 16 | ], 17 | "Stradivarius was famous for making what?":[ 18 | "Spotify", 19 | "Violins", 20 | "Hearing Aids" 21 | ], 22 | "A tarantula hawk is a type of what? ":[ 23 | "Bird", 24 | "Snake", 25 | "Wasp" 26 | ], 27 | "Which country does \"cirque du soleil\" originate from?":[ 28 | "USA", 29 | "Canada", 30 | "France" 31 | ], 32 | "Who are readers asked to find in the \"Where\"s Waldo\" books?": [ 33 | "Michael Bulbe", 34 | "Amelia Earhart", 35 | "Waldo" 36 | ], 37 | "Which of these is a US State?": [ 38 | "Chihuahua", 39 | "Saskatchewan", 40 | "Louisiana" 41 | ], 42 | "Which of these is a common material used in 3D printers?": [ 43 | "Durocarbon filament", 44 | "Polyabsorbic styrene", 45 | "Polyactic acid" 46 | ], 47 | "Which of these songs does not feature whistling?": [ 48 | "Graveyard Whistling", 49 | "Young Folks", 50 | "Pumped Up Kicks" 51 | ], 52 | "Which NFL great started his pro career with 10 straight losses?": [ 53 | "Brett Favre", 54 | "Dan Marino", 55 | "Troy Aikman" 56 | ], 57 | "Which of these movies is about baseball? ": [ 58 | "Kingpin", 59 | "CaddyShack", 60 | "Mr Baseball" 61 | ], 62 | "Who wrote the poem “O Captain! My Captain!”?": [ 63 | "William Shakespeare", 64 | "Walt Whitman", 65 | "Sarah Palin" 66 | ], 67 | "Wallace & Gromit are movie characters in which style of animation?": [ 68 | "Cel animation", 69 | "Claymation", 70 | "3D CGI" 71 | ], 72 | "Which city has two of the four longest suspension bridges in the US?": [ 73 | "San Francisco", 74 | "New York City", 75 | "Tacoma" 76 | ], 77 | "Which of these describes a board used in logistics and transportation?": [ 78 | "Pallet", 79 | "Palette", 80 | "Palate" 81 | ], 82 | "Who wrote the most #1 hit singles after Paul McCartney and Lennon?": [ 83 | "Michael Jackson", 84 | "Max Martin", 85 | "Taylor Swift" 86 | ], 87 | "Which one of these Japanese alcoholic drinks is made from rice, yams and wear or brown sugar?": [ 88 | "Umeshu", 89 | "Shochu", 90 | "Chubai" 91 | ], 92 | "In a 1961 speech, JFK announced his moon ambitions, but also acknowledged which other strategy?": [ 93 | "Navy SEAL training", 94 | "Dismantling the CIA", 95 | "Cambodia opium trade" 96 | ] 97 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # HQ_Bot 🤖 2 | ![License: MIT][ico-license] 3 | 4 | A bot to help answer questions on trivia apps like HQ and CashShow. This bot takes screenshot of the game on the phone and uses googles tesseract OCR to read the questions and options. It automates the process of googling of the answers and gives the most likely answer! It is 70%+ accurate! 5 | 6 | Since it is against the policy of HQ-trivia i do not encourage anyone to use this during a live game and this is purely for educational purposes. 7 | 8 | ## Packages Used 9 | 10 | Use python 3.6. In particular the packages/libraries used are... 11 | 12 | * JSON - Data Storage 13 | * Pillow - Image manipulation 14 | * Google-Search-API - Google searching 15 | * wikipediaapi - Wikipedia searches 16 | * pytesseract - Google's free/open source OCR (requires seperate installtion) 17 | * beautifulsoup4 - Parse google searches/html 18 | * lxml - Beautifulsoup parser 19 | * opencv2 - Image maniplulation 20 | * pyscreenshot - Take screenshot of the game 21 | * wxPython - GUI interface 22 | 23 | *To easily install these* 24 | 1. Install python 3.6 25 | 2. Install above packages 26 | * `$ pip3 install -r requirements.txt` 27 | 3. For tesseract 28 | * `$ brew install tesseract` 29 | 4. For opencv 30 | * `$ brew install opencv` 31 | 32 | 33 | ## Usage 34 | 35 | Make sure all packages above are installed. For android phones use [Vysor][link-vysor] and for iOS use quicktime player. **The code expects the phone to be on the left side of the screen.** If you want to change the screenshot co-ordinates change the values inside the ImageGrab in the `screen_grab()` function. To use the script : 36 | 37 | ```bash 38 | $ git clone https://github.com/sushant10/HQ_Bot 39 | $ cd HQ_Bot 40 | $ pip3 install -r requirements.txt 41 | $ python3 answer_bot.py 42 | Press s to screenshot live game, sampq to run against sample questions or q to quit: 43 | s 44 | ...Question... 45 | ``` 46 | ## Screenshots 47 | 48 | 49 | 50 | 51 | 52 | ## Contributing 53 | 54 | All contributions welcome. 55 | 56 | ## Credits 57 | 58 | - [Sushant Rao][link-author] 59 | - [All Contributors][link-contributors] 60 | 61 | ## Special shout out 62 | [Jake Mor][jake-mor] was the person behind HQuack, the most viral popular bot to help solve HQ questions. His implementation inspired me to try my own. I recommend reading this [article][jake-more] to learn more about the whole story. 63 | 64 | ## Useful links 65 | 66 | - [Wikipedia-API][link-wikiapi] 67 | - [Google-Search-API][link-gapi] 68 | - [Tesseract][link-tesseract] 69 | 70 | ## License 71 | 72 | The MIT License (MIT) 73 | 74 | [ico-license]: https://img.shields.io/badge/license-MIT-brightgreen.svg?style=flat-square 75 | [link-vysor]: https://www.vysor.io/ 76 | [link-author]: https://github.com/sushant10 77 | [link-contributors]: ../../contributors 78 | [link-wikiapi]: https://pypi.python.org/pypi/wikipedia 79 | [link-gapi]: https://github.com/abenassi/Google-Search-API 80 | [link-mike]: https://github.com/mikealmond/hq-trivia-assistant 81 | [link-tesseract]: https://github.com/tesseract-ocr/tesseract/wiki 82 | [jake-mor]: http://jakemor.com/ 83 | [jake-more]: https://medium.com/@jakemor/hquack-my-public-hq-trivia-bot-is-shutting-down-5d9fcdbc9f6e 84 | [sampq]: () 85 | -------------------------------------------------------------------------------- /answer_bot.py: -------------------------------------------------------------------------------- 1 | ''' 2 | 3 | TODO: 4 | * Implement normalize func 5 | * Attempt to google wiki \"...\" part of question 6 | * Rid of common appearances in 3 options 7 | * Automate screenshot process 8 | * Implement Asynchio for concurrency 9 | 10 | //Script is in working condition at all times 11 | //TODO is for improving accuracy 12 | 13 | ''' 14 | 15 | # answering bot for trivia HQ and Cash Show 16 | import json 17 | import urllib.request as urllib2 18 | from bs4 import BeautifulSoup 19 | from google import google 20 | from PIL import Image 21 | import pytesseract 22 | import argparse 23 | import cv2 24 | import os 25 | import pyscreenshot as Imagegrab 26 | import sys 27 | import wx 28 | from halo import Halo 29 | 30 | # for terminal colors 31 | class bcolors: 32 | HEADER = '\033[95m' 33 | OKBLUE = '\033[94m' 34 | OKGREEN = '\033[92m' 35 | WARNING = '\033[93m' 36 | FAIL = '\033[91m' 37 | ENDC = '\033[0m' 38 | BOLD = '\033[1m' 39 | UNDERLINE = '\033[4m' 40 | 41 | # sample questions from previous games 42 | sample_questions = {} 43 | 44 | # list of words to clean from the question during google search 45 | remove_words = [] 46 | 47 | # negative words 48 | negative_words= [] 49 | 50 | # GUI interface 51 | def gui_interface(): 52 | app = wx.App() 53 | frame = wx.Frame(None, -1, 'win.py') 54 | frame.SetDimensions(0,0,640,480) 55 | frame.Show() 56 | app.MainLoop() 57 | return None 58 | 59 | # load sample questions 60 | def load_json(): 61 | global remove_words, sample_questions, negative_words 62 | remove_words = json.loads(open("Data/settings.json").read())["remove_words"] 63 | negative_words = json.loads(open("Data/settings.json").read())["negative_words"] 64 | sample_questions = json.loads(open("Data/questions.json").read()) 65 | 66 | # take screenshot of question 67 | def screen_grab(to_save): 68 | # 31,228 485,620 co-ords of screenshot// left side of screen 69 | im = Imagegrab.grab(bbox=(31,228,485,640)) 70 | im.save(to_save) 71 | 72 | # get OCR text //questions and options 73 | def read_screen(): 74 | spinner = Halo(text='Reading screen', spinner='bouncingBar') 75 | spinner.start() 76 | screenshot_file="Screens/to_ocr.png" 77 | screen_grab(screenshot_file) 78 | 79 | #prepare argparse 80 | ap = argparse.ArgumentParser(description='HQ_Bot') 81 | ap.add_argument("-i", "--image", required=False,default=screenshot_file,help="path to input image to be OCR'd") 82 | ap.add_argument("-p", "--preprocess", type=str, default="thresh", help="type of preprocessing to be done") 83 | args = vars(ap.parse_args()) 84 | 85 | # load the image 86 | image = cv2.imread(args["image"]) 87 | gray = cv2.cvtColor(image, cv2.COLOR_BGR2GRAY) 88 | 89 | if args["preprocess"] == "thresh": 90 | gray = cv2.threshold(gray, 0, 255, 91 | cv2.THRESH_BINARY | cv2.THRESH_OTSU)[1] 92 | elif args["preprocess"] == "blur": 93 | gray = cv2.medianBlur(gray, 3) 94 | 95 | # store grayscale image as a temp file to apply OCR 96 | filename = "Screens/{}.png".format(os.getpid()) 97 | cv2.imwrite(filename, gray) 98 | 99 | # load the image as a PIL/Pillow image, apply OCR, and then delete the temporary file 100 | text = pytesseract.image_to_string(Image.open(filename)) 101 | os.remove(filename) 102 | os.remove(screenshot_file) 103 | 104 | # show the output images 105 | 106 | '''cv2.imshow("Image", image) 107 | cv2.imshow("Output", gray) 108 | os.remove(screenshot_file) 109 | if cv2.waitKey(0): 110 | cv2.destroyAllWindows() 111 | print(text) 112 | ''' 113 | spinner.succeed() 114 | spinner.stop() 115 | return text 116 | 117 | # get questions and options from OCR text 118 | def parse_question(): 119 | text = read_screen() 120 | lines = text.splitlines() 121 | question = "" 122 | options = list() 123 | flag=False 124 | 125 | for line in lines : 126 | if not flag : 127 | question=question+" "+line 128 | 129 | if '?' in line : 130 | flag=True 131 | continue 132 | 133 | if flag : 134 | if line != '' : 135 | options.append(line) 136 | 137 | return question, options 138 | 139 | # simplify question and remove which,what....etc //question is string 140 | def simplify_ques(question): 141 | neg=False 142 | qwords = question.lower().split() 143 | if [i for i in qwords if i in negative_words]: 144 | neg=True 145 | cleanwords = [word for word in qwords if word.lower() not in remove_words] 146 | temp = ' '.join(cleanwords) 147 | clean_question="" 148 | #remove ? 149 | for ch in temp: 150 | if ch!="?" or ch!="\"" or ch!="\'": 151 | clean_question=clean_question+ch 152 | 153 | return clean_question.lower(),neg 154 | 155 | 156 | # get web page 157 | def get_page(link): 158 | try: 159 | if link.find('mailto') != -1: 160 | return '' 161 | req = urllib2.Request(link, headers={'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64)'}) 162 | html = urllib2.urlopen(req).read() 163 | return html 164 | except (urllib2.URLError, urllib2.HTTPError, ValueError) as e: 165 | return '' 166 | 167 | # split the string 168 | def split_string(source): 169 | splitlist = ",!-.;/?@ #" 170 | output = [] 171 | atsplit = True 172 | for char in source: 173 | if char in splitlist: 174 | atsplit = True 175 | else: 176 | if atsplit: 177 | output.append(char) 178 | atsplit = False 179 | else: 180 | output[-1] = output[-1] + char 181 | return output 182 | 183 | # normalize points // get rid of common appearances // "quote" wiki option + ques 184 | def normalize(): 185 | return None 186 | 187 | # take screen shot of screen every 2 seconds and check for question 188 | def check_screen(): 189 | return None 190 | 191 | # wait for certain milli seconds 192 | def wait(msec): 193 | return None 194 | 195 | # answer by combining two words 196 | def smart_answer(content,qwords): 197 | zipped= zip(qwords,qwords[1:]) 198 | points=0 199 | for el in zipped : 200 | if content.count(el[0]+" "+el[1])!=0 : 201 | points+=1000 202 | return points 203 | 204 | # use google to get wiki page 205 | def google_wiki(sim_ques, options, neg): 206 | spinner = Halo(text='Googling and searching Wikipedia', spinner='dots2') 207 | spinner.start() 208 | num_pages = 1 209 | points = list() 210 | content = "" 211 | maxo="" 212 | maxp=-sys.maxsize 213 | words = split_string(sim_ques) 214 | for o in options: 215 | 216 | o = o.lower() 217 | original=o 218 | o += ' wiki' 219 | 220 | # get google search results for option + 'wiki' 221 | search_wiki = google.search(o, num_pages) 222 | 223 | link = search_wiki[0].link 224 | content = get_page(link) 225 | soup = BeautifulSoup(content,"lxml") 226 | page = soup.get_text().lower() 227 | 228 | temp=0 229 | 230 | for word in words: 231 | temp = temp + page.count(word) 232 | temp+=smart_answer(page, words) 233 | if neg: 234 | temp*=-1 235 | points.append(temp) 236 | if temp>maxp: 237 | maxp=temp 238 | maxo=original 239 | spinner.succeed() 240 | spinner.stop() 241 | return points,maxo 242 | 243 | 244 | # return points for sample_questions 245 | def get_points_sample(): 246 | simq = "" 247 | x = 0 248 | for key in sample_questions: 249 | x = x + 1 250 | points = [] 251 | simq,neg = simplify_ques(key) 252 | options = sample_questions[key] 253 | simq = simq.lower() 254 | maxo="" 255 | points, maxo = google_wiki(simq, options,neg) 256 | print("\n" + str(x) + ". " + bcolors.UNDERLINE + key + bcolors.ENDC + "\n") 257 | for point, option in zip(points, options): 258 | if maxo == option.lower(): 259 | option=bcolors.OKGREEN+option+bcolors.ENDC 260 | print(option + " { points: " + bcolors.BOLD + str(point) + bcolors.ENDC + " }\n") 261 | 262 | 263 | # return points for live game // by screenshot 264 | def get_points_live(): 265 | neg= False 266 | question,options=parse_question() 267 | simq = "" 268 | points = [] 269 | simq, neg = simplify_ques(question) 270 | maxo="" 271 | m=1 272 | if neg: 273 | m=-1 274 | points,maxo = google_wiki(simq, options, neg) 275 | print("\n" + bcolors.UNDERLINE + question + bcolors.ENDC + "\n") 276 | for point, option in zip(points, options): 277 | if maxo == option.lower(): 278 | option=bcolors.OKGREEN+option+bcolors.ENDC 279 | print(option + " { points: " + bcolors.BOLD + str(point*m) + bcolors.ENDC + " }\n") 280 | 281 | 282 | # menu// main func 283 | if __name__ == "__main__": 284 | load_json() 285 | while(1): 286 | keypressed = input(bcolors.WARNING +'\nPress s to screenshot live game, sampq to run against sample questions or q to quit:\n' + bcolors.ENDC) 287 | if keypressed == 's': 288 | get_points_live() 289 | elif keypressed == 'sampq': 290 | get_points_sample() 291 | elif keypressed == 'q': 292 | break 293 | else: 294 | print(bcolors.FAIL + "\nUnknown input" + bcolors.ENDC) 295 | 296 | 297 | --------------------------------------------------------------------------------