def poi_for_location( lat, lon) -> tuple[t.Optional[str], t.Optional[bytes]]: # no test coverage try: gmaps = googlemaps.Client(key=config.google_api_key) places = gmaps.places_nearby(location=(lat, lon), radius=poi_radius)["results"] except Exception: log.error("Error getting location data", exc_info=True) return None, None place = next( (p for p in places if not poi_types.isdisjoint(p.get("types", []))), None) if not place: log.info("No interesting point of interest") return None, None name = place["name"] try: photo_data = next(p for p in place["photos"] if p["width"] >= 1000) ref = photo_data["photo_reference"] photo_itr = gmaps.places_photo(ref, max_width=2000) photo = b"".join([chunk for chunk in photo_itr if chunk]) except StopIteration: log.info("No poi photo big enough") return name, None except Exception: log.error("Error getting poi photo", exc_info=True) return name, None return name, photo
def send_sync_reminders(conn: connection, slack_client, steps_data) -> None: reminder_users = queries.get_sync_reminder_users(conn) reminder_users_by_id = {user["id"]: user for user in reminder_users} for datum in steps_data: try: user_data = reminder_users_by_id[datum["gargling_id"]] except KeyError: continue msg = ( f"Du gikk {datum['amount']} skritt i går, by my preliminary calculations. " "Husk å synce hvis dette tallet er for lavt. " f"Denne reminderen kan skrus av <{config.server_name}/health|her>. Stay " "beautiful, doll-face!" ) try: resp = slack_client.chat_postMessage( text=msg, channel=user_data["slack_id"] ) if isinstance(resp, Future): # no test coverage # satisfy mypy raise Exception() if resp.data.get("ok") is True: queries.update_reminder_ts( conn, ts=resp.data["ts"], id=datum["gargling_id"] ) conn.commit() except Exception: # no test coverage log.error( f"Error sending sync reminder for user id: {user_data['slack_id']}", exc_info=True, )
def render_map(map_: StaticMap, retry=True) -> t.Optional[Image.Image]: # no test coverage try: img = map_.render() except Exception: if retry: return render_map(map_, retry=False) log.error("Error rendering map", exc_info=True) img = None return img
def address_for_location( lat, lon) -> tuple[t.Optional[str], t.Optional[str]]: # no test coverage geolocator = Nominatim(user_agent=config.bot_name) try: location = geolocator.reverse(f"{lat}, {lon}", language="en") address = location.address country = location.raw.get("address", {}).get("country") return address, country except Exception: log.error("Error getting address for location", exc_info=True) return None, None
def get_body_data(users: list[HealthUser], date: pendulum.Date) -> list[dict]: all_data = [] for user in users: # no test coverage try: body_data = user.body(date) except Exception: log.error( f"Error getting {user.service.name} body data for {user.first_name}", exc_info=True, ) else: if body_data is None: continue body_data["first_name"] = user.first_name all_data.append(body_data) return all_data
def delete_sync_reminders(conn: connection, slack_client) -> None: reminder_users = queries.get_sync_reminder_users(conn) log.info(reminder_users) for user in reminder_users: if user["last_sync_reminder_ts"] is None: # no test coverage continue try: slack_client.chat_delete( channel=user["slack_id"], ts=user["last_sync_reminder_ts"] ) except Exception: # no test coverage log.error( f"Error deleting sync reminder for user id: {user['id']}", exc_info=True, ) queries.update_reminder_ts(conn, ts=None, id=user["id"]) conn.commit()
def weight_blocks(clients) -> t.List[dict]: now = pendulum.now() blocks = [] for name, (client, service) in clients.items(): try: weight_data = service.value.weight(client) except Exception: log.error(f"Error getting {service} weight data for {name}", exc_info=True) continue if weight_data is None: continue elapsed = (now - weight_data["datetime"]).days if elapsed < 2: desc = f"{name} veier nå *{weight_data['weight']}* kg!" else: desc = f"{name} har ikke veid seg på *{elapsed}* dager. Skjerpings!" blocks.append({"type": "section", "text": {"type": "mrkdwn", "text": desc}}) return blocks
def main() -> None: log.info("GargBot 3000 greeter starter") try: while True: event = Event.next() log.info(f"Next greeting check at: {event.until.end}, " f"sleeping for {event.until.in_words()}") time.sleep(event.until.total_seconds()) try: slack_client = SlackClient(config.slack_bot_user_token) conn = database.connect() event.func(conn, slack_client) except Exception: log.error("Error in command execution", exc_info=True) finally: conn.close() except KeyboardInterrupt: sys.exit()
def street_view_for_location(lat, lon) -> t.Optional[bytes]: # no test coverage def encode_url(domain, endpoint, params): params = params.copy() url_to_sign = endpoint + urllib.parse.urlencode(params) secret = config.google_api_secret decoded_key = base64.urlsafe_b64decode(secret) signature = hmac.new(decoded_key, url_to_sign.encode(), hashlib.sha1) encoded_signature = base64.urlsafe_b64encode(signature.digest()) params["signature"] = encoded_signature.decode() encoded_url = domain + endpoint + urllib.parse.urlencode(params) return encoded_url domain = "https://maps.googleapis.com" metadata_endpoint = "/maps/api/streetview/metadata?" img_endpoint = "/maps/api/streetview?" params = { "size": "600x400", "location": f"{lat}, {lon}", "fov": 120, "heading": 251.74, "pitch": 0, "key": config.google_api_key, } metadata_url = encode_url(domain, metadata_endpoint, params) try: response = requests.get(metadata_url) metadata = response.json() if metadata["status"] != "OK": log.info(f"Metadata indicates no streetview image: {metadata}") return None except Exception: log.error("Error downloading streetview image metadata", exc_info=True) return None photo_url = encode_url(domain, img_endpoint, params) try: response = requests.get(photo_url) data = response.content except Exception: log.error("Error downloading streetview image", exc_info=True) return None return data
def main(options: t.Optional[dict], debug: bool = False): try: app.pool.setup() with app.pool.get_connection() as conn: pictures.queries.define_args(conn) conn.commit() app.dbx = pictures.connect_dbx() if debug is False: gunicorn_app = StandaloneApplication(app, options) gunicorn_app.run() else: # Workaround for a werzeug reloader bug # (https://github.com/pallets/flask/issues/1246) os.environ["PYTHONPATH"] = os.getcwd() app.run(debug=True) except Exception: log.error("Error in server setup", exc_info=True) finally: if app.pool.is_setup: app.pool.closeall()
def main(): slack_client, drop_pics, quotes_db, db_connection = setup() log.info("GargBot 3000 task operational!") try: while True: time.sleep(1) text, channel, user = wait_for_slack_output(slack_client) try: command_str, *args = text.split() except ValueError: command_str = "" args = [] command_str = command_str.lower() command_func = partial( commands.execute, command_str=command_str, args=args, db_connection=db_connection, drop_pics=drop_pics, quotes_db=quotes_db, ) try: response = command_func() except psycopg2.OperationalError as op_exc: db_connection = database_manager.connect_to_database() try: return command_func() except Exception as exc: # OperationalError not caused by connection issue. log.error("Error in command execution", exc_info=True) return commands.cmd_panic(exc) send_response(slack_client, response, channel) except KeyboardInterrupt: sys.exit() finally: database_manager.close_database_connection(db_connection)
def execute( command_str: str, args: List, db_connection: connection, drop_pics: droppics.DropPics, quotes_db: quotes.Quotes, ) -> Dict: log.info(f"command: {command_str}") log.info(f"args: {args}") switch: Dict[str, Callable] = { "ping": cmd_ping, "new_channel": cmd_welcome, "gargbot": cmd_server_explanation, "hvem": partial(cmd_hvem, args, db=db_connection), "pic": partial(cmd_pic, args, db=db_connection, drop_pics=drop_pics), "quote": partial(cmd_quote, args, db=db_connection, quotes_db=quotes_db), "msn": partial(cmd_msn, args, db=db_connection, quotes_db=quotes_db), } try: command_func = switch[command_str] except KeyError: command_func = partial(cmd_not_found, command_str) try: return command_func() except psycopg2.OperationalError: raise except SSLError as ssl_exc: # Dropbox sometimes gives SSLerrors, try again: try: return command_func() except Exception as exc: log.error("Error in command execution", exc_info=True) return cmd_panic(exc) except Exception as exc: log.error("Error in command execution", exc_info=True) return cmd_panic(exc)
def steps(conn: connection, users: list[HealthUser], date: pendulum.Date) -> list[dict]: step_amounts = [] for user in users: try: amount = ( user.steps(date) if not isinstance(user, PolarUser) else user.steps(date, conn) ) except Exception: log.error( f"Error getting {user.service.name} steps data for {user.first_name}", exc_info=True, ) continue if amount is None: # no test coverage continue step_amounts.append( {"amount": amount, "gargling_id": user.gargling_id}, ) # no test coverage return step_amounts
def execute(command_str: str, args: t.List, conn: connection, dbx: dropbox.Dropbox) -> t.Dict: log.info(f"command: {command_str}") log.info(f"args: {args}") switch: t.Dict[str, t.Callable] = { "ping": cmd_ping, "new_channel": cmd_welcome, "gargbot": cmd_server_explanation, "hvem": partial(cmd_hvem, args, conn=conn), "pic": partial(cmd_pic, args, conn=conn, dbx=dbx), "forum": partial(cmd_forum, args, conn=conn), "msn": partial(cmd_msn, args, conn=conn), } try: command_func = switch[command_str] except KeyError: command_func = partial(cmd_not_found, command_str) try: return command_func() except psycopg2.OperationalError: raise except (SSLError, dropbox.exceptions.ApiError): # Dropbox sometimes gives SSLerrors, (or ApiError if file not there) try again: try: log.error("SSLerror/ApiError, retrying", exc_info=True) return command_func() except Exception as exc: log.error("Error in command execution", exc_info=True) return cmd_panic(exc) except Exception as exc: log.error("Error in command execution", exc_info=True) return cmd_panic(exc)
def main(): slack_client, dbx, conn = setup() log.info("GargBot 3000 task operational!") try: while True: time.sleep(1) text, channel, user, thread_ts = wait_for_slack_output( slack_client) try: command_str, *args = text.split() except ValueError: command_str = "" args = [] command_str = command_str.lower() command_func = partial(commands.execute, command_str=command_str, args=args, conn=conn, dbx=dbx) try: response = command_func() except psycopg2.OperationalError: conn = database.connect() try: response = command_func() except Exception as exc: # OperationalError not caused by connection issue. log.error("Error in command execution", exc_info=True) response = commands.cmd_panic(exc) send_response(slack_client, response, channel, thread_ts) except KeyboardInterrupt: sys.exit() finally: conn.close()
def steps_blocks(clients) -> t.List[dict]: step_amounts = [] for name, (client, service) in clients.items(): try: steps = service.value.steps(client) except Exception: log.error(f"Error getting {service} steps data for {name}", exc_info=True) continue if steps is None: continue step_amounts.append((steps, name)) if not step_amounts: return [] step_amounts.sort(reverse=True) steps, name = step_amounts[0] desc = f"{name} gikk *{steps}* skritt i går. " if len(step_amounts) > 1: desc = desc.replace("gikk", "(:star:) gikk") desc += ", ".join( [f"{name} gikk *{steps}* skritt" for steps, name in step_amounts[1:]] ) desc += "." return [{"type": "section", "text": {"type": "mrkdwn", "text": desc}}]