• Как организовать множественное залипание заголовков таблиц?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Закрепить заголовки сразу нескольких таблиц при прокрутке не получится, но вы можете разделить 2 таблицы соответственно на 2 разных листа и для каждого сделать закрепление области (Вид -> Закрепить области):

    66ebf73d4549c482452538.png
    Ответ написан
    Комментировать
  • Как отключить auth_basic для некоторых путей в nginx?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Вас нужно правильно переопределить директиву auth_basic off:
    location / {
        root              /var/www/web/sites;
        try_files         /$host/ @default_host;
        proxy_pass        http://127.0.0.1:8080;
        proxy_redirect    http://127.0.0.1:8080/ /;
        auth_basic "Restricted Access";
        auth_basic_user_file /etc/nginx/auth.htpasswd;
    }
    
    location /bitrix/admin/ {
        proxy_pass        http://127.0.0.1:8080;
        proxy_redirect    http://127.0.0.1:8080/ /;
        auth_basic off;
    }
    Ответ написан
    1 комментарий
  • Как отправить форму со скрытыми полями Django, значения в которых должны вычислятся на основе не скрытых полей?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Как минимум не нужно предварительно отправлять POST-запрос на ваш сервер перед перенаправлением на платёжную страницу Modulbank. Вместо этого вычисления полей, таких как signature, могут происходить на вашем сервере, но сама форма должна отправляться непосредственно на сайт банка.

    from django.shortcuts import render
    from django.http import JsonResponse
    import hashlib
    import time
    
    def calculate_signature(data, secret_key):
        values = f"{data['amount']}{data['client_email']}{secret_key}"
        signature = hashlib.sha1(values.encode()).hexdigest()
        return signature
    
    def payment_view(request):
        if request.method == 'POST':
            amount = request.POST.get('amount')
            client_email = request.POST.get('client_email')
            order_id = 14425840
            secret_key = 'your_secret_key'
            signature = calculate_signature({
                'amount': amount,
                'client_email': client_email
            }, secret_key)
    
            context = {
                'amount': amount,
                'client_email': client_email,
                'order_id': order_id,
                'signature': signature,
                'merchant': 'ad25ef06-1824-413f-8ef1-c08115b9b979',
                'success_url': 'http://yoursite.com/success'
            }
            return render(request, 'payment_form.html', context)
    
        return render(request, 'payment_page.html')


    <form method="post" action="https://pay.modulbank.ru/pay">
        <input type="hidden" name="amount" value="{{ amount }}">
        <input type="hidden" name="order_id" value="{{ order_id }}">
        <input type="hidden" name="signature" value="{{ signature }}">
        <input type="hidden" name="client_email" value="{{ client_email }}">
        <input type="hidden" name="merchant" value="{{ merchant }}">
        <input type="hidden" name="success_url" value="{{ success_url }}">
        <input type="submit" value="Оплатить">
    </form>
    Ответ написан
  • Как хранить диффы изменений файлов?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Вы можете хранить изменения файлов как набор патчей где каждое изменение записывается в виде разницы между состояниями файла. При чтении файла можно применить все патчи к базовому состоянию файла, чтобы получить актуальный контент.

    Пример кода для создания базового патча:
    struct FilePatch {
        int lineNumber;
        char* oldLine;
        char* newLine;
    };
    
    struct FileDiff {
        FilePatch* patches;
        int patchCount;
    };
    
    void applyDiff(char** fileContent, FileDiff* diff) {
        for (int i = 0; i < diff->patchCount; ++i) {
            int lineNumber = diff->patches[i].lineNumber;
            fileContent[lineNumber] = diff->patches[i].newLine;
        }
    }
    Ответ написан
    Комментировать
  • Как правильно настроить права в Docker?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Укажите запуск команды композера от имени текущего пользователя на хосте:
    docker compose run --user $(id -u):$(id -g) composer create-project laravel/laravel .


    Вы также можете задать пользователя по умолчанию в composer.Dockerfile:
    FROM composer:latest
    
    WORKDIR /var/www/laravel
    
    RUN adduser -D -u USER www-data
    USER www-data
    
    ENTRYPOINT ["composer", "--ignore-platform-reqs"]
    Ответ написан
  • Как сделать линию не на всю ширину?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Правильнее будет создать линию и круг в двух отдельных псевдоэлементах, задав размеры и границы:
    Ответ написан
    Комментировать
  • Как максимально эффективно по скорости написать этот алгоритм преобразования строки?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    #include <stdio.h>
    #include <string.h>
    #include <assert.h>
    #include <stdlib.h>
    
    char* get_part_path(const char* s, const char* find_path) {
        const char* start = strstr(s, find_path);
        if (!start) {
            return NULL;
        }
    
        start += strlen(find_path);
    
        while (*start == '/') {
            start++;
        }
    
        const char* end = strchr(start, '/');
        if (!end) {
            end = s + strlen(s);
        }
    
        size_t len = end - start;
        char* result = (char*)malloc(len + 1);
        if (result) {
            strncpy(result, start, len);
            result[len] = '\0';
        }
    
        return result;
    }
    
    int main() {
        assert(strcmp(get_part_path("/base/4", "/"), "base") == 0);
        assert(strcmp(get_part_path("base/4", "/"), "base") == 0);
        assert(strcmp(get_part_path("/base/4", "/base"), "4") == 0);
        assert(strcmp(get_part_path("base/4", "/base"), "4") == 0);
        assert(strcmp(get_part_path("base/4", "/base/4"), "4") == 0);
    
        printf("All tests passed!\n");
        return 0;
    }
    Ответ написан
    Комментировать
  • Laravel найти в коллекциях по ключу и объединить в новую коллекцию?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    use Illuminate\Support\Collection;
    
    $collection1 = collect([
        [
            "имя1" => "John",
            "объем1" => "100",
            "мусор" => "x"
        ],
        [
            "имя1" => "Jane",
            "объем1" => "200",
            "мусор" => "y"
        ]
    ]);
    
    $collection2 = collect([
        [
            "имя2" => "John",
            "объем2" => "300",
            "мусор" => "x"
        ],
        [
            "имя2" => "Jane",
            "объем2" => "400",
            "мусор" => "z"
        ]
    ]);
    
    function normalizeKeys(Collection $collection, $nameKey, $volumeKey)
    {
        return $collection->map(function ($item) use ($nameKey, $volumeKey) {
            return [
                'name' => $item[$nameKey] ?? null,
                'volume' => $item[$volumeKey] ?? null,
            ];
        });
    }
    
    $normalized1 = normalizeKeys($collection1, 'имя1', 'объем1');
    $normalized2 = normalizeKeys($collection2, 'имя2', 'объем2');
    
    $result = $normalized1->map(function ($item1) use ($normalized2) {
        $match = $normalized2->firstWhere('name', $item1['name']);
        
        return [
            'name' => $item1['name'],
            'volume1' => $item1['volume'],
            'volume2' => $match['volume'] ?? null,
        ];
    });
    Ответ написан
    1 комментарий
  • Как вывести поле с типом список?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Сделайте просто доп запрос к методу crm.deal.userfield.get, указав код поля, для получения всех значений, которые могут быть в списке. После этого вы сможете сопоставить ключ (например, 46) с его значением.

    Вот пример кода:
    $userFieldResult = CRest::call(
        'crm.deal.userfield.get',
        array(
            'id' => 'UF_CRM_1726662473238'
        )
    );
    
    if (!empty($userFieldResult['result']['LIST'])) {
        $listValues = array_column($userFieldResult['result']['LIST'], 'VALUE', 'ID');
    
        $dealResult = CRest::call(
            'crm.deal.get',
            array(
                'id' => 580 
            )
        );
    
        $listValueId = $dealResult['result']['UF_CRM_1726662473238'];
    
        if (isset($listValues[$listValueId])) {
            $listValue = $listValues[$listValueId];
        }
    }
    Ответ написан
    Комментировать
  • Как работает loader и defer с await вместе?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Как работает loader и defer сами по себе я объяснять уж не буду, тем более сам знаешь определения.

    В совокупности каждая из этих функций выполняет определённую роль:
    loader отвечает за загрузку данных перед рендерингом маршрута.
    defer позволяет загрузить некоторые данные (не критичные для начального рендеринга) уже после рендеринга компонента.
    await внутри defer используется, чтобы ожидать данные до их использования в компоненте.

    Таким образом, они не противоречат друг другу. Вместо этого они работают вместе, предоставляя гибкость: ты можешь загружать важные данные до рендеринга, а менее важные - после рендеринга.

    Вот простой пример:
    export function loader() {
      return defer({
        importantData: fetchImportantData(), // сразу загружается до рендеринга
        delayedData: fetchDelayedData(), // загружается после рендеринга
      });
    }


    loader синхронно загружает данные при условии, что данные критичны для начального рендеринга, а defer определяет, какие данные загружаются параллельно, то бишь критичные данные могут быть загружены сразу, а остальные можно отложить, чтобы загрузить их после рендеринга с помощью await.

    Что касается useEffect, он никак не противоречит использованию defer, но они решают разные задачи. useEffect выполняется после того, как компонент был отрендерен, а defer помогает управлять загрузкой данных до или после рендеринга. Если ты используешь useEffect, это может быть полезно для обработки данных, уже загруженных с помощью defer, или для выполнения побочных эффектов, но не для непосредственной загрузки данных до рендеринга компонента.
    Ответ написан
    Комментировать
  • Как отправить длинное SMS с помощью SMPP?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Для отправки длинных SMS необходимо вручную разбить сообщение на части и указать, что эти части составляют одно целое сообщение. Для этого нужно использовать механизм сегментирования сообщений, который реализуется через UDH (User Data Header).

    Пример реализации такого скрипта согласно стандарту GSM:

    const smpp = require('smpp');
    const session = smpp.connect('smpp://your-smpp-server');
    
    function splitMessage(message) {
      const messageParts = [];
      const partSize = 153;
      for (let i = 0; i < message.length; i += partSize) {
        messageParts.push(message.substring(i, i + partSize));
      }
      return messageParts;
    }
    
    function sendLongSms(session, destination, source, message) {
      const parts = splitMessage(message);
      const refNumber = Math.floor(Math.random() * 255);
      const totalParts = parts.length;
      
      parts.forEach((part, index) => {
        const udh = Buffer.from([
          0x05, // UDH length
          0x00, // IEI (Information Element Identifier)
          0x03, // Length of header
          refNumber, // Reference number (randomized)
          totalParts, // Total number of parts
          index + 1, // Current part number
        ]);
    
        const shortMessage = Buffer.concat([udh, Buffer.from(part, 'utf-8')]);
    
        session.submit_sm({
          destination_addr: destination,
          source_addr: source,
          short_message: shortMessage,
          data_coding: 0,
        }, (pdu) => {
          if (pdu.command_status === 0) {
            console.log('Message sent successfully.');
          } else {
            console.error('Message failed with status:', pdu.command_status);
          }
        });
      });
    }
    
    session.bind_transceiver({
      system_id: 'your_system_id',
      password: 'your_password',
    }, (pdu) => {
      if (pdu.command_status === 0) {
        console.log('Connected');
    
        const longMessage = 'Your very long message goes here...';
        sendLongSms(session, 'destination_number', 'source_number', longMessage);
      } else {
        console.error('Failed to connect:', pdu.command_status);
      }
    });
    Ответ написан
    4 комментария
  • Когда лучше использовать cms?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Если ты создаешь простой сайт или лендинг, который не требует сложной логики и динамического контента, то стека html, js, css вполне будет достаточно.
    Использование CMS подразумевает то, что контент будет редактироваться через админку. В плане экономии времени разработки это тоже хороший вариант. Из личного опыта я бы посоветовал CMS 1C-Bitrix. Я сейчас не буду расписывать чем та или иная CMS лучше другой, а чем хуже. Нужно будет, почитаете об этом в интернете. Для себя я выбрал битрикс из-за уровня безопасности и удобства использования интерфейса, кастомной разработки компонентов и так далее.

    Для создания интернет-магазина тебе почти всегда лучше использовать CMS со встроенным функционалом для управления товарами и заказами.
    Ответ написан
    4 комментария
  • Пагинация inline кнопок с помощью inline кнопок aiogram?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Сделай просто обновление текущего сообщения вместо отправки нового при нажатии на кнопки "влево" и "вправо", а также сохраняй и извлекай текущую страницу для пользователя.

    Как-то так:

    from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
    from aiogram import types
    
    def create_pagination_buttons(current_page, max_page, chunks):
        buttons = InlineKeyboardMarkup()
        
        for a in chunks[current_page]:
            buttons.add(InlineKeyboardButton(text=a['shortName'], callback_data=f"school-{a['id']}"))
        
        buttons.add(InlineKeyboardButton(text='<', callback_data=f'to-left-{max_page}'),
                    InlineKeyboardButton(text=f'{current_page + 1}/{max_page}', callback_data='count'),
                    InlineKeyboardButton(text='>', callback_data=f'to-right-{max_page}'))
        
        buttons.add(InlineKeyboardButton(text='Поменять город', callback_data='choose_city'))
        
        return buttons
    
    async def send_school_list(message: types.Message, r_json, current_page=0):
        chunk_size = 15
        chunks = [r_json[i:i + chunk_size] for i in range(0, len(r_json), chunk_size)]
        max_page = len(chunks)
    
        buttons = create_pagination_buttons(current_page, max_page, chunks)
        await message.answer('Выберите школу:', reply_markup=buttons)
    
    @dp.callback_query_handler(lambda c: re.search('to-', c.data))
    async def to_page(call: types.CallbackQuery):
        chat_id = call.message.chat.id
        await call.answer()
    
        direction = call.data.split("-")[1]
        max_page = int(call.data.split("-")[2])
        
        current_page = int(db.get_current_page(chat_id))
    
        if direction == 'left':
            current_page = (current_page - 1) % max_page
        elif direction == 'right':
            current_page = (current_page + 1) % max_page
    
        db.set_current_page(chat_id, current_page)
    
        r_json = reqtest.get_schools(449)
        chunk_size = 15
        chunks = [r_json[i:i + chunk_size] for i in range(0, len(r_json), chunk_size)]
    
        buttons = create_pagination_buttons(current_page, max_page, chunks)
        await call.message.edit_reply_markup(reply_markup=buttons)
    Ответ написан
    1 комментарий
  • Как предотвратить изменение пользователем данных во время выполнения async метода сохранения этих данных?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Всё зависит от конкретной ситуации...
    На мой взгляд, наиболее простым и действенным решением будет временное отключение элементов управления, которые позволяют пользователю менять данные. Отключаете элементы интерфейса пользователю, пока выполняется асинхронный процесс сохранения, и будет вам счастье.

    В XAML вы можете сделать что-то вроде этого:
    <Button Content="Сохранить" Command="{Binding SaveCommand}" IsEnabled="{Binding IsSaving}" />
    <TextBox Text="{Binding Person.Name}" IsEnabled="{Binding IsSaving, Converter={StaticResource InverseBoolConverter}}" />


    В ViewModel не забудьте добавить свойство IsSaving, которое будет управлять состоянием элементов:
    public bool IsSaving { get; private set; }
    
    private async Task SaveDataAsync()
    {
        IsSaving = false;
        RaisePropertyChanged(nameof(IsSaving));
    
        await repository.SaveAsync(person);
    
        IsSaving = true;
        RaisePropertyChanged(nameof(IsSaving));
    }
    Ответ написан
    Комментировать
  • Как исправить ошибку отключения распознавания речи?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    В вашем коде pyglet блокирует основной поток программы, рекомендую запускать воспроизведение звука в фоновом потоке.

    import pyautogui
    import pyglet
    import os
    import threading
    
    pyautogui.hotkey('win', 'e')
    
    def play_sound():
        song = pyglet.media.load('speak/yes.mp3')
        song.play()
        pyglet.app.run()
    
    threading.Thread(target=play_sound).start()
    
    print("[F.R.I.D.A.Y]: Запрос выполнен")
    Ответ написан
    5 комментариев
  • Как сделать чтобы sphinx выдавал сперва точные совпадения а потом остальные?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Используйте оператор WEIGHT() для приоритизации точных совпадений:
    SELECT *, WEIGHT() as relevance 
    FROM my_index 
    WHERE MATCH('^"точный запрос"$ | запрос')
    ORDER BY relevance DESC;


    Можно также использовать операторы ORDER BY для сортировки по релевантности и FIELD() для повышения релевантности точных совпадений
    SELECT * 
    FROM my_index 
    WHERE MATCH('запрос') 
    ORDER BY FIELD(my_column, 'точный запрос') DESC, relevance DESC;
    Ответ написан
    6 комментариев
  • Как подключить своего бота к аккаунту тг для автоответов?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Aiogram сам по себе поддерживает автоответы. Убедитесь, что бот правильно обрабатывает входящие сообщения. Возможно ваш бот настроен только на команды, но не на текстовые сообщения. Используйте обработчик для текстовых сообщений:

    from aiogram import types
    
    @dp.message_handler(content_types=types.ContentType.TEXT)
    async def auto_reply(message: types.Message):
        await message.answer("Спасибо за ваше сообщение!")
    Ответ написан
  • Возможно ли включить сортировку во время touchstart?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Вместо того чтобы полностью полагаться на touchstart, используйте его для начала подготовки, но инициализируйте сортировку с помощью других событий, например touchmove. При этом стоит ограничить область touchmove, чтобы сортировка не начиналась до явного движения пальцем. Также нужно вручную инициировать сортировку.

    var sortable = new Sortable(listElement, {
        animation: 150,
        onStart: function (evt) {
            console.log('Начало сортировки');
        }
    });
    
    listElement.addEventListener('touchstart', function (e) {
        var touch = e.touches[0];
        var targetElement = document.elementFromPoint(touch.clientX, touch.clientY);
    
        sortable._onDragStart({
            target: targetElement,
            clientX: touch.clientX,
            clientY: touch.clientY,
            type: 'touchstart'
        });
    }, false);
    Ответ написан
  • Почему неправильно работает сервис?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Разделите логику запуска свайпа и поиск имени, чтобы они синхронизировались правильно. Попробуйте запускать свайп только после того, как будет выполнен поиск имени и произойдёт событие изменения содержимого окна.

    Вот пример:
    @Override
    public void onAccessibilityEvent(AccessibilityEvent event) {
        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED) {
            AccessibilityNodeInfo rootNode = getRootInActiveWindow();
            if (rootNode != null) {
                if (scanForTargetName(rootNode)) {
                    stopSwiping();
                    return;
                }
            }
    
            startSwiping();
        }
    }
    Ответ написан
    Комментировать
  • PHP Отправка xml в EDI kontur через API?

    grantur5707
    @grantur5707
    Full Stack Web Developer
    Возможно вы некорректно формируете массив байтов, либо не задаёте необходимый Content-Type в шапке запроса.
    Вот пример корректного запроса:

    $xmlContent = '<?xml version="1.0" encoding="UTF-8"?><message>Пример сообщения</message>';
    
    $xmlBytes = unpack('C*', $xmlContent);
    
    $byteString = implode(array_map("chr", $xmlBytes));
    
    $url = 'https://edi.kontur.ru/V1/Messages/SendMessage';
    
    $ch = curl_init($url);
    
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, array(
        'Content-Type: application/octet-stream',
        ...
    ));
    
    curl_setopt($ch, CURLOPT_POSTFIELDS, $byteString);
    
    $response = curl_exec($ch);
    
    if (curl_errno($ch)) {
        echo 'Ошибка cURL: ' . curl_error($ch);
    } else {
        echo 'Ответ сервера: ' . $response;
    }
    
    curl_close($ch);
    Ответ написан