tg 0.20.0__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tg/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """
2
+ Terminal client for telegram
3
+ """
4
+
5
+ __version__ = "0.20.0"
tg/__main__.py ADDED
@@ -0,0 +1,78 @@
1
+ import logging.handlers
2
+ import signal
3
+ import threading
4
+ from curses import window, wrapper
5
+ from functools import partial
6
+ from types import FrameType
7
+ from typing import Optional
8
+
9
+ from tg import config, update_handlers, utils
10
+ from tg.controllers import Controller
11
+ from tg.models import Model
12
+ from tg.tdlib import Tdlib
13
+ from tg.views import ChatView, MsgView, StatusView, View
14
+
15
+ log = logging.getLogger(__name__)
16
+
17
+
18
+ def run(tg: Tdlib, stdscr: window) -> None:
19
+ # handle ctrl+c, to avoid interrupting tg when subprocess is called
20
+ def interrupt_signal_handler(sig: int, frame: Optional[FrameType]) -> None:
21
+ # TODO: draw on status pane: to quite press <q>
22
+ log.info("Interrupt signal is handled and ignored on purpose.")
23
+
24
+ signal.signal(signal.SIGINT, interrupt_signal_handler)
25
+
26
+ model = Model(tg)
27
+ status_view = StatusView(stdscr)
28
+ msg_view = MsgView(stdscr, model)
29
+ chat_view = ChatView(stdscr, model)
30
+ view = View(stdscr, chat_view, msg_view, status_view)
31
+ controller = Controller(model, view, tg)
32
+
33
+ # hanlde resize of terminal correctly
34
+ signal.signal(signal.SIGWINCH, controller.resize_handler)
35
+
36
+ for msg_type, handler in update_handlers.handlers.items():
37
+ tg.add_update_handler(msg_type, partial(handler, controller))
38
+
39
+ thread = threading.Thread(target=controller.run)
40
+ thread.daemon = True
41
+ thread.start()
42
+
43
+ controller.draw()
44
+
45
+
46
+ def parse_args() -> None:
47
+ import sys
48
+
49
+ if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--version"):
50
+ import tg
51
+
52
+ print("Terminal Telegram client")
53
+ print("Version:", tg.__version__)
54
+ exit(0)
55
+
56
+
57
+ def main() -> None:
58
+ parse_args()
59
+ utils.cleanup_cache()
60
+ tg = Tdlib(
61
+ api_id=config.API_ID,
62
+ api_hash=config.API_HASH,
63
+ phone=config.PHONE,
64
+ database_encryption_key=config.ENC_KEY,
65
+ files_directory=config.FILES_DIR,
66
+ tdlib_verbosity=config.TDLIB_VERBOSITY,
67
+ library_path=config.TDLIB_PATH,
68
+ )
69
+ tg.login()
70
+
71
+ utils.setup_log()
72
+ utils.set_shorter_esc_delay()
73
+
74
+ wrapper(partial(run, tg))
75
+
76
+
77
+ if __name__ == "__main__":
78
+ main()
tg/colors.py ADDED
@@ -0,0 +1,53 @@
1
+ import curses
2
+
3
+ DEFAULT_FG = curses.COLOR_WHITE
4
+ DEFAULT_BG = curses.COLOR_BLACK
5
+ COLOR_PAIRS = {(10, 10): 0}
6
+ COLOR_PAIRS = {}
7
+
8
+ # colors
9
+ black = curses.COLOR_BLACK
10
+ blue = curses.COLOR_BLUE
11
+ cyan = curses.COLOR_CYAN
12
+ green = curses.COLOR_GREEN
13
+ magenta = curses.COLOR_MAGENTA
14
+ red = curses.COLOR_RED
15
+ white = curses.COLOR_WHITE
16
+ yellow = curses.COLOR_YELLOW
17
+ default = -1
18
+
19
+ # modes
20
+ normal = curses.A_NORMAL
21
+ bold = curses.A_BOLD
22
+ blink = curses.A_BLINK
23
+ reverse = curses.A_REVERSE
24
+ underline = curses.A_UNDERLINE
25
+ invisible = curses.A_INVIS
26
+ dim = curses.A_DIM
27
+
28
+
29
+ def get_color(fg: int, bg: int) -> int:
30
+ """Returns the curses color pair for the given fg/bg combination."""
31
+
32
+ key = (fg, bg)
33
+ if key not in COLOR_PAIRS:
34
+ size = len(COLOR_PAIRS)
35
+ try:
36
+ curses.init_pair(size, fg, bg)
37
+ except curses.error:
38
+ # If curses.use_default_colors() failed during the initialization
39
+ # of curses, then using -1 as fg or bg will fail as well, which
40
+ # we need to handle with fallback-defaults:
41
+ if fg == -1: # -1 is the "default" color
42
+ fg = DEFAULT_FG
43
+ if bg == -1: # -1 is the "default" color
44
+ bg = DEFAULT_BG
45
+
46
+ try:
47
+ curses.init_pair(size, fg, bg)
48
+ except curses.error:
49
+ # If this fails too, colors are probably not supported
50
+ pass
51
+ COLOR_PAIRS[key] = size
52
+
53
+ return curses.color_pair(COLOR_PAIRS[key])
tg/config.py ADDED
@@ -0,0 +1,99 @@
1
+ """
2
+ Every parameter (except for CONFIG_FILE) can be
3
+ overwritten by external config file
4
+ """
5
+
6
+ import os
7
+ import platform
8
+ import runpy
9
+ from typing import Dict, Optional
10
+
11
+ _os_name = platform.system()
12
+ _darwin = "Darwin"
13
+ _linux = "Linux"
14
+
15
+
16
+ CONFIG_DIR = os.path.expanduser("~/.config/tg/")
17
+ CONFIG_FILE = os.path.join(CONFIG_DIR, "conf.py")
18
+ FILES_DIR = os.path.expanduser("~/.cache/tg/")
19
+ MAILCAP_FILE: Optional[str] = None
20
+
21
+ LOG_LEVEL = "INFO"
22
+ LOG_PATH = os.path.expanduser("~/.local/share/tg/")
23
+
24
+ API_ID = 559815
25
+ API_HASH = "fd121358f59d764c57c55871aa0807ca"
26
+
27
+ PHONE = None
28
+ ENC_KEY = ""
29
+
30
+ TDLIB_PATH = None
31
+ TDLIB_VERBOSITY = 0
32
+
33
+ MAX_DOWNLOAD_SIZE = "10MB"
34
+
35
+ # TODO: check platform
36
+ NOTIFY_CMD = "/usr/local/bin/terminal-notifier -title {title} -subtitle {subtitle} -message {msg} -appIcon {icon_path}"
37
+
38
+ VIEW_TEXT_CMD = "less"
39
+ FZF = "fzf"
40
+
41
+ if _os_name == _linux:
42
+ # for more info see https://trac.ffmpeg.org/wiki/Capture/ALSA
43
+ VOICE_RECORD_CMD = "ffmpeg -f alsa -i hw:0 -c:a libopus -b:a 32k {file_path}"
44
+ else:
45
+ VOICE_RECORD_CMD = (
46
+ "ffmpeg -f avfoundation -i ':0' -c:a libopus -b:a 32k {file_path}"
47
+ )
48
+
49
+ # TODO: use mailcap instead of editor
50
+ LONG_MSG_CMD = "vim + -c 'startinsert' {file_path}"
51
+ EDITOR = os.environ.get("EDITOR", "vi")
52
+
53
+ if _os_name == _linux:
54
+ DEFAULT_OPEN = "xdg-open {file_path}"
55
+ else:
56
+ DEFAULT_OPEN = "open {file_path}"
57
+
58
+ if _os_name == _linux:
59
+ if os.environ.get("WAYLAND_DISPLAY"):
60
+ COPY_CMD = "wl-copy"
61
+ else:
62
+ COPY_CMD = "xclip -selection c"
63
+ else:
64
+ COPY_CMD = "pbcopy"
65
+
66
+ CHAT_FLAGS: Dict[str, str] = {}
67
+
68
+ MSG_FLAGS: Dict[str, str] = {}
69
+
70
+ ICON_PATH = os.path.join(os.path.dirname(__file__), "resources", "tg.png")
71
+
72
+ URL_VIEW = "urlview"
73
+
74
+ USERS_COLORS = tuple(range(2, 16))
75
+
76
+ KEEP_MEDIA = 7
77
+
78
+ FILE_PICKER_CMD = "ranger --choosefile={file_path}"
79
+
80
+ DOWNLOAD_DIR = os.path.expanduser("~/Downloads/")
81
+
82
+ if os.path.isfile(CONFIG_FILE):
83
+ config_params = runpy.run_path(CONFIG_FILE)
84
+ for param, value in config_params.items():
85
+ if param.isupper():
86
+ globals()[param] = value
87
+ else:
88
+ os.makedirs(CONFIG_DIR, exist_ok=True)
89
+
90
+ if not PHONE:
91
+ print(
92
+ "Enter your phone number in international format (including country code)"
93
+ )
94
+ PHONE = input("phone> ")
95
+ if not PHONE.startswith("+"):
96
+ PHONE = "+" + PHONE
97
+
98
+ with open(CONFIG_FILE, "w") as f:
99
+ f.write(f"PHONE = '{PHONE}'\n")