<?xml version="1.0" encoding="utf-8" ?><rss version="2.0" xmlns:tt="http://teletype.in/" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:media="http://search.yahoo.com/mrss/"><channel><title>параноик хуесос</title><generator>teletype.in</generator><description><![CDATA[параноик хуесос]]></description><image><url>https://img1.teletype.in/files/84/4b/844b8b02-e71e-4466-9e86-1e73df1d0dc0.png</url><title>параноик хуесос</title><link>https://teletype.in/@paranoikcodit</link></image><link>https://teletype.in/@paranoikcodit?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/paranoikcodit?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/paranoikcodit?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Thu, 23 Apr 2026 02:31:54 GMT</pubDate><lastBuildDate>Thu, 23 Apr 2026 02:31:54 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/gospodstvo-cambria</guid><link>https://teletype.in/@paranoikcodit/gospodstvo-cambria?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/gospodstvo-cambria?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>Тотальное господство в Cambria'ии или как я получил бан за твинк</title><pubDate>Mon, 28 Apr 2025 15:30:00 GMT</pubDate><description><![CDATA[Все началось в начале апреля, когда я узнал о грядущем втором сезоне Камбрии. Тогда я лишь подумывал, стоит ли вообще залететь в эту тему.]]></description><content:encoded><![CDATA[
  <p id="5CcO">Все началось в начале апреля, когда я узнал о грядущем втором сезоне Камбрии. Тогда я лишь подумывал, стоит ли вообще залететь в эту тему.</p>
  <h2 id="gO7J">Начало</h2>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="vqpr">Время шло, сомнения постепенно сменялись надеждой. Я собрал восемь юнитов для отыгровки, и мы начали играть. За каждого пришлось отдать не меньше 300 $ в эквиваленте ETH — в итоге вышло около 1,2 ETH плюс небольшой резерв на непредвиденные расходы.</p>
  </section>
  <h2 id="ojFE">Сомнения</h2>
  <section style="background-color:hsl(hsl(24,  24%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="irR4">В течение всего сезона каждый игрок на сервере так или иначе чувствовал, что команда проекта не готова оперативно решать проблемы. Ботов было море, и условно их можно разделить на два типа:</p>
    <ul id="60yu">
      <li id="4cyw"><strong>фармеры</strong> — добывают мобов / руду / рыбу;</li>
      <li id="Chzq"><strong>ПК-шники (Player Killer)</strong> — боты, убивающие игроков.</li>
    </ul>
    <p id="tl6B">Из-за них нормальный геймплей страдал, прогресс тормозился.</p>
  </section>
  <h2 id="Uol8">Орешники</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="k7Xx">На седьмой день, когда мы уже кое-как развились, в голову пришла классная мысль: а что, если у нас будет собственная дивизия ботов, которые в любой момент смогут мгновенно появиться рядом с нами и прикрыть от противников?</p>
    <p id="BuYA">Так родился проект с кодовым названием <strong>O.R.E.S.H.N.I.K</strong> — сеть ботов, которые быстро пополняли бы наши ряды и служили боевой мощью гильдии.</p>
    <p id="bME6">Я углубился в исходники и понял, насколько всё это криво: игра, по сути, собрана на Phaser.js и Svelte. 😋<br /> Тогда-то я ещё не подозревал, какой пиздец меня ждёт дальше.</p>
  </section>
  <h2 id="M6ZP">Провал</h2>
  <section style="background-color:hsl(hsl(55,  86%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="xKwJ">В день, когда кодовая база уже была готова, разработчики выкатила античит, который должен был поставить крест на гильдиях, использующих подобные уловки и автоматизированных ботов.</p>
    <p id="Kbpc">Я не придал новости особого значения — и поплатился. После успешного теста лёг спать, а утром обнаружил бан на основном аккаунте и такой же бан на тестовом аккаунте бота.</p>
  </section>
  <h2 id="KAvq">Выводы</h2>
  <section style="background-color:hsl(hsl(199, 50%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="w8bC">Тестить бота с одного IP-адреса — ебучая глупость. Больше таких банальных ошибок допускать не планирую.<br /> И вообще, писать ботов стоило ещё во время F2P-стейджа, до начала самого сезона.</p>
  </section>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/JITO-SOLANA</guid><link>https://teletype.in/@paranoikcodit/JITO-SOLANA?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/JITO-SOLANA?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>история о том, как солану чуть не централизовали</title><pubDate>Sun, 17 Mar 2024 22:03:45 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/f0/25/f025c14b-eac6-4aa8-b6d0-f55085af2d56.png"></media:content><description><![CDATA[<img src="https://img1.teletype.in/files/87/82/8782b6e2-0bcb-4046-a416-1350582e03dc.png"></img>Эх, солана-солана, что же ты творишь...]]></description><content:encoded><![CDATA[
  <figure id="6mkk" class="m_column">
    <img src="https://img1.teletype.in/files/87/82/8782b6e2-0bcb-4046-a416-1350582e03dc.png" width="2400" />
  </figure>
  <blockquote id="aPDP">Эх, солана-солана, что же ты творишь...</blockquote>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="jZNE" data-align="center">Солана и мемпул</h2>
  </section>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="8w1o">Соланочка наша любимая архитектурно так устроена, что мемпула в ней нет и быть не может. </p>
    <p id="vQpu">Оптимизации, которые были предприняты для решения вопроса мемпула, позволили решить соланочке не только вопрос с мемпулом, но и повысила пропускную способность.<br /></p>
    <p id="j5d9"><strong>Gulf Stream</strong> - одна из оптимизаций, которая позволяет соланочке быть такой быстрой. Ее главная особенность - каждый раз, как только в валидатор попадает транзакция, валидатор заранее напрявляет транзакцию в лидера, который уже в свою очередь будет исполнять транзакцию. (данные из доки соланы)<br /></p>
    <p id="RmSN"><strong>Proof Of History </strong>- очень сложная штука, которую в двух словах не объяснить. Если говорить ОЧЕНЬ поверхностно, то это последовательность вычислений, которая позволяет определять временной диапазон между транзакциями.</p>
    <p id="OsXD"><em>ну еще в соланке, чем больше стейкинг в валидаторе, тем больше транзакций он может обрабатывать(запомните это на будущее)</em></p>
  </section>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="zYB8">Из этой непонятной на превый взгляд хуйня, можно понять, что в соланочке то по сути и нет пендинговых транзакций, они сразу попадают в &quot;исполнителя&quot;, хранятся там до момента исполнения +- 32 блока, а потом если все очень плохо, то просто пропадают.</p>
  </section>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="0rGM" data-align="center"><strong>JITO и их &quot;мемпул&quot;</strong></h2>
  </section>
  <section style="background-color:hsl(hsl(34,  84%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="NDts">Житовцы, как истинные гои, хотели зарабатывать мильйоны денег. Насмотрелись на MEV на эфирах всяких и решили, а чо бы и на соланочке такое не сделать????<br /><br /><strong>Сели и думают:</strong> <em>ТАКК, КАК БЫ НАМ ТАКОЕ РЕАЛИЗОВАТЬ??</em></p>
  </section>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="rPxB">Жито переписывают валидатор соланы, его главные функции становятся:</p>
    <ul id="20SV">
      <li id="68cd">Перенаправлять транзакции во временное хранилище</li>
      <li id="gJ9N">Исполнять и симулировать бандлы</li>
    </ul>
    <blockquote id="Xh6G"><strong>БАНДЛ</strong> - <em>ПОСЛЕДОВАТЕЛЬНОСТЬ ТРАНЗАКЦИЙ, КОТОРЫЕ ВАЛИДАТОР ИСПОЛНЯЕТ ПОСЛЕДОВАТЕЛЬНО, ТРАНЗАКЦИЮ ЗА ТРАНЗАКЦИЕЙ</em></blockquote>
    <p id="ONBq">В качестве временного хранилища выступил - релеер<br /><strong>Jito-Relayer</strong> - штучка, которая принимает транзакции со всех валидаторов из сети жито, дедублицирует их, перенаправляет их непосредственно в распределительный пункт(block engine), удерживает их какое-то время, а дальше отправляет обратно в валидатора. </p>
    <p id="x1Jg"><strong>Jito Block Engine </strong>- хуйнюшка, которая принимает от релеера все транзакции и направляет их в ботов. Боты получают эти транзакции, обрабатывают и на выходе возвращают тот самый БАНДЛ, в котором хранится эта самая транзакция и нужные для мева другие транзакции. Блок енжин отправляет в релеер этот бандл, а он уже перенаправляет их в валидатор(если не было превышено 200 миллисекунд).  </p>
  </section>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="oNso">Такая сложная связка нескольких инструментов позволила JITO добиться создания &quot;мемпула&quot;, но это было бы невозможно, если бы в их связке не было бы большого количества валидаторов с большим стейкингом. для этого они проводили всякие активности и тыр пыр мыр мыр уже есть валидаторы с большими объемами стейкинга. ВОТ ЗА ЧТО ВЫ ПОЛУЧИЛИ ДРОПЫ, ДОВОЛЬНЫ???</p>
  </section>
  <section style="background-color:hsl(hsl(236, 74%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="uSEp" data-align="center">Что же будет дальше?</h2>
  </section>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="QTFh">Я думаю в ближайшее время люди начнут повторять успех жито, ведь для их успеха им всего лишь нужно:</p>
    <ol id="07xW">
      <li id="LVYe">иметь дохуя денег, чтобы их застейкать в нескольких валидаторах</li>
      <li id="tQYZ">переписать валидатор, чтобы удерживать транзакции и передавать их по какому-нибудь протоколу коммуникации с ботом и получать бандлы обратно</li>
    </ol>
    <p id="SmYJ">казалось бы, ТАКАЯ СЕТЬ, КАК ТАКОЕ МОЖЕТ БЫТЬ, КАК ЕЕ МОГУТ ТАК ЛЕГКО ПРИВЕСТИ К ЦЕНТРАЛИЗАЦИИ? а вот... вот такое вот случается...</p>
  </section>
  <p id="ICBT">ДЛЯ ВАС СТАРАЛСЯ - <a href="https://t.me/dolbaebskicode" target="_blank">https://t.me/dolbaebskicode</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/ethereum-act-one</guid><link>https://teletype.in/@paranoikcodit/ethereum-act-one?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/ethereum-act-one?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>Блокчейн эфириум, контракт нфтишек и ебучий питон... или как написать свой BUYER для эфира. АКТ ПЕРВЫЙ</title><pubDate>Wed, 02 Nov 2022 13:35:31 GMT</pubDate><description><![CDATA[<img src="https://img1.teletype.in/files/43/88/4388a26d-10ad-47bf-9c7a-31a8807b0289.png"></img>Что же такое эфириум минты? - Этим вопросом не задаются грамотные люди, ибо знают, что минтеры - пидорасы.]]></description><content:encoded><![CDATA[
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="DdSl">Что же такое эфириум минты? - Этим вопросом не задаются грамотные люди, ибо знают, что минтеры - пидорасы.</p>
  </section>
  <p id="qNbN">Набрав в поисковике лучшей социальной сети ВКОНТАКТИ - &quot;что такое эфириум&quot;, получил довольно вразумляющий ответ:</p>
  <figure id="cgVt" class="m_original">
    <img src="https://img1.teletype.in/files/43/88/4388a26d-10ad-47bf-9c7a-31a8807b0289.png" width="168" />
  </figure>
  <blockquote id="lkVX"><strong>Ethereum</strong> - это управляемая сообществом технология, на основе которой работает криптовалюта эфир (ETH) и тысячи децентрализованных приложений. <br /><br />Изучите Ethereum<br /><br /> Посмотреть другие варианты на DeepL.com</blockquote>
  <p id="QJZR">Как же он работает эта тварь?</p>
  <h2 id="Dq4j">Блоки и транзакции</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="LXoE">Блоки делятся на несколько видов: алмазный, золотой, железный, блок дерева кхм...,  - блоки это та часть блокчейна, в которых хранится его информация. Blockchain - цепочка блоков, которые идут последовательно и хранят в себе всю information о транзакциях.</p>
    <p id="Z6X2">Транзакция - хуйня для отправки новых данных в блокчейн, эта залупа может быть, как переводом средств, так и вызовом функций из разных контрактов или сообщением ебучего ламера, который выебал мамку разработчиков.</p>
  </section>
  <p id="UxXB">В блоках хранятся транзакции, в транзакциях информация о всех действиях участников блокчейна.</p>
  <h3 id="tRao">Транзакции подробно</h3>
  <hr />
  <p id="IBot">Для следующих тем нужно знать несколько понятий: </p>
  <ol id="SBdP">
    <li id="4SFm">Ethereum - нативная монета, которую мы отдаём уёбкам(ныне называются валидаторами) чтобы они наши транзы подтвердили.</li>
    <li id="jxoL">Gas limit и gas price - дань для ахуевших хуесосов(в простонародии - комиссия), которую мы должны заплатить, чтобы нашу транзу включили в сеть. Gas limit зависит от сложности транзакции(перевод стоит меньше, чем вызов функций контрактов, потому что количество переносимых байтов в транзе меньше, чем вес твоей мамаши), gas price зависит от количество поступаемых пенисов в жопу сети(<a href="https://etherscan.io/gastracker" target="_blank">Чекать тут</a>). </li>
    <li id="xLBD">Data - данные, которые передаются в контракт. </li>
  </ol>
  <p id="i1kq">бля, а что нахуй такое эти ваши контракты?</p>
  <h2 id="abVh">Смарт-контрактики</h2>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="NLQ6">Смарт-контракт это аппка в блокчейне, у которой есть какой-то пул функций и стор. токены, nft, дексы - всё это работает через них.</p>
  </section>
  <p id="E0CZ">Вот, к примеру, метод перевода в контракте юэсдитишки, который мы каждый раз используем когда хотим перевести свои стейблы</p>
  <figure id="4bI0" class="m_column">
    <img src="https://img2.teletype.in/files/19/7d/197d6478-736e-48ca-9d16-96263fb039b5.png" width="1364" />
  </figure>
  <p id="dVAb">У нас есть 3 аргумента:<strong><code>от кого</code></strong>, <code>кому</code> и <code>сколько</code> - аргументы.</p>
  <p id="elhi">Конечный результат вызова функции, так называемая INPUT DATA, штука, которая хранит в себе: нейм вызываемой функции и аргументы:</p>
  <figure id="MKA7" class="m_column">
    <img src="https://img3.teletype.in/files/e8/4a/e84a965a-b02d-48d6-b346-7c46146a4f63.png" width="1364" />
  </figure>
  <p id="WyqN">Если не заглядывать под капот - компьютеру куда проще читать такую хуйню, нежели читаемый текст. в будущем объясню, как эта хуйня воркает, а пока что держи в голове, что любой вызов функции выглядит так.</p>
  <p id="r0kO"></p>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="jjhS">Input data - поле транзакции, в котором передаётся информация о том, какую функцию мы хотим вызвать и какие аргументы в неё передать. В переводе USDT мы используем функцию transferFrom и передаём аргументы from, to, value(от кого, кому и сколько). Но в целом, туда можно написать что угодно при желании, например что-то о матери битка???</p>
  </section>
  <h2 id="oWBw">Вывод</h2>
  <section style="background-color:hsl(hsl(0,   0%,  var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="Zi8r">В целом, краткий ликбез в эфир закончен, вы успешно завершили первый акт познания эфириума.</p>
  </section>
  <p id="m2wL"></p>
  <p id="pKBE">Авторы:</p>
  <p id="ItnN"><a href="https://t.me/leviathan_hell" target="_blank">Левиафан</a> / <a href="https://t.me/dolbaebskicode" target="_blank">Параноик</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/ethereum-rust-transaction-part2</guid><link>https://teletype.in/@paranoikcodit/ethereum-rust-transaction-part2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/ethereum-rust-transaction-part2?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>Эфириум в Расте? Часть вторая</title><pubDate>Fri, 12 Aug 2022 18:33:38 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/fd/56/fd56c7d0-07b1-483a-a8b1-b61ca58a081f.png"></media:content><tt:hashtag>rust</tt:hashtag><tt:hashtag>ethereum</tt:hashtag><tt:hashtag>mempool</tt:hashtag><description><![CDATA[<img src="https://img1.teletype.in/files/0b/f4/0bf4c4fb-459e-4b09-aa39-e45a55c36348.png"></img>В этом руководстве показано, как законнектиться к WebSocket ноде Эфира, генерировать кошельки, подписывать сообщения, чтение мемпула.]]></description><content:encoded><![CDATA[
  <figure id="CCXf" class="m_custom">
    <img src="https://img1.teletype.in/files/0b/f4/0bf4c4fb-459e-4b09-aa39-e45a55c36348.png" width="1027.5275895450145" />
  </figure>
  <p id="A5hk">В этом руководстве показано, как законнектиться к WebSocket ноде Эфира, генерировать кошельки, подписывать сообщения, чтение мемпула.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="FkTa" data-align="center">Окружение</h2>
  </section>
  <p id="5ij9">Про то как скачать и установить раст было в <a href="https://teletype.in/@paranoikcodit/ethereum-rust-transaction#eCzt" target="_blank">прошлой части,</a> так что останавливаться и ждать балласт не будем.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="SwcG" data-align="center">Зависимости</h2>
  </section>
  <p id="rLOI">Теперь мы создадим проект Rust и добавим необходимые зависимости.</p>
  <p id="bwt1">Создайте папку нашего проекта и инициализируйте его с помощью <code>cargo</code>:</p>
  <pre data-lang="bash" id="srPA">mkdir rust-ethereum-tutorial-part2
cd rust-ethereum-tutorial-part2
cargo init</pre>
  <p id="GCfB">Для удобства добавления зависимостей Rust в наш проект мы будем использовать <code>cargo-edit</code>:</p>
  <pre id="erbw">cargo install cargo-edit</pre>
  <p id="g4jN">Добавим необходимые модули:</p>
  <pre id="917R">cargo add tokio eyre ethers +legacy</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="6zWP" data-align="center">Подключение к WebSocket Ноде эфира.</h3>
  </section>
  <p id="PCjL">В отличие от прошлого туториала, конструкция подключения будет немного иная:</p>
  <pre id="vLUA" data-lang="rust">use ethers::prelude::*;

#[tokio::main]
async fn main() -&gt; eyre::Result&lt;()&gt; {
    // Подключаемся ноде, создаем новый экземпляр провайдера.
    let client = Provider::new(
        Ws::connect(&quot;wss://eth-mainnet.g.alchemy.com/v2/rhHsBRIaD9TsoHdKpnymrJimBxPwv5Xp&quot;).await?,
    );
    
    Ok(()) // Пустое возвращаемое значение
}</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="HL8t" data-align="center">Создание кошелька</h3>
  </section>
  <p id="P7M4">Теперь когда клиент создан, можно заняться хуйней, то есть сгенерировать новый кошелек:</p>
  <pre id="D4lp" data-lang="rust">    let wallet = LocalWallet::new(&amp;mut rand::thread_rng()); // Генерация рандомного кошелька
    let address = wallet.address(); // Получение адреса кошелька

    println!(&quot;ебать привет {:?}&quot;, address);</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="1bcN" data-align="center">Подпись сообщения</h3>
  </section>
  <p id="9xIC">Так как мы не долбаебы, то лучше всего изучить какие-то доп. возможности объекта кошелька.</p>
  <p id="d7xG">Одна из возможностей подпись сообщения:</p>
  <pre id="mKbI" data-lang="rust">    let signature = wallet.sign_message(&quot;Я пидор&quot;).await?; // Создание сигнатуры

    println!(&quot;{}&quot;, signature);</pre>
  <p id="0q9Q">Сигнатура подписанного сообщения есть, но как теперь ее верефать?</p>
  <p id="kEe9">Для такого есть метод <code>verify</code>, который на вход получает возможное сообщение и адрес кошелька. </p>
  <p id="DOuz">Попробуем наебать систему и вписать какую-то хуйню:</p>
  <pre id="I75H" data-lang="rust">    match signature.verify(&quot;Я не пидор&quot;, address) {
        Err(_) =&gt; println!(&quot;ХХИХХИХИ, ТЫ ЧО ПИДОР?))&quot;),
        Ok(_) =&gt; println!(&quot;ЧОО ТЫ НЕ ПИДОР?&quot;),
    };</pre>
  <p id="6wXK">Запустив наше детище в ебло прилетит сообщение<code> ХХИХХИХИ, ТЫ ЧО ПИДОР?))</code></p>
  <p id="DKiJ">Так, а теперь попробуем ввести правильное значение, которое соответствует подписанному сообщению:</p>
  <pre id="vinM" data-lang="rust">    match signature.verify(&quot;Я пидор&quot;, address) {
        Ok(_) =&gt; println!(&quot;ЧЧООО ПИДОР??&quot;),
        Err(_) =&gt; println!(&quot;НЕЕЕЕЕЕЕ ПИДОР???&quot;),
    };</pre>
  <p id="Fat4">Что же получится при запуске? - Выведется <code>ЧЧООО ПИДОР??</code></p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="XuC7" data-align="center">Чтение мемпула</h3>
  </section>
  <p id="CGSf">Чтобы полностью овладеть БЛОКЧЕЙНОМ ЭФИРОМ и стать настоящим Терном, нужно научиться читать мемпул.</p>
  <section style="background-color:hsl(hsl(170, 33%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <p id="VXP0"><strong>Mempool</strong> — очень важная часть сети Bitcoin (сеть компьютеров и устройств, подключенных к Интернет и работающих с программным обеспечением Bitcoin Core). Данные, хранящиеся в мемпуле — это неподтвержденные транзакции, которые в настоящее время застряли в сети. Каждая часть мемпула хранится на специальном электронном устройстве и за его содержимым можно наблюдать в режиме реального времени. Чтобы объяснить, как это работает, сначала нужно разобраться, что происходит во время транзакции?</p>
    <p id="NzEi">(я спиздил определение хз нахуя но мне похуй)</p>
  </section>
  <p id="eBTr">ТАААК, чтение мемпула дело простое, но требует некоторых действий.</p>
  <p id="EUpG">Для начала нужно создать фильтр мемпула, то есть какого типа транзакций будут поступать при чтении. </p>
  <p id="ZvV3">В своем примере я решил хуй забить на фильтрацию и сделать его пустым, то есть любого вида транзакции будут поступать:</p>
  <pre id="G6YW" data-lang="rust">    let filter = Filter::new();</pre>
  <p id="btyj">Теперь, чтобы начать читать мемпул, нам нужно передать этот своеобразный фильтр в метод <code>subscribe_logs</code>, который создает новый ручей подписок</p>
  <figure id="s5uq" class="m_custom">
    <img src="https://img1.teletype.in/files/86/ed/86ed8142-aef8-43e0-b7f9-6df43b970856.png" width="616" />
  </figure>
  <pre id="o7h0" data-lang="rust">    let mut stream = client.subscribe_logs(&amp;filter).await?;</pre>
  <p id="ZW1G">Ручей получили, а как в нем купаться? Для этого нам потребуется особенная конструкция:</p>
  <pre id="aR9G" data-lang="rust">    while let Some(log) = stream.next().await {}</pre>
  <p id="wujL">Такая хуета позволяет постоянно получать данные из мемпула.</p>
  <p id="vT0O">Чтобы понять, что находится внутри лога, достаточно ебануть под двум клавишам ctrl+space.</p>
  <p id="3L0o">По аналогии я сделал что-то примерно такое:</p>
  <pre id="AuSr" data-lang="rust">    while let Some(log) = stream.next().await {
        println!(
            &quot;транзакция {:?}, блок {:?}&quot;,
            log.transaction_hash, log.block_number
        );
    }</pre>
  <hr />
  <p id="xQLI">Код готов, пришло время запускать:</p>
  <pre id="S94D">[meqy@meqy-hplaptop15seq2xxx eth_rust]$ cargo run
    Finished dev [unoptimized + debuginfo] target(s) in 0.38s
     Running &#x60;target/debug/eth_rust&#x60;
ебать привет 0xda85e6532cce4869c18025aac2b34386d5c5f8ce
18350d269ad85efee75ee83ed22c84c891dd6209c27868582c9e81a65cc6ad9965002af2fd1c0174ad44bfcacd314ff6af153f1cba02e6e368f25e2d9fc6172c1b
ХХИХХИХИ, ТЫ ЧО ПИДОР?))
ЧЧООО ПИДОР??
^C</pre>
  <p id="UuSL">Дальше мне думать чото лень, так что ждите следующей статьи.</p>
  <hr />
  <tt-tags id="nLEq">
    <tt-tag name="rust">#rust</tt-tag>
    <tt-tag name="ethereum">#ethereum</tt-tag>
    <tt-tag name="mempool">#mempool</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/ethereum-rust-transaction</guid><link>https://teletype.in/@paranoikcodit/ethereum-rust-transaction?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/ethereum-rust-transaction?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>Эфириум в Расте? Часть первая: Отправка простых транзакций.</title><pubDate>Wed, 10 Aug 2022 16:21:33 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/29/b6/29b64cdd-1d41-4a57-bb3e-43f9ef62b7e2.png"></media:content><tt:hashtag>rust</tt:hashtag><tt:hashtag>ethereum</tt:hashtag><tt:hashtag>transaction</tt:hashtag><tt:hashtag>block</tt:hashtag><description><![CDATA[<img src="https://img4.teletype.in/files/78/17/781778f2-39c4-4c79-b111-bbc868d9d606.png"></img>В этом руководстве показано, как запустить локальную виртуальную машину Ethereum (EVM) в Rust, запросить баланс и совершить простые транзакции.]]></description><content:encoded><![CDATA[
  <figure id="tqxg" class="m_custom">
    <img src="https://img4.teletype.in/files/78/17/781778f2-39c4-4c79-b111-bbc868d9d606.png" width="1202" />
  </figure>
  <p id="2NIe">В этом руководстве показано, как запустить локальную виртуальную машину <code>Ethereum (EVM)</code> в Rust, запросить баланс и совершить простые транзакции.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="eCzt" data-align="center">Настройка зависимостей</h2>
  </section>
  <p id="lOIf">Прежде чем писать код, мы должны убедиться, что у нас установлены некоторые необходимые инструменты, а именно <code>Rust</code> и <code>Ganache</code>.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="9GeZ" data-align="center">Rust</h3>
  </section>
  <p id="ggIk">Установите Rust, следуя  <a href="https://www.rust-lang.org/tools/install" target="_blank"><code>инструкциям</code></a>. Или, если вы используете Arch Linux, вы можете следовать <a href="https://wiki.archlinux.org/title/rust" target="_blank"><code>инструкциям здесь</code></a>, чтобы установить Rust.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="a9d2" data-align="center">ganache-cli</h3>
  </section>
  <p id="5nNE"><code><a href="https://trufflesuite.com/docs/ganache/" target="_blank">Ganache</a></code> поможет вам быстро создать среду Ethereum для тестирования. Если вам не нужен весь пакет, вы можете просто установить <code>ganache-cli</code> с помощью npm,</p>
  <pre id="Z2am" data-lang="powershell">npm install -g ganache</pre>
  <p id="scr8">Запустите <code>ganache-cli</code>, чтобы проверить, правильно ли вы его установили.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="zzTP" data-align="center">Настройка проекта Rust</h2>
  </section>
  <p id="rLOI">Теперь мы создадим проект Rust и добавим необходимые зависимости. </p>
  <p id="bwt1">Создайте папку нашего проекта и инициализируйте его с помощью <code>cargo</code>:</p>
  <pre id="BaJd" data-lang="bash">mkdir rust-ethereum-tutorial
cd rust-ethereum-tutorial
cargo init</pre>
  <p id="GCfB">Для удобства добавления зависимостей Rust в наш проект мы будем использовать <code>cargo-edit</code>:</p>
  <pre id="epHJ">cargo install cargo-edit</pre>
  <p id="g4jN">Это добавит последнюю версию <code>ethers</code> в качестве зависимости в <code>Cargo.toml</code>:</p>
  <pre id="8RvU" data-lang="bash">cargo add --no-default-features ethers +legacy</pre>
  <p id="R2l2">и еще несколько библиотек:</p>
  <pre id="bA2o">cargo add tokio +full clap hex eyre</pre>
  <p id="EJM1">Краткое описание этих библиотек: </p>
  <ul id="ho5i">
    <li id="8L7e"><a href="https://docs.rs/tokio/" target="_blank"><code>tokio</code></a> - позволяет нам использовать асинхронные функции в Rust</li>
    <li id="ESxf"><a href="https://docs.rs/clap" target="_blank"><code>clap</code></a> - для работы с аргументами командной строки</li>
    <li id="phTm"><a href="https://docs.rs/hex" target="_blank"><code>hex</code></a> - для работы с шестнадцатеричными строками </li>
    <li id="nDqp"><a href="https://docs.rs/eyre" target="_blank"><code>eyre</code></a> - это библиотека, которая помогает сократить количество кодового кода при обработке ошибок</li>
  </ul>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="9IbT" data-align="center">Подключение к RPC Ethereum Web3</h3>
  </section>
  <p id="4vBb">Узел <code>Ethereum</code> обычно предоставляет конечную точку <em>HTTP</em>, <em>WebSocket</em> или <em>IPC</em> для доступа к сети <code>Ethereum</code>. Для разработки в локальной среде мы можем подключиться к нашей конечной точке, предоставляемой локально запущенным ganache. Чтобы запустить <code>ganache</code> в Rust, мы можем просто использовать <code>Ganache::new().spawn()</code>:</p>
  <pre id="Szy9" data-lang="rust">use ethers::utils::Ganache;
use eyre::Result;#[tokio::main]
async fn main() -&gt; Result&lt;()&gt; {
    // Спавн Ganache экземпляра
    let ganache = Ganache::new().spawn();
    println!(&quot;HTTP Endpoint: {}&quot;, ganache.endpoint());
    Ok(())
}</pre>
  <p id="vVrG">Если мы выполним команду <code>cargo run</code>, мы увидим <code>URL</code> конечной точки, выведенный в консоли:</p>
  <pre id="lPSN">...
HTTP Endpoint: http://localhost:46795</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="7fN0" data-align="center">Доступ к тестовым кошелькам ganache</h3>
  </section>
  <p id="Q5JR">При каждом запуске Ganache будет использовать случайно генерируемые мнемоники.</p>
  <blockquote id="eFd4">Чтобы сделать наш пример более детерминированным, мы можем настроить его на использование определенную мнемонику:</blockquote>
  <pre id="pWbU" data-lang="rust">let mnemonic = &quot;gas monster ski craft below illegal discover limit dog bundle bus artefact&quot;;
let ganache = Ganache::new().mnemonic(mnemonic).spawn();</pre>
  <p id="B0G6">Чтобы получить доступ к кошельку, созданному ganache, мы сначала получим закрытые ключи, а затем преобразуем их в экземпляр <code>LocalWallet</code>:</p>
  <pre id="AZwo" data-lang="rust">// Получите первый кошелек, управляемый ganache
let wallet: LocalWallet = ganache.keys()[0].clone().into();
let wallet_address: String = wallet.address().encode_hex();
println!(&quot;Default wallet address: {}&quot;, wallet_address);</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="SWmu" data-align="center">Подключение через JSON RPC</h3>
  </section>
  <p id="AAsQ">Для взаимодействия с узлом или сетью Ethereum нам потребуется создать клиент, который подключается к конечной точке ganache:</p>
  <pre id="Y0tU" data-lang="rust">// Провайдер - это клиент Ethereum JsonRPC.
let provider = Provider::try_from(ganache.endpoint())?.interval(Duration::from_millis(10));</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="fMSR" data-align="center">Запрос баланса по адресу</h3>
  </section>
  <p id="MRar">Теперь, когда наш клиент подключен к сети Ethereum, мы можем запросить баланс любого адреса в нашем кошельке:</p>
  <pre id="PHpC" data-lang="rust">// Запросить баланс нашего счета
let first_balance = provider.get_balance(first_address, None).await?;
println!(&quot;Баланс первого адреса кошелька: {}&quot;, first_balance);</pre>
  <p id="PrKJ">Если мы снова запустим программу <code>cargo run</code>, то узнаем, что по этому адресу у нас есть 1000ETH. Это добавляется программой Ganache автоматически.</p>
  <p id="JOii">Чтобы запросить баланс, используя адрес в формате шестнадцатеричной строки, нам нужно сначала преобразовать строку в тип <code>Address:</code></p>
  <pre id="RXBw" data-lang="rust">// Запросить баланс случайного счета
let other_address_hex = &quot;0xaf206dCE72A0ef76643dfeDa34DB764E2126E646&quot;;
let other_address = &quot;0xaf206dCE72A0ef76643dfeDa34DB764E2126E646&quot;.parse::&lt;Address&gt;()?;
let other_balance = provider.get_balance(other_address, None).await?;
println!(
    &quot;Баланс адреса {}: {}&quot;,
    other_address_hex, other_balance
);</pre>
  <p id="cv5L">Попробуйте <code>cargo run</code>, и вы увидите, что баланс этого адреса равен нулю:</p>
  <pre id="Pzvq">Баланс адреса 0xaf206dCE72A0ef76643dfeDa34DB764E2126E646: 0</pre>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h3 id="XEEg" data-align="center">Создать простую транзакцию</h3>
  </section>
  <p id="xEzx">Далее мы создадим простую транзакцию для перевода нескольких токенов Ethereum на другой адрес.</p>
  <pre id="zV1S" data-lang="rust">// Создайте транзакцию для перевода 1000 wei на &#x60;other_address&#x60;.
let tx = TransactionRequest::pay(other_address, U256::from(1000u64)).from(first_address);
// Отправляем транзакцию и ждем получения
let receipt = provider
    .send_transaction(tx, None)
    .await?
    .log_msg(&quot;Ожидание трансфера&quot;)
    .await?
    .context(&quot;Нет получателя&quot;)?;
    
println!(
    &quot;Транзакция замайнена на блоке {}&quot;,
    receipt.block_number.context(&quot;Не получилось получить блок транзакции&quot;)?
);
println!(
    &quot;Баланс у {} {}&quot;,
    other_address_hex,
    provider.get_balance(other_address, None).await?
);</pre>
  <p id="HKd0">Выполните cargo run, вы получите следующие результаты:</p>
  <pre id="Ogc8">Ожидание трансфера: 
0xff6153310304732bb28856ca3a90ab9c94c5c9e20cf51e8a2803f3483670cd6f
Транзакция замайнена на блоке 1
Баланс у 0xaf206dCE72A0ef76643dfeDa34DB764E2126E646 1000</pre>
  <hr />
  <tt-tags id="X5Bs">
    <tt-tag name="rust">#rust</tt-tag>
    <tt-tag name="ethereum">#ethereum</tt-tag>
    <tt-tag name="transaction">#transaction</tt-tag>
    <tt-tag name="block">#block</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/create-rust-web-api</guid><link>https://teletype.in/@paranoikcodit/create-rust-web-api?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/create-rust-web-api?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>Реализация своего АПИ на Rust с помощью Tokio и Wrap</title><pubDate>Wed, 10 Aug 2022 12:30:19 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/5a/54/5a549979-5656-4e78-87ab-caf6389b4cac.png"></media:content><tt:hashtag>rust</tt:hashtag><tt:hashtag>wrap</tt:hashtag><tt:hashtag>mutex</tt:hashtag><tt:hashtag>arc</tt:hashtag><tt:hashtag>dyn</tt:hashtag><tt:hashtag>server</tt:hashtag><description><![CDATA[<img src="https://img2.teletype.in/files/d6/af/d6af85a0-635c-4f28-8bb4-990b1fed9aca.png"></img>Сейчас вам поведаю о создании своего API сервиса на основе Warp и Tokio. 
Это является перевод данной статьи.  ]]></description><content:encoded><![CDATA[
  <figure id="0UEK" class="m_custom">
    <img src="https://img2.teletype.in/files/d6/af/d6af85a0-635c-4f28-8bb4-990b1fed9aca.png" width="1294" />
  </figure>
  <p id="F9ip">Сейчас вам поведаю о создании своего API сервиса на основе Warp и Tokio. <br />Это является перевод <a href="https://levelup.gitconnected.com/building-an-api-using-warp-and-tokio-26a52173860a" target="_blank">данной статьи.</a>  </p>
  <hr />
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="1hQX" data-align="center">Структура</h2>
  </section>
  <p id="4uN7">Прежде чем приступить к написанию кода, нужно немного продумать структуру API. Это поможет определить нужные эндпоинты, контроллеры и способы хранения данных.</p>
  <h3 id="U33g">Роуты</h3>
  <p id="mhPD">Для своего апи я определил два роута</p>
  <pre id="ebR3">/customers
     - GET -&gt; получить список пользователей
     - POST -&gt; создать нового пользователя и добавить инфомацию в хранилище
/customers/{guid}
     - GET -&gt; получить информацию о пользователе
     - POST -&gt; обновить информацию о пользователе
     - DELETE -&gt; удалить пользователя из хранилища</pre>
  <h3 id="8vlx">Обработчики</h3>
  <p id="a9pf">На основе маршрутов мне потребовалось определить несколько обработчиков</p>
  <pre id="Fbhu">list_customers -&gt; вернуть список пользователей
create_customer -&gt; создать нового пользователя и добавить его в базу данных
get_customer -&gt; вернуть информацию о конкретном пользователе
update_customer -&gt; обновить информацию о пользователе 
delete_customer -&gt; удалить пользователя из базы данных</pre>
  <h3 id="znFP">База данныхчто</h3>
  <p id="vxua">Для примера я буду использовать in-memory хранилище.</p>
  <p id="4jku">Чтобы сгенерировать необходимый набор данных я воспользовался <a href="https://www.mockaroo" target="_blank">mockaroo</a>.</p>
  <figure id="Itmt" class="m_original">
    <img src="https://img2.teletype.in/files/9c/ec/9cec6691-2905-4a79-87b3-82f00c3db261.png" width="797" />
  </figure>
  <p id="fO23">Кроме того, модуль для работы с бд должен уметь инициализировать хранилище после запуска сервера.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="KhPw" data-align="center">Зависимости</h2>
  </section>
  <ul id="zkRb">
    <li id="ht1n"><a href="https://crates.io/crates/warp" target="_blank">Warp</a> — Фреймворк для создания веб-сервера на Расте.</li>
    <li id="3OiW"><a href="https://crates.io/crates/tokio" target="_blank">Tokio</a> — Асинхронная среда выполнения для Раста.</li>
    <li id="Nn7K"><a href="https://crates.io/crates/serde" target="_blank">Serde</a> — Библиотека для де/сериализации данных в типизированные данные Раста.</li>
  </ul>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="FZRF" data-align="center">Реализация</h2>
  </section>
  <h3 id="adfb">Модели</h3>
  <p id="ZKZu">Первое, что я хочу сделать, это определить свою модель пользователя, а также добавить некоторую структуру в код. </p>
  <p id="Mvo2">В <strong><em><code>main.rs</code></em> </strong>определите новый модуль с именем <strong><em><code>models</code></em></strong> следующим образом:</p>
  <pre id="wE5f" data-lang="rust">mod models;
fn main() { /* Логика */ }</pre>
  <p id="VKfS">Затем создать новый файл с именем <strong><em><code>models.rs</code></em></strong> и добавьте следующее:</p>
  <pre id="NJiJ" data-lang="rust">pub struct Customer {
    pub guid: String,    
    pub first_name: String,
    pub last_name: String,    
    pub email: String,    
    pub address: String,
}</pre>
  <p id="v29t">Так как я разрабатываю API, эта структура данных должна иметь возможность <em>сериализации</em> и <em>десериализации</em> JSON. Я также хочу иметь возможность копировать структуру в хранилище данных и из него, не беспокоясь о проверке <em>заимствования</em>.</p>
  <p id="0Rax">Для этого я добавлю оператор <em><strong>drive</strong> </em>для структуры пользователя<em>, </em>чтобы использовать пару макросов из библиотеки <strong><code>Serde</code></strong> и пару из Rust. Сейчас <em><strong>models.rs</strong></em> выглядит так:</p>
  <figure id="kdLz" class="m_custom">
    <img src="https://miro.medium.com/max/565/0*sue-KWrU-R1S1vwg.png" width="565" />
  </figure>
  <h3 id="WivM">База данных</h3>
  <p id="FzEX">База данных для этого API будет являться in-memory(то есть находиться в оперативной памяти и после отключения сервера очистится), которая будет являться вектором структуры <em><code>Customer</code>. </em>Но хранилище должно быть общим для всех маршрутов, поэтому я буду использовать <em>смарт поинтеры<a href="https://doc.rust-lang.org/book/ch15-00-smart-pointers.html" target="_blank">[</a><a href="https://doc.rust-lang.ru/book/ch15-00-smart-pointers.html" target="_blank">0</a><a href="https://doc.rust-lang.org/book/ch15-00-smart-pointers.html" target="_blank">]</a> Раста</em> вместе с <em>Mutex<a href="https://doc.rust-lang.ru/book/ch16-03-shared-state.html#%D0%9C%D1%8C%D1%8E%D1%82%D0%B5%D0%BA%D1%81%D1%8B-%D0%BF%D1%80%D0%B5%D0%B4%D0%BE%D1%81%D1%82%D0%B0%D0%B2%D0%BB%D1%8F%D1%8E%D1%82-%D0%B4%D0%BE%D1%81%D1%82%D1%83%D0%BF-%D0%BA-%D0%B4%D0%B0%D0%BD%D0%BD%D1%8B%D0%BC-%D0%B8%D0%B7-%D0%BE%D0%B4%D0%BD%D0%BE%D0%B3%D0%BE-%D0%BF%D0%BE%D1%82%D0%BE%D0%BA%D0%B0-%D0%B7%D0%B0-%D1%80%D0%B0%D0%B7" target="_blank">[1]</a></em>, чтобы обеспечить <strong>безопасность потоков</strong>.</p>
  <p id="wtn8">Во-первых, добавим в <strong><code>main.rs</code></strong> новый модуль <em>db</em>:</p>
  <pre id="i1h1" data-lang="rust">mod db;
mod models;
fn main() { /* Логика */ }</pre>
  <p id="bxSu">Теперь создадим новый файл <strong><code>db.rs</code></strong>.</p>
  <p id="GgJL">В этом файле нужно сделать несколько вещей, но первое, что нужно сделать, это определить, как будет выглядеть хранилище данных.</p>
  <p id="cnSJ">Наше простое хранилище это лишь вектор структур <em><strong><code>Customer</code>,</strong></em> но его необходимо обернуть в <strong><em>thread safe</em></strong> ссылку, чтобы иметь возможность использовать несколько ссылок на хранилище данных в нескольких асинхронных обработчиках.</p>
  <p id="AGuy">Добавим примерно такое в <strong><em><code>db.rs</code></em></strong>:</p>
  <pre id="euEh" data-lang="rust">use std::sync::Arc;
use tokio::sync::Mutex;

use crate::models::Customer;

pub type Db = Arc&lt;Mutex&lt;Vec&lt;Customer&gt;&gt;&gt;;</pre>
  <p id="8Pl5">Теперь, когда мы определили структуру хранилища данных, нам нужен способ его инициализации. Инициализация хранилища данных имеет два результата: либо пустое хранилище данных, либо хранилище данных, наполненное данными из файла.</p>
  <p id="3S5o">Инициализация пустого хранилища довольно проста:</p>
  <pre id="wv3e" data-lang="rust">pub fn init_db() -&gt; Db {
    Arc::new(Mutex::new(Vec::new()))
}</pre>
  <p id="jRSd">Но чтобы получить данные из файла, нам придется добавить еще одну зависимость:</p>
  <ul id="IiJ6">
    <li id="3846"><a href="https://crates.io/crates/serde_json" target="_blank">serde_json</a> — Для чтения необработанного JSON</li>
  </ul>
  <p id="8Izx">Добавим его в <code>Cargo.toml</code>:</p>
  <pre id="SNqw">serde_json = &quot;1.0&quot;</pre>
  <p id="vpdh">Теперь мы можем изменить <code>db.rs</code> следующим образом:</p>
  <pre id="9a27" data-lang="rust">use std::fs::File;
use serde_json::from_reader;

pub fn init_db() -&gt; Db {
    let file = File::open(&quot;./data/customers.json&quot;);
    match file =&gt; {
        Ok(json) =&gt; {
            let customers = from_reader(json).unwrap();
            Arc::new(Mutex::new(customers))
        },
        Err(_) =&gt; {
            Arc::new(Mutex::new(Vec::new()))
        }
    }
}</pre>
  <p id="nZOI">Эта функция пытается прочитать файл <code>./data/customers.json</code>. В случае успеха функция возвращает хранилище данных, загруженное данными клиента, в противном случае она возвращает пустой вектор.</p>
  <p id="FaDd"><code>db.rs</code> должен выглядеть примерно так:</p>
  <figure id="tSFm" class="m_custom">
    <img src="https://miro.medium.com/max/628/0*VkFMRfyUv7oWLHhL.png" width="628" />
  </figure>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="360s" data-align="center">Обработчики</h2>
  </section>
  <p id="e5pp">На текущем этапе мы имеем модели и настройку базы данных. Теперь у нас нужда в общем связывающем инструменте. Тут в дело вступают обработчики.</p>
  <p id="WwZu">Создадим файл <code>handlers.rs</code> и определим его, как модуль в файле <code>main.rs:</code></p>
  <pre id="HY5s" data-lang="rust">mod handlers;</pre>
  <p id="Bcl7">Добавим в handlers.rs несколько импортов:</p>
  <pre id="1H71" data-lang="rust">use std::convert::Infallible;
use warp;

use crate::models::Customer;
use crate::db::Db;</pre>
  <p id="K90o">Этот кусок позволяет обращаться к примитивам <code>Customer</code> и <code>Db</code>, которые мы определили ранее, из модуля обработчиков. Также тут мы импортируем модуль <code>warp</code> и перечисление <code><a href="https://doc.rust-lang.org/std/convert/enum.Infallible.html" target="_blank">Infallible</a></code>, которое является типом ошибок, что никогда не могут произойти.</p>
  <h3 id="cE4j">Список пользователей</h3>
  <p id="CuKV">Обработчик <code>list_customers</code>вобработчик принимает на вход ссылку на хранилище и возвращает <code>Result</code>, который оборачивает JSON-ответ.</p>
  <pre id="rlPw" data-lang="rust">pub async fn list_customers(db: Db) -&gt; Result&lt;impl warp::Reply, Infallible&gt; { 
   // ...  
} </pre>
  <p id="1BfU">В теле функции нужно реализовать получение данных из хранилища и сериализация их в JSON объект. Для удобства <code>wrap</code> предоставляет функцию, которая преобразовывает вектор в JSON объект.</p>
  <figure id="ds6W" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*uuRtqjJYa_2q87XG.png" width="700" />
  </figure>
  <p id="Sa4Z">Строчка <code>let customers = db.lock().await;</code> дожидается блокировки выполнения задачи, чтобы можно было безопасно обратиться к хранилищу.</p>
  <p id="cXFN">Строка <code>let customers: Vec&lt;Customer&gt; = customers.clone()</code> клонирует вектор из <code>MutexGuard.</code></p>
  <p id="T3cQ">Последняя строка <code>Ok(warp::reply::json(&amp;customers))</code> оборачивает вектор в JSON объект и возвращает его.</p>
  <h3 id="FBH6">Создание пользователя</h3>
  <p id="EyBB">Обработчик <code>create_customer</code> принимает на вход объект <code>Customer</code> и ссылку на хранилище. В случае успешного создания возвращает статус код <code>CREATED</code>, в ином случае <code>BAD_REQUEST.</code></p>
  <p id="dBus">Но прежде нужно обновить импорты <code>wrap</code>&#x27;a в файле <code>handlers.rs</code>.</p>
  <p id="r4JG">Заменяем строчку <code>use wrap;</code> на;</p>
  <pre id="uSgP" data-lang="rust">use warp::{self, http::StatusCode};</pre>
  <p id="9d65">Данное изменение позволит использовать перечисление <code>StatusCode</code>, как <em>респонс</em>.</p>
  <p id="Yyts">По аналогии с <code>list_customers</code> определяем обработчик <code>create_customer</code> с подобным содержанием:</p>
  <figure id="JMZ1" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*bfnu_sZg4bXg1MgK.png" width="700" />
  </figure>
  <h3 id="h2KQ">Получение пользователя</h3>
  <p id="OBmb">Обработчик get_customer на вход берет guid определенного пользователя и ссылку на базу данных и возвращает JSON-объект, если пользователь найден, иначе вернет заглушку в виде дефолтного объекта пользователя.</p>
  <p id="emEt">Перед этим добавим еще один макрос в структуру пользователя:</p>
  <figure id="P9Tj" class="m_custom">
    <img src="https://miro.medium.com/max/628/0*tIwigBAmb-Hy3eAy.png" width="628" />
  </figure>
  <p id="A0KU">По аналогии с другими обработчиками добавляет что-то подобное в код:</p>
  <pre id="Kgye" data-lang="rust">pub async fn get_customer(guid: String, db: Db) -&gt; Result&lt;Box&lt;dyn warp::Reply&gt;, Infallible&gt; {
}</pre>
  <p id="jiRV">Возвращаемый примитив немного отличный от других, потому что на выходе мы получаем либо StatusCode, либо JSON-объект. Так как оба этих примитива реализуют warp::Reply, то мы можем использовать <a href="https://doc.rust-lang.ru/book/ch17-02-trait-objects.html#%D0%9E%D0%BF%D1%80%D0%B5%D0%B4%D0%B5%D0%BB%D0%B5%D0%BD%D0%B8%D0%B5-%D1%82%D0%B8%D0%BF%D0%B0%D0%B6%D0%B0-%D0%B4%D0%BB%D1%8F-%D0%BE%D0%B1%D1%89%D0%B5%D0%B3%D0%BE-%D0%BF%D0%BE%D0%B2%D0%B5%D0%B4%D0%B5%D0%BD%D0%B8%D1%8F" target="_blank">динамическое приведение</a> через <code>dyn.</code></p>
  <figure id="xXhG" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*96M88fxKgbAMTsLz.png" width="700" />
  </figure>
  <h3 id="tsIQ">Обновить пользователя</h3>
  <p id="CeHO">Обработчик <code>update_customer</code> на вход принимает <code>guid</code> пользователя, измененный объект <code>Customer</code> и ссылку на <em>бд</em>. Возвращает <code>OK</code>, если получилось изменить пользователя, <code>NOT_FOUND</code> в случае, если нет пользователя в хранилище.</p>
  <figure id="BYsC" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*ROhNI2DEvwVwFpp6.png" width="700" />
  </figure>
  <h3 id="89cS">Удаление пользователя</h3>
  <p id="drxg">Обработчик <code>delete_customer</code> на вход принимает <code>guid</code> пользователя, которого нужно удалить и ссылку на хранилище.</p>
  <p id="eiOl">Если удалось удалить пользователя из базы данных, то функция вернет <code>NO_CONTENT</code>, иначе <code>NOT_FOUND</code>.</p>
  <figure id="NfPm" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*ngz_PG8k5CD6L2C8.png" width="700" />
  </figure>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="PiEH" data-align="center">Роутинг</h2>
  </section>
  <p id="ayW2">Теперь у нас все обработчики собраны, теперь присвоим их соответствующим роутам.</p>
  <p id="eDkM">В <code>main.rs</code> определим еще один модуль:</p>
  <pre id="2LU6" data-lang="rust">mod routes;</pre>
  <p id="yCoR">Теперь создадим файл <code>routes.rs</code>:</p>
  <pre id="N2sS" data-lang="rust">use std::convert::Infallible;
use warp::{self, Filter};

use crate::db::Db;
use crate::handlers;
use crate::models::Customer;</pre>
  <p id="ndED">Сначала нам нужна вспомогательная функция для передачи ссылки на хранилище данных в обработчики из маршрутов.</p>
  <pre id="7qkB" data-lang="rust">fn with_db(db: Db) -&gt; impl Filter&lt;Extract = (Db,), Error = Infallible&gt; {
    warp::any().map(move || db.clone())
}</pre>
  <p id="hwDw">Функция позволяет инжектить хранилище в роут и передавать его обработчику. Filter это трейт, который предоставляет функциональность для подбора маршрутов, что являются результатом одного или нескольких методов Filter.</p>
  <h3 id="8ff9">GET /customers</h3>
  <figure id="Nr3z" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*hFQoRobNfeG2eIvh.png" width="700" />
  </figure>
  <p id="jq3x">Функция возвращает тип, реализующий трейт <code>Filter</code>. <code>Extract</code> используется, когда происходит совпадение, и возвращается значение <code>Extract</code>.</p>
  <p id="OiRB">По сути, функция определяет маршрут, который соответствует запрошенному пути «/customers» и является GET запросом.</p>
  <p id="gMTy">Кроме того, чтобы сохранить некоторую работу в будущем, я реализую еще одну функцию, которая будет служить оболочкой для всех маршрутов пользователей. Позже будет легче, когда мы соединим все вместе.</p>
  <pre id="XfDA" data-lang="rust">pub fn customer_routes(db: Db) -&gt; impl Filter&lt;Extract = impl warp::Reply, Error = warp::Rejection&gt; + Clone {
    customers_list(db.clone())
}</pre>
  <h3 id="29e3">POST /customers</h3>
  <p id="30VW">Этот маршрут добавит нового клиента в хранилище данных, если он еще не существует.</p>
  <p id="XnFb">Одна вещь, которую нужно добавить, прежде чем мы реализуем функцию для маршрута, — это вспомогательная функция для извлечения JSON из тела POST запроса.</p>
  <pre id="ueRb" data-lang="rust">fn json_body() -&gt; impl Filter&lt;Extract = (Customer,), Error = warp::Rejection&gt; + Clone {
    warp::body::content_length_limit(1024 * 16)
        .and(warp::body::json())
}</pre>
  <p id="ZPNU">Функция будет очень похожа на <code>customers_list</code>, за исключением обработчика. Добавьте в <code>route.rs</code> следующее:<br /></p>
  <figure id="pwHY" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*QO6bpjm4AvFUeDwa.png" width="700" />
  </figure>
  <p id="ijU2">Эта функция определяет маршрут, который соответствует пути «/customers» и является POST запросом. Затем JSON из POST запроса и ссылка на хранилище извлекаются и передаются обработчику.</p>
  <h3 id="d4a8">GET /customers/{guid}</h3>
  <p id="lAEm">Этот маршрут попытается получить одного клиента из хранилища данных. </p>
  <p id="QPpb">В этой функции мы используем макрос <code>path!</code> из <code>wrap</code>, который позволяет передавать путь с переменной.</p>
  <figure id="ho5k" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*R700W6kAAIAS_sHR.png" width="700" />
  </figure>
  <p id="T82t">Это определяет маршрут, который будет соответствовать «customers/{какое-то значение}» и GET запросу. Затем он извлекает хранилище и передает его обработчику.</p>
  <p id="yLZo">Одна вещь, которую следует учитывать для маршрутов, заключается в том, что наиболее конкретный маршрут должен быть проверен первым, иначе маршрут может не совпасть.</p>
  <p id="8Jkl">Например, если вспомогательная функция для маршрутов обновлена таким образом:</p>
  <pre id="LMiv" data-lang="rust">pub fn customer_routes(
    db: Db,
) -&gt; impl Filter&lt;Extract = impl warp::Reply, Error = warp::Rejection&gt; + Clone {
    customers_list(db.clone())
        .or(create_customer(db.clone()))
        .or(get_customer(db.clone()))
}</pre>
  <p id="whc5">Маршрут get_customer никогда не будет совпадать, потому что они имеют общий корневой путь — «/customers», что означает, что маршрут списка клиентов будет соответствовать «/customers» и «/customers/{guid}».</p>
  <p id="oitk">Чтобы устранить проблему, упорядочите маршрут так, чтобы наиболее точное совпадение было первым. Как это:</p>
  <pre id="B6JG" data-lang="rust">pub fn customer_routes(
    db: Db,
) -&gt; impl Filter&lt;Extract = impl warp::Reply, Error = warp::Rejection&gt; + Clone {
    get_customer(db.clone())
        .or(customers_list(db.clone()))
        .or(create_customer(db.clone()))
}</pre>
  <h3 id="d4a5">PUT /customers/{guid}</h3>
  <p id="3VNi">Этот маршрут попытается обновить клиента, если он существует, и вернуть код состояния <code>OK</code>, в противном случае возвращается код состояния <code>NOT_FOUND</code>.</p>
  <p id="YYM3">По аналогии с созданием определяем обработчик:</p>
  <figure id="KAh9" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*p4qZ4YEyFNYvPJqf.png" width="700" />
  </figure>
  <p id="T0iH">Затем обновим обертку над маршрутом пользователя:</p>
  <pre id="fYMW" data-lang="rust">pub fn customer_routes(
    db: Db,
) -&gt; impl Filter&lt;Extract = impl warp::Reply, Error = warp::Rejection&gt; + Clone {
    get_customer(db.clone())
        .or(update_customer(db.clone()))
        .or(create_customer(db.clone()))
        .or(customers_list(db))
}</pre>
  <h3 id="4ce6">DELETE /customers/{guid}</h3>
  <p id="Xwm2">Последний маршрут просто удаляет клиента из хранилища данных, если он соответствует заданному <code>guid</code>, а затем возвращает код состояния <code>NO_CONTENT</code>, в противном случае возвращается код состояния <code>NOT_FOUND</code>.</p>
  <figure id="lwRO" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*ihEuT6hZ44IwlHAV.png" width="700" />
  </figure>
  <p id="HkwI">Затем обновим оболочку маршрута клиента. После добавления всех маршрутов обертка должна выглядеть так:</p>
  <figure id="FjHB" class="m_custom">
    <img src="https://miro.medium.com/max/700/0*26Lt7FDRsebMAWXP.png" width="700" />
  </figure>
  <p id="NZOY">На этом все маршруты заканчиваются. Теперь мы можем перейти к связыванию всего вместе.</p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="QBGV" data-align="center">Функция main</h2>
  </section>
  <p id="ParQ">Файл <code>main.rs</code> собирает все части кода воедино. Он инициализирует хранилище данных, получает все маршруты и запускает сервер. Это также довольно короткий файл, поэтому я просто покажу его целиком:</p>
  <figure id="ts75" class="m_custom">
    <img src="https://miro.medium.com/max/610/0*ll-s_yV799pUOgTA.png" width="610" />
  </figure>
  <p id="n1ox">Мы уже видели первые несколько строк, так что давайте пройдемся по основной функции. </p>
  <p id="NJxF">Атрибут функции <code>#[tokio::main] </code>устанавливает точку входа для среды выполнения <code>tokio</code>. Это позволяет нам объявить основную функцию как асинхронную. </p>
  <p id="8gcn">Первые две строки <code>main</code> — это просто вызовы функций из наших модулей. Первый инициализирует хранилище данных, а второй получает оболочку маршрутов наших клиентов. </p>
  <p id="tdd9">В последней строке используется <code>warp::server</code> для создания сервера, а затем <code>run</code> для запуска сервера на указанном хосте и порте. Мы используем ключевое слово <code>await</code>, чтобы выполнить код до тех пор, пока функция запуска не завершится.</p>
  <hr />
  <tt-tags id="nDxO">
    <tt-tag name="rust">#rust</tt-tag>
    <tt-tag name="wrap">#wrap</tt-tag>
    <tt-tag name="mutex">#mutex</tt-tag>
    <tt-tag name="arc">#arc</tt-tag>
    <tt-tag name="dyn">#dyn</tt-tag>
    <tt-tag name="server">#server</tt-tag>
  </tt-tags>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@paranoikcodit/bMJklbBgQej</guid><link>https://teletype.in/@paranoikcodit/bMJklbBgQej?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit</link><comments>https://teletype.in/@paranoikcodit/bMJklbBgQej?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=paranoikcodit#comments</comments><dc:creator>paranoikcodit</dc:creator><title>Подборка библиотек для персонализации терминальных приложений на Python</title><pubDate>Tue, 09 Aug 2022 12:14:11 GMT</pubDate><media:content medium="image" url="https://img3.teletype.in/files/eb/35/eb3541d9-1e7b-4ecf-85ec-9725fcdb92fc.png"></media:content><tt:hashtag>python</tt:hashtag><tt:hashtag>питон</tt:hashtag><tt:hashtag>пайтон</tt:hashtag><tt:hashtag>программирование</tt:hashtag><tt:hashtag>textual</tt:hashtag><tt:hashtag>prompt</tt:hashtag><tt:hashtag>toolkit</tt:hashtag><tt:hashtag>teminal</tt:hashtag><tt:hashtag>tui</tt:hashtag><tt:hashtag>terminaluserinterface</tt:hashtag><tt:hashtag>anchor_exampletextuserinterfac</tt:hashtag><description><![CDATA[<img src="https://img4.teletype.in/files/71/11/7111ccf9-2b14-4572-9df1-9b97fdc57e24.png"></img>приветики ^//^, тут представлена небольшая подборка библиотек, которые я так или иначе использовал в своих проектах для кастомизации консольного вывода.]]></description><content:encoded><![CDATA[
  <figure id="1pNM" class="m_custom">
    <img src="https://img4.teletype.in/files/71/11/7111ccf9-2b14-4572-9df1-9b97fdc57e24.png" width="1116" />
  </figure>
  <p id="8drZ">приветики ^//^, тут представлена небольшая подборка библиотек, которые я так или иначе использовал в своих проектах для кастомизации консольного вывода.</p>
  <p id="MUAN"></p>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <h2 id="YopW">Навигация:</h2>
    <ol id="7qmt">
      <li id="Wv7C"><strong><a href="https://teletype.in/@paranoikcodit/bMJklbBgQej#HdOA" target="_blank">asciimatics</a></strong></li>
      <li id="4HDn"><strong><a href="https://teletype.in/@paranoikcodit/bMJklbBgQej#gRLm" target="_blank">textual</a></strong></li>
      <li id="PYCC"><strong><a href="https://teletype.in/@paranoikcodit/bMJklbBgQej#JsrG" target="_blank">rich</a></strong></li>
      <li id="NYTD"><strong><a href="https://teletype.in/@paranoikcodit/bMJklbBgQej#vyjg" target="_blank">questionary</a></strong></li>
      <li id="blEm"><strong><a href="https://teletype.in/@paranoikcodit/bMJklbBgQej#CHqu" target="_blank">python-prompt-toolkit</a></strong></li>
    </ol>
  </section>
  <h2 id="HdOA" data-align="center">1. <a href="https://github.com/peterbrittain/asciimatics" target="_blank">asciimatics</a></h2>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <figure id="ZrKE" class="m_custom">
      <img src="https://camo.githubusercontent.com/8e41949c9c884aea639c7a6e1c63cb74fbc72c22d8b53fdc3f735162e306d098/68747470733a2f2f61736369696e656d612e6f72672f612f31383735362e706e67" width="599.1198769918926" />
    </figure>
    <p id="lQOA"><strong><em>Asciimatics</em></strong> — это пакет, помогающий людям создавать полноэкранные <u>текстовые пользовательские интерфейсы</u> (от интерактивных форм до анимации ASCII) на любой платформе. Он распространяется под лицензией Apache Software Foundation License 2.0.</p>
    <p id="ozOY">Является безусловным <strong>фаворитом</strong> в больших проектах, которые используют консоль, как ui. Имеет <em>не очень</em> сложную структуру, <em>приятный</em> отклик, возможность анимировать объекты, а также большой ассортимент <em>винджетов</em> и <em>контейнеров</em>.</p>
  </section>
  <h2 id="gRLm" data-align="center">2. <a href="https://github.com/Textualize/textual" target="_blank">textual</a></h2>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <figure id="RU4S" class="m_original">
      <img src="https://github.com/Textualize/textual/raw/main/imgs/textual.png" width="1280" />
    </figure>
    <p id="1yoD"><strong>Textual</strong> — это полноценный фреймворк TUI (текстовый пользовательский интерфейс) для Python, вдохновленный современной веб-разработкой. </p>
    <p id="yg2w">Все обработчики событий <em>асинхронные</em>, из-за чего все виджеты могут <em>независимо обновляться</em>, а также умеют общаться <u>друг с другом</u> <em>отправкой сообщений</em>.</p>
    <p id="4KYw">Имеется также поддержка <em>кастомизации</em> виджетов средствами <strong>CSS-стилей</strong>. Также использует некоторые техники позаимствованные из <em>вью</em> и других <em>веб-фреймворков</em>. </p>
  </section>
  <h2 id="JsrG" data-align="center"><strong>3. <a href="https://github.com/Textualize/rich" target="_blank">rich</a></strong></h2>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <figure id="og4q" class="m_original">
      <img src="https://img2.teletype.in/files/db/cc/dbcc3122-64b4-4803-bc3c-ad470e8888f0.png" width="750" />
    </figure>
    <p id="bgld"><em>Rich</em> это Python библиотека, позволяющая отображать <em>красивый</em> текст и форматировать терминал.</p>
    <p id="kCHH"><a href="https://rich.readthedocs.io/en/latest/" target="_blank">Rich API</a> упрощает добавление цветов и стилей к выводу терминала. Rich также позволяет отображать <em>красивые таблицы</em>, <em>прогресс бары</em>, <em>markdown</em>, <em>код с отображением синтаксиса</em>, <em>ошибки</em>, и т.д. — прямо после установки.</p>
  </section>
  <h2 id="vyjg" data-align="center">4. <a href="https://github.com/tmbo/questionary" target="_blank">questionary</a></h2>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <figure id="OkIY" class="m_original">
      <img src="https://img4.teletype.in/files/f6/3f/f63fbc7e-e25a-4db9-b9a7-03d5f006bb12.png" width="836" />
    </figure>
    <p id="gbNL">Questionary — это библиотека Python для простого создания красивых интерфейсов командной строки.</p>
    <p id="7m9d">Позволяет создавать <em>красивые промпты</em>, которые украсят дефолтный <em>питоновский инпут</em>.</p>
    <p id="t3ll">Поддерживаемые промпты:</p>
    <ul id="NAJH">
      <li id="hbGn"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#text" target="_blank">Text</a></li>
      <li id="ya6m"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#password" target="_blank">Password</a></li>
      <li id="nNKA"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#file-path" target="_blank">File Path</a></li>
      <li id="4zbP"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#confirmation" target="_blank">Confirmation</a></li>
      <li id="qvbp"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#select" target="_blank">Select</a></li>
      <li id="HR2G"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#raw-select" target="_blank">Raw select</a></li>
      <li id="FgFb"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#checkbox" target="_blank">Checkbox</a></li>
      <li id="jq7u"><a href="https://questionary.readthedocs.io/en/stable/pages/types.html#autocomplete" target="_blank">Autocomplete</a></li>
    </ul>
  </section>
  <h2 id="CHqu" data-align="center">5. <a href="https://github.com/prompt-toolkit/python-prompt-toolkit" target="_blank">python-prompt-toolkit</a></h2>
  <section style="background-color:hsl(hsl(0, 0%, var(--autocolor-background-lightness, 95%)), 85%, 85%);">
    <figure id="3mvO" class="m_original">
      <img src="https://github.com/prompt-toolkit/python-prompt-toolkit/raw/master/docs/images/logo_400px.png" width="400" />
    </figure>
    <p id="W90w">python-prompt-toolkit - библиотека, позволяющая создавать интерактивные программы командной строки прямо на Python.</p>
    <p id="aT9A">Обилие простых виджетов, <em>подсветка синтаксиса во время печати</em>, <em>автокомплиты, поддержка нажатий мыши </em>и множество других возможностей не оставят данную библиотеку в стороне. </p>
  </section>
  <hr />
  <tt-tags id="wTMv">
    <tt-tag name="python">#python</tt-tag>
    <tt-tag name="питон">#питон</tt-tag>
    <tt-tag name="пайтон">#пайтон</tt-tag>
    <tt-tag name="программирование">#программирование</tt-tag>
    <tt-tag name="textual">#textual</tt-tag>
    <tt-tag name="prompt">#prompt</tt-tag>
    <tt-tag name="toolkit">#toolkit</tt-tag>
    <tt-tag name="teminal">#teminal</tt-tag>
    <tt-tag name="tui">#tui</tt-tag>
    <tt-tag name="terminaluserinterface">#terminaluserinterface</tt-tag>
    <tt-tag name="anchor_exampletextuserinterfac">#anchor_exampletextuserinterfac</tt-tag>
  </tt-tags>

]]></content:encoded></item></channel></rss>