Пример #1
0
def pic(args: t.Optional[str] = None):
    gargling_id = get_jwt_identity()
    log.info(gargling_id)
    arg_list = args.split(",") if args is not None else []
    with app.pool.get_connection() as conn:
        pic_url, *_ = pictures.get_pic(conn, app.dbx, arg_list=arg_list)
    return jsonify({"url": pic_url})
Пример #2
0
    def add_faces_in_pic(self, cursor: LoggingCursor, pic: Path,
                         dbx_path: str):
        sql_command = "SELECT pic_id FROM dbx_pictures WHERE path = %(path)s"
        data = {"path": dbx_path}
        cursor.execute(sql_command, data)
        try:
            pic_id = cursor.fetchone()["pic_id"]
        except KeyError:
            log.info(f"pic not in db: {dbx_path}")
            return

        sql_command = "SELECT * FROM dbx_pictures_faces WHERE pic_id = %(pic_id)s"
        data = {"pic_id": pic_id}
        cursor.execute(sql_command, data)
        result = cursor.fetchone()
        if result is not None:
            log.info(f"{dbx_path} pic faces already in db with id {pic_id}")
            return

        tags = DropPics.get_tags(pic)
        if tags is None:
            return
        faces = set(tags).intersection(self.firstname_to_db_id)
        for face in faces:
            db_id = self.firstname_to_db_id[face]
            sql_command = ("INSERT INTO dbx_pictures_faces (db_id, pic_id) "
                           "VALUES (%(db_id)s, %(pic_id)s);")
            data = {"db_id": db_id, "pic_id": pic_id}
            cursor.execute(sql_command, data)
Пример #3
0
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)
Пример #4
0
    def add_pics_in_folder(self, folder: Path, topic: str,
                           dbx_folder: str) -> None:
        if not self.conn:
            raise Exception
        cursor = self.conn.cursor()
        for pic in folder.iterdir():
            if not pic.suffix.lower() in {".jpg", ".jpeg"}:
                continue
            dbx_path = dbx_folder + "/" + pic.name.lower()

            sql_command = "SELECT pic_id FROM dbx_pictures WHERE path = %(path)s"
            data = {"path": dbx_path}
            cursor.execute(sql_command, data)
            if cursor.fetchone() is not None:
                log.info(f"{dbx_path} pic already in db")
                continue

            date_obj = DropPics.get_date_taken(pic)
            timestr = date_obj.strftime("%Y-%m-%d %H:%M:%S")

            sql_command = """INSERT INTO dbx_pictures (path, topic, taken)
            VALUES (%(path)s,
                   %(topic)s,
                   %(taken)s);"""
            data = {"path": dbx_path, "topic": topic, "taken": timestr}
            cursor.execute(sql_command, data)

            self.add_faces_in_pic(cursor, pic, dbx_path)
        self.conn.commit()
Пример #5
0
def send_response(slack_client: SlackClient, response: Dict, channel: str):
    log.info(dt.datetime.now())
    log.info(f"response: {response}")
    slack_client.api_call("chat.postMessage",
                          channel=channel,
                          as_user=True,
                          **response)
Пример #6
0
def send_congrats(conn: connection, slack_client: SlackClient):
    dbx = pictures.connect_dbx()
    recipients = todays_recipients(conn)
    log.info(f"Recipients today {recipients}")
    for recipient in recipients:
        greet = formulate_congrat(recipient, conn, dbx)
        task.send_response(slack_client, greet, channel=config.main_channel)
Пример #7
0
def main():  # no test coverage
    try:
        log.info("Starting gargbot_3000")
        parser = argparse.ArgumentParser()
        parser.add_argument("--mode", "-m")
        parser.add_argument("--debug", "-d", action="store_true")
        parser.add_argument("--bind", "-b", default="0.0.0.0")
        parser.add_argument("--workers", "-w", default=3)
        parser.add_argument("--port", "-p", default=":5000")
        args = parser.parse_args()

        if args.mode == "server":
            options = {
                "bind": "%s%s" % (args.bind, args.port),
                "workers": args.workers
            }
            server.main(options=options, debug=args.debug)
        elif args.mode == "scheduler":
            scheduler.main()
        elif args.mode == "migrate":
            database.migrate()
        else:
            raise Exception(f"Incorrect mode, {args.mode}")

    except Exception as exc:
        log.exception(exc)
        raise
Пример #8
0
def delete_ephemeral(response_url):
    delete_original = {
        "response_type": "ephemeral",
        "replace_original": True,
        "text": "Sharing is caring!",
    }
    r = requests.post(response_url, json=delete_original)
    log.info(r.text)
Пример #9
0
def backup():  # no test coverage
    log.info("Backing up database")
    cmd = f"pg_dump --no-owner --dbname={config.db_uri}"
    result = subprocess.check_output(cmd, shell=True)
    dbx = Dropbox(config.dropbox_token)
    date = pendulum.now().date()
    filename = f"{config.db_name}_{date.year}_{date.month}_{date.day}.sql"
    path = config.dbx_db_backup_folder / filename
    dbx.files_upload(f=result, path=path.as_posix(), autorename=True)
Пример #10
0
def send_response(
    slack_client,
    response: dict,
    channel: str,
    thread_ts: t.Optional[str] = None,
):  # no test coverage
    log.info("Sending to slack: ", response)
    slack_client.chat_postMessage(channel=channel,
                                  thread_ts=thread_ts,
                                  **response)
Пример #11
0
    def main(self, cursor):
        for fname in os.listdir(os.path.join(config.home, "data", "logs")):
            if not fname.lower().endswith(".xml"):
                continue

            log.info(fname)
            for message_data in MSN.parse_log(fname):
                MSN.add_entry(cursor, *message_data)

        self.conn.commit()
Пример #12
0
def authorize(service_name: str):
    gargling_id = get_jwt_identity()
    if gargling_id is None:  # no test coverage
        raise Exception("JWT token issued to None")
    log.info(f"gargling_id: {gargling_id}")
    service = init_service(service_name)
    url = service.authorization_url()
    response = jsonify(is_registered=False, auth_url=url)
    log.info(response)
    return response
Пример #13
0
def migrate() -> None:
    conn = connect()
    queries = aiosql.from_path("sql/migrations.sql", "psycopg2")
    queries.migrations(conn)
    conn.commit()
    remaining_diffs = get_migrations()
    remaining_diffs.set_safety(False)
    remaining_diffs.add_all_changes()
    if remaining_diffs.statements:
        log.info(remaining_diffs.sql)
Пример #14
0
 def steps(self, date: pendulum.Date) -> t.Optional[int]:
     start_dt = pendulum.datetime(date.year, date.month,
                                  date.day).in_timezone(config.tz)
     start_ms = start_dt.timestamp() * 1000
     end_ms = start_dt.add(days=1).timestamp() * 1000
     data = self._steps_api_call(start_ms, end_ms)
     log.info(data)
     try:
         return data["bucket"][0]["dataset"][0]["point"][0]["value"][0][
             "intVal"]
     except IndexError:
         return 0
Пример #15
0
 def weight(client: FitbitApi) -> t.Optional[dict]:
     data = client.get_bodyweight(period="7d")
     if len(data["weight"]) == 0:
         log.info("No weight data")
         return None
     entries = data["weight"]
     for entry in entries:
         entry["datetime"] = pendulum.parse(f"{entry['date']}T{entry['time']}")
     entries.sort(key=itemgetter("datetime"), reverse=True)
     most_recent = entries[0]
     log.info(f"weight data: {most_recent}")
     return most_recent
Пример #16
0
def send_congrats() -> None:  # no test coverage
    dbx = pictures.connect_dbx()
    conn = database.connect()
    recipients = todays_recipients(conn)
    if not recipients:
        return
    log.info(f"Recipients today {recipients}")
    slack_client = slack.WebClient(config.slack_bot_user_token)
    for recipient in recipients:
        greet = formulate_congrat(recipient, conn, dbx)
        commands.send_response(slack_client,
                               greet,
                               channel=config.main_channel)
Пример #17
0
def get_pic(
        conn: connection, dbx: Dropbox,
        arg_list: t.Optional[t.List[str]]) -> t.Tuple[str, dt.datetime, str]:
    description = ""

    if not arg_list:
        url, taken_at = get_random_pic(conn, dbx)
        return url, taken_at, description

    args = {arg.lower() for arg in arg_list}
    parsed = queries.parse_args(conn, args=list(args))
    valid_args, invalid_args = sortout_args(args, **parsed)

    if invalid_args:
        all_args = queries.get_possible_args(conn)
        description = get_description_for_invalid_args(invalid_args,
                                                       **all_args)
        if not valid_args:
            description += "Her er et tilfeldig bilde i stedet."
            url, taken_at = get_random_pic(conn, dbx)
            return url, taken_at, description

    data = queries.pic_for_topic_year_garglings(conn, **parsed)
    if data is not None:
        valid_args_fmt = ", ".join(f"`{arg}`" for arg in valid_args)
        description += f"Her er et bilde med {valid_args_fmt}."
        path = data["path"]
        taken_at = data["taken_at"]
        url = get_url_for_dbx_path(dbx, path)
        return url, taken_at, description

    # No pics found for arg-combination. Reduce args until pic found
    valid_args_fmt = ", ".join(f"`{arg}`" for arg in valid_args)
    description += f"Fant ikke bilde med {valid_args_fmt}. "
    for arg_combination in reduce_arg_combinations(valid_args):
        log.info(f"arg_combination: {arg_combination}")
        parsed = queries.parse_args(conn, args=list(arg_combination))
        data = queries.pic_for_topic_year_garglings(conn, **parsed)
        if data is None:
            continue

        arg_combination_fmt = ", ".join(f"`{arg}`" for arg in arg_combination)
        description += f"Her er et bilde med {arg_combination_fmt} i stedet."
        taken_at = data["taken_at"]
        url = get_url_for_dbx_path(dbx, path=data["path"])
        return url, taken_at, description

    #  No pics found for any args
    url, taken_at = get_random_pic(conn, dbx)
    return url, taken_at, description
Пример #18
0
def send_response(
    slack_client: SlackClient,
    response: t.Dict,
    channel: str,
    thread_ts: t.Optional[str] = None,
):
    log.info(dt.datetime.now())
    log.info(f"response: {response}")
    slack_client.api_call(
        "chat.postMessage",
        channel=channel,
        as_user=True,
        thread_ts=thread_ts,
        **response,
    )
Пример #19
0
def migrate() -> None:  # no test coverage
    conn = connect()
    queries = aiosql.from_path("sql/migrations.sql", "psycopg2")
    queries.migrations(conn)
    conn.commit()
    previous_level = logging.root.manager.disable  # type: ignore
    logging.disable(logging.CRITICAL)
    remaining_diffs = get_migrations()
    remaining_diffs.set_safety(False)
    remaining_diffs.add_all_changes()
    logging.disable(previous_level)
    if remaining_diffs.statements:
        log.info(remaining_diffs.sql)
    else:
        log.info("Migrations up to date")
Пример #20
0
def interactive():
    log.info("incoming interactive request:")
    data = json.loads(request.form["payload"])
    log.info(data)
    if not data.get("token") == config.slack_verification_token:
        return Response(status=403)
    action = data["actions"][0]["name"]
    if action in {"share", "shuffle", "cancel"}:
        result = handle_share_interaction(action, data)
    elif action in {"pic", "quote", "msn"}:
        trigger_id = data["trigger_id"]
        result = handle_command(command_str=action,
                                args=[],
                                trigger_id=trigger_id)
    return json_response(result)
Пример #21
0
def handle_message(event_data):  # no test coverage
    log.info("Receiving Slack event")
    log.info(event_data)
    AT_BOT = f"<@{config.bot_id}>"
    message = event_data["event"]
    channel = message["channel"]
    text = message.get("text").replace(AT_BOT, "").strip()
    if message.get("subtype") is not None:
        return
    try:
        command_str, *args = text.replace("@", "").lower().split()
    except ValueError:
        command_str = ""
        args = []
    result = handle_command(command_str, args, buttons=False)
    commands.send_response(app.slack_client, result, channel)
Пример #22
0
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()
Пример #23
0
 def weight(client: WithingsApi) -> t.Optional[dict]:
     return None
     data = client.measure_get_meas(
         startdate=pendulum.today().subtract(weeks=1),
         enddate=pendulum.today(),
         meastype=MeasureType.WEIGHT,
     ).measuregrps
     if not data:
         log.info("No weight data")
         return None
     data = sorted(data, key=attrgetter("date"), reverse=True)
     most_recent = data[0]
     measure = most_recent.measures[0]
     return {
         "weight": float(measure.value * pow(10, measure.unit)),
         "datetime": most_recent.date,
     }
Пример #24
0
def handle_redirect(service_name: str):
    gargling_id = get_jwt_identity()
    if gargling_id is None:
        raise Exception("JWT token issued to None")
    log.info(f"gargling_id: {gargling_id}")
    service = Service[service_name]
    service_user_id, token = service.value.handle_redirect(request)
    with current_app.pool.get_connection() as conn:
        service.value.persist_token(token, conn)
        queries.match_ids(
            conn,
            gargling_id=gargling_id,
            service_user_id=service_user_id,
            service=service.name,
        )
        conn.commit()
    return Response(status=200)
Пример #25
0
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()
Пример #26
0
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
Пример #27
0
def handle_congrats(slack_client: SlackClient, drop_pics):
    db_connection = database_manager.connect_to_database()
    birthdays = congrats.get_birthdays(db_connection)
    db_connection.close()
    for birthday in itertools.cycle(birthdays):
        log.info(f"Next birthday: {birthday.nick}, at {birthday.next_bday}")
        try:
            time.sleep(birthday.seconds_to_bday())
        except OverflowError:
            log.info(
                f"Too long sleep length for OS. Restart before next birthday, at {birthday.next_bday}"
            )
            break
        db_connection = database_manager.connect_to_database()
        response = congrats.get_greeting(birthday, db_connection, drop_pics)
        send_response(slack_client,
                      response=response,
                      channel=config.main_channel)
        db_connection.close()
Пример #28
0
def authorize(service_name: str):
    gargling_id = get_jwt_identity()
    if gargling_id is None:
        raise Exception("JWT token issued to None")
    log.info(f"gargling_id: {gargling_id}")
    with current_app.pool.get_connection() as conn:
        data = queries.is_registered(
            conn, gargling_id=gargling_id, service=service_name
        )
    if data is None:
        log.info("not registered")
        url = Service[service_name].value.authorize_user()
        log.info(url)
        response = jsonify(auth_url=url)
    else:
        report_enabled = data["enable_report"]
        log.info(f"registered, report enabled: {report_enabled}")
        response = jsonify(report_enabled=report_enabled)
    log.info(response)
    return response
Пример #29
0
 def _steps_api_call(self, date: pendulum.Date) -> dict:  # no test coverage
     kwargs = {
         "resource": "activities/steps",
         "base_date": date,
         "period": "1d"
     }
     exc = None
     data = None
     for _ in range(10):
         try:
             data = self.client.time_series(**kwargs)
             break
         except fitbit.exceptions.HTTPServerError as e:
             log.info("Error fetching fitbit data. Retrying")
             exc = e
             continue
     if data is None:
         assert exc is not None
         raise exc
     return data
Пример #30
0
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)