Skip to content

Commit

Permalink
Merge pull request #14 from stepanzubkov/dev
Browse files Browse the repository at this point in the history
Version v0.9.0: Basic pastes commands
  • Loading branch information
stepanzubkov authored Jul 1, 2023
2 parents 4007b68 + 8fac01c commit 5687be1
Show file tree
Hide file tree
Showing 14 changed files with 476 additions and 56 deletions.
3 changes: 2 additions & 1 deletion florgon_cc_cli/commands/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
from .logout import logout
from .host import host
from .config import config
from .paste import paste

__all__ = ["url", "login", "logout", "host", "config"]
__all__ = ["url", "login", "logout", "host", "config", "paste"]
177 changes: 177 additions & 0 deletions florgon_cc_cli/commands/paste.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
"""
Pastes management command.
"""
from io import TextIOWrapper
from datetime import datetime
from typing import List, Optional

import click

from florgon_cc_cli.services.config import get_access_token
from florgon_cc_cli.services.paste import (
build_paste_open_url,
create_paste,
get_pastes_list,
request_hash_from_pastes_list,
get_paste_info_by_hash,
delete_paste_by_hash,
extract_hash_from_paste_short_url,
)
from florgon_cc_cli.services.files import concat_files


@click.group()
def paste():
"""
Command that interacts with single paste or list.
"""


@paste.command()
@click.option("-o", "--only-url", is_flag=True, default=False, help="Outputs single url to paste.")
@click.option(
"-d", "--do-not-save", is_flag=True, default=False, help="Do not save paste in local history."
)
@click.option(
"-s",
"--stats-is-public",
is_flag=True,
default=False,
help="Make paste stats public. Auth required.",
)
@click.option(
"-b",
"--burn-after-read",
is_flag=True,
default=False,
help="Deletes paste after first reading.",
)
@click.option(
"-f",
"--from-file",
"from_files",
type=click.File("r"),
multiple=True,
help="Read paste from file.",
)
@click.option("-t", "--text", type=str, help="Paste text.")
def create(
only_url: bool,
do_not_save: bool,
stats_is_public: bool,
burn_after_read: bool,
text: Optional[str],
from_files: List[TextIOWrapper],
):
"""Creates paste from text or file."""
if from_files and text:
click.secho("Pass --from-file or --text, but not both!", fg="red", err=True)
return
if not from_files and not text:
click.secho("Pass --from-file or --text!", fg="red", err=True)
return
if from_files:
text = concat_files(from_files)

access_token = get_access_token()
if stats_is_public and access_token is None:
click.secho("Auth required for --stats-is-public flag!", fg="red", err=True)
return

success, response = create_paste(
text,
stats_is_public=stats_is_public,
burn_after_read=burn_after_read,
access_token=access_token,
)
if not success:
click.secho(response["message"], err=True, fg="red")
return

short_url = build_paste_open_url(response["hash"])
if only_url:
click.echo(short_url)
return

click.echo("Short url: " + click.style(short_url, fg="green"))
click.echo(f"Text: \n{response['text']}")
if response["burn_after_read"]:
click.secho("This paste will burn after reading!", fg="bright_yellow")
click.echo(f"Expires at: {datetime.fromtimestamp(response['expires_at'])}")
if response["stats_is_public"]:
click.echo("Stats is public")


@paste.command()
@click.option(
"-e", "--exclude-expired", is_flag=True, default=False, help="Do not show expired pastes."
)
def list(exclude_expired: bool):
"""Prints a list of your pastes. Auth expired."""
success, response = get_pastes_list(access_token=get_access_token())
if not success:
click.secho(response["message"], err=True, fg="red")
return

click.echo("Your pastes:")
for paste in response:
# NOTE: This is temporary solution. Should be moved to cc-api.
if paste["is_expired"] and exclude_expired:
continue

text_preview = paste["text"].split("\n")[0][:50] + "..."
if paste["is_expired"]:
click.secho(
f"{build_paste_open_url(paste['hash'])} - {text_preview} (expired)", fg="red"
)
else:
click.echo(f"{build_paste_open_url(paste['hash'])} - {text_preview}")


@paste.command()
@click.option("-s", "--short_url", type=str, help="Short url.")
@click.option("-o", "--only-text", is_flag=True, default=False, help="Prints only paste text.")
def read(short_url, only_text):
"""Prints text and info about paste."""
if short_url:
short_url_hash = extract_hash_from_paste_short_url(short_url)
else:
click.echo("Short url is not specified, requesting for list of your pastes.")
short_url_hash = request_hash_from_pastes_list(access_token=get_access_token())

success, response = get_paste_info_by_hash(short_url_hash)
if not success:
click.secho(response["message"], err=True, fg="red")
return
if only_text:
click.echo("Text:\n" + response["text"].replace("\\n", "\n"))
return
click.echo(f"Expires at: {datetime.fromtimestamp(response['expires_at'])}")
if response["stats_is_public"]:
click.echo("Stats is public")
if response["burn_after_read"]:
click.secho("This paste will burn after reading!", fg="bright_yellow")
click.echo("Text:\n" + response["text"].replace("\\n", "\n"))


@paste.command()
@click.option("-s", "--short-url", type=str, help="Short url.")
def delete(short_url: str):
"""
Deletes paste. Auth Required.
"""
if short_url:
short_url_hash = extract_hash_from_paste_short_url(short_url)
else:
click.echo("Short url is not specified, requesting for list of your pastes.")
short_url_hash = request_hash_from_pastes_list(access_token=get_access_token())

success, *response = delete_paste_by_hash(
hash=short_url_hash,
access_token=get_access_token(),
)
if not success:
click.secho(response[0]["message"], err=True, fg="red")
return
else:
click.secho("Paste was successfully deleted!", fg="green")
24 changes: 9 additions & 15 deletions florgon_cc_cli/commands/url.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

import click

from florgon_cc_cli.services.config import get_value_from_config
from florgon_cc_cli.services.config import get_value_from_config, get_access_token
from florgon_cc_cli.services.url import (
build_open_url,
create_url,
Expand All @@ -21,17 +21,14 @@

@click.group()
def url():
"""Commands that interacts with single url."""
"""Commands that interacts with single url or list."""


@url.command()
@click.option("-o", "--only-url", is_flag=True, default=False, help="Outputs single short url.")
@click.option(
"-d", "--do-not-save", is_flag=True, default=False, help="Do not save url in local history."
)
@click.option(
"-a", "--anonymous", is_flag=True, default=False, help="Do not use access token for request."
)
@click.option(
"-s",
"--stats-is-public",
Expand All @@ -40,11 +37,9 @@ def url():
help="Make url stats public. Auth required.",
)
@click.argument("long_url", type=str)
def create(
only_url: bool, do_not_save: bool, long_url: str, anonymous: bool, stats_is_public: bool
):
def create(only_url: bool, do_not_save: bool, long_url: str, stats_is_public: bool):
"""Creates short url."""
access_token = None if anonymous else get_value_from_config("access_token")
access_token = get_access_token()
if stats_is_public and access_token is None:
click.secho("Auth required for --stats-is-public flag!", fg="red", err=True)
return
Expand Down Expand Up @@ -119,7 +114,7 @@ def stats(short_url: str, referers_as: str, dates_as: str):
short_url_hash,
url_views_by_referers_as=referers_as,
url_views_by_dates_as=dates_as,
access_token=get_value_from_config("access_token"),
access_token=get_access_token(),
)
if not success:
click.secho(response["message"], err=True, fg="red")
Expand All @@ -145,9 +140,9 @@ def stats(short_url: str, referers_as: str, dates_as: str):
@url.command()
def list():
"""
Prints list of short urls created by user. Auth required.
Prints list of your short urls. Auth required.
"""
success, response = get_urls_list(access_token=get_value_from_config("access_token"))
success, response = get_urls_list(access_token=get_access_token())
if not success:
click.secho(response["message"], err=True, fg="red")
return
Expand Down Expand Up @@ -176,7 +171,7 @@ def delete(short_url: str):

success, *response = delete_url_by_hash(
hash=short_url_hash,
access_token=get_value_from_config("access_token"),
access_token=get_access_token(),
)
if not success:
click.secho(response[0]["message"], err=True, fg="red")
Expand All @@ -198,8 +193,7 @@ def clear_stats(short_url: str):
short_url_hash = request_hash_from_urls_list()

success, *response = clear_url_stats_by_hash(
hash=short_url_hash,
access_token=get_value_from_config("access_token")
hash=short_url_hash, access_token=get_access_token()
)
if not success:
click.secho(response[0]["message"], err=True, fg="red")
Expand Down
1 change: 1 addition & 0 deletions florgon_cc_cli/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

CC_API_URL = "https://api-cc.florgon.com/v1"
URL_OPEN_PROVIDER = "https://cc.florgon.com/o"
URL_PASTE_OPEN_PROVIDER = "https://cc.florgon.com/p"
URL_QR_PROVIDER = "https://cc.florgon.com/qr"

CONFIG_DIR = Path.home() / ".config" / "florgon-cc"
Expand Down
10 changes: 7 additions & 3 deletions florgon_cc_cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"""
import click

from florgon_cc_cli.commands import url, login, logout, host, config
from florgon_cc_cli.commands import url, login, logout, host, config, paste


@click.group()
Expand All @@ -17,9 +17,12 @@
default=False,
help="Enable debug features like printing api responses.",
)
@click.option(
"-a", "--anonymous", is_flag=True, default=False, help="Do not use access token for request."
)
@click.pass_context
def main(ctx: click.Context, debug: bool):
ctx.obj = {"DEBUG": debug}
def main(ctx: click.Context, debug: bool, anonymous: bool):
ctx.obj = {"DEBUG": debug, "ANONYMOUS": anonymous}
"""Florgon CC CLI - url shortener and paste manager."""


Expand All @@ -28,6 +31,7 @@ def main(ctx: click.Context, debug: bool):
main.add_command(logout)
main.add_command(host)
main.add_command(config)
main.add_command(paste)

if __name__ == "__main__":
main()
36 changes: 36 additions & 0 deletions florgon_cc_cli/models/paste.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
"""
Paste TypedDict model from CC API responses.
"""
from typing import TypedDict, Optional


class PasteLink(TypedDict):
"""
Single paste link.
"""

href: str


class PasteLinks(TypedDict):
"""
Paste links in paste field `_links`.
"""

stats: PasteLink


class Paste(TypedDict):
"""
Paste model from API.
"""

id: int
text: str
hash: str
expires_at: float
is_expired: bool
stats_is_public: bool
is_deleted: bool
burn_after_read: bool
_links: Optional[PasteLinks]
1 change: 1 addition & 0 deletions florgon_cc_cli/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
click
pick
toml
2 changes: 1 addition & 1 deletion florgon_cc_cli/services/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def execute_json_api_method(
data: Dict[str, Any] = {},
params: Dict[str, Any] = {},
access_token: Optional[str] = None,
) -> Dict[str, Any] | NoReturn:
) -> Union[Dict[str, Any], NoReturn]:
"""
Executes API method.
:param str http_method: GET, POST, PUT, PATCH, DELETE or OPTIONS
Expand Down
16 changes: 15 additions & 1 deletion florgon_cc_cli/services/config.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,25 @@
"""
Services for working with user config.
"""
from typing import Any, Dict
from typing import Any, Dict, Optional
import toml

import click

from florgon_cc_cli import config


def get_access_token() -> Optional[str]:
"""
Return access_token from user config or
returns None if passed --anonymous flag.
:returns: access token
:rtype: Optional[str]
"""
ctx = click.get_current_context()
return None if ctx.obj["ANONYMOUS"] else get_value_from_config("access_token")


def save_value_to_config(key: str, value: Any) -> None:
"""
Saves value to user config by key.
Expand All @@ -25,6 +38,7 @@ def save_value_to_config(key: str, value: Any) -> None:
def get_value_from_config(key: str) -> Any:
"""
Returns value from config by key.
NOTE: Do not use this function to get access token, use get_access_token() instead!
:param str key: key for value
:rtype: Any
"""
Expand Down
Loading

0 comments on commit 5687be1

Please sign in to comment.