Esempio n. 1
0
async def check_floors(query: types.CallbackQuery,
                       pois: List[POI],
                       house: str = None):
    if not pois:
        kbd = types.InlineKeyboardMarkup().add(
            types.InlineKeyboardButton(tr('add_poi'), callback_data='new'))
        await bot.send_message(query.from_user.id,
                               tr('no_poi_around'),
                               reply_markup=kbd)
        return

    floors = set([p.floor for p in pois])
    if len(floors) >= 2:
        khouse = '-' if house is None else house
        kbd = types.InlineKeyboardMarkup(row_width=3)
        for ifloor in floors:
            label = ifloor if ifloor is not None else tr(
                ('review', 'no_floor'))
            kbd.insert(
                types.InlineKeyboardButton(label,
                                           callback_data=FLOOR_CB.new(
                                               house=khouse,
                                               floor=ifloor or '-')))
        kbd.insert(
            types.InlineKeyboardButton(tr(('review', 'all_floors')),
                                       callback_data=FLOOR_CB.new(house=khouse,
                                                                  floor='*')))
        await bot.edit_message_reply_markup(query.from_user.id,
                                            query.message.message_id,
                                            reply_markup=kbd)
    else:
        # Just one floor, so doesn't matter
        await start_review(query.from_user, house)
Esempio n. 2
0
async def print_edit_message(message: types.Message,
                             state: FSMContext,
                             attr: str,
                             dash: bool = False,
                             poi_attr: str = None,
                             msg_attr: str = None,
                             kbd=None,
                             value='-',
                             content: str = None):
    reply0 = None
    if value is not None:
        poi = (await state.get_data())['poi']
        if value == '-':
            pvalue = getattr(poi, poi_attr or attr)
        else:
            pvalue = value(poi)
        if pvalue:
            reply0 = (await message.answer(str(pvalue))).message_id

    if not content:
        content = tr(('editor', msg_attr or attr))
        if dash:
            content += ' ' + tr(('editor', 'dash'))
    if not kbd:
        kbd = cancel_attr_kbd()
    await delete_msg(message, state)
    reply = await message.answer(content,
                                 reply_markup=kbd,
                                 disable_web_page_preview=True)
    await EditState.attr.set()
    await state.update_data(attr=attr, reply=[reply.message_id, reply0])
Esempio n. 3
0
async def edit_house(message: types.Message, state: FSMContext):
    poi = (await state.get_data())['poi']
    houses = await db.get_houses()
    houses.sort(key=lambda h: poi.location.distance(h.location))
    houses = houses[:3]

    # Prepare the map
    map_file = get_map([h.location for h in houses], ref=poi.location)
    # Prepare the keyboard
    kbd = types.InlineKeyboardMarkup(row_width=1)
    for i, house in enumerate(houses, 1):
        prefix = '✅ ' if house == poi.house else ''
        kbd.add(
            types.InlineKeyboardButton(
                f'{prefix} {i} {house.name}',
                callback_data=HOUSE_CB.new(hid=house.key)))
    kbd.add(
        types.InlineKeyboardButton(tr(('editor', 'cancel')),
                                   callback_data='cancel_attr'))

    # Finally send the reply
    await delete_msg(message, state)
    if map_file:
        await message.answer_photo(types.InputFile(map_file.name),
                                   caption=tr(('editor', 'house')),
                                   reply_markup=kbd)
        map_file.close()
    else:
        await message.answer(tr(('editor', 'house')), reply_markup=kbd)
Esempio n. 4
0
async def message_info(message: types.Message):
    info = await get_user(message.from_user)
    if info.is_moderator():
        await message.answer(tr('message_self'))
        return
    await message.answer(tr('message'))
    await MsgState.sending.set()
Esempio n. 5
0
async def update_review(query: types.CallbackQuery, callback_data: Dict[str,
                                                                        str]):
    info = await get_user(query.from_user)
    if not info.review:
        await query.answer(tr(('review', 'no_review')))
        return

    poi_id = int(callback_data['id'])
    review_record = [r for r in info.review if r[0] == poi_id]
    if not review_record:
        await query.answer(tr(('review', 'no_record')))
        return

    if review_record[0][1]:
        # We have old updated, revert to it
        await db.set_updated(poi_id, review_record[0][1])
        review_record[0][1] = None
    else:
        review_record[0][1] = await db.set_updated(poi_id)

    # Update keyboard
    pois = await db.get_poi_by_ids([r[0] for r in info.review])
    kbd = await make_review_keyboard(pois)
    await bot.edit_message_reply_markup(query.from_user.id,
                                        query.message.message_id,
                                        reply_markup=kbd)
Esempio n. 6
0
async def store_photo(query: types.CallbackQuery,
                      callback_data: Dict[str, str], state: FSMContext):
    poi = (await state.get_data())['poi']
    name = callback_data['name']
    path = os.path.join(config.PHOTOS, name + '.jpg')
    if not os.path.exists(path):
        await query.answer(tr(('editor', 'photo_lost')))
        return
    which = callback_data['which']
    if which == 'out':
        poi.photo_out = name
    elif which == 'in':
        poi.photo_in = name
    elif which == 'unlink':
        if poi.photo_out == name:
            poi.photo_out = None
        elif poi.photo_in == name:
            poi.photo_in = None
    elif which == 'del':
        os.remove(path)
        await query.answer(tr(('editor', 'photo_deleted')))
    else:
        await query.answer(tr(('editor', 'photo_forgot')))
    await delete_msg(query)
    await state.set_data({'poi': poi})
    await print_edit_options(query.from_user, state)
Esempio n. 7
0
def edit_loc_kbd(poi):
    return types.InlineKeyboardMarkup().add(
        types.InlineKeyboardButton(tr(('editor', 'latlon')),
                                   url='https://zverik.github.io/latlon/#18/'
                                   f'{poi.location.lat}/{poi.location.lon}"'),
        types.InlineKeyboardButton(tr(('editor', 'cancel')),
                                   callback_data='cancel_attr'))
Esempio n. 8
0
async def message_info_callback(query: types.CallbackQuery):
    info = await get_user(query.from_user)
    if info.is_moderator():
        await query.answer(tr('message_self'))
        return
    await bot.send_message(query.from_user.id, tr('message'))
    await MsgState.sending.set()
Esempio n. 9
0
async def process_queue(query: types.CallbackQuery, callback_data: Dict[str,
                                                                        str]):
    action = callback_data['action']
    q = await db.get_queue_msg(int(callback_data['id']))
    if not q:
        await query.answer(tr(('queue', 'missing')))
        return

    if action == 'del':
        await db.delete_queue(q)
        await query.answer(tr(('queue', 'deleted')))
    elif action == 'apply':
        await db.apply_queue(query.from_user.id, q)
        await query.answer(tr(('queue', 'applied')))
    elif action == 'look':
        poi = await db.get_poi_by_id(q.poi_id)
        if not poi:
            await query.answer(tr(('queue', 'poi_lost_del')))
            await db.delete_queue(q)
        else:
            await print_poi(query.from_user, poi, buttons=False)
            return
    else:
        await query.answer(f'Wrong queue action: "{action}"')

    await print_next_queued(query.from_user)
Esempio n. 10
0
def location_keyboard():
    return types.InlineKeyboardMarkup().add(
        types.InlineKeyboardButton(
            tr(('editor', 'latlon')),
            url='https://zverik.github.io/latlon/#16/53.9312/27.6525'),
        types.InlineKeyboardButton('❌ ' + tr('cancel'),
                                   callback_data='cancel'),
    )
Esempio n. 11
0
async def new_poi(query: types.CallbackQuery):
    if config.MAINTENANCE:
        await bot.send_message(query.from_user.id, tr('maintenance'))
        return
    await EditState.name.set()
    await bot.send_message(query.from_user.id,
                           tr(('new_poi', 'name')),
                           reply_markup=cancel_keyboard())
Esempio n. 12
0
async def validate_poi(query: types.CallbackQuery, callback_data: Dict[str,
                                                                       str]):
    poi = await db.get_poi_by_id(int(callback_data['id']))
    if not poi:
        await query.answer(tr(('queue', 'poi_lost')))
        return
    await db.validate_poi(poi.id)
    await query.answer(tr(('queue', 'validated_ok')))
    await print_next_queued(query.from_user)
Esempio n. 13
0
async def new_name(message: types.Message, state: FSMContext):
    name = message.text.strip()
    if len(name) < 3:
        await message.answer(tr(('new_poi', 'name_too_short')))
        return
    await state.set_data({'name': name})
    await EditState.location.set()
    await message.answer(tr(('new_poi', 'location')),
                         reply_markup=location_keyboard())
Esempio n. 14
0
def relative_day(next_day):
    days = (next_day.date() - datetime.now().date()).days
    if days < 1:
        opens_day = ''
    elif days == 1:
        opens_day = tr('tomorrow')
    else:
        opens_day = tr('relative_days')[next_day.weekday()]
    return opens_day
Esempio n. 15
0
async def message_intro(message: types.Message, state: FSMContext):
    user = await get_user(message.from_user)
    if user.is_moderator():
        await message.answer(tr(('editor', 'cant_message')))
        return
    await delete_msg(message, state)
    reply = await message.answer(tr(('editor', 'message')),
                                 reply_markup=cancel_attr_kbd())
    await EditState.message.set()
    await state.update_data(reply=reply.message_id)
Esempio n. 16
0
async def process_reply(message: types.Message):
    info = await get_user(message.from_user)
    to = await get_user(message.reply_to_message.forward_from)
    if info.is_moderator():
        # Notify other moderators that it has been replied
        # TODO: can we do it just once per user?
        await broadcast_str(tr('reply_sent', to.name),
                            info.id,
                            disable_notification=True)
    await bot.send_message(to.id, tr('do_reply'))
    await message.forward(to.id)
Esempio n. 17
0
async def set_loc(message):
    location = Location(message.location.longitude, message.location.latitude)
    info = await get_user(message.from_user)
    info.location = location
    if info.is_moderator():
        # Suggest review mode
        kbd = types.InlineKeyboardMarkup().add(
            types.InlineKeyboardButton(tr(('review', 'start')),
                                       callback_data='start_review'))
    else:
        kbd = get_buttons()
    await message.answer(tr('location'), reply_markup=kbd)
Esempio n. 18
0
def boolean_kbd(attr: str):
    return types.InlineKeyboardMarkup().add(
        types.InlineKeyboardButton(tr(('editor', 'bool_true')),
                                   callback_data=BOOL_CB.new(attr=attr,
                                                             value='true')),
        types.InlineKeyboardButton(tr(('editor', 'bool_false')),
                                   callback_data=BOOL_CB.new(attr=attr,
                                                             value='false')),
        types.InlineKeyboardButton(tr(('editor', 'bool_none')),
                                   callback_data=BOOL_CB.new(attr=attr,
                                                             value='null')),
        types.InlineKeyboardButton(tr(('editor', 'cancel')),
                                   callback_data='cancel_attr'))
Esempio n. 19
0
async def new_cancel(query: types.CallbackQuery, state: FSMContext):
    await delete_msg(query, state)
    await state.finish()
    user = await get_user(query.from_user)
    if user.review:
        kbd = types.InlineKeyboardMarkup().add(
            types.InlineKeyboardButton('🗒️ ' + tr(('review', 'continue')),
                                       callback_data='continue_review'))
    else:
        kbd = get_buttons()
    await bot.send_message(query.from_user.id,
                           tr(('new_poi', 'cancel')),
                           reply_markup=kbd)
Esempio n. 20
0
async def edit_links(message: types.Message, state: FSMContext):
    poi = (await state.get_data())['poi']
    if poi.links:
        content = tr(('editor', 'links_have')) + '\n\n'
        content += '\n'.join([f'🔗 {h(l[0])}: {h(l[1])}' for l in poi.links])
    else:
        content = tr(('editor', 'no_links'))
    content += '\n\n' + tr(('editor', 'links'))
    await print_edit_message(message,
                             state,
                             'links',
                             value=None,
                             content=content)
Esempio n. 21
0
async def new_keywords(message: types.Message, state: FSMContext):
    keywords = split_tokens(message.text)
    if not keywords:
        await message.answer(tr(('new_poi', 'no_keywords')))
        return
    # Create a POI
    data = await state.get_data()
    poi = POI(name=data['name'],
              location=Location(lat=data['lat'], lon=data['lon']),
              keywords=' '.join(keywords))
    await state.set_data({'poi': poi})
    await EditState.confirm.set()
    await print_edit_options(message.from_user, state,
                             tr(('new_poi', 'confirm')))
Esempio n. 22
0
async def new_location(message: types.Message, state: FSMContext):
    loc = parse_location(message)
    if not loc:
        await message.answer(tr(('new_poi', 'no_location')),
                             reply_markup=location_keyboard())
        return
    if not valid_location(loc):
        await message.answer(tr(('new_poi', 'location_out')),
                             reply_markup=location_keyboard())
        return
    await state.update_data(lon=loc.lon, lat=loc.lat)
    await EditState.keywords.set()
    await message.answer(tr(('new_poi', 'keywords')),
                         reply_markup=cancel_keyboard())
Esempio n. 23
0
async def simlar_poi(query: types.CallbackQuery, callback_data: Dict[str, str],
                     state: FSMContext):
    poi = await db.get_poi_by_id(int(callback_data['id']))
    if not poi or not poi.tag:
        await query.answer(tr('query_fail'))
    else:
        pois = await db.get_poi_by_tag(poi.tag)
        if len(pois) == 1:
            await query.answer(tr('no_similar'))
        else:
            tag_names = config.TAGS['tags'].get(poi.tag)
            pquery = poi.tag if not tag_names else tag_names[0]
            await PoiState.poi_list.set()
            await state.set_data({'query': pquery, 'poi': [p.id for p in pois]})
            await print_poi_list(query.from_user, pquery, pois, relative_to=poi.location)
Esempio n. 24
0
async def undelete_poi(message: types.Message, state: FSMContext):
    user = await get_user(message.from_user)
    if not user.is_moderator():
        await message.answer(tr(('editor', 'cant_restore')))
        return
    poi = (await state.get_data())['poi']
    await db.restore_poi(message.from_user.id, poi)
    await state.finish()
    if user.review:
        kbd = types.InlineKeyboardMarkup().add(
            types.InlineKeyboardButton('🗒️ ' + tr(('review', 'continue')),
                                       callback_data='continue_review'))
    else:
        kbd = get_buttons()
    await message.answer(tr(('editor', 'restored')), reply_markup=kbd)
Esempio n. 25
0
def format(v, yes=None, no=None, null=None):
    if v is None or v == '':
        return f'<i>{null or tr(("editor", "unknown"))}</i>'
    if isinstance(v, str):
        return h(v)
    if isinstance(v, bool):
        return (yes or tr(('editor', 'bool_yes'))) if v else (no or tr(
            ('editor', 'bool_no')))
    if isinstance(v, (int, float)):
        return v
    if isinstance(v, hoh.OHParser):
        return h(v.field)
    if isinstance(v, Location):
        return f'v.lat, v.lon'
    return str(v)
Esempio n. 26
0
async def add_mod(message: types.Message, state: FSMContext):
    if message.from_user.id != config.ADMIN:
        raise SkipHandler
    if not message.is_forward():
        await message.answer(tr(('admin', 'forward')))
        return
    await state.finish()
    me = await get_user(message.from_user)
    new_user = await get_user(message.forward_from)
    if new_user.is_moderator():
        await message.answer(tr(('admin', 'mod_already')))
        return
    await db.add_user_to_role(new_user, 'moderator', me)
    forget_user(new_user.id)
    await message.answer(tr(('admin', 'mod_added'), new_user.name))
    await bot.send_message(new_user.id, tr(('admin', 'mod_you')))
Esempio n. 27
0
async def in_house_callback(query: types.CallbackQuery, callback_data: Dict[str, str],
                            state: FSMContext):
    house = callback_data['house']
    floor = callback_data['floor']
    data = await db.get_poi_by_key(house)
    pois = await db.get_poi_by_house(house, None if floor == '-' else floor)
    if floor == '-' and len(pois) > 9:
        floors = await db.get_floors_by_house(house)
        if len(floors) >= 2 and None not in floors:
            # We have floors - add another selection
            kbd = types.InlineKeyboardMarkup(row_width=3)
            for ifloor in floors:
                kbd.insert(types.InlineKeyboardButton(
                    ifloor, callback_data=POI_HOUSE_CB.new(house=house, floor=ifloor)))
            await bot.edit_message_reply_markup(
                query.from_user.id, query.message.message_id, reply_markup=kbd)
            return

    if not pois:
        await query.answer(tr('no_poi_in_house'))
    elif len(pois) == 1:
        await PoiState.poi.set()
        await state.set_data({'poi': pois[0].id})
        await print_poi(query.from_user, pois[0])
    else:
        await PoiState.poi_list.set()
        await state.set_data({'query': query, 'poi': [p.id for p in pois]})
        await print_poi_list(query.from_user, data.name, pois, True)
Esempio n. 28
0
async def print_next_added(user: types.User):
    info = await get_user(user)
    if not info.is_moderator():
        return False
    poi = await db.get_next_unchecked()
    if not poi:
        await bot.send_message(user.id, tr(('queue', 'empty')))
        return True
    await print_poi(user, poi)

    content = tr(('queue', 'new_poi'))
    kbd = types.InlineKeyboardMarkup().add(
        types.InlineKeyboardButton(
            '✔️ ' + tr(('queue', 'validated')),
            callback_data=POI_VALIDATE_CB.new(id=str(poi.id))))
    await bot.send_message(user.id, content, reply_markup=kbd)
Esempio n. 29
0
def parse_hours(s):
    def norm_hour(h):
        if not h:
            return None
        if len(h) < 4:
            h += ':00'
        return h.replace('.', ':').rjust(5, '0')

    if s in ('24', '24/7'):
        return '24/7'

    HOURS_WEEK = {tr(('editor', 'hours_abbr'))[i]: DOW[i] for i in range(7)}
    HOURS_WEEK.update({DOW[i].lower(): DOW[i] for i in range(7)})

    parts = []
    for part in s.split(','):
        m = RE_HOURS.match(part.strip().lower())
        if not m:
            raise ValueError(part)
        wd = 'Mo-Su' if not m.group(1) else HOURS_WEEK[m.group(1)]
        if m.group(2):
            wd += '-' + HOURS_WEEK[m.group(2)]
        h1 = norm_hour(m.group(3))
        h2 = norm_hour(m.group(4))
        l1 = norm_hour(m.group(5))
        l2 = norm_hour(m.group(6))
        if l1:
            wd += f' {h1}-{l1},{l2}-{h2}'
        else:
            wd += f' {h1}-{h2}'
        parts.append(wd)
    return '; '.join(parts)
Esempio n. 30
0
async def delete_poi_prompt(message: types.Message, state: FSMContext):
    poi = (await state.get_data())['poi']
    if poi.delete_reason:
        user = await get_user(message.from_user)
        if not user.is_moderator():
            await message.answer(tr(('editor', 'delete_twice')))
        else:
            await db.delete_poi_forever(user.id, poi)
            await state.finish()
            await message.answer(tr(('editor', 'deleted2')),
                                 reply_markup=get_buttons())
    else:
        await message.answer(tr(('editor', 'delete')),
                             reply_markup=cancel_attr_kbd())
        await EditState.attr.set()
        await state.update_data(attr='delete')