async def receive_new_project_settings(message: types.Message, user: User,
                                       state: FSMContext):
    file = await bot.get_file(message.document['file_id'])
    settings = io.BytesIO()
    await file.download(settings)
    try:
        s = json.load(settings)
        async with OrmSession() as session:
            async with state.proxy() as data:
                project = await session.get(Project, data['project_id'])
            project.settings = s
            await session.commit()
        await message.reply(text(text('Проект', project.name),
                                 text('Новые настройки для проекта',
                                      project.name),
                                 code(json.dumps(s, indent=4, sort_keys=True)),
                                 sep='\n'),
                            parse_mode=types.ParseMode.MARKDOWN)
        await state.finish()
    except json.decoder.JSONDecodeError:
        await message.reply(
            'Это совсем не похоже на json-файл, пришлите другой файл',
            reply_markup=types.ForceReply(selective=True))
    except UnicodeDecodeError:
        await message.reply(
            'Это совсем не похоже на json-файл, у него вообще неизвестный бинарный формат, пришлите другой файл',
            reply_markup=types.ForceReply(selective=True))
Example #2
0
async def process_project_settigns_file(message: types.Message, user: User, chat: Chat, state: FSMContext):
    dt = message.date
    file = await bot.get_file(message.document['file_id'])
    settings = io.BytesIO()
    await file.download(settings)
    try:
        s = json.load(settings)
        async with OrmSession() as session:
            async with state.proxy() as data:
                project = Project(
                    owner_user_id=user.id,
                    name=data['project_name'],
                    chat_id=chat.id,
                    period_bucket_mode=human_period_bucket_modes[data['human_period_bucket_mode']],
                    created_dt=dt,
                    settings=s
                )
                session.add(project)
                await session.commit()
                await message.reply(
                    text(
                        text('Проект', project.name, 'создан'),
                        text('Примененные настройки:'),
                        code(json.dumps(s, indent=4, sort_keys=True)),
                        sep='\n'
                    ),
                    parse_mode=types.ParseMode.MARKDOWN
                )
                await state.finish()
    except json.decoder.JSONDecodeError:
        await message.reply(
            'Это совсем не похоже на json-файл, пришлите другой файл',
            reply_markup=types.ForceReply(selective=True)
        )
Example #3
0
async def process_note_text(message: types.Message, chat: Chat,
                            state: FSMContext):
    dt = message.date
    async with state.proxy() as data:
        async with OrmSession() as session:
            select_stmt = select(Project)\
                .where(
                    Project.chat_id == chat.id
                )\
                .order_by(Project.id)
            projects_result = await session.execute(select_stmt)
            project = projects_result.scalars().first()

            item_in_list_pos = data['item_in_list_pos']
            item_id = data['items_ids'][item_in_list_pos]
            log_message = ItemNote(project_id=project.id,
                                   item_id=item_id,
                                   text=message.text,
                                   created_dt=dt)
            logging.info(log_message)
            session.add(log_message)
            await session.commit()
    await message.reply(emojize(
        text(text('Все, так и запишу:'),
             text('    :pencil2:', message.text),
             sep='\n')),
                        disable_web_page_preview=True)
    await state.finish()
Example #4
0
async def process_project_name(message: types.Message, user: User, state: FSMContext):
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
            Project.name == message.text,
            Project.owner_user_id == user.id,
        )
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()
        if not project:
            await state.update_data(project_name=message.text)
            await NewProjectForm.next()

            keyboard = [
                [types.KeyboardButton(mode_name)]
                for mode_name in human_period_bucket_modes.keys()
            ]
            await message.reply(
                f'Выберите, когда обновлять списки задач',
                reply_markup=types.ReplyKeyboardMarkup(keyboard=keyboard, selective=True)
            )
        else:
            await message.reply(
                f'Проект "{message.text}" уже существует, выберите другое имя',
                reply_markup=types.ForceReply(selective=True)
            )
Example #5
0
async def notification_processing_loop():
    nsktz = pytz.timezone('Asia/Novosibirsk')
    while True:
        async with OrmSession() as session:
            select_stmt = select(Project)
            projects_result = await session.execute(select_stmt)
            for project in projects_result.scalars():
                await process_project_settings_and_send_messages(
                    session, project, datetime.now(nsktz))
        await asyncio.sleep(10)
async def log_message_about_work(message: types.Message, user: User,
                                 chat: Chat, state: FSMContext):
    dt = message.date
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.chat_id == chat.id
            ) \
            .order_by(Project.id)
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()

        bucket = PeriodBucket.new(project.period_bucket_mode, dt)
        items_list = await project.get_for_period(session, bucket)
        items_num = len(items_list.items) if items_list else 0
        if items_num > 0:
            items_texts = [
                item.text
                for item in sorted(items_list.items, key=lambda i: i.id)
            ]
            item_new_status = message.get_command(pure=True)
            await state.update_data(item_new_status=item_new_status)
            await state.update_data(items_num=items_num)
            await state.update_data(items_texts=items_texts)
            await state.update_data(items_ids=[
                item.id
                for item in sorted(items_list.items, key=lambda i: i.id)
            ])
            if items_num > 1:
                await ChangeItemStatusForm.item_id.set()
                keyboard = [[
                    types.KeyboardButton(
                        f'{i+1}. {txt if len(txt) < 32 else txt[:29] + "..."}')
                ] for i, txt in enumerate(items_texts)]
                await message.reply(
                    'Выберите, пожалуйста, задачу из списка '
                    f'текущего периода по порядковому номеру от 1 до {items_num}',
                    reply_markup=types.ReplyKeyboardMarkup(keyboard=keyboard,
                                                           selective=True))
            else:
                items_list.items[0].status = ItemStatus[item_new_status]
                await session.commit()
                await message.reply(emojize(
                    text(text('В плане один единственный пункт:'),
                         text('    :pushpin:', items_texts[0]),
                         text(human_item_status[items_list.items[0].status]),
                         sep='\n')),
                                    disable_web_page_preview=True)
        else:
            await message.answer(
                'Извините, у вас ничего не запланировано в ближайшее время')
Example #7
0
async def todo_for_today(message: types.Message, chat: Chat):
    dt = message.date
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.chat_id == chat.id
            ) \
            .order_by(Project.id)
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()

        bucket = PeriodBucket.new(project.period_bucket_mode, dt)
        todo_list = await project.get_for_period(session,
                                                 bucket,
                                                 with_log_messages=True)
        if project.period_bucket_mode == PeriodBucketModes.daily:
            period_bucket_word = 'на сегодня'
        elif project.period_bucket_mode == PeriodBucketModes.onworkingdays:
            period_bucket_word = 'на сегодня'
        elif project.period_bucket_mode == PeriodBucketModes.weekly:
            period_bucket_word = 'на текущую неделю'
        elif project.period_bucket_mode == PeriodBucketModes.monthly:
            period_bucket_word = 'в этом месяце'
        elif project.period_bucket_mode == PeriodBucketModes.yearly:
            period_bucket_word = 'в этом году'
        else:
            period_bucket_word = ''
        if todo_list:
            message_content = [
                text('Вот, что вы', period_bucket_word, 'планировали:')
                if period_bucket_word else text('Вот, что вы планировали:'),
                text('')
            ]
            for item in sorted(todo_list.items, key=lambda i: i.id):
                message_content.append(text(':pushpin:', item.text))
                for log_message in item.notes:
                    message_content.append(
                        text('    :paperclip:', log_message.text))
            message_content += [text(''), text('Все точно получится!')]
        else:
            message_content = [
                text('Никаких планов нет.'),
                # TODO предложение создать план и описание способа
            ]
        await message.answer(
            emojize(text(*message_content, sep='\n')),
            disable_web_page_preview=True,
        )
Example #8
0
async def todo_for_next_time(message: types.Message, chat: Chat):
    # TODO почти copy-paste, разобраться
    dt = message.date
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.chat_id == chat.id
            ) \
            .order_by(Project.id)
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()

        bucket = PeriodBucket.new(project.period_bucket_mode, dt).get_next()
        todo_list = await project.get_for_period(session, bucket)
        if todo_list:
            if project.period_bucket_mode == PeriodBucketModes.daily:
                period_bucket_word = 'на следующий день'
            elif project.period_bucket_mode == PeriodBucketModes.onworkingdays:
                period_bucket_word = 'на следующий рабочий день'
            elif project.period_bucket_mode == PeriodBucketModes.weekly:
                period_bucket_word = 'на следующую неделю'
            elif project.period_bucket_mode == PeriodBucketModes.monthly:
                period_bucket_word = 'на следующий месяц'
            elif project.period_bucket_mode == PeriodBucketModes.yearly:
                period_bucket_word = 'в следующем году'
            else:
                period_bucket_word = ''
            message_content = [
                text('Вот, что запланировано вами ',
                     period_bucket_word,
                     ':',
                     sep='') if period_bucket_word else
                text('Вот, что запланировано вами:'),
                text('')
            ] + [
                text(':pushpin: ' + item.text)
                for item in sorted(todo_list.items, key=lambda i: i.id)
            ] + [text(''), text('Но не отвлекайтесь, пожалуйста.')]
        else:
            message_content = [
                text('Никаких планов нет.'),
                # TODO предложение создать план и описание способа
            ]
        await message.answer(
            emojize(text(*message_content, sep='\n')),
            disable_web_page_preview=True,
        )
Example #9
0
async def log_message_about_work(message: types.Message, chat: Chat,
                                 state: FSMContext):
    dt = message.date
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.chat_id == chat.id
            ) \
            .order_by(Project.id)
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()

        bucket = PeriodBucket.new(project.period_bucket_mode, dt)
        items_list = await project.get_for_period(session, bucket)
        items_num = len(items_list.items) if items_list else 0
        if items_num > 0:
            items_texts = [item.text for item in items_list.items]
            await state.update_data(items_num=items_num)
            await state.update_data(items_texts=items_texts)
            await state.update_data(
                items_ids=[item.id for item in items_list.items])
            if items_num > 1:
                await AddNoteToItemForm.item_id.set()
                keyboard = [[
                    types.KeyboardButton(
                        f'{i+1}. {txt if len(txt) < 32 else txt[:29] + "..."}')
                ] for i, txt in enumerate(items_texts)]
                await message.reply(
                    f'Напишите, пожалуйста, порядковый номер сегодняшней задачи от 1 до {items_num}',
                    reply_markup=types.ReplyKeyboardMarkup(keyboard=keyboard,
                                                           selective=True))
            else:
                await state.update_data(item_in_list_pos=0)
                await AddNoteToItemForm.note_text.set()
                await message.reply(
                    emojize(
                        text(text('В плане один единственный пункт:'),
                             text('    :pushpin:', items_texts[0]),
                             text('Напишите свое сообщение и я его сохраню'),
                             sep='\n')),
                    reply_markup=types.ForceReply(selective=True),
                    disable_web_page_preview=True)
        else:
            await message.answer(
                'Извините, записи можно вести пока только по сегодняшним планам, '
                'а у вас ничего не запланировано')
async def start_project_settings_updating(message: types.Message, user: User):
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.owner_user_id == user.id,
            )
        projects_result = await session.execute(select_stmt)
        keyboard = []
        for project in projects_result.scalars():
            if not keyboard or len(keyboard[-1]) < 2:
                keyboard.append([])
            keyboard[-1].append(types.KeyboardButton(project.name))
        if keyboard:
            await UpdateProjectSettingsForm.project_name.set()
            await message.reply('Выберите проект',
                                reply_markup=types.ReplyKeyboardMarkup(
                                    keyboard=keyboard, selective=True))
        else:
            await message.reply('У вас нет проектов')
async def receive_project_name(message: types.Message, user: User,
                               state: FSMContext):
    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.name == message.text,
                Project.owner_user_id == user.id,
            )
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()
        if project:
            await state.update_data(project_id=project.id)
            await UpdateProjectSettingsForm.next()
            await message.reply(
                'Проект ' + message.text +
                '\nПришлите, пожалуйста, json-файл с настройками',
                reply_markup=types.ForceReply(selective=True))
        else:
            await message.reply(f'Проекта "{message.text}" не существует')
Example #12
0
 async def pre_process(self, obj, data, *args):
     from_user = types.User.get_current()
     async with OrmSession() as session:
         # TODO take care about caching
         user = await session.get(User, from_user.id)
         data['user'] = user
         data['is_new_user'] = False
         if not data['user']:
             dt = obj.date
             log.info(f'Creating user @{from_user.username} with id {from_user.id}')
             user = User(
                 id=from_user.id,
                 username=from_user.username,
                 first_name=from_user.first_name,
                 last_name=from_user.last_name,
                 lang=from_user.language_code,
                 joined_dt=dt
             )
             session.add(user)
             await session.commit()
             data['user'] = user
             data['is_new_user'] = True
async def process_item_id(message: types.Message, state: FSMContext):
    async with state.proxy() as data:
        mo = re.match(r'(\d+)(?:\. )?(.*)', message.text)
        if mo:
            item_in_list_pos = int(mo.group(1)) - 1
            text_fragment = mo.group(2)
            if not text_fragment or text_fragment == data['items_texts'][
                    item_in_list_pos] or data['items_texts'][
                        item_in_list_pos].startswith(text_fragment[:-3]):
                item_new_status = data['item_new_status']
                items_num = data['items_num']
                if item_in_list_pos < items_num:
                    item_id = data['items_ids'][item_in_list_pos]
                    async with OrmSession() as session:
                        item = await session.get(Item, item_id)
                        item.status = ItemStatus[item_new_status]
                        await session.commit()
                        await message.reply(
                            emojize(
                                text(text(human_item_status[item.status],
                                          ':',
                                          sep=''),
                                     text(':pushpin:', item.text),
                                     sep='\n')),
                            reply_markup=types.ReplyKeyboardRemove(),
                            disable_web_page_preview=True)
                        await state.finish()
                else:
                    await message.reply(
                        f'В вашем сегодняшнем плане нет столько пунктов, напишите число от 1 до {items_num}'
                    )
            else:
                await message.reply(
                    'Укажите, пожалуйста, либо просто порядковый номер без всего, либо нажмите на предложенную кнопку'
                )
        else:
            await message.reply(
                'Укажите, пожалуйста, либо просто порядковый номер без всего, либо нажмите на предложенную кнопку'
            )
Example #14
0
 async def pre_process(self, obj, data, *args):
     if isinstance(obj, types.ChatMemberUpdated):
         sender_chat = types.ChatMemberUpdated.get_current().chat
     else:
         sender_chat = types.Chat.get_current()
     async with OrmSession() as session:
         # TODO take care about caching
         chat = await session.get(Chat, sender_chat.id)
         data['chat'] = chat
         data['is_new_chat'] = False
         if not data['chat']:
             dt = obj.date
             log.info(f'Creating chat @{sender_chat.username} with id {sender_chat.id}')
             chat = Chat(
                 id=sender_chat.id,
                 type=sender_chat.type,
                 username=sender_chat.username,
                 joined_dt=dt
             )
             session.add(chat)
             await session.commit()
             data['chat'] = chat
             data['is_new_chat'] = True
Example #15
0
async def create_tomorrow_todo_list(message: types.Message, chat: Chat):
    dt = message.date
    async with OrmSession() as session:
        parsed_todo_items = get_bulleted_items_list_from_message(message)
        if parsed_todo_items:
            select_stmt = select(Project) \
                .where(
                    Project.chat_id == chat.id
                ) \
                .order_by(Project.id)
            projects_result = await session.execute(select_stmt)
            project = projects_result.scalars().first()

            bucket = PeriodBucket.new(project.period_bucket_mode,
                                      dt).get_next()
            new_todo_list = await project.create_new_for_period_with_items_or_append_to_existing(
                session, bucket, dt, parsed_todo_items)
            await session.commit()
            if project.period_bucket_mode == PeriodBucketModes.daily:
                reply_message_text = text(
                    text('Я запишу, что вы запланировали:') if new_todo_list
                    else text('К тому, что вы уже запланировали я добавлю:'),
                    text(''),
                    *[
                        text(':inbox_tray: ', parsed_item)
                        for parsed_item in parsed_todo_items
                    ],
                    text(''),
                    text(
                        'Завтра я напомню об этом. Чтобы посмотреть планы в любой момент, можно набрать /planned'
                    ),
                    sep='\n')
            else:
                if project.period_bucket_mode == PeriodBucketModes.weekly:
                    period_bucket_word = 'на следующую неделю'
                elif project.period_bucket_mode == PeriodBucketModes.monthly:
                    period_bucket_word = 'на следующий месяц'
                elif project.period_bucket_mode == PeriodBucketModes.yearly:
                    period_bucket_word = 'на следующий год'
                else:
                    period_bucket_word = ''
                reply_message_text = text(
                    (text('Я запишу ваши планы на ',
                          period_bucket_word,
                          ':',
                          sep='') if new_todo_list else
                     text('К вашим планам', period_bucket_word, 'я добавлю:'))
                    if period_bucket_word else
                    (text('Я запишу ваши планы')
                     if new_todo_list else text('К вашим планам я добавлю:')),
                    text(''),
                    *[
                        text(':inbox_tray: ', parsed_item)
                        for parsed_item in parsed_todo_items
                    ],
                    text(''),
                    text(
                        'Завтра я напомню об этом. Чтобы посмотреть планы в любой момент, можно набрать /planned'
                    ),
                    sep='\n')
            await message.reply(
                emojize(reply_message_text),
                disable_web_page_preview=True,
            )
Example #16
0
async def create_today_todo_list(message: types.Message, chat: Chat):
    dt = message.date
    async with OrmSession() as session:
        parsed_todo_items = get_bulleted_items_list_from_message(message)
        if parsed_todo_items:
            select_stmt = select(Project) \
                .where(
                    Project.chat_id == chat.id
                ) \
                .order_by(Project.id)
            projects_result = await session.execute(select_stmt)
            project = projects_result.scalars().first()

            bucket = PeriodBucket.new(project.period_bucket_mode, dt)
            if bucket.is_valid():
                new_todo_list = await project.create_new_for_period_with_items_or_append_to_existing(
                    session, bucket, dt, parsed_todo_items)
                await session.commit()
                if project.period_bucket_mode in [
                        PeriodBucketModes.daily,
                        PeriodBucketModes.onworkingdays
                ]:
                    reply_message_text = text(
                        text(
                            'План составлен не с вечера, но и день в день - тоже замечательно. Вот, пожалуйста:'
                        ) if new_todo_list else
                        text('К вашим сегодняшним планам я добавлю:'),
                        text(''),
                        *[
                            text(':inbox_tray:', parsed_item)
                            for parsed_item in parsed_todo_items
                        ],
                        text(''),
                        text(
                            'Чтобы свериться со списком запланированных дел, можно набрать /todo'
                        ),
                        sep='\n')
                else:
                    if project.period_bucket_mode == PeriodBucketModes.weekly:
                        period_bucket_word = 'на текущую неделю'
                    elif project.period_bucket_mode == PeriodBucketModes.monthly:
                        period_bucket_word = 'на текущий месяц'
                    elif project.period_bucket_mode == PeriodBucketModes.yearly:
                        period_bucket_word = 'на текущий год'
                    else:
                        period_bucket_word = ''
                    reply_message_text = text(
                        (text('Я запишу ваши планы на ',
                              period_bucket_word,
                              ':',
                              sep='') if new_todo_list else text(
                                  'К вашим планам', period_bucket_word,
                                  'я добавлю:')) if period_bucket_word else
                        (text('Я запишу ваши планы') if new_todo_list else
                         text('К вашим планам я добавлю:')),
                        text(''),
                        *[
                            text(':inbox_tray:', parsed_item)
                            for parsed_item in parsed_todo_items
                        ],
                        text(''),
                        text(
                            'Чтобы свериться со списком запланированных дел, можно набрать /todo'
                        ),
                        sep='\n')
                await message.reply(
                    emojize(reply_message_text),
                    disable_web_page_preview=True,
                )
            else:
                await message.reply(
                    text(
                        'Сегодня выходной, никаких планов, пожалуйста. Ничего не буду добавлять'
                    ),
                    disable_web_page_preview=True,
                )
Example #17
0
async def status_report(message: types.Message, chat: Chat):
    if message.get_command() == '/wsr':
        start_dt = message.date + relativedelta(
            weekday=WE(-1), hour=0, minute=0, second=0, microsecond=0)
        end_dt = message.date + relativedelta(weekday=WE, hour=0, minute=0, second=0, microsecond=0) \
                 - relativedelta(days=1)
    else:
        start_dt = message.date + relativedelta(
            day=1, hour=0, minute=0, second=0, microsecond=0)
        end_dt = message.date + relativedelta(months=1,
                                              day=1,
                                              hour=0,
                                              minute=0,
                                              second=0,
                                              microsecond=0,
                                              days=-1)
    grid = generate_grid(start_dt, end_dt)
    grid = [[[i[0], i[1]] for i in week] for week in grid]

    async with OrmSession() as session:
        select_stmt = select(Project) \
            .where(
                Project.chat_id == chat.id
            ) \
            .order_by(Project.id)
        projects_result = await session.execute(select_stmt)
        project = projects_result.scalars().first()
        from_period = PeriodBucket.new(project.period_bucket_mode, start_dt)

        todo_lists = await project.get_since(session,
                                             from_period,
                                             with_log_messages=True)
        message_content = []
        for todo_list in todo_lists:
            bucket = PeriodBucket.get_by_key(todo_list.period_bucket_key)
            for todo_item in todo_list.items:
                message_content.append(
                    text(':spiral_calendar_pad:', str(bucket), ':pushpin:',
                         todo_item.text))
                for log_message in todo_item.notes:
                    message_content.append(
                        text(':paperclip:', log_message.text))
                message_content.append(text(''))

            if bucket.start():
                for week in grid:
                    for i in week:
                        if i[1].date() == bucket.start().date():
                            i[0] = i[0].replace('white', 'purple')

        import io
        file = io.StringIO(emojize(text(*message_content, sep='\n')))
        for week in grid:
            for i in week:
                if i[1].date() == datetime.now().date():
                    if 'white' in i[0] or 'black' in i[0]:
                        i[0] = i[0].replace('circle', 'large_square')
                    else:
                        i[0] = i[0].replace('circle', 'square')
        grid = [[i[0] for i in week] for week in grid]
        await message.answer_document(
            file,
            caption=emojize(
                text(text(
                    f'Отчет о проделанной работе с {start_dt.date()} по {end_dt.date()}'
                ),
                     text(''),
                     text('Пн Вт Ср Чт Пт Сб Вс'),
                     *[text(*week, sep='') for week in grid],
                     sep='\n')))