[CORE] improved code, formatting and fixed some bugs
- Added a better `.gitignore` - Formatted the `do` script better, made it better in general - Improved instalation and upgrading instructons - Removed arrow key support - Imrproved `README.md` formatting - Improved the runtime (`__main__.py`) script, improved formatting, handling of arguments and added docstrings - Renamed `colors` to `colours` - Added type hints to the config file and improved how it handles some things - Improved chat action strings (e.g `uploading video` -> `is uploading a video`) - Made `COPY_CMD` deprecated, using `pyperclip` instead - Changed dependency for stability from dynamic to exact (`python-telegram>=....` to `python-telegram==....`) Signed-off-by: Ari Archer <truncateddinosour@gmail.com>
This commit is contained in:
parent
b22d3100dc
commit
9bfc08f357
156
.gitignore
vendored
156
.gitignore
vendored
|
@ -1,14 +1,156 @@
|
|||
.mypy_cache/
|
||||
venv/
|
||||
__pycache__
|
||||
.env
|
||||
dist
|
||||
# Random
|
||||
*.log*
|
||||
Makefile
|
||||
*monkeytype.sqlite3
|
||||
|
||||
# Editors
|
||||
.idea/
|
||||
.vim/
|
||||
*monkeytype.sqlite3
|
||||
.vscode/
|
||||
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# Distribution / packaging
|
||||
.Python
|
||||
build/
|
||||
develop-eggs/
|
||||
dist/
|
||||
downloads/
|
||||
eggs/
|
||||
.eggs/
|
||||
lib/
|
||||
lib64/
|
||||
parts/
|
||||
sdist/
|
||||
var/
|
||||
wheels/
|
||||
share/python-wheels/
|
||||
*.egg-info/
|
||||
.installed.cfg
|
||||
*.egg
|
||||
MANIFEST
|
||||
arigram.egg-info/
|
||||
|
||||
# PyInstaller
|
||||
# Usually these files are written by a python script from a template
|
||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
||||
*.manifest
|
||||
*.spec
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
pip-delete-this-directory.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
htmlcov/
|
||||
.tox/
|
||||
.nox/
|
||||
.coverage
|
||||
.coverage.*
|
||||
.cache
|
||||
nosetests.xml
|
||||
coverage.xml
|
||||
*.cover
|
||||
*.py,cover
|
||||
.hypothesis/
|
||||
.pytest_cache/
|
||||
cover/
|
||||
|
||||
# Translations
|
||||
*.mo
|
||||
*.pot
|
||||
|
||||
# Django stuff:
|
||||
*.log
|
||||
local_settings.py
|
||||
db.sqlite3
|
||||
db.sqlite3-journal
|
||||
|
||||
# Flask stuff:
|
||||
instance/
|
||||
.webassets-cache
|
||||
|
||||
# Scrapy stuff:
|
||||
.scrapy
|
||||
|
||||
# Sphinx documentation
|
||||
docs/_build/
|
||||
|
||||
# PyBuilder
|
||||
.pybuilder/
|
||||
target/
|
||||
|
||||
# Jupyter Notebook
|
||||
.ipynb_checkpoints
|
||||
|
||||
# IPython
|
||||
profile_default/
|
||||
ipython_config.py
|
||||
|
||||
# pyenv
|
||||
# For a library or package, you might want to ignore these files since the code is
|
||||
# intended to run in multiple environments; otherwise, check them in:
|
||||
# .python-version
|
||||
|
||||
# pipenv
|
||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
||||
# install all needed dependencies.
|
||||
#Pipfile.lock
|
||||
|
||||
# poetry
|
||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
||||
# commonly ignored for libraries.
|
||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
||||
#poetry.lock
|
||||
|
||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
||||
__pypackages__/
|
||||
|
||||
# Celery stuff
|
||||
celerybeat-schedule
|
||||
celerybeat.pid
|
||||
|
||||
# SageMath parsed files
|
||||
*.sage.py
|
||||
|
||||
# Environments
|
||||
.env
|
||||
.venv
|
||||
env/
|
||||
venv/
|
||||
ENV/
|
||||
env.bak/
|
||||
venv.bak/
|
||||
|
||||
# Spyder project settings
|
||||
.spyderproject
|
||||
.spyproject
|
||||
|
||||
# Rope project settings
|
||||
.ropeproject
|
||||
|
||||
# mkdocs documentation
|
||||
/site
|
||||
|
||||
# mypy
|
||||
.mypy_cache/
|
||||
.dmypy.json
|
||||
dmypy.json
|
||||
|
||||
# Pyre type checker
|
||||
.pyre/
|
||||
|
||||
# pytype static type analyzer
|
||||
.pytype/
|
||||
|
||||
# Cython debug symbols
|
||||
cython_debug/
|
||||
|
||||
|
|
35
README.md
35
README.md
|
@ -36,6 +36,7 @@ A fork of [tg](https://github.com/paul-nameless/tg) -- a hackable telegram TUI c
|
|||
- [ ] profile pictures
|
||||
- [ ] message/chat archiving
|
||||
- [ ] better error reporting
|
||||
- [ ] joining of groups and channels based on t.me links and @s
|
||||
|
||||
|
||||
## Requirements
|
||||
|
@ -81,8 +82,7 @@ mkdir -p ~/.local/src
|
|||
cd ~/.local/src
|
||||
git clone https://github.com/TruncatedDinosour/arigram.git
|
||||
cd arigram
|
||||
pip install --upgrade --user -r requirements.txt
|
||||
pip install --upgrade --user .
|
||||
./do local
|
||||
```
|
||||
|
||||
And add this to `~/.bashrc` or whatever POSIX complient shell you use:
|
||||
|
@ -111,10 +111,16 @@ This option is recommended:
|
|||
|
||||
```sh
|
||||
cd ~/.local/src/arigram
|
||||
git reset --hard # This discards every change you made
|
||||
git reset --hard # This discards every change you made locally
|
||||
git pull
|
||||
pip install --upgrade --user -r requirements.txt
|
||||
pip install --upgrade --user .
|
||||
./do upgrade
|
||||
```
|
||||
|
||||
or if you want to keep local changes
|
||||
|
||||
```sh
|
||||
cd ~/.local/src/arigram
|
||||
./do local
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
@ -126,6 +132,7 @@ Config file should be stored at `~/.config/arigram/config.py`. This is simple py
|
|||
```python
|
||||
# should start with + (plus) and contain country code
|
||||
PHONE = "[phone number in international format]"
|
||||
# For enable `PHONE = "+1234567890"`
|
||||
```
|
||||
|
||||
### Advanced configuration:
|
||||
|
@ -301,9 +308,7 @@ text/*; vim "%s"
|
|||
|
||||
## Keybindings
|
||||
|
||||
vi like keybindings are used in the project. Can be used commands like `4j` - 4 lines down.
|
||||
|
||||
For navigation arrow keys also can be used.
|
||||
ViM like keybindings are used in the project. Can be used commands like `4j` - 4 lines down.
|
||||
|
||||
### Chats:
|
||||
|
||||
|
@ -329,12 +334,14 @@ For navigation arrow keys also can be used.
|
|||
- `G`: move to the last msg (at the bottom)
|
||||
- `D`: download file
|
||||
- `l`: if video, pics or audio then open app specified in mailcap file, for example:
|
||||
```ini
|
||||
# Images
|
||||
image/png; qView "%s"
|
||||
audio/*; mpv "%s"
|
||||
```
|
||||
if text, open in `less` (to view multiline msgs)
|
||||
|
||||
```ini
|
||||
# Images
|
||||
image/png; qView "%s"
|
||||
audio/*; mpv "%s"
|
||||
```
|
||||
|
||||
If text, open in `less` (to view multiline msgs)
|
||||
- `e`: edit current msg
|
||||
- `<space>`: select msg and jump one msg down (use for deletion or forwarding)
|
||||
- `<ctrl+space>`: same as space but jumps one msg up
|
||||
|
|
7
TODO.md
7
TODO.md
|
@ -1,7 +0,0 @@
|
|||
- [] local passwords:
|
||||
|
||||
```
|
||||
- You need to save authorization keys on the disk.
|
||||
- So you can take the local passcode, derive an encryption key from this passcode and use this key to encrypt the authorization keys before saving. So when you start the app you can't decrypt the keys (and start using the API) without providing a correct local passcode for that. You can also save some hash (like SHA256) of the original keys along so that you can check whether you've decrypted the correct keys or just garbage because of an incorrect passcode.
|
||||
```
|
||||
|
|
@ -1,10 +1,12 @@
|
|||
import logging.handlers
|
||||
import signal
|
||||
import sys
|
||||
import threading
|
||||
from curses import window, wrapper # type: ignore
|
||||
from functools import partial
|
||||
from types import FrameType
|
||||
|
||||
import arigram
|
||||
from arigram import config, update_handlers, utils
|
||||
from arigram.controllers import Controller
|
||||
from arigram.models import Model
|
||||
|
@ -22,7 +24,7 @@ def run(tg: Tdlib, stdscr: window) -> None:
|
|||
del sig, frame
|
||||
log.info("Interrupt signal is handled and ignored on purpose.")
|
||||
|
||||
signal.signal(signal.SIGINT, interrupt_signal_handler)
|
||||
signal.signal(signal.SIGINT, interrupt_signal_handler) # type: ignore
|
||||
|
||||
model = Model(tg)
|
||||
status_view = StatusView(stdscr)
|
||||
|
@ -46,19 +48,24 @@ def run(tg: Tdlib, stdscr: window) -> None:
|
|||
|
||||
|
||||
def parse_args() -> None:
|
||||
import sys
|
||||
"""Parse CLI arguments"""
|
||||
|
||||
if len(sys.argv) > 1 and sys.argv[1] in ("-v", "--version"):
|
||||
import arigram
|
||||
if len(sys.argv) < 2:
|
||||
return
|
||||
|
||||
if sys.argv[1] in ("-v", "--version"):
|
||||
print("Terminal Telegram client")
|
||||
print("Version:", arigram.__version__)
|
||||
exit(0)
|
||||
sys.exit()
|
||||
|
||||
|
||||
def main() -> None:
|
||||
"""Main function"""
|
||||
|
||||
parse_args()
|
||||
|
||||
utils.cleanup_cache()
|
||||
|
||||
tg = Tdlib(
|
||||
config.EXTRA_TDLIB_HEADEARS,
|
||||
api_id=config.API_ID,
|
||||
|
|
|
@ -26,7 +26,7 @@ invisible = curses.A_INVIS
|
|||
dim = curses.A_DIM
|
||||
|
||||
|
||||
def get_color(fg: int, bg: int) -> int:
|
||||
def get_colour(fg: int, bg: int) -> int:
|
||||
"""Returns the curses color pair for the given fg/bg combination."""
|
||||
|
||||
key = (fg, bg)
|
|
@ -6,87 +6,75 @@ import mailcap
|
|||
import os
|
||||
import platform
|
||||
import runpy
|
||||
from typing import Any, Dict, Optional
|
||||
from typing import Any, Dict, List, Optional, Tuple
|
||||
|
||||
_os_name = platform.system()
|
||||
_linux = "Linux"
|
||||
_global_mailcap = mailcap.getcaps()
|
||||
|
||||
|
||||
CONFIG_DIR = os.path.expanduser("~/.config/arigram/")
|
||||
CONFIG_FILE = os.path.join(CONFIG_DIR, "config.py")
|
||||
FILES_DIR = os.path.expanduser("~/.cache/arigram/")
|
||||
DRAFTS_FILE = os.path.join(FILES_DIR, "drafts.json")
|
||||
CONFIG_DIR: str = os.path.expanduser("~/.config/arigram/")
|
||||
CONFIG_FILE: str = os.path.join(CONFIG_DIR, "config.py")
|
||||
FILES_DIR: str = os.path.expanduser("~/.cache/arigram/")
|
||||
DRAFTS_FILE: str = os.path.join(FILES_DIR, "drafts.json")
|
||||
MAILCAP_FILE: Optional[str] = None
|
||||
|
||||
LOG_LEVEL = "INFO"
|
||||
LOG_PATH = os.path.expanduser("~/.local/share/arigram/")
|
||||
LOG_LEVEL: str = "INFO"
|
||||
LOG_PATH: str = os.path.expanduser("~/.local/share/arigram/")
|
||||
|
||||
API_ID = "559815"
|
||||
API_HASH = "fd121358f59d764c57c55871aa0807ca"
|
||||
API_ID: str = "559815"
|
||||
API_HASH: str = "fd121358f59d764c57c55871aa0807ca"
|
||||
|
||||
PHONE = None
|
||||
ENC_KEY = ""
|
||||
PHONE: Optional[str] = None
|
||||
ENC_KEY: str = ""
|
||||
|
||||
TDLIB_PATH = None
|
||||
TDLIB_VERBOSITY = 0
|
||||
TDLIB_PATH: Optional[str] = None
|
||||
TDLIB_VERBOSITY: int = 0
|
||||
|
||||
MAX_DOWNLOAD_SIZE = "10MB"
|
||||
MAX_DOWNLOAD_SIZE: str = "10MB"
|
||||
|
||||
NOTIFY_FUNCTION = None
|
||||
NOTIFY_FUNCTION: Optional[Any] = None
|
||||
|
||||
VIEW_TEXT_CMD = "less"
|
||||
FZF = "fzf"
|
||||
VIEW_TEXT_CMD: str = "less"
|
||||
|
||||
if _os_name == _linux:
|
||||
# for more info see https://trac.ffmpeg.org/wiki/Capture/ALSA
|
||||
VOICE_RECORD_CMD = (
|
||||
"ffmpeg -f alsa -i hw:0 -c:a libopus -b:a 32k {file_path}"
|
||||
)
|
||||
else:
|
||||
VOICE_RECORD_CMD = (
|
||||
"ffmpeg -f avfoundation -i ':0' -c:a libopus -b:a 32k {file_path}"
|
||||
)
|
||||
# for more info see https://trac.ffmpeg.org/wiki/Capture/ALSA
|
||||
VOICE_RECORD_CMD: str = (
|
||||
"ffmpeg -f alsa -i hw:0 -c:a libopus -b:a 32k {file_path}"
|
||||
if _os_name == _linux
|
||||
else "ffmpeg -f avfoundation -i ':0' -c:a libopus -b:a 32k {file_path}"
|
||||
)
|
||||
|
||||
|
||||
EDITOR = os.environ.get("EDITOR", "vim")
|
||||
EDITOR: str = os.environ.get("EDITOR", "vim")
|
||||
_, __MAILCAP_EDITOR = mailcap.findmatch(_global_mailcap, "text/markdown")
|
||||
|
||||
if __MAILCAP_EDITOR:
|
||||
EDITOR = str(__MAILCAP_EDITOR["view"]).split(" ", 1)[0]
|
||||
|
||||
LONG_MSG_CMD = f"{EDITOR} '{{file_path}}'"
|
||||
LONG_MSG_CMD: str = f"{EDITOR} '{{file_path}}'"
|
||||
|
||||
if _os_name == _linux:
|
||||
DEFAULT_OPEN = "xdg-open {file_path}"
|
||||
else:
|
||||
DEFAULT_OPEN = "open {file_path}"
|
||||
|
||||
if _os_name == _linux:
|
||||
if os.environ.get("WAYLAND_DISPLAY"):
|
||||
COPY_CMD = "wl-copy"
|
||||
else:
|
||||
COPY_CMD = "xclip -selection cliboard"
|
||||
else:
|
||||
COPY_CMD = "pbcopy"
|
||||
DEFAULT_OPEN: str = (
|
||||
"xdg-open {file_path}" if _os_name == _linux else "open {file_path}"
|
||||
)
|
||||
|
||||
CHAT_FLAGS: Dict[str, str] = {}
|
||||
|
||||
MSG_FLAGS: Dict[str, str] = {}
|
||||
|
||||
ICON_PATH = os.path.join(os.path.dirname(__file__), "resources", "arigram.png")
|
||||
ICON_PATH: str = os.path.join(
|
||||
os.path.dirname(__file__), "resources", "arigram.png"
|
||||
)
|
||||
|
||||
URL_VIEW = None
|
||||
URL_VIEW: Optional[str] = None
|
||||
|
||||
USERS_COLORS = tuple(range(2, 16))
|
||||
USERS_COLOURS: Tuple[int, ...] = tuple(range(2, 16))
|
||||
|
||||
KEEP_MEDIA = 7
|
||||
KEEP_MEDIA: int = 7
|
||||
|
||||
FILE_PICKER_CMD = None
|
||||
FILE_PICKER_CMD: Optional[str] = None
|
||||
|
||||
DOWNLOAD_DIR = os.path.expanduser("~/Downloads/")
|
||||
DOWNLOAD_DIR: str = os.path.expanduser("~/Downloads/")
|
||||
|
||||
EXTRA_FILE_CHOOSER_PATHS = ["..", "/", "~"]
|
||||
EXTRA_FILE_CHOOSER_PATHS: List[str] = ["..", "/", "~"]
|
||||
|
||||
CUSTOM_KEYBINDS: Dict[str, Dict[str, Any]] = {}
|
||||
|
||||
|
|
|
@ -196,7 +196,7 @@ class Controller:
|
|||
def quit(self) -> str:
|
||||
return "QUIT"
|
||||
|
||||
@bind(msg_handler, ["h", "^D"])
|
||||
@bind(msg_handler, ["h"])
|
||||
def back(self) -> str:
|
||||
return "BACK"
|
||||
|
||||
|
@ -276,7 +276,7 @@ class Controller:
|
|||
if self.model.jump_bottom():
|
||||
self.render_msgs()
|
||||
|
||||
@bind(msg_handler, ["j", "^B", "^N"], repeat_factor=True)
|
||||
@bind(msg_handler, ["j"], repeat_factor=True)
|
||||
def next_msg(self, repeat_factor: int = 1) -> None:
|
||||
if self.model.next_msg(repeat_factor):
|
||||
self.render_msgs()
|
||||
|
@ -285,7 +285,7 @@ class Controller:
|
|||
def jump_10_msgs_down(self) -> None:
|
||||
self.next_msg(10)
|
||||
|
||||
@bind(msg_handler, ["k", "^C", "^P"], repeat_factor=True)
|
||||
@bind(msg_handler, ["k"], repeat_factor=True)
|
||||
def prev_msg(self, repeat_factor: int = 1) -> Optional[bool]:
|
||||
if self.model.prev_msg(repeat_factor):
|
||||
self.render_msgs()
|
||||
|
@ -327,7 +327,7 @@ class Controller:
|
|||
f.write(insert_replied_msg(msg))
|
||||
f.seek(0)
|
||||
s.call(config.LONG_MSG_CMD.format(file_path=shlex.quote(f.name)))
|
||||
with open(f.name) as f:
|
||||
with open(f.name) as f: # type: ignore
|
||||
if replied_msg := strip_replied_msg(f.read().strip()):
|
||||
self.model.view_all_msgs()
|
||||
self.tg.reply_message(chat_id, reply_to_msg, replied_msg)
|
||||
|
@ -397,7 +397,7 @@ class Controller:
|
|||
) as s:
|
||||
self.tg.send_chat_action(chat_id, ChatAction.chatActionTyping)
|
||||
s.call(config.LONG_MSG_CMD.format(file_path=shlex.quote(f.name)))
|
||||
with open(f.name) as f:
|
||||
with open(f.name) as f: # type: ignore
|
||||
if msg := f.read().strip():
|
||||
self.model.send_message(text=msg)
|
||||
self.present_info("Message sent")
|
||||
|
@ -442,7 +442,7 @@ class Controller:
|
|||
else:
|
||||
s.call(config.FILE_PICKER_CMD.format(file_path=f.name)) # type: ignore
|
||||
|
||||
with open(f.name) as f:
|
||||
with open(f.name) as f: # type: ignore
|
||||
file_path = f.read().strip()
|
||||
|
||||
with suspend(self.view) as s:
|
||||
|
@ -617,7 +617,7 @@ class Controller:
|
|||
)
|
||||
return self._open_msg(msg, cmd)
|
||||
|
||||
@bind(msg_handler, ["l", "^J"])
|
||||
@bind(msg_handler, ["l"])
|
||||
def open_current_msg(self) -> None:
|
||||
"""Open msg or file with cmd in mailcap"""
|
||||
msg = MsgProxy(self.model.current_msg)
|
||||
|
@ -656,7 +656,7 @@ class Controller:
|
|||
f.write(msg.text_content)
|
||||
f.flush()
|
||||
s.call(f"{config.EDITOR} {f.name}")
|
||||
with open(f.name) as f:
|
||||
with open(f.name) as f: # type: ignore
|
||||
if text := f.read().strip():
|
||||
self.model.edit_message(text=text)
|
||||
self.present_info("Message edited")
|
||||
|
@ -668,18 +668,20 @@ class Controller:
|
|||
int(cols / 2),
|
||||
max(len(user.name) for user in users),
|
||||
)
|
||||
users_out = "\n".join(
|
||||
users_out = [
|
||||
f"{user.id}\t{user.name:<{limit}} | {user.status}"
|
||||
for user in sorted(users, key=lambda user: user.order)
|
||||
)
|
||||
cmd = config.FZF + " -n 2"
|
||||
if is_multiple:
|
||||
cmd += " -m"
|
||||
]
|
||||
fzf_flags = "-n 2"
|
||||
|
||||
with NamedTemporaryFile("r+") as tmp, suspend(self.view) as s:
|
||||
s.run_with_input(f"{cmd} > {tmp.name}", users_out)
|
||||
with open(tmp.name) as f:
|
||||
return [int(line.split()[0]) for line in f.readlines()]
|
||||
if is_multiple:
|
||||
fzf_flags += " -m"
|
||||
|
||||
with suspend(self.view):
|
||||
return [
|
||||
int(uid.split()[0].strip())
|
||||
for uid in pyfzf.FzfPrompt().prompt(users_out, fzf_flags)
|
||||
]
|
||||
|
||||
@bind(chat_handler, ["ns"])
|
||||
def new_secret(self) -> None:
|
||||
|
@ -789,7 +791,7 @@ class Controller:
|
|||
def view_contacts(self) -> None:
|
||||
self._get_user_ids()
|
||||
|
||||
@bind(chat_handler, ["l", "^J", "^E"])
|
||||
@bind(chat_handler, ["l"])
|
||||
def handle_msgs(self) -> Optional[str]:
|
||||
rc = self.handle(msg_handler, 0.2)
|
||||
if rc == "QUIT":
|
||||
|
@ -802,13 +804,13 @@ class Controller:
|
|||
if self.model.first_chat():
|
||||
self.render()
|
||||
|
||||
@bind(chat_handler, ["j", "^B", "^N"], repeat_factor=True)
|
||||
@bind(chat_handler, ["j"], repeat_factor=True)
|
||||
@bind(msg_handler, ["]"])
|
||||
def next_chat(self, repeat_factor: int = 1) -> None:
|
||||
if self.model.next_chat(repeat_factor):
|
||||
self.render()
|
||||
|
||||
@bind(chat_handler, ["k", "^C", "^P"], repeat_factor=True)
|
||||
@bind(chat_handler, ["k"], repeat_factor=True)
|
||||
@bind(msg_handler, ["["])
|
||||
def prev_chat(self, repeat_factor: int = 1) -> None:
|
||||
if self.model.prev_chat(repeat_factor):
|
||||
|
|
|
@ -5,19 +5,20 @@ from telegram.client import AsyncResult, Telegram
|
|||
|
||||
|
||||
class ChatAction(Enum):
|
||||
chatActionTyping = "typing"
|
||||
chatActionTyping = "is typing"
|
||||
chatActionCancel = "cancel"
|
||||
chatActionRecordingVideo = "recording video"
|
||||
chatActionUploadingVideo = "uploading video"
|
||||
chatActionRecordingVoiceNote = "recording voice"
|
||||
chatActionUploadingVoiceNote = "uploading voice"
|
||||
chatActionUploadingPhoto = "uploading photo"
|
||||
chatActionUploadingDocument = "uploading document"
|
||||
chatActionChoosingLocation = "choosing location"
|
||||
chatActionChoosingContact = "choosing contact"
|
||||
chatActionStartPlayingGame = "start playing game"
|
||||
chatActionRecordingVideoNote = "recording video"
|
||||
chatActionUploadingVideoNote = "uploading video"
|
||||
chatActionRecordingVideo = "is recording a video"
|
||||
chatActionUploadingVideo = "is uploading a video"
|
||||
chatActionRecordingVoiceNote = "is recording a voice message"
|
||||
chatActionUploadingVoiceNote = "is uploading a voice message"
|
||||
chatActionUploadingPhoto = "is uploading a photo"
|
||||
chatActionUploadingDocument = "is uploading a document"
|
||||
chatActionChoosingLocation = "is choosing a location"
|
||||
chatActionChoosingContact = "is choosing a contact"
|
||||
chatActionStartPlayingGame = "started playing a game"
|
||||
chatActionRecordingVideoNote = "is recording a video"
|
||||
chatActionUploadingVideoNote = "is uploading a video"
|
||||
chatActionChoosingSticker = "is choosing a sticker"
|
||||
|
||||
|
||||
class ChatType(Enum):
|
||||
|
|
|
@ -21,6 +21,8 @@ from subprocess import CompletedProcess
|
|||
from types import TracebackType
|
||||
from typing import Any, Callable, Dict, Optional, Tuple, Type
|
||||
|
||||
from pyperclip import copy as copy_clipboard
|
||||
|
||||
from arigram import config
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
@ -237,9 +239,7 @@ def truncate_to_len(string: str, width: int) -> str:
|
|||
|
||||
|
||||
def copy_to_clipboard(text: str) -> None:
|
||||
subprocess.run(
|
||||
config.COPY_CMD, universal_newlines=True, input=text, shell=True
|
||||
)
|
||||
copy_clipboard(text)
|
||||
|
||||
|
||||
class suspend:
|
||||
|
@ -333,11 +333,11 @@ def pretty_ts(ts: int) -> str:
|
|||
|
||||
|
||||
@lru_cache(maxsize=256)
|
||||
def get_color_by_str(user: str) -> int:
|
||||
def get_colour_by_str(user: str) -> int:
|
||||
index = int(hashlib.sha1(user.encode()).hexdigest(), 16) % len(
|
||||
config.USERS_COLORS
|
||||
config.USERS_COLOURS
|
||||
)
|
||||
return config.USERS_COLORS[index]
|
||||
return config.USERS_COLOURS[index]
|
||||
|
||||
|
||||
def cleanup_cache() -> None:
|
||||
|
|
|
@ -6,10 +6,10 @@ from typing import Any, Dict, List, Optional, Tuple, Union, cast
|
|||
from _curses import window # type: ignore
|
||||
|
||||
from arigram import config
|
||||
from arigram.colors import (
|
||||
from arigram.colours import (
|
||||
bold,
|
||||
cyan,
|
||||
get_color,
|
||||
get_colour,
|
||||
magenta,
|
||||
reverse,
|
||||
white,
|
||||
|
@ -19,7 +19,7 @@ from arigram.models import Model, UserModel
|
|||
from arigram.msg import MsgProxy
|
||||
from arigram.tdlib import ChatType, get_chat_type, is_group
|
||||
from arigram.utils import (
|
||||
get_color_by_str,
|
||||
get_colour_by_str,
|
||||
num,
|
||||
string_len_dwc,
|
||||
truncate_to_len,
|
||||
|
@ -75,7 +75,7 @@ class View:
|
|||
curses.start_color()
|
||||
curses.use_default_colors()
|
||||
# init white color first to initialize colors correctly
|
||||
get_color(white, -1)
|
||||
get_colour(white, -1)
|
||||
|
||||
self.stdscr = stdscr
|
||||
self.chats = chat_view
|
||||
|
@ -189,13 +189,13 @@ class ChatView:
|
|||
self.win.resize(self.h, self.w)
|
||||
|
||||
def _msg_color(self, is_selected: bool = False) -> int:
|
||||
color = get_color(white, -1)
|
||||
color = get_colour(white, -1)
|
||||
if is_selected:
|
||||
return color | reverse
|
||||
return color
|
||||
|
||||
def _unread_color(self, is_selected: bool = False) -> int:
|
||||
color = get_color(magenta, -1)
|
||||
color = get_colour(magenta, -1)
|
||||
if is_selected:
|
||||
return color | reverse
|
||||
return color
|
||||
|
@ -204,9 +204,9 @@ class ChatView:
|
|||
self, is_selected: bool, title: str, user: Optional[str]
|
||||
) -> Tuple[int, ...]:
|
||||
attrs = (
|
||||
get_color(cyan, -1),
|
||||
get_color(get_color_by_str(title), -1),
|
||||
get_color(get_color_by_str(user or ""), -1),
|
||||
get_colour(cyan, -1),
|
||||
get_colour(get_colour_by_str(title), -1),
|
||||
get_colour(get_colour_by_str(user or ""), -1),
|
||||
self._msg_color(is_selected),
|
||||
)
|
||||
if is_selected:
|
||||
|
@ -222,7 +222,7 @@ class ChatView:
|
|||
|
||||
self.win.vline(0, width, line, self.h)
|
||||
self.win.addstr(
|
||||
0, 0, title.center(width)[:width], get_color(cyan, -1) | bold
|
||||
0, 0, title.center(width)[:width], get_colour(cyan, -1) | bold
|
||||
)
|
||||
|
||||
for i, chat in enumerate(chats, 1):
|
||||
|
@ -571,7 +571,7 @@ class MsgView:
|
|||
column += string_len_dwc(elem)
|
||||
|
||||
self.win.addstr(
|
||||
0, 0, self._msg_title(chat), get_color(cyan, -1) | bold
|
||||
0, 0, self._msg_title(chat), get_colour(cyan, -1) | bold
|
||||
)
|
||||
|
||||
self._refresh()
|
||||
|
@ -604,10 +604,10 @@ class MsgView:
|
|||
|
||||
def _msg_attributes(self, is_selected: bool, user: str) -> Tuple[int, ...]:
|
||||
attrs = (
|
||||
get_color(cyan, -1),
|
||||
get_color(get_color_by_str(user), -1),
|
||||
get_color(yellow, -1),
|
||||
get_color(white, -1),
|
||||
get_colour(cyan, -1),
|
||||
get_colour(get_colour_by_str(user), -1),
|
||||
get_colour(yellow, -1),
|
||||
get_colour(white, -1),
|
||||
)
|
||||
|
||||
if is_selected:
|
||||
|
|
87
do
87
do
|
@ -1,35 +1,31 @@
|
|||
#!/bin/bash
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e
|
||||
set -xe
|
||||
|
||||
SRC=$(dirname $0)
|
||||
main() {
|
||||
SRC="$(dirname "$0")"
|
||||
cd "$SRC"
|
||||
|
||||
cd $SRC
|
||||
|
||||
ARG=${1:-""}
|
||||
|
||||
|
||||
case $ARG in
|
||||
build)
|
||||
python3 -m pip install --upgrade setuptools wheel
|
||||
python3 setup.py sdist bdist_wheel
|
||||
python3 -m pip install --upgrade twine
|
||||
python3 -m twine upload --repository testpypi dist/*
|
||||
;;
|
||||
ARG=${1:-""}
|
||||
|
||||
case $ARG in
|
||||
review)
|
||||
gh pr create -f
|
||||
;;
|
||||
|
||||
push)
|
||||
isort arigram/*.py
|
||||
black arigram
|
||||
black .
|
||||
|
||||
python3 -m poetry check
|
||||
python3 -m poetry lock
|
||||
git diff > /tmp/arigram.diff
|
||||
|
||||
$0 check
|
||||
$0 local
|
||||
|
||||
git diff >/tmp/arigram.diff
|
||||
git add -A
|
||||
git commit -S
|
||||
git commit -sa
|
||||
git push -u origin main
|
||||
;;
|
||||
|
||||
|
@ -41,58 +37,23 @@ case $ARG in
|
|||
python3 -m pip install --user --upgrade .
|
||||
;;
|
||||
|
||||
release)
|
||||
CURRENT_VERSION=$(cat arigram/__init__.py | grep version | cut -d '"' -f 2)
|
||||
echo Current version $CURRENT_VERSION
|
||||
|
||||
NEW_VERSION=$(echo $CURRENT_VERSION | awk -F. '{print $1 "." $2+1 "." $3}')
|
||||
echo New version $NEW_VERSION
|
||||
sed -i '' "s|$CURRENT_VERSION|$NEW_VERSION|g" arigram/__init__.py
|
||||
poetry version $NEW_VERSION
|
||||
|
||||
git add -u arigram/__init__.py pyproject.toml
|
||||
git commit -m "Release v$NEW_VERSION"
|
||||
git tag v$NEW_VERSION
|
||||
|
||||
poetry build
|
||||
poetry publish -u $(pass show i/pypi | grep username | cut -d ' ' -f 2 | tr -d '\n') -p $(pass show i/pypi | head -n 1 | tr -d '\n')
|
||||
git log --pretty=format:"%cn: %s" v$CURRENT_VERSION...v$NEW_VERSION | grep -v -e "Merge" | grep -v "Release"| awk '!x[$0]++' > changelog.md
|
||||
git push origin master --tags
|
||||
gh release create v$NEW_VERSION -F changelog.md
|
||||
rm changelog.md
|
||||
;;
|
||||
|
||||
release-brew)
|
||||
CURRENT_VERSION=$(cat arigram/__init__.py | grep version | cut -d '"' -f 2)
|
||||
echo Current version $CURRENT_VERSION
|
||||
|
||||
URL="https://github.com/TruncatedDinosour/arigram/archive/refs/tags/v$CURRENT_VERSION.tar.gz"
|
||||
echo $URL
|
||||
wget $URL -O /tmp/arigram.tar.gz
|
||||
HASH=$(sha256sum /tmp/arigram.tar.gz | cut -d ' ' -f 1)
|
||||
rm /tmp/arigram.tar.gz
|
||||
|
||||
cd /opt/homebrew/Library/Taps/TruncatedDinosour/dino-bar
|
||||
sed -i '' "6s|.*| url \"https://github.com/TruncatedDinosour/arigram/archive/refs/tags/v$CURRENT_VERSION.tar.gz\"|" arigram.rb
|
||||
sed -i '' "7s|.*| sha256 \"$HASH\"|" arigram.rb
|
||||
|
||||
brew audit --new arigram
|
||||
brew uninstall arigram || true
|
||||
brew install arigram
|
||||
brew test arigram
|
||||
|
||||
git add -u arigram.rb
|
||||
git commit -m "Release arigram.rb v$CURRENT_VERSION"
|
||||
git push origin master
|
||||
upgrade)
|
||||
git reset --hard
|
||||
git pull
|
||||
$0 local
|
||||
;;
|
||||
|
||||
check)
|
||||
black .
|
||||
isort arigram/*.py
|
||||
sh check.sh
|
||||
chmod a+rx ./check.sh
|
||||
./check.sh
|
||||
;;
|
||||
|
||||
*)
|
||||
python3 -m arigram
|
||||
;;
|
||||
esac
|
||||
esac
|
||||
}
|
||||
|
||||
main "$@"
|
||||
|
|
49
poetry.lock
generated
49
poetry.lock
generated
|
@ -115,14 +115,14 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
|||
|
||||
[[package]]
|
||||
name = "plumbum"
|
||||
version = "1.7.1"
|
||||
version = "1.7.2"
|
||||
description = "Plumbum: shell combinators library"
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7"
|
||||
|
||||
[package.dependencies]
|
||||
pypiwin32 = {version = "*", markers = "platform_system == \"Windows\" and platform_python_implementation != \"PyPy\""}
|
||||
pywin32 = {version = "*", markers = "platform_system == \"Windows\" and platform_python_implementation != \"PyPy\""}
|
||||
|
||||
[package.extras]
|
||||
dev = ["paramiko", "psutil", "pytest", "pytest-cov", "pytest-mock", "pytest-timeout"]
|
||||
|
@ -164,17 +164,6 @@ category = "main"
|
|||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[[package]]
|
||||
name = "pypiwin32"
|
||||
version = "223"
|
||||
description = ""
|
||||
category = "main"
|
||||
optional = false
|
||||
python-versions = "*"
|
||||
|
||||
[package.dependencies]
|
||||
pywin32 = ">=223"
|
||||
|
||||
[[package]]
|
||||
name = "python-telegram"
|
||||
version = "0.15.0"
|
||||
|
@ -185,7 +174,7 @@ python-versions = "*"
|
|||
|
||||
[[package]]
|
||||
name = "pywin32"
|
||||
version = "302"
|
||||
version = "303"
|
||||
description = "Python for Window Extensions"
|
||||
category = "main"
|
||||
optional = false
|
||||
|
@ -226,7 +215,7 @@ python-versions = ">=3.6"
|
|||
[metadata]
|
||||
lock-version = "1.1"
|
||||
python-versions = "^3.8"
|
||||
content-hash = "061484270c70aa1136531e656408afb105e5d532473e9a642961595878ecbde9"
|
||||
content-hash = "d09bad5856588aa83f522a7573c118d8f930c23020657822ec409d2d9da274b9"
|
||||
|
||||
[metadata.files]
|
||||
appdirs = [
|
||||
|
@ -289,8 +278,8 @@ pathspec = [
|
|||
{file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"},
|
||||
]
|
||||
plumbum = [
|
||||
{file = "plumbum-1.7.1-py2.py3-none-any.whl", hash = "sha256:e97229fdf218698b4e9e939355f4d20d20bf57f8a02710f81f92442e2b2db038"},
|
||||
{file = "plumbum-1.7.1.tar.gz", hash = "sha256:3c0ac8c4ee57b2adddc82909d3c738a62ef5f77faf24ec7cb6f0a117e1679740"},
|
||||
{file = "plumbum-1.7.2-py2.py3-none-any.whl", hash = "sha256:0bbf431e31da988405de2fb36c3226f09c0c9cdf69c8480f8997f4b94b7370a1"},
|
||||
{file = "plumbum-1.7.2.tar.gz", hash = "sha256:0d1bf908076bbd0484d16412479cb97d6843069ee19f99e267e11dd980040523"},
|
||||
]
|
||||
pycodestyle = [
|
||||
{file = "pycodestyle-2.6.0-py2.py3-none-any.whl", hash = "sha256:2295e7b2f6b5bd100585ebcb1f616591b652db8a741695b3d8f5d28bdc934367"},
|
||||
|
@ -307,24 +296,22 @@ pyfzf = [
|
|||
pyperclip = [
|
||||
{file = "pyperclip-1.8.2.tar.gz", hash = "sha256:105254a8b04934f0bc84e9c24eb360a591aaf6535c9def5f29d92af107a9bf57"},
|
||||
]
|
||||
pypiwin32 = [
|
||||
{file = "pypiwin32-223-py3-none-any.whl", hash = "sha256:67adf399debc1d5d14dffc1ab5acacb800da569754fafdc576b2a039485aa775"},
|
||||
{file = "pypiwin32-223.tar.gz", hash = "sha256:71be40c1fbd28594214ecaecb58e7aa8b708eabfa0125c8a109ebd51edbd776a"},
|
||||
]
|
||||
python-telegram = [
|
||||
{file = "python-telegram-0.15.0.tar.gz", hash = "sha256:35ad8d22256061ec4a6c4aa3379f217e4831c057424cbed9ec3dd61212f8c170"},
|
||||
]
|
||||
pywin32 = [
|
||||
{file = "pywin32-302-cp310-cp310-win32.whl", hash = "sha256:251b7a9367355ccd1a4cd69cd8dd24bd57b29ad83edb2957cfa30f7ed9941efa"},
|
||||
{file = "pywin32-302-cp310-cp310-win_amd64.whl", hash = "sha256:79cf7e6ddaaf1cd47a9e50cc74b5d770801a9db6594464137b1b86aa91edafcc"},
|
||||
{file = "pywin32-302-cp36-cp36m-win32.whl", hash = "sha256:fe21c2fb332d03dac29de070f191bdbf14095167f8f2165fdc57db59b1ecc006"},
|
||||
{file = "pywin32-302-cp36-cp36m-win_amd64.whl", hash = "sha256:d3761ab4e8c5c2dbc156e2c9ccf38dd51f936dc77e58deb940ffbc4b82a30528"},
|
||||
{file = "pywin32-302-cp37-cp37m-win32.whl", hash = "sha256:48dd4e348f1ee9538dd4440bf201ea8c110ea6d9f3a5010d79452e9fa80480d9"},
|
||||
{file = "pywin32-302-cp37-cp37m-win_amd64.whl", hash = "sha256:496df89f10c054c9285cc99f9d509e243f4e14ec8dfc6d78c9f0bf147a893ab1"},
|
||||
{file = "pywin32-302-cp38-cp38-win32.whl", hash = "sha256:e372e477d938a49266136bff78279ed14445e00718b6c75543334351bf535259"},
|
||||
{file = "pywin32-302-cp38-cp38-win_amd64.whl", hash = "sha256:543552e66936378bd2d673c5a0a3d9903dba0b0a87235ef0c584f058ceef5872"},
|
||||
{file = "pywin32-302-cp39-cp39-win32.whl", hash = "sha256:2393c1a40dc4497fd6161b76801b8acd727c5610167762b7c3e9fd058ef4a6ab"},
|
||||
{file = "pywin32-302-cp39-cp39-win_amd64.whl", hash = "sha256:af5aea18167a31efcacc9f98a2ca932c6b6a6d91ebe31f007509e293dea12580"},
|
||||
{file = "pywin32-303-cp310-cp310-win32.whl", hash = "sha256:6fed4af057039f309263fd3285d7b8042d41507343cd5fa781d98fcc5b90e8bb"},
|
||||
{file = "pywin32-303-cp310-cp310-win_amd64.whl", hash = "sha256:51cb52c5ec6709f96c3f26e7795b0bf169ee0d8395b2c1d7eb2c029a5008ed51"},
|
||||
{file = "pywin32-303-cp311-cp311-win32.whl", hash = "sha256:d9b5d87ca944eb3aa4cd45516203ead4b37ab06b8b777c54aedc35975dec0dee"},
|
||||
{file = "pywin32-303-cp311-cp311-win_amd64.whl", hash = "sha256:fcf44032f5b14fcda86028cdf49b6ebdaea091230eb0a757282aa656e4732439"},
|
||||
{file = "pywin32-303-cp36-cp36m-win32.whl", hash = "sha256:aad484d52ec58008ca36bd4ad14a71d7dd0a99db1a4ca71072213f63bf49c7d9"},
|
||||
{file = "pywin32-303-cp36-cp36m-win_amd64.whl", hash = "sha256:2a09632916b6bb231ba49983fe989f2f625cea237219530e81a69239cd0c4559"},
|
||||
{file = "pywin32-303-cp37-cp37m-win32.whl", hash = "sha256:b1675d82bcf6dbc96363fca747bac8bff6f6e4a447a4287ac652aa4b9adc796e"},
|
||||
{file = "pywin32-303-cp37-cp37m-win_amd64.whl", hash = "sha256:c268040769b48a13367221fced6d4232ed52f044ffafeda247bd9d2c6bdc29ca"},
|
||||
{file = "pywin32-303-cp38-cp38-win32.whl", hash = "sha256:5f9ec054f5a46a0f4dfd72af2ce1372f3d5a6e4052af20b858aa7df2df7d355b"},
|
||||
{file = "pywin32-303-cp38-cp38-win_amd64.whl", hash = "sha256:793bf74fce164bcffd9d57bb13c2c15d56e43c9542a7b9687b4fccf8f8a41aba"},
|
||||
{file = "pywin32-303-cp39-cp39-win32.whl", hash = "sha256:7d3271c98434617a11921c5ccf74615794d97b079e22ed7773790822735cc352"},
|
||||
{file = "pywin32-303-cp39-cp39-win_amd64.whl", hash = "sha256:79cbb862c11b9af19bcb682891c1b91942ec2ff7de8151e2aea2e175899cda34"},
|
||||
]
|
||||
regex = [
|
||||
{file = "regex-2021.11.10-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf"},
|
||||
|
|
|
@ -11,7 +11,7 @@ repository = "https://github.com/TruncatedDinosour/arigram"
|
|||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.8"
|
||||
python-telegram = "^0.15.0^"
|
||||
python-telegram = "0.15.0"
|
||||
pyfzf = "^0.2.2"
|
||||
pyperclip = "^1.8.2"
|
||||
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
python-telegram>=0.15.0
|
||||
python-telegram==0.15.0
|
||||
pyfzf>=0.2.2
|
||||
pyperclip>=1.8.2
|
||||
|
||||
|
|
Loading…
Reference in a new issue