├── 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 |
--------------------------------------------------------------------------------