Compare commits
2 Commits
5dc9c8f0d1
...
20fa3eb8a1
| Author | SHA1 | Date | |
|---|---|---|---|
| 20fa3eb8a1 | |||
| 63044be106 |
@@ -59,8 +59,9 @@ class ClockifyAPI(ClockifyConfig):
|
|||||||
|
|
||||||
if not tag:
|
if not tag:
|
||||||
tag = self.get_tags(True, tag_id=tag_id, **{"page-size": self.tags_count})
|
tag = self.get_tags(True, tag_id=tag_id, **{"page-size": self.tags_count})
|
||||||
if tag:
|
if isinstance(tag, Tag):
|
||||||
db.insert_tags([tag] if isinstance(tag, Tag) else tag)
|
db.insert_tags([tag])
|
||||||
|
tags.append(tag)
|
||||||
from config import red
|
from config import red
|
||||||
try:
|
try:
|
||||||
task_id = parse_task_id(tag.title)
|
task_id = parse_task_id(tag.title)
|
||||||
@@ -72,6 +73,7 @@ class ClockifyAPI(ClockifyConfig):
|
|||||||
db.insert_tasks([task])
|
db.insert_tasks([task])
|
||||||
else:
|
else:
|
||||||
logger.error(f"{Fore.RED} Тег не найден: {tag_id}{Fore.RESET}")
|
logger.error(f"{Fore.RED} Тег не найден: {tag_id}{Fore.RESET}")
|
||||||
|
continue
|
||||||
else:
|
else:
|
||||||
tag = tag[0]
|
tag = tag[0]
|
||||||
task_id = parse_task_id(tag.title)
|
task_id = parse_task_id(tag.title)
|
||||||
@@ -98,7 +100,7 @@ class ClockifyAPI(ClockifyConfig):
|
|||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def get_tags(self, is_updating=False, tag_id="", **kwargs) -> Union[list[Tag], Tag]:
|
def get_tags(self, is_updating=False, tag_id="", **kwargs) -> Union[list[Tag], Tag, None]:
|
||||||
"""
|
"""
|
||||||
Получает с сервера Clockify теги
|
Получает с сервера Clockify теги
|
||||||
name: str - optional
|
name: str - optional
|
||||||
@@ -108,26 +110,40 @@ class ClockifyAPI(ClockifyConfig):
|
|||||||
"""
|
"""
|
||||||
if is_updating:
|
if is_updating:
|
||||||
page = 1
|
page = 1
|
||||||
kwargs["page"] = page
|
page_size = int(kwargs.get("page-size", self.tags_count))
|
||||||
tags = requests.get(self.base_url + self.tags_url, params={"sort-column": "name", "sort-order": "descending", **kwargs}, headers={"X-API-KEY": self.token}).json()
|
result = []
|
||||||
while len(tags) == self.tags_count:
|
while True:
|
||||||
if tag_id:
|
params = {
|
||||||
for tag in tags:
|
"sort-column": "name",
|
||||||
if tag_id == tag["id"]:
|
"sort-order": "descending",
|
||||||
return Tag(
|
**kwargs,
|
||||||
id=tag["id"],
|
"page": page
|
||||||
title=tag["name"]
|
}
|
||||||
)
|
tags = requests.get(
|
||||||
page += 1
|
self.base_url + self.tags_url,
|
||||||
kwargs["page"] = page
|
params=params,
|
||||||
tags = requests.get(self.base_url + self.tags_url, params={"sort-column": "name", "sort-order": "descending", **kwargs}, headers={"X-API-KEY": self.token}).json()
|
headers={"X-API-KEY": self.token}
|
||||||
else:
|
).json()
|
||||||
|
|
||||||
|
for tag in tags:
|
||||||
|
clockify_tag = Tag(
|
||||||
|
id=tag["id"],
|
||||||
|
title=tag["name"]
|
||||||
|
)
|
||||||
|
if tag_id and tag_id == clockify_tag.id:
|
||||||
|
return clockify_tag
|
||||||
|
if not tag_id:
|
||||||
|
result.append(clockify_tag)
|
||||||
|
|
||||||
|
if len(tags) < page_size:
|
||||||
break
|
break
|
||||||
logger.debug(f"{Fore.YELLOW}Получено тегов: {len(tags)}.{Fore.RESET}")
|
page += 1
|
||||||
return [Tag(
|
|
||||||
id=tag["id"],
|
if tag_id:
|
||||||
title=tag["name"]
|
return None
|
||||||
) for tag in tags]
|
|
||||||
|
logger.debug(f"{Fore.YELLOW}Получено тегов: {len(result)}.{Fore.RESET}")
|
||||||
|
return result
|
||||||
return db.get_tags()
|
return db.get_tags()
|
||||||
|
|
||||||
def get_vetro_projects(self) -> list[VetroProject]:
|
def get_vetro_projects(self) -> list[VetroProject]:
|
||||||
|
|||||||
@@ -1,29 +0,0 @@
|
|||||||
import logging
|
|
||||||
|
|
||||||
import asyncio
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from bot.commands import router as command_router
|
|
||||||
from bot.factory import bot, dp, init_bot_and_dp
|
|
||||||
from config import config
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s %(name)s:%(lineno)d %(levelname)s %(message)s',
|
|
||||||
handlers=[
|
|
||||||
logging.FileHandler('app.log', encoding='utf-8'),
|
|
||||||
logging.StreamHandler(sys.stdout)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
bot, dp = init_bot_and_dp(f"socks5://{config.PROXY_URL}", config.BOT_TOKEN)
|
|
||||||
dp.include_router(command_router)
|
|
||||||
await dp.start_polling(bot)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
from argparse import Namespace
|
|
||||||
|
|
||||||
from aiogram import Router
|
|
||||||
from aiogram.filters.command import Command, CommandStart
|
|
||||||
from aiogram.types import Message
|
|
||||||
|
|
||||||
from bot.factory import bot
|
|
||||||
from tracker.commands import end_workday
|
|
||||||
|
|
||||||
|
|
||||||
router = Router()
|
|
||||||
|
|
||||||
@router.message(CommandStart())
|
|
||||||
async def start_command(message: Message):
|
|
||||||
await message.answer(f"Привет, {message.from_user.first_name}!")
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("track"))
|
|
||||||
async def track_command(message: Message):
|
|
||||||
await message.answer("Эта фича пока в разработке, но скоро она поможет стартовать и останавливать таймер.")
|
|
||||||
|
|
||||||
|
|
||||||
@router.message(Command("end"))
|
|
||||||
async def end_command(message: Message):
|
|
||||||
args = message.text.replace("/end", "").strip().split()
|
|
||||||
if "-i" in args or "--init-db" in args:
|
|
||||||
args = Namespace(init_db=True)
|
|
||||||
await message.answer("Начинаю процесс занесения трудочасов...")
|
|
||||||
await end_workday(args, bot, message.from_user.id)
|
|
||||||
await message.answer("Трудочасы занесены!")
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from aiogram import Bot, Dispatcher
|
|
||||||
from aiogram.client.session.aiohttp import AiohttpSession
|
|
||||||
|
|
||||||
|
|
||||||
bot: Bot = None # type: ignore
|
|
||||||
dp: Dispatcher = None
|
|
||||||
|
|
||||||
def init_bot_and_dp(proxy_url: str, token: str):
|
|
||||||
global bot, dp
|
|
||||||
bot = Bot(token=token, session=AiohttpSession(proxy=proxy_url) if proxy_url else None)
|
|
||||||
dp = Dispatcher()
|
|
||||||
return bot, dp
|
|
||||||
@@ -16,9 +16,6 @@ class Config(BaseSettings):
|
|||||||
REDMINE_TOKEN: str
|
REDMINE_TOKEN: str
|
||||||
USER_ID: str = None
|
USER_ID: str = None
|
||||||
WORKSPACE_ID: str = None
|
WORKSPACE_ID: str = None
|
||||||
BOT_TOKEN: str
|
|
||||||
|
|
||||||
PROXY_URL: str
|
|
||||||
|
|
||||||
MODE: str = "prod"
|
MODE: str = "prod"
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,3 @@ CLOCKIFY_TOKEN=
|
|||||||
USER_ID=
|
USER_ID=
|
||||||
WORKSPACE_ID=
|
WORKSPACE_ID=
|
||||||
TRIAL_END_DATE=
|
TRIAL_END_DATE=
|
||||||
|
|
||||||
BOT_TOKEN=
|
|
||||||
PROXY_URL=
|
|
||||||
18
main.py
18
main.py
@@ -1,18 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from tracker.commands import end_workday
|
|
||||||
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s %(name)s:%(lineno)d %(levelname)s %(message)s',
|
|
||||||
handlers=[
|
|
||||||
logging.FileHandler('app.log', encoding='utf-8'),
|
|
||||||
logging.StreamHandler(sys.stdout)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(end_workday())
|
|
||||||
@@ -1,28 +1,15 @@
|
|||||||
aiofiles==25.1.0
|
|
||||||
aiogram==3.27.0
|
|
||||||
aiohappyeyeballs==2.6.1
|
|
||||||
aiohttp==3.13.5
|
|
||||||
aiohttp_socks==0.11.0
|
|
||||||
aiosignal==1.4.0
|
|
||||||
annotated-types==0.7.0
|
annotated-types==0.7.0
|
||||||
asarPy==1.0.1
|
asarPy==1.0.1
|
||||||
attrs==26.1.0
|
|
||||||
certifi==2025.4.26
|
certifi==2025.4.26
|
||||||
charset-normalizer==3.4.2
|
charset-normalizer==3.4.2
|
||||||
colorama==0.4.6
|
colorama==0.4.6
|
||||||
frozenlist==1.8.0
|
|
||||||
idna==3.10
|
idna==3.10
|
||||||
magic-filter==1.0.12
|
|
||||||
multidict==6.7.1
|
|
||||||
propcache==0.4.1
|
|
||||||
pydantic==2.11.4
|
pydantic==2.11.4
|
||||||
pydantic-settings==2.9.1
|
pydantic-settings==2.9.1
|
||||||
pydantic_core==2.33.2
|
pydantic_core==2.33.2
|
||||||
python-dotenv==1.1.0
|
python-dotenv==1.1.0
|
||||||
python-socks==2.8.1
|
|
||||||
requests==2.32.3
|
requests==2.32.3
|
||||||
tqdm==4.67.1
|
tqdm==4.67.1
|
||||||
typing-inspection==0.4.0
|
typing-inspection==0.4.0
|
||||||
typing_extensions==4.13.2
|
typing_extensions==4.13.2
|
||||||
urllib3==2.4.0
|
urllib3==2.4.0
|
||||||
yarl==1.23.0
|
|
||||||
|
|||||||
@@ -1,20 +1,24 @@
|
|||||||
import logging
|
import logging
|
||||||
from argparse import Namespace
|
import sys
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
level=logging.INFO,
|
||||||
|
format='%(asctime)s %(name)s:%(lineno)d %(levelname)s %(message)s',
|
||||||
|
handlers=[
|
||||||
|
logging.FileHandler('app.log', encoding='utf-8'),
|
||||||
|
logging.StreamHandler(sys.stdout)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
from aiogram import Bot
|
|
||||||
from colorama import Fore # noqa: E402
|
from colorama import Fore # noqa: E402
|
||||||
|
|
||||||
from bot.factory import bot
|
|
||||||
from config import arg_parser, clock, config, db, red # noqa: E402
|
from config import arg_parser, clock, config, db, red # noqa: E402
|
||||||
from utils import check_envs, db_update, format_date, get_start_end_dates # noqa: E402
|
from utils import check_envs, db_update, format_date, get_start_end_dates # noqa: E402
|
||||||
|
|
||||||
|
|
||||||
async def end_workday(
|
def main():
|
||||||
args: Namespace = None,
|
args = arg_parser.parse_args()
|
||||||
bot: Bot = None,
|
|
||||||
user_id: int = None
|
|
||||||
):
|
|
||||||
args = args or arg_parser.parse_args()
|
|
||||||
|
|
||||||
if is_debug := args.debug:
|
if is_debug := args.debug:
|
||||||
config.MODE = "debug"
|
config.MODE = "debug"
|
||||||
@@ -43,7 +47,7 @@ async def end_workday(
|
|||||||
}
|
}
|
||||||
db.insert_user(**clock_user, **red_user)
|
db.insert_user(**clock_user, **red_user)
|
||||||
if args.init_db:
|
if args.init_db:
|
||||||
await db_update(bot)
|
db_update()
|
||||||
|
|
||||||
activities = sorted(clock.get_activities(
|
activities = sorted(clock.get_activities(
|
||||||
**{
|
**{
|
||||||
@@ -54,19 +58,16 @@ async def end_workday(
|
|||||||
), key=lambda x: x.date_start)
|
), key=lambda x: x.date_start)
|
||||||
if not activities:
|
if not activities:
|
||||||
logger.info(Fore.YELLOW + "Нет задач для занесения трудочасов" + Fore.RESET)
|
logger.info(Fore.YELLOW + "Нет задач для занесения трудочасов" + Fore.RESET)
|
||||||
if bot:
|
|
||||||
await bot.send_message(user_id, "Нет задач для занесения трудочасов")
|
|
||||||
return
|
return
|
||||||
db.insert_activities(activities)
|
db.insert_activities(activities)
|
||||||
try:
|
try:
|
||||||
tracked_activities = red.track_activities(activities)
|
tracked_activities = red.track_activities(activities)
|
||||||
if bot:
|
|
||||||
await bot.send_message(user_id, f"Трудочасы занесены: {len(tracked_activities)}/{len(activities)}.")
|
|
||||||
clock.mark_tracked(tracked_activities)
|
clock.mark_tracked(tracked_activities)
|
||||||
if not is_debug:
|
if not is_debug:
|
||||||
db.insert_time_entries(tracked_activities)
|
db.insert_time_entries(tracked_activities)
|
||||||
db.insert_activities(tracked_activities)
|
db.insert_activities(tracked_activities)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if bot:
|
|
||||||
await bot.send_message(user_id, f"Ошибка при трекинге трудочасов: {e}")
|
|
||||||
logger.error(e)
|
logger.error(e)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
5
utils.py
5
utils.py
@@ -5,7 +5,6 @@ import os
|
|||||||
import re
|
import re
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from aiogram import Bot
|
|
||||||
from colorama import Fore
|
from colorama import Fore
|
||||||
|
|
||||||
from config import config
|
from config import config
|
||||||
@@ -41,11 +40,9 @@ def format_date(date: datetime) -> datetime:
|
|||||||
def parse_task_id(tag: str) -> int:
|
def parse_task_id(tag: str) -> int:
|
||||||
return int(re.search(r"^(?P<task_id>\d{5})", tag).group("task_id"))
|
return int(re.search(r"^(?P<task_id>\d{5})", tag).group("task_id"))
|
||||||
|
|
||||||
async def db_update(bot: Bot = None, user_id: int = None):
|
def db_update():
|
||||||
from config import clock, db, red
|
from config import clock, db, red
|
||||||
logger.info(Fore.YELLOW + "Обновление базы данных..." + Fore.RESET)
|
logger.info(Fore.YELLOW + "Обновление базы данных..." + Fore.RESET)
|
||||||
if bot:
|
|
||||||
await bot.send_message(user_id, "Обновление базы данных...")
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Обновление Тегов
|
# Обновление Тегов
|
||||||
|
|||||||
Reference in New Issue
Block a user