Телеграм-бот для автоматизации обменника криптовалюты / Хабр

 

googleQ7/fastcoinbot

This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Latest commit

Git stats

Files

Failed to load latest commit information.

README.md

Это краткая документация по боту.

Логика работы бота

Создайте .env файл в папке с ботом и откройте его в текстовом редакторе. Заполните его следующим образом:

  1. Токен бота, который вы болучается от BotFather
  2. URL вебхука на который будут приходить сообщения см. документацию BOT API
  3. Приватный ключ от главного Bitcoin-кошелька, см. документация Bitcoin
  4. Telegram-Id администратора, можно получить в @myidbot
  5. Номер Сбербанк-карты для перевода

Для работы бота необходимо установить следующие зависимости:

  • Python 3.x
  • Redis-server
  • Произвести установки библиотек с помощью PIP: pip3 install -r requirements.txt
  1. Обновите окружение, добавив переменные из файла .env с помощью команды source .env
  2. Запустите redis-server: redis-server
  3. Для тестового запуска бота, можете воспользоваться стандартным интепритатором Python: python3 app.py
  4. Для продакшена советую воспользоваться Gunicorn: gunicorn app:app

Ядро бота состоит из двух модулей в корне проекта: app.py и bot.py

Первый модуль представляет из себя стартовый модуль, который запускает и инициализирует бота, он отвечает за создание Flask веб-приложения, создание объекта бота, инициализацию бота и вебхуков, получение и обработку данных с вебхуков.

Bot.py отвечает за класс Bot , который как представляет из центральную часть прилоежения, которая отвечает за работу бота. В нем описаны методы работы с: базой данных, Telegram API, обработку сообщений пользователя, загрузку модулей бота, работу с пользовательской сессией, генерирование пользовательских клавиатур и сообщений из шаблонов.

Модули представляют из себя некоторые скрипты, сценарии, которым бот передает сообщения от пользователя, а они в свою очередь решают что делать в той или иной ситуации. Модули состоят из некоторых функций обработчиков handler‘ов. Самое первое сообщение пользователя обрабатывается стандартным хендлером, который указан в конфиге в дальнейшей работе бота хендлеры могут не только получать данные, но и указывать какой хендлер будет обрабатывать следующее сообщение. Таким образом строится неявный граф обработки сообщений пользователя.

Внутри модулей хендлеры могут оперировать любой информацией:

  • Получать и записывать данные в базу данных
  • Отправлять сообщение через Telegram API
  • Запрашивать или отправлять средства по средствам Bitcoin API
  • Отрисовывать пользовательские клавиатуры
  • И многое другое..

Конфиги представляют из себя статические JSON-файлы, которые хранят необходимую боту информацию. На данный момент в боте существует три конфиг-файлов: init.json , keyboards.json , messages.json .

Первый файл отвечает за основные настройки бота:

  • default-handler — стандартный обработчик сообщения, когда пользователь пишет первый раз или не указан обработчик который будет обрабатывать следующее собщение
  • menu-button — сообщение при получении которого, бот всегда будет возвращаться в главное меню
  • comission — Bitcoin комиссия для совершения транзакции, указвается в сатоши!

keyboards.json — отвечает за хранение шаблонов клавиатур, о работе с которыми вы можете прочитать далее messages.json — отвечает за хранение шаблонов сообщений, о работе с которыми вы можете прочитать далее

Для работы с Bitcoin используется библиотека pybitcointools, которая была склонирована в папку с проектом и дописана для совместимости с третьим питоном.

За работу кошельков отвечают два модуля: wallet.py и main_wallet.py . Первый — отвечает за работу с кошельками пользователей, второй — за работу с главным кошельком.

Здесь описаны все публичные методы и переменные класса Bot.

Работа с базой данных

Методы отвечающие за хранение, получение и удаление информации о пользователе.

user_set(user_id, field, value)

Метод отвечает за запись некоторой информации для определенного пользователя, по определенному полю. Значение value автоматически сериализуется в json и записывается в базу, в нашем случае redis.

В качестве значения u_id, передается telegram-id пользователя, который можно получить из объекта сообщения: message.u_id. По договоренности нулевой id используется для хранения системных данных.

user_get(user_id, field, default=None)

Метод отвечает за получение некоторой информации для определенного пользователя, по определенному полю. Значение default отвечает за значение, которое будет возвращаться в случае если значения по данному полю не существует.

В качестве значения u_id, передается telegram-id пользователя, который можно получить из объекта сообщения: message.u_id. По договоренности нулевой id используется для хранения системных данных.

Метод отвечает за удаление некоторой информации для определенного пользователя, по определенному полю.

Работа с API Telegram

Для работы с Telegram API используется библиотека pytelegrambotapi. Для того, что бы взаимодействовать с telegram api обратитесь к объекту телеграм бота: bot.telegram и вызовите необходимый метод. Например, отправка сообщений: bot.telegram.send_message(u_id, message). Подробную документацию о библиотеке читайте здесь.

Работа с сообщениями и калавиатурами

Сообщения в telegram предстваляют из себя обычный текст, который мы передаем параметром в функцию. В данном боте встроена система шаблонизации сообщений, шаблоны хранятся в файле /config/messages.json . Сообщения используют шаблонизатор Jinja2, документацию читать можно здесь.
Так же шаблоны сообщений могут содержать в себе разметку Markdown, но для этого в метод send_message должен быть передан параметр parse_mode=»Markdown» .

Телеграм имеет два вида клавиатур: инлайн и обычные.

Данный тип клавиатур появляется вместо клавиатуры набора текста и обычно представляют готовые варианты для набора. При нажатии на любую кнопку данной клавиатуры отправляется сообщение.

Шаблоны клавиатур распологаются в файле `config/keyboards.json’ и представляют из себя следующую структуру.

Для отрисовки клавиатуры используется метод bot.get_keyboard(«имя-клавиатуры») , после чего полученый объект передается в метод bot.telegram.send_message парметром reply_markup=. .

Хендлер который получит сообщение с вашей кнопкой должен вызвать метод bot.get_key(«название-клавиатуры», «текст-полученного-сообщения») , после чего будет возвращена строка с значением кнопки, или None если такой кнопки нет.

Читать статью  BTCBIT - обменник криптовалют. Обзор, отзывы, инструкции

Инлайн клавиатуры отличаются тем, что данная клавиатура прикреплена к сообщению и при нажжатии на нее не происходит отправка сообщения, а вызывается callback функция.

Формат хранения и место хранения не отличается от обычных клавиатур, однако вместо «значения» кнопки указывается имя вызываемой callback функции в ответ на нажатие кнопки, о том как создать подобную функцию читайте далее.

Хендлеры представляют из себя некоторые функции, которые обрабатывают различные типы сообщений. Существует два вида хендлеров: callback и стандартные.

Callback хендлеры — функции обрабатывающие запросы от инлайн клавиатур. Для создания callback-handler, просто создайте функцию с двумя входными параметрами: bot и query.

Первый парметр — объект бота, с помощью которого вы можете обращаться к базе данных, отправлять сообщения и многое другое.

Второй параметр — объект типа inlineQuery который несет всю необходимую информацию о запросе.

После создания функции, просто добавьте ее в список callback хендлеров бота уазав ее имя: bot.callback_handlers[«имя-коллбека»] = функция

Работа с этим типом хендлеров описана в главе «Клавиатуры».

Данный тип хендлеров отличается от предыдущих тем, что отвечает за обработку обычных сообщений пользователя, в связи с чем вместо объекта query используется объект типа message

Добавление функции в список выглядит следующим образом: bot.handlers[«имя-хенлера»] = функция

Для установки хендлера, который будет обрабатывать следующее сообщение пользователя воспользуйтесь методом: bot.set_next-handler(u_id, «название-хендлера»)

Для вынужденного вызова хендлера из текущего хендлера воспользуйтесь методом: bot.call_handler(«название-хендлера», message, forward_flag=True)

Параметр message — объект сообщения, который пришел в текущий хендлер, параметр forward_flag — указывает, ставить ли флаг в сообшении о том, что оно было переадресовано из другого хендлера, получить значение этого флага можно следующим образом: message.forward .

Написание своих модулей

Для того, что бы создать свой модуль просто создайте файл с расширением .py в папке /modules . В файле обязательно должен присутсвовать метод init(bot) . Данный метов вызывается при инициализации модуля и в него передается объект бота. Данный метод необходим для установки начальных настроект модуля, например в этом методе инициализируются все хендлеры (добавляются в бота), о том как это сделать читайте в главе «Хендлеры».

Для работы с Bitcoin используется библиотека pybitcointools, вся работа с ней реализована в модулях main_wallet и wallet.

Wallet — класс кошелька пользователя, при создании объекта в него передается user id, по которому в базе ищется приватный ключ пользователя, если его нет (пользователь впервые использует бота) — он создается.

Класс содержит следующие методы:

  • get_curency — Запрос текущего курса BTC/RUB
  • get_balance — Получение текущего баланса кошелька пользователя
  • send_money — Отправка средств на кошелек другого пользователя

MainWallet — класс главного кошелька, на котором хранится запас BTC для совершения операций покупки. Класс наследует все методы Walllet и имеет некоторые особенности.

При создании передается приватный ключ с имеющимися на счету BTC. Пользователь может передать в кошелек свою транзакцию для перевода денег на его кошелек (запрос на покупку), транзакция представляет из себя следующий объект:

Администратор в свою очередь может подтвердить прохождение транзакции или отменить ее.

Телеграм-бот для автоматизации обменника криптовалюты

В этой статье я буду в общих чертах рассказывать про то, в каком направлении нужно двигаться, чтобы сделать полуавтоматический обменник криптовалюты с возможностью управлять сделками с любого устройства в любой точке планеты 24/7. Вы не найдете здесь деталей реалиализации, т.к. этот материал предназначен скорее для получения базового набора знаний, необходимых для запуска такого стартапа. Результат вы можете посмотреть на exbtc.pro

Полуавтоматический обменник криптовалюты.

Как-то очень давно я у же немного писал про использование телеграм-бота для автоматизации некоторых процессов. Надо сказать, что уже не мало времени прошло, но я продолжаю использовать некоторые идеи, что были изложены в том небольшом материале.

Обмен криптовалюты сегодня — это уже не просто реальность, в какой-то мере это уже потребность. Время беспощадно и теперь цифровое золото становится очень важной частью активов миллионов людей. Обменники в интернете бывает нескольких видов, основные из которых:

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

Необходимый набор навыков.

Говоря довольно простой, я наверное выражаюсь несовсем корректно. В наше время навыки, которыми должен обладать разработчик, оцениваются в тысячи часов времени, которое он должен потратить на свое обучение. Я сегодня буду предельно краток, так что давайте сразу перейдем к делу. Для реализации задачи нам понадобится следующий набор иснтрументов:

Linux, zsh, vim, systemd

ES6, Material Ui, React, eslint, webpack, scss

python3, asyncio, aiohttp, peewee

telegram bot api

И такие паттерны как:

MVC — шаблон архитектуры системы

Abstract Factory, Factory Method, Builder, Facade, Prototype — генерация объектов

Scheduler — многопоточный постановщик задач

Event Listner, State — события, сосстояния

Proxy — заместитель для балансировки нагрузки

В общих чертах это вроде как все, что должно пригодится, согласитесь не мало. Давайте пробежимся теперь немного более подробно, чтобы понимать как все это барахло должно быть настроено, чтобы даже работать.

Теперь я начинаю с фронта

Если вы попробуете поискать в сети с чего начинать веб разработку — с фронтенда или с бэкэнда, вы наверное не найдете ничего более дельного, чем информация о том, что все это лучше делать параллельно и каких-то особых протоколов на этот счет нет. Т.е. фронтендер делает свою работу, бэкэндер свою, они встречаются на созвонах и в чате, обсуждают все проблемы: все хорошо. Но что, если вы собираетесь делать и фронт и бэк самостоятельно (например в случае небольшого приложения как криптообменник) — какая будет точка отправления?

Начинать лучше с фронта, потому как он может работать на моковых данных и бэкэнд ему собственно нужен только абсолютно гипотетически. Фронтенд — независимое приложение, он должен работать корректно в разных браузерах, на разных устройствах. Мы будем делать Single Page Application, а значит нам потребуется протокол взаимодействия, давайте выберем json-rpc . Для транспортного протокола используем tcp, а на прикладном уровне остановимся на http.

Читать статью  Обменники электронной валюты онлайн ТОП-40 — лучший и выгодный обмен электронных денег

Дальше все довольно не трудно. Ставим Node Package Manager , создаем новое реакт приложение, добавляем туда react router, настраиваем eslint для форматирования кода, node-sass для возможности использования css препроцессора, webpack для сборки проекта.

Правильная структура проекта — залог успеха. Компоненты делаем модульными — файлы стилей лежат внутри дирректории рядом с компонентом. Компоненты по мере возможностей реализуем как stateless. Я бы пожалуй еще рекоммендовал дважды задуматься перед внедерением redux в приложение — делайте это только если вы точно уверены, что вам это нужно.

При работе с фронтом важно всегда помнить об области видимости переменных, потому как современный фронтенд это сложное многопоточное, асинхронное приложение. При этом при правильной организации файлов и компонентов, react и material ui позволяют делать все достаточно быстро. Если у вас нет готового дизайна — просто выберите сайт обменника, который вам нравится и не стесняясь копируйте его, превнося свои изменения — в дальнейшем ваш обменник все равно еще претерпит кучу изменений и не стоит беспокоится о том как это смотрится на текущей стадии.

Вот так может выглядеть ваш реакт компонент, отвечающий за рендеринг главной страницы:

Бэкэнд — это сложно, но куда веселее

Бэкэнд тоже должен быть асинхронным. P2P приложения должны быть ориентированы под высокую нагрузку, а значит сразу стоит закладывать немного больше, чем может показаться нужным. Мы будем делать монолитный бэкэнд, потому как серверная часть не будет очень большой. Микросервисы это здорово, но не всегда необходимо, и в данном случае мы не будем использовать этот подход.

asyncio позволяет работать с петлей событий, что в свою очередь предоставляет возможность асинхронного программирования и управления заданиями. В нашем случае у нас будет несколько заданий, которые должны будут работать независимо и параллельно основному приложению. Это задание на обновление курсов BTC/USD и USD/RUB, и задание, которое будет отменять устаревшие заявки на обмен валюты. Курсы вылют можно получать get запросом из апи всех популярных бирж, например coinbase, kraken, bitmex. Благо aiohttp client позволяет это делать в несколько строчек:

Для того, чтобы сериализовывать данные, необходимые фронту для построения интерфейса, сервер должен использовать объектно-ориентированную модель данных, проще говоря — нужно поделить наш обменник на модели, а эти модели сложить в таблицы бд, чтобы можно было строить к ней запросы.

При создании моделей стоит особое внимание уделить инкапсуляции и наследованию — хорошей идеей будет сразу создать BaseModel, в которую поменстить, например, поля created_at, updated_at и, например, datetime_serializer, который вам точно пригодится, а остальные модели наследовать от этой модели:

Для взаимодействия с блокчейн придется получить API KEY, например на blockchain.com. Хочу сразу отметить, что тут есть своего рода «подводный камень». Как работает блокчейн апи? После того, как создается транзакция, для ее завершения необходимы подтверждения от майнеров. Каждое подтверждение — это своего рода события, информацию о котором вы будете получать на свой сервер. В этом событии есть адрес кошелька, на который поступает криптовалюта. Теперь предположим, что для покупки криптовалюты в нашем обменнике мы всем пользователям будем предоставлять одинаковый кошелек для перевода. Это было бы довольно удобно, так как все биткоины были бы сосредоточены у нас на одном адресе, одной суммой. На первый взгляд. Но в таком случае при поступлении средств от пользователя на кошелек и последующих веб хуках от блокчейн на callback_url, мы не сможем определить от какого конкретно пользователя поступил платеж. Можно конечно использовать параметр в webhook url но есть еще один интересный нюанс. Нам важно знать курс по которому была совершена та или иная транзакция.Опять же, есть вариант хранить свзяь между транзакицей и курсом, но есть и альтернативное решение. Оно состоит в том, что каждому пользователю системы должен генерироваться свой уникальный BTC кошелек. И в случае, когда этот самый пользователь хочет совершить сделку в нашем обменном пункте и продавть свои кровные BTC, мы будем скидывать ему его уникальный адрес.

Дальше может покзаться, что целесообразно со всех этих адресов собирать все деньги на master wallet, но это не так, ведь за каждый перевод вы будете платить комиссию майнерам. К слову эту комиссию нужно считать ручками. Получить информацию от сети можно в любой момент:

Таким образом получается, что баланс нашего обменника сосредоточен децентрализованно на разных кошельках всех пользователей. При каждой транзакции мы записываем курс, по которому она была осуществлена, а ее статус (так же как впрочем и статус документа по этой транзакции) мы меняем в зависимости от подтверждений blockchain. К слову сразу имеет смысл подумать над реализацией классов Billing и Processing, для создания и проводки документов.

Для продажи крипты нам необходим механизм ручного взаимодействия с этими документами: нужно искать документ с выгодным на текущий момент курсом, проверять текущий баланс кошелька из этого документа, и выполнять перевод с этого кошелька на кошелек, который указывает пользователь в своей заявке на покупку в нашем обменнике. И вот мы наконец подобрались к кульминации нашего материала: к нам снова на помощь приходит телеграм.

Telegram bot

Тут все совсем не трудно. Создаем бота у @BotFather, настраиваем, берём токен, кладем его в конфиг (делаем два конфига и два бота — один на прод, один на дев).

Из этого токена мы можем создать бота и использовать его для получения важных данных, а так же для запуска протокола rpc , не требующего дополнительной авторизации и шифрования.

Теперь мы можем слать сообщения в телеграм из нашего бэкэнда, для этого мы создадим приватный чат и добавим бота администратором туда. Далее нам потребуется создать вебхук и контроллер для его обработки (читайте об этом в предыдущем материале про мониторинг). В контроллере нам необходимо создать диспетчер запросов, который будет определять обработчик.

Запросы от тг могут быть разные, нам пока нужны будут только message и callback_query (reply клавиатура и inline клавиатура).

Читать статью  Не приходит Биткоин на кошелек! Что делать и куда бежать?

Далее мы будем отправлять в этот чат сообщения с кнопками, которые позволят контролировать значения в базе данных. Здесь обращу внимание на race condition, и трудно-уловимые ошибки, по этому всегда используйте atomic_db_query

Кнопки вы можете нажать после ручной модерации заявки — ну т.е. вы посмотрели, что деньги к вам на счет реально поступили, и только после этого нажали на кнопку, которая запустит механизм для завершения документооборота. Так же можно, например создать механизм subscription , который позволит информировать подписавшихся пользователей на обновления курсов, например:

Это довольно удобно, ведь телеграм всегда под рукой, особенно после разблокировки . 24/7 все, кто находятся в приватной группе, смогут получать информационные сообщения, а так же управлять состоянием документов:

Настройка production

Нужно все это барахло завернуть в докер, настроить системный даемон для запуска юнита, в идеале конечно настроить CI-CD, но это все наверное уже детали.

В базовом варианте можно деплоится через гит, использовать переменные окружения для чтения конфигов, использовать ipython для проведения миграций в бд:

Нужно уметь использовать настраивать nginx , и понимать, как работает mod_rewrite .

Кстати для добавления вашего обменника в мониторинги, вам понадобится xml выгрузки файла курсов, так что этот rewrite вам может еще пригодится.

Наверное вы захотите сделать какую-то админку — для этого отлично сгодится механизм Basic Auth и bootstrap admin template . Вам останется только пробросить в шаблоны необходимый контекст и немного поиграть с контролами:

Послевкусие

Это все сложновато, но в тоже время и не очень, если не наступать на грабли, которые, надо сказать, присутствуют. Не забывайте о JWT , SSL , CORS , и еще куче прелестей, которые по пути обязательно появятся у вас на пути. Но в целом это рабочая схема автоматизации механизмов, которые могут пригодится не только при создании обменника. Я не претендую на роль эксперта в этом деле, я лишь высказываю свои умозаключения, после довольно трудоемкого процесса прохождения через все вышесказанное. Не стоит принимать буквально — многое является весьма субъективным и не претендует на роль аксиомы. Я бы сказал бОльшая часть. Но под лежачий камень вода не течет, и лучшее решение на сегодня — это развитие и движение дальше.

Надеюсь кому-то покажется эта информация полезной. Просто захотелось немного поделиться переживаниями и опытом, полученным от процесса.

Дальнейшие развитие позиционируется как p2p платформа для совершения обмена. Буду рад любым вопросам и предложениям, и большое спасибо за потраченное время.

[Мошенничество, нац.дискриминация] Exchanger CMS Скрипт обменника криптовалют

exchangerCMS

и не нужно писать что вас якобы кинули, ни копейки с ваших обменников не пропало, они и не могли пропасть т.к. скрипт НЕ автоматический, никакие данные которые могут быть использованы для кражы средств указывать при настройке скрипта не нужно. Лицензии отозваны у всех россиян за войну и зверства по отношению к Украине. Нравится вам это или нет, но проект продолжит работу, мнение русского оккупанта нам не интересно.

Поставщик скрипта берет на себя право разделять нации на «чистые» и «преступные»

Для публикации сообщений создайте учётную запись или авторизуйтесь

Вы должны быть пользователем, чтобы оставить комментарий

Создать учетную запись

Зарегистрируйте новую учётную запись в нашем сообществе. Это очень просто!

Войти

Уже есть аккаунт? Войти в систему.

Последние посетители 0 пользователей онлайн

  • Ни одного зарегистрированного пользователя не просматривает данную страницу

Similar Topics

[Мошенничество] Криптонатор

Криптонатор — самый популярный в России мультикриптовалютный онлайн кошелек, который позволяет работать с различными криптовалютами, такими как Биткоин и другими. Вы можете безопасно хранить, быстро и просто получать, отправлять и обменивать Биткоин и другие криптовалюты используя свой персональный счет. Доступ к персональному мультивалютному счету доступен круглосуточно, из любой точки планеты, как с компьютера, так и со смартфона. Мультивалютный счет Управляйте различными крипт

Мошенничество под видом Metamask

TokenScope проведено расследование обстоятельств хищения средств с кошелька Metamask, которое оказалось связанным с более крупным хищением средств в размере $40 млн., похищенных и выведенных через кошельки сервиса Tornado. Хищение средств с кошелька Metamask | TokenScope

23 май 2022, 13:27 в Флейм

Билли Маркус: «95% криптоактивов – мошенничество и мусор»

Сооснователь Dogecoin Билли Маркус назвал 95% криптоактивов «мусором», а создатель проекта Terra До Квон заблокировал его в Twitter за слишком категоричные высказывания. Билли Маркус (Billy Marcus), известный под псевдонимом Shibetoshi Nakamoto, выразил мнение в Twitter по поводу вложений инвесторов в криптовалютные проекты, в результате чего люди лишь теряют миллиарды долларов. По словам Маркуса, 95% криптоактивов – «мошенничество и мусор», а большинство людей, занимающихся крипт

Трейдер Джереми Спенс приговорен к 42 месяцам тюрьмы за мошенничество

Американский криптовалютный трейдер Джереми Спенс, известный под псевдонимом «Coin Signals», получил 42 месяца тюрьмы за мошенничество на $5 млн. Министерство юстиции обвинило Джереми Спенса (Jeremy Spence) в обмане 170 инвесторов и растрате $5 млн. По данным прокуроров, трейдер заявлял о своей «крайне прибыльной стратегии торговли», но фактически его сделки были убыточными практически всегда. Его арестовали в начале 2021 года, осенью он признал свою вину, а сейчас суд Южного округа Нью-Йор

Мошенничество на localbitcoins. Как избежать мошенников на локалбиткоинс?

От Helber (2020г.): После чтения топика были собраны распространенные способы обмана, которые могут быть использованы мошенниками на P2P-площадках , таких, как Localbitcoins. 1) Классический «треугольник»: Пример: на первой странице данного топика, нижняя часть первого поста. Советы по избежанию: первый, второй Дополнительный совет для случаев, когда контрагент отказывается делать фото карты, т.к. она будто бы не имеет физического воплощения (всякие ApplePay и

Источник https://github.com/googleQ7/fastcoinbot

Источник https://habr.com/ru/post/542244/

Источник https://forum.bits.media/index.php?/topic/157696-%D0%BC%D0%BE%D1%88%D0%B5%D0%BD%D0%BD%D0%B8%D1%87%D0%B5%D1%81%D1%82%D0%B2%D0%BE-%D0%BD%D0%B0%D1%86%D0%B4%D0%B8%D1%81%D0%BA%D1%80%D0%B8%D0%BC%D0%B8%D0%BD%D0%B0%D1%86%D0%B8%D1%8F-exchanger-cms-%D1%81%D0%BA%D1%80%D0%B8%D0%BF%D1%82-%D0%BE%D0%B1%D0%BC%D0%B5%D0%BD%D0%BD%D0%B8%D0%BA%D0%B0-%D0%BA%D1%80%D0%B8%D0%BF%D1%82%D0%BE%D0%B2%D0%B0%D0%BB%D1%8E%D1%82/

X

Читайте также:

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

span.hidden-link { color: #DCDCDC; /*-цвет ссылки-*/ text-decoration: underline; /*-подчеркивание-*/ cursor: pointer; /*-указатель в виде пальца-*/ }