Дневник стартапа #2: ищем бэкендера, разрабатываем ИИ-гуру и помогаем HR и CTO защитить бюджет
Расскажу, как разрабатываем гуру-ИИ, почему HR и CTO проще всех интегрировать ИИ в компании, и зачем переходить из GPT к нам. А еще мы ищем того, кто починит нам мобильную версию.
Ищем бэкенд-разработчика
Наш проект активно развивается, и мы ищем человека, который усилит команду. Делимся здесь ключевыми требованиями к бэкендеру:
Обзор навыка «Пост в телеграм»
Мы стали активно вести телеграм-канал, и, конечно, для создания текстов используем наш сервис. Сегодня расскажу, почему для написания постов в телеграм проще и результативнее пользоваться им, чем GPT.
Генерация поста в навыке «Пост в телеграм»
Как работает навык:
В навыке «Пост в телеграм» вам не нужно думать о промптах, достаточно лишь кратко описать тему поста, аудиторию и выбрать стиль текста.
В интерфейсе сервиса вы видите только названия стилей, для нейронки мы подробно проработали их в промптах.
Посты пишутся по принципу перевернутой пирамиды: сначала главная идея, затем детали.
ИИ работает и над SEO-оптимизацией постов, учитывая ключевые слова.
В промптах ко всем текстовым навыкам заданы подсказки по методу написания, требованиям к связности и логичности текста — подробнее рассказывать не будем, это наш секретный ингредиент :)
Отдельным пунктом отмечу чат для корректировки ответа. Если вам хочется изменить или дополнить текст, достаточно попросить об этом в строке под результатом генерации. Например, убрать эмоджи из текста, переписать пост в другом стиле или на другом языке.
ИИ-гуру для консультаций по сложным вопросам
Помимо простых прикладных навыков вроде поста в соцсети, мы разрабатываем многослойные, которые помогут пользователям получать более развернутые ответы на свои вопросы. Один из таких навыков сейчас находится в разработке, он называется «Гуру».
Если посмотреть, как он создается, можно увидеть объемную цепочку из различных нод. Каждый из этих инструментов вносит свой вклад в работу навыка, беря на себя узкую специализацию. «Гуру» может предложить вам 6 самых эффективных решений любого вашего вопроса.
Как выглядит навык внутри
Как это происходит:
Вы задаете «Гуру» вопрос на любом языке.
Нода-переводчик переводит его на английский.
Исходя из вопроса ИИ формирует «глубокую роль» — персонажа-специалиста в данной сфере.
От лица роли нейронка предлагает 6 решений, указывая для каждого плюсы, минусы и формирует 3 ключевых фактора успеха, критически важных для решения вашей проблемы. Они складываются из анализа запроса, проблемы и решений.
Полученный ответ в сокращенном и переведенном виде передается пользователю.
Вся эта магия основана на технологии Chain of Thought, или «Цепочка мыслей». Ее суть в пошаговом решении задачи ИИ, при котором пользователю не нужно принимать участие в каждом из этих шагов.
«Гуру» может рассмотреть вопрос под разными углами, предложить несколько решений и помочь выбрать наилучший путь, не затрачивая ваше время на детальный анализ каждого возможного варианта.
HR и CTO — главные двигатели ИИ в компаниях
В этом месяце мы запустили 3 масштабных проекта по White Label. Один из них использует нашу платформу как сервис, на котором пользователи создают контент. А два других — как корпоративную платформу для сотрудников. Мы научились адаптировать наш проект к расширенному набору требований. Теперь мы можем создать платформу с любыми навыками, которые нужны нашим клиентам, и оформлять ее в любом стиле.
Большим открытием для нас стало то, что двигателями ИИ в компаниях являются не маркетологи, как нам казалось раньше, а HR и CTO (технические директора). Из 10 пришедших к нам клиентов был всего 1 маркетолог, 1 сейлз, 3 CTO и 5 HR-специалистов.
Сотрудники HR-отдела приходят от крупных организаций, занимающих ведущие позиции в своей отрасли как в offline, так и в online, и со штатом более 1000 человек.
HR стремятся усилить корпоративный бренд, создать дополнительную ценность, рационализируют расходы бюджета, повышая привлекательность компаний как для текущих, так и для потенциальных сотрудников. Как правило, у них есть хороший бюджет, скажем, в 10 млн рублей, и выбор — потратить эти деньги на пирожки и мерч или внедрить ИИ для команды, тем самым облегчив жизнь коллегам и сделав компанию более современной.
Мы не оспариваем, что еда, корпоративы и мерч — это важно, просто делимся своими наблюдениями: в отличие от продажников и маркетологов, HR проще защитить бюджет на внедрение ИИ. Когда продажник или маркетолог приходит в финотдел с просьбой дать бюджет на ИИ для оптимизации своих задач, его отправляют считать KPI, как это скажется на эффективности работы его отдела. А если приходит HR и предлагает вместо футболок и толстовок настроить ИИ-платформу, на которой будет работать вся команда, с этим охотнее соглашаются.
Рады, что мы начинаем сотрудничество с такими крутыми ребятами, которые хотят, чтобы их компания развивалась, а сотрудники не тратили время на рутинные задачи, которые можно делегировать нейронке.
Также теми, кто пушит ИИ в бизнесе, стали CTO. Оказалось, что у них есть отдельный KPI по внедрению ИИ в свои компании, и на это выделен отдельный бюджет. При решении этой задачи они сталкиваются с классическими болями — нужно найти специалистов, настроить интеграцию всех необходимых нейронок, решить вопрос с их оплатами. Вместо этого те, с кем мы общались, выбирают готовую платформу, которую можно развивать под своим брендом, выдавая нам как разработчикам ТЗ на создание кастомных решений (навыков) для своих коллег.
Новые форматы использования навыков
Мы не перестаем думать о том, как сделать Aigital удобнее и доступнее. Сейчас размышляем над созданием Chrome Extension, чтобы все функции были у пользователей (и у нас) под рукой, не выходя из браузера.
Иногда в наш бот поддержки пишут не вопросы по работе сервиса, а запросы на генерацию. И мы решили уточнить, а в каком формате вам реально было бы удобно пользоваться сервисом? Проголосуйте или напишите свой ответ в комментариях. Я передам команде, какой формат было бы лучше взять в разработку :)
А в нашем первом дайджесте мы рассказывали о смене маскота, рекламной кампании и ребрендинге.
Озвучка / Набираем людей на озвучку фильмов Команда Paper pirates
Актеры с которыми мы уже поработали
Нашей командой Paper pirates уже озвучено 24 фильма и на данный момент работаем над 2-мя проектами ( артхаус и финская новинка). Команда существует на донатах от зрителей/ подписчиков и кроме нас в проектах часто принимают участие известные актеры дубляжа.
Андрей Зайцев озвучил у нас главную роль в фильме Контроль (2003) Венгрия
В фильме Мад (2012) были задействованы Петр Иващенко (гл роль), Владимир Еремин и многие другие актеры.
На данный момент у нас в планах примерно 500 фильмов , 10 сериалов, около 50 мультов и аниме. Все проекты или не имеют озвучку или имеют, но только одноголоски или слабые любительские озвучки .
Недавно мы выпустили аргентинскую классику - Мужчина, глядящий на юго-восток (1986) -
Фрагмент венгерского фильма Контроль (2003) -
Некоторые фильмы есть на Бусти (где-то 7 + надо стену там полистать) - https://boosty.to/zverevsuper
Часть фильмов появились в нашем тг - https://t.me/mrBordo
Наша команда в основном состоит из 6 постоянных голосов и сейчас мы ищем новые голоса и готовы принять несколько человек. Желательно тех кто имеет опыт в озвучке. Также можем рассмотреть вариант совсем новичков ( при наличии неплохого голоса + оборудования) .
Людей набираем на озвучку старых (и иногда новых) ужастиков, на европейский артхаус и тд. На короткие проекты хронометражем 80-90 минут ( на основных проектах пока профи). Это необходимо для большего количества контента, тк выпускаем по 1 крупному проекту в месяц , а так планируем выпускать 3 проекта в месяц (крупный и 2 более простых с 2-3 голосами на фильм).
Если что присылайте свои аудио и видео с озвучкой / голос в комменты )) Потом есть шанс озвучить фильмы вместе с вышеперечисленными актерами ))
Это точно
Самая примечательная деталь в ,,сериале слово,, пацана, что для съёмки кадров СССР 80 годов до сих пор не нужны никакие компьютерные спецэффекты и декорации.
Труд основы человеческой цивилизации
Класс собственников отчуждает результаты труда, что делает труд унизительным.
Сам себе экосистема. Часть 4: как я реализовал клиент Telegram на Android-смартфоне 14-летней давности?
С момента выхода первой части статьи из рубрики «сам себе экосистема» прошёл уже практически год! За это время, мы успели с вами реализовать клиенты VK и YouTube, которые работают на Android 2.2+, а также на Windows Phone 8, написать небольшую 2D-игру с нуля весом менее 1Мб, которая работает практически везде и довести существующее приложение до ума, дабы оно работало даже на смартфоне с дисплеем 240x320! Но на дворе 2024 год, люди стремительно переходят из соц. сетей в продвинутые мессенджеры и уже сложно себе представить современного человека, который не пользовался бы «телегой» или даже «вайбером» в качестве основного средства общения. Поэтому я решил реализовать клиент Telegram на смартфоне 14-летней давности на базе официальной реализации MTProto от команды Telegram — TDLib. Сегодня мы с вами: узнаем новые причины мотивации вернуть в строй смартфоны прошлых лет, напишем на C# реле-сервер, который обрабатывает пакеты MTProto и кодирует их в простой текстовый формат датасетов, который можно моментально обработать даже при нестабильном GPRS-соединении на 21-летнем Siemens C60, а также узнаем о разработке миниатюрных Android-приложений на базе «голого» API-системы, которые не тянут за собой никаких зависимостей, в том числе и AppCompat/androidx. Интересно? Тогда жду вас под катом!
❯ Содержание
❯ Но зачем всё это?
На дворе уже стукнул 2024 год, современные смартфоны предлагают какие-то немыслимые мощности относительно тех, которые когда-то были в первых Android-девайсах. Сейчас за сотню баксов можно купить смартфон с хорошей 1080p IPS-матрицей, 4Гб ОЗУ и 8-ядерным шустрым чипсетом, который вполне способен плавно тянуть даже стремительно «жиреющие» на ресурсы клиенты социальных сетей, банков и прочие необходимые в повседневной жизни приложения.
И казалось бы: всё хорошо, покупай себе редмик раз в год или айфон раз в несколько лет и наслаждайся всеми прелестями работы современных приложений…
Для многих людей смартфон — это лишь инструмент, повседневный компаньон, который помогает облегчить выполнение каких-то задач. Им совершенно не важно, как он выглядит, как ощущается в руках, какой у него дисплей и железо «под капотом», лишь бы работал да и нормально. Но есть и другая категория людей, для которых телефоны, смартфоны и любые портативные гаджеты — это не просто утилитарный девайс, а настоящее инженерное произведение искусства, с которого буквально сдувают пылинки и стараются до последнего пользоваться ими как повседневными устройствами. Хотите пример? Смотрите ниже:
Фактически, среди современных смартфонов по сути и нет представителей такого нынче вымершего форм-фактора, как сайдслайдеры с физической QWERTY-клавиатурой, боковые раскладушки с двумя дисплеями и даже из QWERTY-моноблоков есть только смартфоны от Unihertz. Даже среди моноблоков с тачскринами нет никакого разнообразия, лишь без-рамочные одинаковые девайсы за исключением устройств от Sony.
Galaxy S Plus
Раньше меня часто спрашивали, мол, да как ты вообще можешь пользоваться смартфоном 10-летней давности, на котором давно нет официальных клиентов популярных сервисов и только недавно, с развитием блога, мне перестали задавать этот вопрос, поняв, что это бесполезно — ведь это дело принципа и порыва энтузиазма! Смотрите сами: у нас уже есть простенькие, но вполне рабочие клиенты ВК, YouTube, сейчас я допиливаю клиент «Сбера» на СМСках, реализую карты OpenStreetMap (правда пока без адекватной навигации), а в будущем планирую написать приложение для мониторинга погоды и трекинга посылок. Кроме того, в рамках этой статьи мы реализуем с вами клиент Telegram: так чем же это не функционал современного смартфона?
Но хорошо, с функционалом разобрались, однако для многих читателей слова «старый смартфон» это прямые синонимы «тормозной смартфон», мол «фуу, да как можно пользоваться этим тормозным кирпичом, он же лагает в последней версии моей ВКшечки!». Но давайте поставим вопрос ребром: может, это не столько девайсы немощные, сколько сами приложения, с кодовой базой, которая тянется более 10 лет, откровенно жиреют, обрастают костылями и хаками после далеко не одного поколения программистов, которые над ними работали? :) Один, вот, предпочитал пользоваться чистым AppCompat'ом, другой решил притащить зависимость, которая, например, оптимизирует виртуализацию ListView, третий решил заменить всю сериализацию Json со встроенных классов в Android на что-то стороннее и реализовал это костылями и вот так, по чуть-чуть изначально оптимальный и шустрый код превращается в неповоротливое УГ, которое не рефакторили кучу лет.
На видео Galaxy Pocket Neo — очень дешёвый Android-смартфон из 2011 года с 1-ядерным чипсетом на ~800МГц и 256Мб ОЗУ. При этом всём, Android софтварно рисует все анимации на процессоре, без участия GPU.
А значит у стареньких девайсов всё равно есть шанс быть полезными и стать полноценными повседневными смартфонами даже спустя более чем десять лет после выхода! И в сегодняшнем материале, я вам расскажу об особенностях разработки самопального клиента Telegram с собственным прокси-сервером, которое концептуально допускает реализацию даже на кнопочном Siemens C60 2003 года. Как? Читаем ниже!
❯ Принцип работы
В отличии от ВК (который разрабатывали те же самые люди, что и Telegram), API которого построено на базе REST-запросов и концепции Longpolling'а для моментального получения событий с сервера, Telegram построен на базе собственного протокола под названием MTProto, который может работать поверх любого «транспорта» (протокола нижнего уровня) — TCP, HTTP, WebSocket и т.п. Сам по себе MTProto в современном виде, разработка прожженного математика Николая Дурова и его команды — протокол относительно сложный для реализации «на коленке» и в первую очередь требует довольно серьезного понимания принципов работы современной криптографии, да и документирован он всё ещё не особо хорошо. Кроме того, у MTProto весьма интересный бинарный формат пакетов, эдакий велосипед Protobuf. В долгосрочной перспективе поддерживать свой велосипед MTProto может быть весьма проблематично, учитывая не самую лучшую документацию.
Но городить велосипед и не нужно, поскольку у команды Telegram есть официальная реализация MTProto — библиотека TDLib, которая инкапсулирует в себе не только детали реализации протокола, но и сетевой ввод/вывод и выбор транспорта, хранение базы данных сообщений и авторизации, автоматическую загрузку фото и видео, конвертация объектов из бинарного формата MTProto в JSON и полная многопоточность и частичная потоко-безопасность. С одной стороны это плюс — уже готовое решение для реализации клиента на новой поддерживаемой платформе, где есть OpenSSL (можно статически слинковать), zlib (линкуется статически), сокеты и файловый ввод/вывод, а также довольно неплохой механизм JSON-based API, которое позволяет использовать библиотеку в любом языке, который поддерживает вызов C-функций, а с другой и минус — библиотека довольно много весит, в одиночку прибавляя ~20Мб веса приложения для каждой архитектуры, у неё течёт память и у нее странный механизм получения данных с сервера (например, нельзя ответить на сообщение, зная его ID, если сообщение предварительно не загружено, при том что на сервере весь ответ — это просто ID, на какое сообщение прилетел ответ).
Понятное дело, что на стареньком смартфоне использовать оригинальный TDLib будет проблематичным — даже если собрать либы современным NDK и запилить JNI-интерфейс, библиотека «жрёт» много ОЗУ (20-100Мб «вхолостую», в зависимости от числа диалогов и частоты прилетающих событий, плюс со временем течет до 1-2Гб, если не использовать базу данных сообщений. Скорее всего, это косяк в реализации пулов, объекты из которых выгружаются при сбросе в базу, но не выгружаются при высоком потреблении ОЗУ) и уж тем-более TDLib не запустить на любимых кнопочных Java-сонериках! Поэтому я решил написать прокси-сервер, который отправляет команды, слушает ивенты TDLib и предоставляет REST-like API для клиентских программ, которые просто вызывают какой-либо метод, а в ответ получают простой и короткий строковой датасет только с необходимыми полями, весом до 10Кб (что позволяет его быстро загрузить даже с GPRS-интернетом), который можно быстро распарсить даже на преусловутом Siemens C60!
К сожалению, поскольку TDLib прожорлив, я не смогу захостить на своём сервере инстансы для читателей, которые хотят поюзать приложение, поэтому вам придется ставить и запускать сервер на своём VDS/компьютере с белым IP/роутере, если под него есть .NET Core :)
Клиентом же будет выступать Android-смартфон, где приложение будет фронтэндом данных с сервера. Ничего сложного на первое время нет: первое окно — это список диалогов, второе окно — список сообщений в диалоге + поле для написания сообщения, третье окно — информация о пользователе. Всё это я реализовал за три дня не-напряжной работы «на коленке».
Давайте же перейдем к реализации сервера!
❯ Прокси-сервер
Сервер я решил писать на C#, поскольку у .NET Core сейчас всё очень хорошо с кроссплатформенностью и производительностью. Его можно даже на Raspberry Pi запустить :)
Итак, какая-же архитектура такого сервера может быть? Программа инициализирует TDLib, начинает слушать её события в отдельном потоке, пока в основном потоке крутится HTTP-сервер, который обрабатывает каждый отдельный запрос с клиентского приложения. Почему синхронно? Потому что TDLib фактически не возвращает никаких идентификаторов для возвращаемых датасетов, дабы их можно было отличить друг от друга. Приведу пример: у нас есть метод getChatHistory, который возвращает n-сообщений. При этом TDLib сам определяет, сколько хочет сообщений вернуть (и в первый вызов возвращает одно сообщение вне зависимости от настрое и отправляем пакет message n-раз. При этом в пакете message нет какого-либо ID, который позволял бы ассоциировать текущий объект с какой-либо операцией. Увы!
Начинаем с коммуникации с TDLib. Для работы с библиотекой, мы будем использовать json-интерфейс. Для .NET есть биндинги через C++/CLI, но в таком случае, сервер не будет работать на Linux. Для работы с библиотекой хватит лишь три функции: CreateClientID, которая аллокейтит новый инстанс клиента, Send, которая асинхронно отправляет JSON-объект с командой, которую затем обработает TDLib и Receive, которая ждёт N-секунд и возвращает в виде ASCII-строки (!) JSON-объект с описанием события или данными после одного из запросов. За это у нас отвечает класс TDLibInterface, который объявляет PInvoke-методы для вызова нативных методов из библиотеки. .NET Core сам подгрузит библиотеку tdjson (причём на Linux он добавит ей префикс а-ля libtdjson.so, а на Windows загрузит tdjson.dll) и сам разберется с маршаллингом аргументов функций: например, string автоматически преобразует в const char*. Тем не менее, с const char* возвратами нужно быть аккуратнее — у меня был SIGSEGV, пока я ручками не конвертировал их в обычную строку.
З.Ы: На Пикабу нет отдельного тега для кода, а вставить листинги картинками я не могу из-за ограничения на 25 медиаэлементов. Так что листинги будут совсем без табов, но алгоритм их работы понять можно :)
[DllImport(Library, EntryPoint = "td_create_client_id", CallingConvention = CallingConvention.Cdecl)]
public static extern int CreateClientID();
[DllImport(Library, EntryPoint = "td_send", CallingConvention = CallingConvention.Cdecl)]
public static extern void Send(int id, string request);
[DllImport(Library, EntryPoint = "td_receive", CallingConvention = CallingConvention.Cdecl)]
private static extern IntPtr RawReceive(double timeOut);
[DllImport(Library, EntryPoint = "td_execute")]
public static extern StringBuilder Execute(string request);
public static unsafe string Receive(double timeOut)
{
IntPtr str = RawReceive(timeOut);
return str != IntPtr.Zero ? new string((sbyte*)str.ToPointer()) : null;
}
Позволю себе чуточку критики в сторону TDLib. Во первых, почему нет s-версии функции с возможностью указать длину входной строки, а tdjson полагается исключительно на \0 в конце строки? Во вторых, почему const char*, а не wchar_t*? Сейчас юникод во входной строке приходится escape'ами превращать в \u-последовательности.
После этого, нам нужно написать обёртку над TDLib, которая будет вызывать для зарегистрированных событий специальные функции, называемые коллбэками. При этом закомментированный WriteLine снизу — это «дебаг» для того, чтобы узнать названия неизвестных мне ивентов :)
В каждом объекте, полученном с помощью receive, есть поле "@type", которое содержит в себе имя класса возвращаемого объекта. Первый же вопрос от читателей — почему я использую JObject с ручным дерганьем нужных полей и вручную пишу JSON в виде строковых литералов вместо нормальной сериализации/десериализации? Ответ прост: во-первых, для актуализации Data-классов придется писать кодогенератор из TL-схемы, а во-вторых иногда TDLib может возвращать немного разные объекты в JSON, из-за чего приходится мудрить с атрибутами на этих самых Data-классах, иначе десериализатор выбросит исключение. Это решается нормальными юнит-тестами на всех вариантах данных, но зачем себе в колени стрелять, если нужен конкретный фиксированный функционал и лишь малое число от всех полей, возвращаемых TDLib?
string recv = NativeInterface.Receive(10.0d);
if (recv != null)
{
JObject json = JObject.Parse(recv);
string type = json["@type"].ToString();
if (!handlers.ContainsKey(type))
{
//Console.WriteLine("Unknown event type: {0}", type);
continue;
}
handlers[type](recv, json);
}
Теперь переходим к самому интересному — обработке событий и реализации синхронного клиента, который позволяет без async/await просто запросить список сообщений и сразу же его получить (такой подход может быть полезен и юзерботам, которые не хотят размазывать стейты по всей программе). Почему без асинков? Честно сказать, мне они просто не нравятся: как привык к концепции wait/notify и коллбэков из Java, так их и юзаю всю жизнь :)
Сначала TDLib запрашивает параметры инициализации (стейт authorizationStateWaitTdlibParameters), затем если пользователь не авторизован — запрашивает номер телефона и код подтверждения (плюс дополнительные шаги для авторизации если они есть). В конце, TDLib возвращает стейт Ready, что означает готовность библиотеки к работе:
private void OnAuthState(string raw, JObject obj)
{
JObject authState = (JObject)obj["authorization_state"];
string type = authState["@type"].ToString();
if (type == "authorizationStateWaitTdlibParameters")
{
Console.WriteLine("Preparing TDLib parameters...");
NativeInterface.Send(InstanceID,
Utils.Format("{" +
"\"@type\": \"setTdlibParameters\", " +
"\"database_directory\": \"tdlib\", " +
"\"api_id\": {0}, " +
"\"api_hash\": \"{1}\", " +
"\"use_chat_info_database\": true," +
"\"use_file_database\": true," +
"\"use_message_database\": true," +
"\"system_language_code\": \"en\", " +
"\"device_model\": \"Phone\", " +
"\"application_version\": \"1.0\" " +
"}", APIId, APIHash));
}
if (type == "authorizationStateWaitPhoneNumber")
{
Console.WriteLine("Sending phone number");
NativeInterface.Send(InstanceID, Utils.Format("{\"@type\": \"setAuthenticationPhoneNumber\", \"phone_number\": \"{0}\" }", PhoneNumber));
}
if(type == "authorizationStateWaitCode")
{
NativeInterface.Send(InstanceID, Utils.Format("{\"@type\": \"checkAuthenticationCode\", \"code\": \"{0}\" }", WaitCode));
}
if(type == "authorizationStateReady")
{
Console.WriteLine("Authorized");
waitHandle.Set();
}
}
...
Client.AttachEventHandler("updateAuthorizationState", OnAuthState);
После этого, можно начать работу с данными. Обратите внимание, мой подход потоко-небезопасен, его нельзя дергать из нескольких потоков одновременно! В коде ниже, я вызываю метод для фетча сообщений, а затем в соответствующем коллбэке от TDLib обрабатываю данные (дабы статья не разрасталась на 20+ минут, я чуть урезал все листинги).
public List<Message> QueryMessagesInChat(long chatId, long lastMessage, int count)
{
messages.Clear();
requestMessageCount = count;
string json = Utils.Format("{\"@type\": \"getChatHistory\", \"chat_id\": \"{0}\", \"from_message_id\": {1}, \"limit\": {2} }", chatId, lastMessage, count);
NativeInterface.Send(InstanceID, json);
waitHandle.WaitOne();
return messages;
}
public User QueryUser(long userId)
{
string json = Utils.Format("{\"@type\": \"getUser\", \"user_id\": \"{0}\" }", userId);
NativeInterface.Send(InstanceID, json);
waitHandle.WaitOne();
return user;
}
Переходим к реализации самого сервера, для наших целей хватит обычного HttpListener. Сначала мы зарегистрируем все поддерживаемые методы и занесем их в ассоциативный список ключ-значение. Сами методы реализованы в виде делегатов, которые принимают лишь один аргумент — список параметров из строки запроса, а возвращают строку — все ответы, за исключением особых (связанных с загрузкой вложений) — текстовые.
private HttpListener listener;
private List<HttpMethodHandler> methods;
private ScheduledRestart restartManager;
private void AddMethod(HttpMethodHandler info)
{
if(info != null)
{
methods.Add(info);
Console.WriteLine("Registered method: {0}", info.Method.Name);
}
}
private void PrepareMethods()
{
AddMethod(Chats.QueryChats);
AddMethod(Chats.QueryMessages);
AddMethod(Chats.SendMessage);
AddMethod(Users.QueryUserInfo);
}
private void PrepareState()
{
// We should fetch dialog list due to TDLib nature of preloading-everything
Client.QueryChats(15);
}
public HttpServer()
{
listener = new HttpListener();
listener.Prefixes.Add("http://+:13377/");
Client = new SyncClient("test");
Client.Start();
Client.WaitUntilReady();
//restartManager = new ScheduledRestart(5);
//restartManager.Start();
methods = new List<HttpMethodHandler>();
PrepareMethods();
PrepareState();
}
...
public void Start()
{
listener.Start();
while(listener.IsListening)
{
HandleRequest(listener.GetContext());
}
}
Переходим к обработке запроса. Метод ищет, зарегистрирован ли запрошенный метод и если да, то парсит строку запроса, которая начинается с "?", которую затем передаёт в виде коллекции ключ->значения обработчику метода:
private void HandleRequest(HttpListenerContext ctx)
{
string method = ctx.Request.Url.LocalPath.Substring(1).ToLower();
if (method.Length < 0)
{
SendResponse(HttpGenericResponse.MethodRequired.ToString(), ctx);
return;
}
foreach(HttpMethodHandler handler in methods)
{
if(method == handler.Method.Name.ToLower())
{
string result = "";
if (ctx.Request.Url.Query.Length > 0)
{
string[] args = ctx.Request.Url.Query.Substring(1).Split('&', StringSplitOptions.RemoveEmptyEntries);
Dictionary<string, string> keyValuePairs = new Dictionary<string, string>();
foreach (string arg in args)
{
if (arg.IndexOf('=') >= 0)
keyValuePairs.Add(arg.Substring(0, arg.IndexOf('=')), arg.Substring(arg.IndexOf('=') + 1));
else
keyValuePairs.Add(arg.Substring(0, arg.IndexOf('=')), "");
}
result = handler(keyValuePairs);
if (result == null || result.Length < 1)
{
Console.WriteLine("Suspicious result from {0}", handler.Method.Name);
}
}
SendResponse(result, ctx);
return;
}
}
SendResponse(HttpGenericResponse.UnknownMethod.ToString(), ctx);
}
А сами методы, в свою очередь, дергают соответствующие функции из клиента и формируют на их основе датасет в примитивном формате:
public static string QueryChats(Dictionary<string, string> args)
{
if(args.ContainsKey("count"))
{
int count = int.Parse(args["count"]);
StringBuilder ret = new StringBuilder();
List<Chat> chats = HttpServer.Instance.Client.QueryChats(count);
ret.AppendLine(string.Format("Count={0}", chats.Count));
foreach(Chat chat in chats)
{
ret.AppendLine("Begin");
ret.AppendLine("ID=" + chat.ID);
ret.AppendLine("Date=" + chat.LastMessageDate);
ret.AppendLine("Name=" + chat.Name);
ret.AppendLine("Text=" + Uri.EscapeDataString(chat.LastMessageText));
ret.AppendLine("MsgId=" + chat.LastMessageID);
ret.AppendLine("End");
}
return ret.ToString();
}
return HttpGenericResponse.InternalException.ToString();
}
В результате получаем вот такой простой датасет, который, как я и говорил, легко распарсить и на Siemens C60, и на Atmega328 — да где угодно! В целом, такой сервер можно использовать для реализации бота в телеграме, который будет передавать показания каких-то датчиков, сигнализацию и прочие клевые штуки!
Переходим к реализации клиента, т.е. приложения на Android. Здесь будет не менее интересно!
❯ Пилим для Android
В геймдеве есть своеобразный мем — некоторые инди-разработчики сначала начинают делать меню, вместо основного геймплея, что становится предметом насмешек среди других разработчиков. Но в разработке приложений для смартфонов всё по другому — здесь как-раз таки хорошо заранее продумывать макет будущего приложения!
Поскольку у нас с вами мессенджер, то главный экран должен представлять из себя список чатов (ListView) и верхнюю панельку, где в будущем могут разместиться настройки и свайп-менюшка:
Такой вот простой макет.
Каждый пункт меню — это тоже отдельный layout, в котором мы по шаблону строим внешний вид будущего элемента списка. На немолодых устройствах есть смысл использовать как можно меньше контейнеров в layout'е, поскольку пересчет позиций и размеров элементов — одна из самых «тяжелых» операций в UI-фреймворке вообще. Кроме того, не стоит использовать кучу картинок и drawable — в Android 2.x всё 2D рисуется софтварно, аппаратное ускорение появилось только в 3.0 (частично).
Но дабы в списке диалогов что-то появилось, нужно сначала реализовать фетчинг (получение) этих самых диалогов с сервера! Сам объект, который занимается обработкой запросов называется ClientManager и является синглтоном — он в единственном экземпляре на все время работы программы. Помимо менеджмента «ноды» (т.е. прокси-сервера), токена для авторизации и обработчика ошибок, ClientManager реализует метод для асинхронного запроса информации с сервера и, собственно, формирует строки запросов с помощью соответствующих методов:
public void queryChats(int count, Response resp) {
sendRequest(String.format("%s/QueryChats?count=%d&auth_key=%s", nodeAddress, count, token), resp);
}
Подгрузка чатов и сообщений реализована через Adapter — концепция «виртуальных» списков, которая предполагает что система создаст не 50 элементов интерфейса на каждую кнопку чата, а только 5 и будет их виртуально «мотать по кругу», обновляя только данные в уже существующих элементах. Это позволяет значительно ускорить отрисовку, учитывая то, что Android 2.x Canvas рисуется программно.
private void updateDialogList() {
ClientManager.getCurrent().queryChats(50, new ClientManager.Response() {
@override
public void onReady(String str) {
try {
List<Packets.Chat> chats = Packets.parseChatListFromQueryResponse(str);
DialogAdapter adapter = new DialogAdapter();
adapter.setChats(chats);
((ListView) findViewById(R.id.messages_view)).setAdapter(adapter);
} catch (Exception e) {
Toast.makeText(MainActivity.this, "Упс!", Toast.LENGTH_SHORT);
}
}
});
}
Ну вы уже явно замучились видеть простыни кода, давайте посмотрим что у нас вышло!
Шустренько, да? А ведь это ультрабюджетник Alcatel OT-916D, один из последних массовых дешевых QWERTY-смартфонов за 5 000 рублей из 2012 года. Кстати, смартфон подарил мне читатель chuvakoff с Хабра!
Переходим к окну чата. Основной макет почти такой-же, как и у основного окна: только добавилась панелька для ввода сообщения снизу.
Концептуально, всё тоже самое — запрашиваем данные с сервера, парсим их и загружаем в адаптер, благодаря чему мы сможем листать наш диалог. Однако в сообщения я добавил контекстное меню с стандартными фишками типа копирования, ответа и прочих подобных действий. Поскольку у нас нет ни пушей, ни еще каких-либо средств для поулчения данных о новых сообщениях, я раз в определенный интервал просто получаю сообщения — и если новый датасет отличается от старого — обновляю окошко чата.
view.setOnCreateContextMenuListener(new View.OnCreateContextMenuListener() {
@Override
public void onCreateContextMenu(ContextMenu contextMenu, View view, ContextMenu.ContextMenuInfo contextMenuInfo) {
// Reply to...
contextMenu.add(getString(R.string.reply)).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
setReplyContext((Packets.Message) view.getTag());
return true;
}
});
// Copy
contextMenu.add(getString(R.string.copy)).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
ViewGroup vg = (ViewGroup)view;
android.text.ClipboardManager manager = (android.text.ClipboardManager) view.getContext().getSystemService(CLIPBOARD_SERVICE);
manager.setText(((TextView)vg.findViewById(R.id.message_content)).getText());
return true;
}
});
// Send to...
contextMenu.add(getString(R.string.resend)).setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem menuItem) {
ViewGroup vg = (ViewGroup)view;
String text = ((TextView)vg.findViewById(R.id.message_content)).getText().toString();
Intent intent = new Intent();
intent.setAction(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_TEXT, text);
intent.setType("text/plain");
startActivity(Intent.createChooser(intent, null));
return true;
}
});
}
});
Переходим к реализации поля для ввода сообщения. Здесь всё просто — на серверсайде за это отвечает метод SendMessage. Однако для того, чтобы с нашего клиента можно было ответить на другие сообщения, я ввёл также «контекст ответа», в котором запоминается сообщение, на которое мы хотим ответить. Telegram также поддерживает Markdown, однако его полная поддержка пока не реализована.
EditText editText = ((EditText)findViewById(R.id.message_text));
if(editText.getText().length() > 0) {
long replyTo = replyContext != null ? replyContext.ID : 0;
ClientManager.getCurrent().sendTextMessage(chat.ID, editText.getText().toString(), replyTo, new ClientManager.Response() {
@Override
public void onReady(String str) {
}
});
editText.setText("");
setReplyContext(null);
}
В остальном же, функционал конечно пока совсем базовый, однако клиент работает очень шустро даже бюджетной X10 Mini Pro и позволяет чатится с моими читателями в Telegram. В будущем хотелось бы допилить:
Поддержка картинок: Сейчас уже есть кривоватый механизм кэширования изображений на стороне сервера, который позволяет загружать аватарки чатов. В будущем, я добавлю поддержку «галерей» с картинками!
Поддержка голосовых сообщений: Не все их любят, но они порой удобны и выручают. Реализую как прослушивание, так и запись!
Подробный просмотр профилей и менеджмент чатов: Удаление сообщений, чатов и прочие фишечки из официальных клиентов.
Казалось бы — до официальных клиентов ещё очень далеко. Но сам факт, чтобы всё это работало достаточно шустро на девайсах, которым уже более 10 лет!
❯ Звучит интересно! Как заюзать твой клиент?
Тут всё очень и очень просто! В первую очередь, нам понадобится ПК с белым IP, роутер (если под него есть сборка dotnet), либо VDS. Виртуальные сервера сейчас стоят копейки, у ТаймВеба есть тариф за 188 рублей в месяц, которого с головой хватит для нашего сервера.
Такая вот рекламная интеграция (к слову, прокси для всех приложений уже более года крутятся именно на мощностях TimeWeb Cloud)!
Берём уже собранный TDLib и сервер под Windows, или собираем TDLib под Linux, накатываем .NET Core. Пример для Debian/Ubuntu:
sudo apt-get install dotnet
Затем запускаем сервер:
dotnet tdsrv.dll
Программа сначала запросит номер телефона, а затем код подтверждения Telegram. После этого будет создана папка tdlib/, где будут хранится данные вашей сессии, а также файл authkey.txt, где хранится случайный ключ для сессии (md5 phone_number + response code + псевдослучайное число). Не оставляйте его в /var/www/!
Если всё нормально, программа начнёт слушать порт 13377 на всех сетевых интерфейсах, в т.ч и в локальной сети. После этого, ставим уже предварительно собранный, либо собираем сами в Android Studio APK и в окне авторизации пишем адрес ноды и ключ авторизации. Если всё настроено верно — программа запомнит сервер и будет работать без проблем! Вот так всё легко :) Как видите — всё очень и очень просто!
Кроме того, буквально за пару дней до публикации статьи я сел вечерком из интереса что-нить под Java-телефоны попилить… и, как и обещал, реализовал Proof of Concept возможности работы Telegram даже на сонериках, которым скоро 20 лет стукнет! А ведь если ещё чуть заморочится, можно запустить приложение даже на преусловутых монохромных сименсах!
❯ Заключение
Вот такой у нас получился проект с реализацией лёгкого, примитивного, но тем не менее рабочего клиента Telegram, который на клиентской части вообще не использует никаких зависимостей. Вес собранного APK в release-версии — всего 54 килобайта! Понятное дело что с ростом функционала, вес программы будет увеличиваться, но я обещаю — больше 1Мб он не вырастет :)
Ну а вам, моим читателям, надеюсь было интересно прочитать такой «двойной материал» не только о разработке сетевой части без использования Apache/nginx/IIS, но и UI-фронтэнда для Android-смартфонов, которым уже более 10 лет!
Исходный код проекта можно найти на моём GitHub: как приложения, так и сервера, а также убедиться в отсутствии каких либо закладок и, если совсем не доверяете, собрать бинарники сами! Для сборки понадобится VS2017 или свежее, а также Android Studio 2.3.2 (если собираете для Android 2.1 и ниже).
Друзья! Сейчас на Хабре опросы сломаны, поэтому если у вас есть желание, вы можете проголосовать в комментариях: какой стиль статей вам больше нравится — где больше конкретики и кода с пояснением как конкретно работает та или иная часть программы, или наоборот стиль ближе к научпопу, где фрагментов кода нет, или их значительно меньше? Пишите своё мнение о проекте в комментариях!
Кроме того, у меня есть канал в Telegram, куда я публикую бэкстейдж статей, ссылки на новый материал, свои наработки, а также посты о ремонте девайсов и различные мысли.
Статья подготовлена при поддержке TimeWeb Cloud. Подписывайтесь на меня и @Timeweb.Cloud, чтобы не пропускать новые статьи каждую неделю!
Фантазия
А вы прикиньте чё бы было если у взаправдашних революционеров и их противников в 1917г. был бы Телеграм, Твитер и Ютуб?
Что общего у логопеда из фильма «Король говорит» и Ларисы Олеговны с Урала?
Я устал от тупого copy/paste контента!
Лайонел родился в 1880 году и это нормально что он уже не с нами.
Одним миром мазаны.
Кто такая Лариса Олеговна?
В этой статье я попытался разобраться в тонкостях логопедии и прошу совета как быть с претендентом в подборку.
Кадр из фильма Король говорит
Телеграм плотно вошел в мою жизнь, но я устал от тупого copy/paste контента. Решил собрать подборку интересных авторов и сделать мульти тематический каталог телеграм каналов. Каталог поможет разгрузить ленту и собрать всё в одном месте. Здесь я рассматриваю каналы – претенденты и выслушиваю ваше мнение.
Критерии, по которым оцениваю каналы:
В канале преобладает авторский подход к контенту
Канал несет пользу для читателя
Канал не нарушает 149-ФЗ
Удобная навигация по контенту
Регулярное обновление постов
Мой — Каталог телеграм каналов
Канал претендент — Декрет и логопед
Начать с того, что Лайонел Лог мертв
Лайонел Лог родился в 1880 году и это нормально что он уже не с нами. Зато он вылечил короля Великобритании Георга VI от заикания и тем самым вошел в историю. Внук Лога написал книгу об отношениях Лайонела с королём Георгом VI. В последствии по этой книге был написан сценарий и снят фильм «Король говорит». Вот такая красивая история случилась.
А все благодаря тому, что Лайонел был не обычным логопедом, а неординарной личностью. И отличало его как раз то же качество что и присуще героине этого рассказа - Ларисе. К месту сказать, что Лариса Олеговна жива, прекрасно себя чувствует и продолжает свой 17 летний путь детского логопеда. Но время скоротечно, и вам не стоит затягивать если проблемы королей актуальны вашим принцам и принцессам.
Есть все основания полагать что Лариса и Лайонел одним миром мазаны. Но обо всем по порядку.
Упорядочим так:
Голос нации
Сэр логопед
Прямая речь с Урала
К чему так много букв
Как король заика стал голосом нации
Когда речь заходит о сильных мира сего, обычно представляется властный и уверенный в себе человек. Но высокий статус не всегда определяет личность, а иногда, наоборот, может вызывать в человеке глубокий страх перед невероятной ответственностью.
Принц Альберт, позднее ставший королем Георгом VI, с детства страдал от страшного заикания и рос тихим, застенчивым и болезненным ребенком. Родители уделяли мало внимания младшему сыну, больше заботясь о старшем.
Берти, как его звали в детстве, остро нуждался в поддержке семьи, но сложные отношения с родственниками еще больше усугубляли неуверенность и зажатость. Роль сильного и авторитетного лидера нации совсем не подходила Альберту. Однако жизнь подкинула ему новое испытание, когда старший брат отрекся от престола, то Альберту пришлось принять на себя бразды правления. Ему предстояло стать лидером нации.
Критическим моментом Короля заики, стало выступление на стадионе Уэмбли, которое закончилось провалом, и после этого Альберт много лет боялся выступать публично. Но его супруга всегда была рядом, поддерживая и помогая ему бороться с дефектом речи. Она посоветовала ему обратиться к австралийскому логопеду Лайонелу Логу, и благодаря его помощи король наконец-то смог преодолеть свое заикание.
Альберт и Лайонел оставались близкими друзьями до конца жизни. В знак признательности за его помощь король наградил Лайонела орденом и повысил его до Командора.
Георг VI без воды:
Был вторым сыном в династии - рос застенчивым, болезненным и страдал от нервного заикания.
Братьям и сестрам разрешалось его высмеивать.
Довольствовался жизнью в тени брата и был запасным наследником.
Вынужден был стать монархом после того как брат оставил престол ради разведенной американки.
Вместе с супругой, во время войны, при бомбардировках, оставался в городе и чуть не погиб.
Был редким примером монарха, который ставил себя в те же условия, что и народ.
Своей аскетичностью опроверг миф о помпезности и снобизме королей.
Был любимым монархом нации и когда он скончался, похороны были душераздирающими.
Не должен был стать королем, но оказался одним из самых успешных монархов в истории своей страны.
Король Георг VI заикается во время открытия выставки. 1938 год.
Австралийский логопед, который спас британскую корону
Лайонел Лог по происхождению был Австралийцем. Лог не был дипломированным логопедом, он был инженером и увлекался изучением риторики. До того, как стал лечить Монарха, разрабатывал собственную методику подготовки к публичным выступлениям. Помогал ставить речь актёрам, чтецам и преподавателям.
Лайонел изучал анатомию и придумывал физические упражнения, которые улучшали работу лёгких и гортани. Вместе с тем, Лог разрабатывал методы для восстановления поврежденных голосовых связок отравленных газом ветеранов войны и военным, страдавшим от последствий контузии. Подходы в лечении дефектов речи шли вразрез с официальной медициной того времени, но давали отличный результат.
Методы совмещали работу логопеда с практикой психотерапевта и были новаторскими в области логопедии. Характер и личность Лайонела Лога - его дар практического сочувствия в сочетании со здравым смыслом, юмор и обаяние - были важными факторами успеха лечения. Приобретя известность, Лайонел Лог хорошо зарабатывал, взимая с богатых покровителей значительную плату, он предоставлял бесплатные услуги более бедным людям.
Его подход к лечению заключался в том, чтобы внушить каждому пациенту твердую веру в возможность окончательного освобождения от заикания, что это освобождение будет достигнуто собственным усилием воли, смелостью и решимостью пациента; практика упражнений на дыхание, голос и речь, выполнение речевых заданий были средствами постепенного утверждения уверенности в себе.
На протяжении всей жизни, с момента знакомства с королем Георгом VI и до его смерти, сопровождал и помогал готовиться монарху ко всем публичным выступлениям.
Короли уходят, а проблемы остаются. Лариса с Урала – прямая речь
Уже семнадцать лет я работаю с детьми, у которых сложности с развитием речи. Это период постоянного развития, поисков, разочарований и открытий, который изменил мою жизнь навсегда. Я постоянно учусь – учусь всегда и везде, и мне это нравится.
Оптимизм, терпение и целеустремленность помогают мне в познании профессии логопеда. Для меня особенно ценны моменты, когда дети преодолевают речевые проблемы, и я получаю благодарные слова их родителей. Важно, чтобы каждый ребенок обрел интерес и веру в себя.
Шаг за шагом я следую к поставленной цели. Удивительно видеть, как улучшается речь у детей и как они радуются своим успехам. Мне приносит радость и удовлетворение то, что я могу помочь им. Люблю свою профессию за возможность преодолевать трудности, за радость в глазах детей и за то, что моя работа является шагом к их успешному и интеллектуальному развитию.
Теперь знакомы вы с профессией прекрасной,
Вам лишь осталось постучаться в кабинет.
С табличкой скромной «Логопед».
Телеграм канал логопеда Декрет и логопед
К чему так много букв, когда можно было просто дать ссылку и попросить читателя оценить канал и высказать свое мнение?
Все началось с того что, отбирая телеграм каналы для каталога, наткнулся на авторский блог, детского логопеда, который по началу не произвел впечатления. То ли тематика не показалась актуальной, толи не разглядел сути за хромающим оформлением постов. Насмотренность большая и глаз привык оценивать ленту каналов по одежке. Если автор канала не смог привести вид своей ленты к единому стилю и концепции, то и содержимое вряд ли будет интересным.
С таким апломбом я и оценил этот канал с первого взгляда. Закрыл и стал искать что-то другое. Но произошли события, которые меня заставили вспомнить, что недавно видел информацию про детишек с отставанием в развитии речи. Стал вспоминать и в итоге нашел снова этот канал – «Декрет и логопед». Полистал ленту внимательнее, отбросив предрассудки и наткнулся на короткие видео автора канала. На этих видео, девушка с приятным голосом, чистой речью рассказывала о своей работе и о том, чем она может быть полезна людям, у которых есть вопросы по её профессиональной специализации.
Через эти видео чувствовалось с какой большой и чистой человеческой любовью, Лариса относится к миру и окружающим людям. Вспомнился фильм «Король говорит» и его герои. Захотелось поглубже вникнуть в детали этой области. Будучи сам, по одному из образований, близок к медицине, почувствовал, что здесь есть что-то интересное. И на поверку оказалось, что так и есть. Современная логопедия включает в себя симбиоз нескольких областей:
Анатомия
Психология
Педагогика
Психиатрия
И ситуативно еще несколько вспомогательных направлений. Аккумулировать в себе такой набор знаний и практик может далеко не каждый человек. Для этого нужно обладать незаурядными талантами.
Осознав это, посмотрел на творчество автора телеграм канала, с другой стороны. Стало понятно, что информация и знания, которые тут собраны имеют ценность. Тогда я подумал: ну хорошо, автор и его знания ценны и скорее всего востребованы.
Но нужен ли такой канал в подборке?
Будут ли люди искать ответы на такие вопросы в телеграм?
Вот тут я и решил с вами посоветоваться. Вспомнив историю короля и его логопеда, я постарался проанализировать и помочь увидеть вам то, что как со стороны пациента, так и со стороны специалиста, способного решить проблему, как правило стоят личности с неординарной судьбой и незаурядным талантом. И видимо это необходимые условие для терапевтического триумфа. Причем незаурядный талант логопеда играет ведущую роль в этом тандеме. Только как услышать скрипку разглядеть талант среди посредственностей? Вопрос риторический.
Мой — Каталог телеграм каналов
Канал претендент — Декрет и логопед
Если вы админ тг канала и хотите его добавить в мою подборку, пообщайтесь с админом: https://t.me/ss_admin_top
P.S. Это удовольствие бесплатно