diff --git a/.gitignore b/.gitignore index 51b987f..6538044 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ /trivia.questions /trivia.scores /config.json +/brids.urls diff --git a/main.py b/main.py index d6ff074..1162f7b 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,19 @@ +# { +# 'speciesCode': 'dowwoo', +# 'comName': 'Downy Woodpecker', +# 'sciName': 'Dryobates pubescens', +# 'locId': 'L36986367', +# 'locName': 'Home', +# 'obsDt': '2024-12-14 17:06', +# 'howMany': 1, +# 'lat': 36.0006572, +# 'lng': -95.0865753, +# 'obsValid': True, +# 'obsReviewed': False, +# 'locationPrivate': True, +# 'subId': 'S205413945' +# } + from random import choice import requests from time import sleep @@ -15,7 +31,7 @@ host = "localhost" port = 6667 nick = config["nick"] realname = "a bot by ~nebula" -helptext = "!trivia, !trscores, !aitrivia, !aiscores for trivia game. contact ~nebula for help, feedback or problem reports. https://git.tilde.town/nebula/mysterious_cube" +helptext = "!birds, !trivia, !trscores, !aitrivia, !aiscores. contact ~nebula for help, feedback or problem reports. https://git.tilde.town/nebula/mysterious_cube" channels = config["channels"] channel_re = re.compile(r"PRIVMSG (#*\w+)") @@ -28,12 +44,23 @@ llama_headers = { "Authorization": config["llama_key"] } +geonames_url = "http://api.geonames.org/searchJSON" +geonames_user = config["geonames_user"] + +ebird_url = "https://api.ebird.org/v2/data/obs/geo/recent" +ebird_key = config["ebird_key"] + +google_url = "https://customsearch.googleapis.com/customsearch/v1" +google_key = config["google_key"] +google_cx = config["google_cx"] + trivia_questions_file = "trivia.questions" trivia_state_file = "trivia.state" trivia_score_file = "trivia.scores" trivia_unselected_file = "trivia.unselected" ai_state_file = "trivia.aistate" ai_score_file = "trivia.aiscores" +bird_url_file = "brids.urls" try: with open(trivia_questions_file, "r") as f: @@ -71,6 +98,12 @@ try: except FileNotFoundError: ai_scores = {} +try: + with open(bird_url_file, "r") as f: + bird_urls = load(f) +except FileNotFoundError: + bird_urls = {} + def write_state(): with open(trivia_state_file, "w") as f: dump(trivia_state, f) @@ -82,6 +115,68 @@ def write_state(): dump(ai_scores, f) with open(ai_state_file, "w") as f: dump(ai_state, f) + with open(bird_url_file, "w") as f: + dump(bird_urls, f) + +def get_location(query): + params = { + "username": geonames_user, + "q": query, + "maxRows": 1 + } + try: + response = requests.get(geonames_url, params=params) + data = response.json()["geonames"][0] + if "lat" not in data: + return None + else: + return data + except IndexError: + return None + +def cache_bird_url(sciName): + global bird_urls + if sciName in bird_urls.keys(): + return bird_urls[sciName] + search = requests.get(google_url, params={ + "q": sciName, + "key": google_key, + "cx": google_cx + }) + data = search.json() + try: + link = data["items"][0]["link"] + bird_urls[sciName] = link + write_state() + return link + except (IndexError, KeyError): + return None + +def get_birds(location): + params = { + "key": ebird_key, + "dist": 50, + "lat": location["lat"], + "lng": location["lng"] + } + request = requests.get(ebird_url, params=params) + data = request.json()[:4] + line = f"Location: {location['name']}; " + for sighting in data: + url = cache_bird_url(sighting['sciName']).rstrip("/id") + line += f"{sighting['comName']} [ {url} ]; " + return line.rstrip("; ") + +def post_birds(channel, username, arguments): + if not arguments: + return "Posts recently sighted birds. Give a location name with this command, eg !birds Dodge City, KS" + location = get_location(arguments) + if not location: + return f"No data found for {arguments}" + birds = get_birds(location) + if not birds: + return f"No data found for {arguments}" + return birds def get_question(ai_enabled=False): global trivia_questions @@ -274,7 +369,9 @@ class IRCBot(): self.sendline(f"JOIN {channel}") def sendline(self, line): - return self.s.send(bytes(f"{line}\r\n", "UTF-8")) + if line: + return self.s.send(bytes(f"{line}\r\n", "UTF-8")) + return None def send(self, channel, content): if isinstance(content, list): @@ -310,22 +407,24 @@ class IRCBot(): name = None if name and not channel.startswith("#"): channel = name - for command, callback in self.commands: - if line.lower().endswith(command): - try: - arguments = line[line.index(command) + len(command) + 1:] - except (IndexError, ValueError): - arguments = None - result = callback(channel, name, arguments) - if result: - self.send(channel, result) try: message_body = line[line.index(" :") + 2:] except (IndexError, ValueError): message_body = "" if message_body: for callback in self.searchers: - callback(message_body) + result = callback(message_body) + if result: + self.send(channel, result) + for command, callback in self.commands: + if message_body.lower().startswith(command): + try: + arguments = line[line.index(command) + len(command) + 1:] + except (IndexError, ValueError): + arguments = None + result = callback(channel, name, arguments) + if result: + self.send(channel, result) def run(): bot = IRCBot( @@ -335,6 +434,7 @@ def run(): [ # endswith commands ("!help", post_help), ("!rollcall", post_help), + ("!birds", post_birds), ("!trivia", post_question), ("!aitrivia", post_ai_question), ("!trscores", post_top_scores), @@ -354,5 +454,5 @@ def run(): sleep(0.5) bot.command_loop() -if __name__ == "__main__": - run() +# if __name__ == "__main__": +# run()