<?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[Канал де я кидаю всякі криптовалютні темки, проги, баги і так далі.
Автор: @rostcraft]]></description><image><url>https://img3.teletype.in/files/29/93/29936a8c-7191-4ff6-bc32-14c42cf1f5ca.png</url><title>Криптовалютний підвал програміста</title><link>https://teletype.in/@cryptopidval</link></image><link>https://teletype.in/@cryptopidval?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><atom:link rel="self" type="application/rss+xml" href="https://teletype.in/rss/cryptopidval?offset=0"></atom:link><atom:link rel="next" type="application/rss+xml" href="https://teletype.in/rss/cryptopidval?offset=10"></atom:link><atom:link rel="search" type="application/opensearchdescription+xml" title="Teletype" href="https://teletype.in/opensearch.xml"></atom:link><pubDate>Wed, 22 Apr 2026 08:38:07 GMT</pubDate><lastBuildDate>Wed, 22 Apr 2026 08:38:07 GMT</lastBuildDate><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/blum-drop-game</guid><link>https://teletype.in/@cryptopidval/blum-drop-game?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/blum-drop-game?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Burp Suite ламає ігри або абузимо blum drop game</title><pubDate>Fri, 05 Jul 2024 16:17:57 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/10/89/1089a31b-9ca8-4659-8cd3-3913f681c6e9.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/67/46/67461b87-e46d-42ee-acdc-b869dc2c7303.png"></img>В blum якийсь час вже є drop game і я згадавши сьогодні про цю гру я вирішив її поламати.
(Через певні тести зроблені  мною в процесі написання цієї статті мене можливо забанять з Blum, раджу вам бути обережними якщо вам не байдуже на цей проект)]]></description><content:encoded><![CDATA[
  <p id="8bc7">В blum якийсь час вже є drop game і я згадавши сьогодні про цю гру я вирішив її поламати.<br />(Через певні тести зроблені  мною в процесі написання цієї статті мене можливо забанять з Blum, раджу вам бути обережними якщо вам не байдуже на цей проект)</p>
  <h3 id="cE2r">Як це роблять зазвичай</h3>
  <p id="cFB8">Більшість людей запускають просто клікер і радіють життю, але в нашому підвалі клікери не те щоб вітаються(мені лінь їх кодити). </p>
  <h3 id="Uaor">План</h3>
  <ol id="1BnT">
    <li id="KmQR">Через Burp Suite перехопити запити зроблені Blum.</li>
    <li id="IdQZ">Поміняти в запиті на зміну рахунку кількість поінтів.</li>
    <li id="sjkv">Автоматизувати кидання таких запитів через скрипт.</li>
    <li id="PIMU">Профіт</li>
  </ol>
  <h3 id="P0BP">Виконання плану(або ж основна частина статті)</h3>
  <p id="ykUS">Я вирішив перехоплювати запити з мобільного девайсу.<br />Для цього треба додати сертифікат з Burp Suite на ваш девайс(так само як зазвичай, але можливо доведеться поміняти формат сертифікату для чого існує <a href="https://www.openssh.com/" target="_blank">openssh</a>).<br />Також в самому Burp треба в налаштуваннях проксі додати ваш локальний айпі як доступний для використання(нижче скріншоти з тим як це робити).<br /></p>
  <figure id="EdIB" class="m_original">
    <img src="https://img3.teletype.in/files/67/46/67461b87-e46d-42ee-acdc-b869dc2c7303.png" width="966" />
  </figure>
  <figure id="g8HD" class="m_original">
    <img src="https://img4.teletype.in/files/39/5f/395f5c29-1fe6-49e1-9dea-dcd843e09db4.png" width="1362" />
  </figure>
  <figure id="09E5" class="m_original">
    <img src="https://img3.teletype.in/files/60/c4/60c4c5a3-16df-402f-a254-55456b1f9bd0.png" width="759" />
  </figure>
  <p id="bBdC">Тепер перехопивши запити ми можемо помітити два цікавих для нас запити на адреси <code>game-domain.blum.codes/api/v1/game/play</code> та <code>game-domain.blum.codes/api/v1/game/claim</code>.<br />Структура запитів максимально проста, перший запит має лише хедери авторизації і у відповідь ми отримуємо gameId.<br />В другому надісланні данні складаються з цього самого gameId та поінтів.<br />Таким чином ми можемо просто відправляти ці запити і вказувати потрібну кількість поінтів.</p>
  <p id="NGRi">Ось невеличкий PoC(proof of concept) скрипт на rust який це робить.<br />Цей скрипт схожий на скрипт зі <a href="https://teletype.in/@cryptopidval/tonpokerwheel" target="_blank">статті про tonpoker wheel</a>, частина речей пояснена трохи більш детально там.<br />Для початку dependencies з Cargo.toml(версії бібліотек тут найновіші, не обов&#x27;язково використовувати саме ці)</p>
  <pre id="ShAy" data-lang="toml">[dependencies]
rand = &quot;0.8.5&quot;
reqwest = { version = &quot;0.12.5&quot;, features = [&quot;json&quot;] }
serde = { version = &quot;1.0.203&quot;, features = [&quot;derive&quot;] }
tokio = { version = &quot;1.38.0&quot;, features = [&quot;full&quot;] }</pre>
  <pre id="oqfX" data-lang="rust">use rand::Rng;
use reqwest::header::HeaderMap;
use reqwest::{self, StatusCode};
use serde::{Deserialize, Serialize};
use std::time::Duration;</pre>
  <p id="YvVY">Імпортуємо потрібні функції з бібліотек.</p>
  <pre id="f5Sk" data-lang="rust">#[derive(Serialize, Deserialize, Debug)]
struct GameClaimRequest {
    gameId: String,
    points: i32,
}

#[derive(Serialize, Deserialize, Debug)]
struct GamePlayResponse {
    gameId: String,
}</pre>
  <p id="RQCs">Структури для нашого запиту на клейм та для відповіді на наш запит на початок гри.</p>
  <pre id="VADP" data-lang="rust">#[tokio::main]
async fn main() {
    let repeat_count = 1;
    let min_points = 350;
    let max_points = 450;
    let auth_token = &quot;&quot;;
    let wait_duration = Duration::from_secs(31);</pre>
  <p id="SOtb">Початок основної функції, вказуємо кількість повторів, мінімальну кількість поінтів для рандмому і максимальну, токен авторизації, який можна взяти з burp suite і час очікування(blum наче як вимагає 30 секунд, ставимо 31 секунду для безпеки).<br /></p>
  <figure id="LyUH" class="m_original">
    <img src="https://img2.teletype.in/files/5e/77/5e77a1d7-78e3-4561-888d-c539987039ad.png" width="1065" />
    <figcaption>Червоним закритий токен який і треба скопіювати</figcaption>
  </figure>
  <pre id="M2e0" data-lang="rust">let mut rng = rand::thread_rng();
let client = reqwest::Client::new();
let mut headers = HeaderMap::new();
headers.insert(
    &quot;accept&quot;,
    &quot;application/json, text/plain, */*&quot;.try_into().unwrap(),
);
headers.insert(
    &quot;authorization&quot;,
    format!(&quot;Bearer {}&quot;, auth_token).try_into().unwrap(),
);
headers.insert(&quot;sec-fetch-site&quot;, &quot;same-site&quot;.try_into().unwrap());
headers.insert(&quot;accept-encoding&quot;, &quot;gzip, deflate, br&quot;.try_into().unwrap());
headers.insert(&quot;accept-language&quot;, &quot;en-GB,en;q=0.9&quot;.try_into().unwrap());
headers.insert(&quot;sec-fetch-mode&quot;, &quot;cors&quot;.try_into().unwrap());
headers.insert(&quot;origin&quot;, &quot;https://telegram.blum.codes&quot;.try_into().unwrap());
headers.insert(
    &quot;user-agent&quot;,
    &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)&quot;
        .try_into()
        .unwrap(),
);
headers.insert(&quot;sec-fetch-dest&quot;, &quot;empty&quot;.try_into().unwrap());</pre>
  <p id="V9FV">Ініціалізуємо рандом, reqwest, і ставимо хедери(скопіювані з Burp Suite).<br />Оскільки значення хедерів мають тип HeaderValue то ми можемо використати try_into щоб конвертувати значення в цей тип і unwrap який закінчує виконання помилкою якщо не виникає помилка конвертації.<br />Почитати про try_into можна <a href="https://doc.rust-lang.org/rust-by-example/conversion/try_from_try_into.html" target="_blank">тут</a> або <a href="https://doc.rust-lang.org/std/convert/trait.TryInto.html" target="_blank">тут</a>.<br />Про unwrap <a href="https://doc.rust-lang.org/book/ch09-02-recoverable-errors-with-result.html#shortcuts-for-panic-on-error-unwrap-and-expect" target="_blank">тут</a>.</p>
  <pre id="ZWaH" data-lang="rust">for _ in 0..repeat_count {
    let req = client
        .post(&quot;https://game-domain.blum.codes/api/v1/game/play&quot;)
        .headers(headers.clone())
        .send()
        .await
        .unwrap();</pre>
  <p id="uoab">Починаємо цикл для використання наших тікетів.<br />Запитуємо початок гри у сервера і отримуємо gameId.</p>
  <pre id="fJpv" data-lang="rust">let status = req.status();
match status {
    StatusCode::OK =&gt; {
        let deserialized = req.json::&lt;GamePlayResponse&gt;().await;</pre>
  <p id="ljnw">У випадку якщо статус запиту 200(OK) то десеріалізуємо відповідь на запит в нашу структуру.</p>
  <pre id="mcfc" data-lang="rust">match deserialized {
    Ok(deserialized) =&gt; {
        println!(&quot;Got game id: {}&quot;, deserialized.gameId);
        println!(&quot;Sleeping for {}ms&quot;, wait_duration.as_millis());
        tokio::time::sleep(wait_duration).await;
        println!(&quot;Finished sleeping&quot;);
        let points = rng.gen_range(min_points..max_points);
        let params = GameClaimRequest {
gameId: deserialized.gameId,
            points,
        };
        println!(&quot;Trying to claim {} points&quot;, points);
        let response = client
            .post(&quot;https://game-domain.blum.codes/api/v1/game/claim&quot;)
            .headers(headers.clone())
            .json(&amp;params)
            .send()
            .await
            .unwrap();
        println!(
                &quot;Claim response status {}, {}&quot;,
                response.status(),
                response.text().await.unwrap()
                )
    }
    Err(_) =&gt; {
        println!(&quot;Unable to deserialize&quot;);
        println!(&quot;Sleeping for {}ms&quot;, wait_duration.as_millis());
        tokio::time::sleep(wait_duration).await;
        println!(&quot;Finished sleeping&quot;);
    }</pre>
  <p id="USZk">Якщо десеріалізація успішна то чекаємо wait_duration і випадковим чином вибираємо кількість поінтів.<br />Далі формуємо данні запиту через нашу структуру і відсилаємо наш запит на сервер.<br />У випадку помилки десеріалізації просто чекаємо wait_duration.</p>
  <pre id="zNk7" data-lang="rust">_ =&gt; {
    println!(&quot;{}&quot;, status);
    println!(
            &quot;Sleeping for {}ms hoping it will work&quot;,
            wait_duration.as_millis()
            );
    tokio::time::sleep(wait_duration).await;
    println!(&quot;Finished sleeping&quot;);
}</pre>
  <p id="cfuj">Якщо відповідь на наш початковий запит була не 200(OK) то також чекаємо час очікування.</p>
  <figure id="HGfO" class="m_original">
    <img src="https://img4.teletype.in/files/fe/1d/fe1dc256-f918-438a-aab2-421739992219.png" width="675" />
    <figcaption>Приклад роботи програми</figcaption>
  </figure>
  <p id="jQbX">Весь код:</p>
  <pre id="rZK2" data-lang="rust">use rand::Rng;
use reqwest::header::HeaderMap;
use reqwest::{self, StatusCode};
use serde::{Deserialize, Serialize};
use std::time::Duration;

#[derive(Serialize, Deserialize, Debug)]
struct GameClaimRequest {
    gameId: String,
    points: i32,
}

#[derive(Serialize, Deserialize, Debug)]
struct GamePlayResponse {
    gameId: String,
}

#[tokio::main]
async fn main() {
    let repeat_count = 1;
    let min_points = 350;
    let max_points = 450;
    let auth_token = &quot;&quot;;
    let wait_duration = Duration::from_secs(31);

    let mut rng = rand::thread_rng();
    let client = reqwest::Client::new();
    let mut headers = HeaderMap::new();
    headers.insert(
        &quot;accept&quot;,
        &quot;application/json, text/plain, */*&quot;.try_into().unwrap(),
    );
    headers.insert(
        &quot;authorization&quot;,
        format!(&quot;Bearer {}&quot;, auth_token).try_into().unwrap(),
    );
    headers.insert(&quot;sec-fetch-site&quot;, &quot;same-site&quot;.try_into().unwrap());
    headers.insert(&quot;accept-encoding&quot;, &quot;gzip, deflate, br&quot;.try_into().unwrap());
    headers.insert(&quot;accept-language&quot;, &quot;en-GB,en;q=0.9&quot;.try_into().unwrap());
    headers.insert(&quot;sec-fetch-mode&quot;, &quot;cors&quot;.try_into().unwrap());
    headers.insert(&quot;origin&quot;, &quot;https://telegram.blum.codes&quot;.try_into().unwrap());
    headers.insert(
        &quot;user-agent&quot;,
        &quot;Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko)&quot;
            .try_into()
            .unwrap(),
    );
    headers.insert(&quot;sec-fetch-dest&quot;, &quot;empty&quot;.try_into().unwrap());

    for _ in 0..repeat_count {
        let req = client
            .post(&quot;https://game-domain.blum.codes/api/v1/game/play&quot;)
            .headers(headers.clone())
            .send()
            .await
            .unwrap();
        let status = req.status();
        match status {
            StatusCode::OK =&gt; {
                let deserialized = req.json::&lt;GamePlayResponse&gt;().await;
                match deserialized {
                    Ok(deserialized) =&gt; {
                        println!(&quot;Got game id: {}&quot;, deserialized.gameId);
                        println!(&quot;Sleeping for {}ms&quot;, wait_duration.as_millis());
                        tokio::time::sleep(wait_duration).await;
                        println!(&quot;Finished sleeping&quot;);
                        let points = rng.gen_range(min_points..max_points);
                        let params = GameClaimRequest {
                            gameId: deserialized.gameId,
                            points,
                        };
                        println!(&quot;Trying to claim {} points&quot;, points);
                        let response = client
                            .post(&quot;https://game-domain.blum.codes/api/v1/game/claim&quot;)
                            .headers(headers.clone())
                            .json(&amp;params)
                            .send()
                            .await
                            .unwrap();
                        println!(
                            &quot;Claim response status {}, {}&quot;,
                            response.status(),
                            response.text().await.unwrap()
                        )
                    }
                    Err(_) =&gt; {
                        println!(&quot;Unable to deserialize&quot;);
                        println!(&quot;Sleeping for {}ms&quot;, wait_duration.as_millis());
                        tokio::time::sleep(wait_duration).await;
                        println!(&quot;Finished sleeping&quot;);
                    }
                }
            }
            _ =&gt; {
                println!(&quot;{}&quot;, status);
                println!(
                    &quot;Sleeping for {}ms hoping it will work&quot;,
                    wait_duration.as_millis()
                );
                tokio::time::sleep(wait_duration).await;
                println!(&quot;Finished sleeping&quot;);
            }
        }
    }
}</pre>
  <p id="sZvr">Таким простим чином можна абузити Blum без усіх цих клікерів.<br />Чи можуть цього бота відслідкувати?<br />Мабуть так, але швидше лише тому що ми кидаємо мало запитів по балансу і тд, що можна додати про потребі.</p>
  <p id="PKMy">Ресурси для вивчення Rust:</p>
  <ul id="arwM">
    <li id="Lrxu"><a href="https://doc.rust-lang.org/book/" target="_blank">The Rust Book</a> — основний ресурс для вивчення Rust.</li>
    <li id="diOp"><a href="https://doc.rust-lang.org/rust-by-example/" target="_blank">Rust by Example </a>— Rust показаний на прикладах коду.</li>
    <li id="lSUS"><a href="https://github.com/rust-lang/rustlings/" target="_blank">Rustlings</a> — вправи де вам дають поламаний код і вам треба його поремонтувати.</li>
  </ul>
  <p id="JrSA">Всі бібліотеки використані в статті можна знайти на <a href="http://crates.io" target="_blank">crates.io</a>, а документації до них на <a href="http://docs.rs" target="_blank">docs.rs</a>.</p>
  <p id="N6dM">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/tonpokerwheel</guid><link>https://teletype.in/@cryptopidval/tonpokerwheel?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/tonpokerwheel?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Tonpoker Wheel точно не скам або казочка про детектива</title><pubDate>Sun, 26 May 2024 00:10:23 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/1a/b7/1ab788cd-14bf-4691-9a0c-8a2eeafbcacf.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/ae/d2/aed2e861-9bd1-4d72-aea6-9a88465f283c.png"></img>Якось ввечері я помічаю пост в телеграм каналі Catizen Announcement пост про їхній спільний івент з Ton Poker та Playdeck.
Зайшовши з основного акаунта я отримую лише один спін(мабуть інші були прокручені раніше) і отримую 1 цент.
Прокрутивши ще спін на 1 цент з другого акаунту в мене закрадається підозра, що тут завжди випадає якесь сміття.
Я відкриваю Burp Suite вмикаю intercept(щоб запити не відправлялися на сервер а зупинялися). 
Я не бачу запитів в яких є хоч якийсь рандом і припускаю(як виявиться потім помилково), що рандому або нема або він на стороні клієнта.
Відкривши дебагер я починаю пробувати розібратися що відбувається.
Щоб знайти який шматок відповідає за сам спін я вмикаю event listener breakpoint на click.]]></description><content:encoded><![CDATA[
  <p id="17Kf">Якось ввечері я помічаю пост в телеграм каналі Catizen Announcement пост про їхній спільний івент з Ton Poker та Playdeck.<br />Зайшовши з основного акаунта я отримую лише один спін(мабуть інші були прокручені раніше) і отримую 1 цент.<br />Прокрутивши ще спін на 1 цент з другого акаунту в мене закрадається підозра, що тут завжди випадає якесь сміття.<br />Я відкриваю <a href="https://portswigger.net/burp" target="_blank">Burp Suite</a> вмикаю intercept(щоб запити не відправлялися на сервер а зупинялися). <br />Я не бачу запитів в яких є хоч якийсь рандом і припускаю(як виявиться потім помилково), що рандому або нема або він на стороні клієнта.<br />Відкривши дебагер я починаю пробувати розібратися що відбувається.<br />Щоб знайти який шматок відповідає за сам спін я вмикаю event listener breakpoint на click.<br /><br /></p>
  <figure id="N5Xp" class="m_original">
    <img src="https://img3.teletype.in/files/ae/d2/aed2e861-9bd1-4d72-aea6-9a88465f283c.png" width="356" />
  </figure>
  <p id="SIFf">Таким чином поставивши брейкпоінти в коді я знаходжу функцію яка якраз оброблює спін.</p>
  <figure id="4vxW" class="m_original">
    <img src="https://img1.teletype.in/files/c8/59/c85977cb-ddff-4ff6-b0fb-d3aac574f52d.png" width="540" />
    <figcaption>Обфускований код</figcaption>
  </figure>
  <p id="OdIo">Код був обфускований але я більш менш зрозумів що він робить.<br />Із геніальних мувів при обфускації(вона можливо була навіть зроблена руками):</p>
  <ul id="pDuu">
    <li id="eLBD">Функції в змінних з однолітерними назвами.</li>
    <li id="8t5c">Функції дублюються в декількох змінних, деякі з цих змінних навіть не використовуються.</li>
    <li id="Q5bn">Індекс рандомного значення завжди більший на 0.5, від нього віднімають 0.5 щоб зробити правильним.</li>
    <li id="gznJ">І можливо навіть більше того, на що я не звернув увагу.</li>
  </ul>
  <p id="BSKn">Передивившись історію запитів я помітив, що рандомне значення відсилається зразу після відкриття колеса, після прокрутки просто відправляється запит на підтвердження спіну за його id(sp).</p>
  <figure id="6kKb" class="m_original">
    <img src="https://img1.teletype.in/files/00/ff/00ff246b-6f69-48f1-aca4-e9287ff39cf8.png" width="2187" />
    <figcaption>Початковий запит.</figcaption>
  </figure>
  <figure id="o0Xw" class="m_original">
    <img src="https://img4.teletype.in/files/70/97/70978233-5cab-425e-8709-a8c5af277924.png" width="1098" />
    <figcaption>Запит для підтвердження спіна.</figcaption>
  </figure>
  <p id="qomj">Отож у нас два варіанти, або вони завжди видають невигідний результат, або ми можемо респінити поки не випаде дуже хороший результат і розвести їх на купу бабла(думаю очевидно який більш ймовірний).<br />Але звісно це так собі пруф і це не цікаво.<br />Отож я вирішив написати скрипт який буде відправляти такі запити і дивитися що випадає.<br />Написати я вирішив його на <a href="https://www.rust-lang.org/" target="_blank">Rust</a>.<br />Зараз буде код і пояснення до нього, перейти до результатів можна за цим <a href="#XSK6">лінком</a>.</p>
  <pre id="rr6X" data-lang="rust">use rand::Rng;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{self, StatusCode};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use tokio;</pre>
  <p id="AQeU">Підключаємо ліби:</p>
  <ul id="Ue0w">
    <li id="yZB3"><a href="https://crates.io/crates/rand" target="_blank">rand</a> — рандом, для рандомізації часу між запитами.</li>
    <li id="Ydyn"><a href="https://crates.io/crates/reqwest" target="_blank">reqwest</a> — серце програми, бібліотека для кидання запитів.</li>
    <li id="M0sD"><a href="https://crates.io/crates/serde/" target="_blank">serde</a> — білбіотека для серіалізації та десеріалізації, яку ми юзаємо щоб парсити json.</li>
    <li id="vRaH"><a href="https://crates.io/crates/tokio/" target="_blank">tokio</a> — бібліотека для роботи з асинхронністю(тут асинхронність не дуже грала роль насправді).</li>
    <li id="bBuP">std — стандартна бібліотека rust.</li>
  </ul>
  <pre id="7iHQ" data-lang="rust">#[derive(Serialize, Deserialize, Debug)]
struct PokerSector {
    key: String,
    r#type: String,
    label: String,
    color: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct PokerResponse {
    tickets: i32,
    sectors: Vec&lt;PokerSector&gt;,
    sp: String,
    userShareLink: String,
    randInd: f32,
}</pre>
  <p id="3cAy">Оголошуємо структури які відповідають відповідді на запит.<br />r# це raw identifier бо type вже використовується.</p>
  <pre id="EZ8w" data-lang="rust">#[tokio::main]
async fn main() {</pre>
  <p id="cJai">Оголошуємо головну функцію і даємо tokio знати що вона є головною(по дефолту вона не асинхронна).</p>
  <pre id="tuvz" data-lang="rust">let mut rng = rand::thread_rng();</pre>
  <p id="upPp">Ініціалізуємо рандом.<br />let — це оголошення змінної.<br />mut — означає що змінна може міняти значення(по дефолту всі змінні в расті сталі).</p>
  <pre id="aH7B" data-lang="rust">let wait_millis = 1000;
let mut requests_count = 0;</pre>
  <p id="Qly6">Ставимо початковий час очікування між запитами.<br />Та ставимо рахівник запитів на 0.</p>
  <pre id="CpMo" data-lang="rust">let client = reqwest::Client::new();</pre>
  <p id="TGve">Оголошуємо наш клієнт для запитів.</p>
  <pre id="QT9b" data-lang="rust">let mut params = HashMap::new();
params.insert(&quot;initData&quot;, &quot;&lt;initData&gt;&quot;);</pre>
  <p id="0uoa">Оголошуємо параметри для нашого майбутнього запиту.<br />Замість &lt;initData&gt; має бути initData, який можна взяти з запитів в браузері, але я брав з Burp Suite.</p>
  <pre id="Rhhp" data-lang="rust">let mut headers = HeaderMap::new();
headers.insert(
    &quot;sec-ch-ua&quot;,
    HeaderValue::from_str(r#&quot;&quot;Brave&quot;;v=&quot;123&quot;, &quot;Not:A-Brand&quot;;v=&quot;8&quot;, &quot;Chromium&quot;;v=&quot;123&quot;&quot;#)
        .unwrap(),
);
headers.insert(
    &quot;sec-ch-ua-platform&quot;,
    HeaderValue::from_str(r#&quot;&quot;Linux&quot;&quot;#).unwrap(),
);
headers.insert(&quot;sec-ch-ua-mobile&quot;, HeaderValue::from_str(r#&quot;?0&quot;#).unwrap());
headers.insert(&quot;user-agent&quot;, HeaderValue::from_str(r#&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36&quot;#).unwrap());
headers.insert(
    &quot;accept-language&quot;,
    HeaderValue::from_str(r#&quot;en-US,en;q=0.8&quot;#).unwrap(),
);
headers.insert(
    &quot;origin&quot;,
    HeaderValue::from_str(r#&quot;https://tgwa.tonpoker.online&quot;#).unwrap(),
);
headers.insert(
    &quot;sec-fetch-site&quot;,
    HeaderValue::from_str(r#&quot;same-site&quot;#).unwrap(),
);
headers.insert(&quot;sec-fetch-mode&quot;, HeaderValue::from_str(r#&quot;cors&quot;#).unwrap());
headers.insert(&quot;sec-fetch-dest&quot;, HeaderValue::from_str(r#&quot;empty&quot;#).unwrap());
headers.insert(
    &quot;referer&quot;,
    HeaderValue::from_str(r#&quot;https://tgwa.tonpoker.online/&quot;#).unwrap(),
);</pre>
  <p id="XO2d">Оголошуємо наші заголовки запиту і заповнюємо їх, не знаю чи всі вони треба, бо я їх просто скопіював з Burp Suite з запита який кидався через браузер.</p>
  <pre id="3sYx" data-lang="rust">loop {
    let req = client
        .post(&quot;https://bridge.tonpoker.online/wheel/get&quot;)
        .headers(headers.clone())
        .json(&amp;params)
        .send()
        .await
        .unwrap();
</pre>
  <p id="Hy5W">Починаємо нескінченний цикл запитів і кидаємо запит.<br />.post означає що це POST запит, використовуємо .await щоб викликати future(асинхронна функція). .unwrap() означає дістати значення і у випадку помилки завершити програму(де-факто припустити що помилки нема і спробувати виконатися так).</p>
  <pre id="Rl6I" data-lang="rust">let status = req.status();</pre>
  <p id="leca">Записуємо статус запита в змінну, в цьому випадку я зустрівчав лише два статуси:</p>
  <ul id="2o0f">
    <li id="Ka6A">200 OK</li>
    <li id="yYhc">429 Too Many Requests</li>
  </ul>
  <pre id="rYQn" data-lang="rust">match status {
    StatusCode::OK =&gt; {</pre>
  <p id="FFjD">Конструкція match дає можливість нам розглянути різні випадки статусу(це зручніше чим купа if-else).<br />Для початку розглянемо статсу OK(200).</p>
  <pre id="ztZA" data-lang="rust">let deserialized = req.json::&lt;PokerResponse&gt;().await;</pre>
  <p id="dE38">Парсимо json, тут ми вже не припускаємо що помилки не буде, бо іноді може бути некоректна інфа і при 200(так один раз сталося).</p>
  <pre id="KEyd" data-lang="rust">match deserialized {
    Ok(deserialized) =&gt; {
        let index: usize = (deserialized.randInd - 0.5) as usize;
        requests_count += 1;
        println!(
            &quot;Count: {} Status:{} Index: {} Type: {} Label: {} SP: {}&quot;,
            requests_count,
            status,
            index,
            deserialized.sectors[index].r#type,
            deserialized.sectors[index].label,
            deserialized.sp
        );
    }
    Err(e) =&gt; {
        println!(&quot;Strange error with OK status&quot;);
    }
}</pre>
  <p id="wa94">Знову конструкція match, якщо запит запирсився то отримуємо індекс(віднімаємо 0.5 і переводимо в тип для індексів usize), збільшуємо рахівник запитів на 1 і виводимо в консоль інформацію про запит.<br />У випадку помилки пишемо в консоль про це(це не найкращий спосіб логів, краще розділяти помилки і дефолтну інфу, але це маленький скрипт тож можна й так).</p>
  <pre id="lvVK" data-lang="rust">let wait_time = Duration::from_millis(wait_millis + rng.gen_range(0..200));
println!(&quot;Sleeping for {}&quot;, wait_time.as_millis());
tokio::time::sleep(wait_time).await;
println!(&quot;Finished sleeping&quot;);</pre>
  <p id="Zgh7">Генеруємо число від 0 до 200(0..200 це range схоже на range(0,200) в python) і додаємо його до базового часу очікування.<br />Конкретно тут ми могли і синхронно очікувати, але в асинхронних програмах краще робити це асинхронно, бо в цей час інші частини програми могли б щось робити.</p>
  <pre id="JMvB" data-lang="rust">_ =&gt; {
    println!(&quot;{}&quot;, status);
    let wait_time =
        Duration::from_millis(wait_millis + 1000 + rng.gen_range(1000..3000));
    println!(&quot;Sleeping for {}&quot;, wait_time.as_millis());
    tokio::time::sleep(wait_time).await;
    println!(&quot;Finished sleeping&quot;);
}</pre>
  <p id="kaSX">Ми закінчили розглядати випадок OK(200), тепер для усіх інших випадків ми виводимо статус в консоль і анлогічо очікуємо, але тепер вже довший час.</p>
  <p id="z6dD">Це кінець програми, весь код ось:</p>
  <pre id="aTqF" data-lang="rust">use rand::Rng;
use reqwest::header::{HeaderMap, HeaderValue};
use reqwest::{self, StatusCode};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::time::Duration;
use tokio;

#[derive(Serialize, Deserialize, Debug)]
struct PokerSector {
    key: String,
    r#type: String,
    label: String,
    color: String,
}

#[derive(Serialize, Deserialize, Debug)]
struct PokerResponse {
    tickets: i32,
    sectors: Vec&lt;PokerSector&gt;,
    sp: String,
    userShareLink: String,
    randInd: f32,
}

#[tokio::main]
async fn main() {
    let mut rng = rand::thread_rng();
    let wait_millis = 1000;
    let mut requests_count = 0;
    let client = reqwest::Client::new();
    let mut params = HashMap::new();
    params.insert(&quot;initData&quot;, &quot;&lt;initData&gt;&quot;);
    let mut headers = HeaderMap::new();
    headers.insert(
        &quot;sec-ch-ua&quot;,
        HeaderValue::from_str(r#&quot;&quot;Brave&quot;;v=&quot;123&quot;, &quot;Not:A-Brand&quot;;v=&quot;8&quot;, &quot;Chromium&quot;;v=&quot;123&quot;&quot;#)
            .unwrap(),
    );
    headers.insert(
        &quot;sec-ch-ua-platform&quot;,
        HeaderValue::from_str(r#&quot;&quot;Linux&quot;&quot;#).unwrap(),
    );
    headers.insert(&quot;sec-ch-ua-mobile&quot;, HeaderValue::from_str(r#&quot;?0&quot;#).unwrap());
    headers.insert(&quot;user-agent&quot;, HeaderValue::from_str(r#&quot;Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36&quot;#).unwrap());
    headers.insert(
        &quot;accept-language&quot;,
        HeaderValue::from_str(r#&quot;en-US,en;q=0.8&quot;#).unwrap(),
    );
    headers.insert(
        &quot;origin&quot;,
        HeaderValue::from_str(r#&quot;https://tgwa.tonpoker.online&quot;#).unwrap(),
    );
    headers.insert(
        &quot;sec-fetch-site&quot;,
        HeaderValue::from_str(r#&quot;same-site&quot;#).unwrap(),
    );
    headers.insert(&quot;sec-fetch-mode&quot;, HeaderValue::from_str(r#&quot;cors&quot;#).unwrap());
    headers.insert(&quot;sec-fetch-dest&quot;, HeaderValue::from_str(r#&quot;empty&quot;#).unwrap());
    headers.insert(
        &quot;referer&quot;,
        HeaderValue::from_str(r#&quot;https://tgwa.tonpoker.online/&quot;#).unwrap(),
    );
    loop {
        let req = client
            .post(&quot;https://bridge.tonpoker.online/wheel/get&quot;)
            .headers(headers.clone())
            .json(&amp;params)
            .send()
            .await
            .unwrap();
        let status = req.status();
        match status {
            StatusCode::OK =&gt; {
                let deserialized = req.json::&lt;PokerResponse&gt;().await;
                match deserialized {
                    Ok(deserialized) =&gt; {
                        let index: usize = (deserialized.randInd - 0.5) as usize;
                        requests_count += 1;
                        println!(
                            &quot;Count: {} Status:{} Index: {} Type: {} Label: {} SP: {}&quot;,
                            requests_count,
                            status,
                            index,
                            deserialized.sectors[index].r#type,
                            deserialized.sectors[index].label,
                            deserialized.sp
                        );
                    }
                    Err(e) =&gt; {
                        println!(&quot;Strange error with OK status&quot;);
                    }
                }
                let wait_time = Duration::from_millis(wait_millis + rng.gen_range(0..200));
                println!(&quot;Sleeping for {}&quot;, wait_time.as_millis());
                tokio::time::sleep(wait_time).await;
                println!(&quot;Finished sleeping&quot;);
            }
            _ =&gt; {
                println!(&quot;{}&quot;, status);
                let wait_time =
                    Duration::from_millis(wait_millis + 1000 + rng.gen_range(1000..3000));
                println!(&quot;Sleeping for {}&quot;, wait_time.as_millis());
                tokio::time::sleep(wait_time).await;
                println!(&quot;Finished sleeping&quot;);
            }
        }
    }
}</pre>
  <p id="LLiE">Якщо ви з якоїсь причини хочете це запустити(краще почитайте до кінця щоб зрозуміти чому ви мабуть не хочете цього робити), то не забудьте замінити &lt;initData&gt; на ваше значення initData.</p>
  <p id="XSK6">Отож які результати?<br />Під час тестів я один раз зміг вибити 5 центів і не зберіг sp, із того що я зберіг в файлик, таке:</p>
  <ul id="fjMH">
    <li id="2U5Z">Всього успішних запитів 94.</li>
    <li id="B9oX">На 10 центів 1 раз(я не пробував його активувати бо я отримую постійно Too Many Requests тепер).</li>
    <li id="JRbx">На 1 цент 66 разів.</li>
    <li id="EpOw">Фріспін(перекрутка) 27 разів.</li>
  </ul>
  <figure id="7G9X" class="m_original">
    <img src="https://img1.teletype.in/files/81/82/8182929c-c61b-4eef-a63e-9d4718327b54.png" width="1081" />
    <figcaption>Графік результатів</figcaption>
  </figure>
  <p id="GbtN">Отож чи гарантовано там не можна виграти більше? Ні.<br />Але як можна помітити шанс на це дуже маленький.</p>
  <p id="PKMy">Ресурси для вивчення Rust:</p>
  <ul id="arwM">
    <li id="Lrxu"><a href="https://doc.rust-lang.org/book/" target="_blank">The Rust Book</a> — основний ресурс для вивчення Rust.</li>
    <li id="diOp"><a href="https://doc.rust-lang.org/rust-by-example/" target="_blank">Rust by Example </a>— Rust показаний на прикладах коду.</li>
    <li id="lSUS"><a href="https://github.com/rust-lang/rustlings/" target="_blank">Rustlings</a> — вправи де вам дають поламаний код і вам треба його поремонтувати.</li>
  </ul>
  <p id="JrSA">Всі бібліотеки використані в статті можна знайти на <a href="http://crates.io" target="_blank">crates.io</a>, а документації до них на <a href="http://docs.rs" target="_blank">docs.rs</a>.</p>
  <p id="N6dM">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Vzq2qRF7jY3</guid><link>https://teletype.in/@cryptopidval/Vzq2qRF7jY3?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Vzq2qRF7jY3?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Як спростити собі життя в selenium</title><pubDate>Tue, 25 Oct 2022 19:58:51 GMT</pubDate><description><![CDATA[В цій статті я розкажу як ви можете писати програми на selenium швидше, бо підозрюю що ви теж не дуже раді кожен раз копіювати xpath)))]]></description><content:encoded><![CDATA[
  <p id="TKxQ">В цій статті я розкажу як ви можете писати програми на selenium швидше, бо підозрюю що ви теж не дуже раді кожен раз копіювати xpath)))</p>
  <p id="QT0H">Отже ви можете подумати чому б нам не вкористовувати Selenium IDE, але нажаль він генерує код просто підряд командами і не дочікується завантаження сторінки, отже нам не підходить.</p>
  <p id="Xf89">Ми будемо використовувати ui vision rpa, так він не вміє конвертувати в код на python, але за те він гарно записує дії в json файл і я сам написав конвертор який конвертує його json файли в код на python.<br />Він звісно не підтримує всього функціоналу цього розширення, але ті дії які воно може записати, він підтримує.</p>
  <h2 id="VC9r">Встановлюємо розширення</h2>
  <p id="Tm8Z">Встановіть його <a href="https://chrome.google.com/webstore/detail/uivision-rpa/gcbalfbdmfieckjlnblleoemohcganoc" target="_blank">тут</a> і створіть нове макро.<br />Коли запишите макро скопіюйте його json і запишіть у файл.</p>
  <h2 id="gYXj">Встановлюємо конвертер</h2>
  <p id="bih4">Тож як завантажити конвертер?<br />Все просто він є на нашому гітхабі <a href="https://github.com/cryptopidval/urpsei" target="_blank">тут</a>.<br />Вам просто треба запустити converter.py<br />Тепер розберемо режими.</p>
  <h3 id="TQh2"><strong>Автоматичний режим</strong></h3>
  <p id="Zm4d">Щоб використати автоматичний режим дайте відповідь &quot;y&quot; на питання &quot;Do you want to use default targets(y/n)?&quot;.<br />Тоді програма буде використовувати всюди ціль з поля target.<br />В більшості випадків вам підійте цей режим хоча іноді ціль з поля target не працює.</p>
  <h3 id="QNIc">Напіватоматичний режим</h3>
  <p id="DUfS">Буде викоистовуватись якщо ви відповісте &quot;n&quot; на питання &quot;Do you want to use default targets(y/n)?&quot;.<br />Вам треба буде ввести порядок за яким треба використовувати цілі.<br />Цілі будуть пронумеровані.</p>
  <h3 id="DdAK">Ручний режим</h3>
  <p id="CAFJ">Тут в кожній команді вам треба самим вибрати ціль.</p>
  <p id="fePa">Отже таким чином ви можете полегшити собі програмування на selenium, я постараюсь підготувати статтю про те, як працює цей конвертер тож підпишіться щоб не пропустити.</p>
  <p id="FNuf">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/l1Gc41GwkOM</guid><link>https://teletype.in/@cryptopidval/l1Gc41GwkOM?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/l1Gc41GwkOM?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Як я захистив свої програми від піратства</title><pubDate>Sat, 10 Sep 2022 16:04:16 GMT</pubDate><description><![CDATA[В цьому пості я розповім про те як зробити перевірку ліцензій в своїй програмі і не тільки.]]></description><content:encoded><![CDATA[
  <p id="aIeM">В цьому пості я розповім про те як зробити перевірку ліцензій в своїй програмі і не тільки.</p>
  <h3 id="egtR"><strong>Як перевіряються ліцензії?</strong></h3>
  <p id="E9Cc">Значить наша програма відправляє назву програми, телеграм айді користувача, а також ліцензійний ключ на сервер.</p>
  <p id="YhZy">На сервері ми маємо зберігати якось данні про користувачів ось які я вибрав колонки:active(чи активна ліцензія),id(унікальний айді в базі данних, призначається автоматично),tg_id(телеграм айді користувача),tg_username(телеграм юзернейм користувача),software_name(назва програми до якої цей ліцензій ключ),key(хеш ліцензійного ключа).<br />Завдяки хешу якщо хтось отрмає доступ нашої бази данних він не побачить паролю.</p>
  <p id="Xs6d">Для хешування ми будемо використовувати argon2.<br />Для роботи з ним є бібліотека <a href="https://pypi.org/project/argon2-cffi/" target="_blank">argon2-cffi</a>, ставиться вона так:</p>
  <pre id="XjZH">pip install argon2-cffi</pre>
  <p id="cuAY">Використовувати її наприклад так:</p>
  <pre id="XdSY">from argon2 import PasswordHasher
hasher = PasswordHasher()
password_hash = hasher.hash(&quot;&lt;якийсь пароль&gt;&quot;)
print(password_hash)</pre>
  <p id="42O3">Ось <a href="https://argon2-cffi.readthedocs.io/" target="_blank">документація</a>.</p>
  <h3 id="IyNA">Створення бази данних.</h3>
  <p id="iFhY">Тепер коли ми знаємо як створювати хеші паролів як створити саму базу данних куди ми їх зможемо записати.<br />Базу данних ми напишемо на mysql.<br />Для полегшення життя є прога <a href="https://dbeaver.io/" target="_blank">DBeaver</a> для роботи з базами данних.<br />Ось сам код для створення бази:</p>
  <pre id="suPR">CREATE TABLE &#x60;licenses&#x60; (
  &#x60;active&#x60; tinyint(1),
  &#x60;id&#x60; int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
  &#x60;tg_username&#x60; varchar(100),
  &#x60;tg_id&#x60; varchar(30),
  &#x60;software_name&#x60; varchar(300),
  &#x60;key&#x60; varchar(100)
);
</pre>
  <p id="8rOi">Тепер пояснення що це все таке.</p>
  <pre id="1FCR">CREATE TABLE &#x60;licenses&#x60; (</pre>
  <p id="1XeC">Вказуємо що наша таблиця буде називатись &quot;licenses&quot;.</p>
  <pre id="0C6t"> &#x60;active&#x60; tinyint(1),</pre>
  <p id="bus8">Поле active, може бути лише 1 або 0.<br />Tinyint найменший тип чисел в mysql(наскільки я знаю).</p>
  <pre id="wI1i">  &#x60;id&#x60; int(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,</pre>
  <p id="0gww">Унікальне айді кожного елемента в базі.<br />Int стандартний тип чисел.</p>
  <pre id="Og1H">&#x60;tg_username&#x60; varchar(100),</pre>
  <p id="lqoK">Юзернейм в телеграмі користувача.<br />Varchar це якийсь недовгий текст.</p>
  <pre id="Rtwr">&#x60;tg_id&#x60; varchar(30),</pre>
  <p id="MAMW">Айді користувача в телеграмі.<br />Айді легше зберігати як символи оскільки так воно не впреться в ліміти чисел(можливо не найкраще рішення).</p>
  <pre id="rc6A">&#x60;software_name&#x60; varchar(300),</pre>
  <p id="ke8F">Назва програми, важливе поле щоб не можна було використовувати один ключ для всіх програм.</p>
  <pre id="Afxa">&#x60;key&#x60; varchar(100)</pre>
  <p id="Lzl3">Ключ, найважливіше поле, конкретніше це хеш пароля(ключа).</p>
  <pre id="Thj3">);</pre>
  <p id="sbd2">Кінець команди CREATE(а не сумний смайлик).</p>
  <h3 id="YAbJ">Створення сервера для перевірки ліцензій.</h3>
  <p id="E14f">Тепер коли у нас є база данних, саме час створити сервер для перевірки ліцензій.<br />Писати я буду на php бо хостинг який я використовую підтримує лише його.</p>
  <pre id="qK70">&lt;?php</pre>
  <p id="LAsm">Відкриття тегу php.</p>
  <pre id="myEq">session_start();
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$link = new mysqli(&#x27;host&#x27;, &#x27;username&#x27;, &#x27;password&#x27;, &#x27;database&#x27;);</pre>
  <p id="9P82">Запускаємо сесію mysqli(драйвер в php для роботи з mysql).<br />Створюємо підключення до бази, тут треба замінити host, username, password, database на ваші данні від бази данних.<br />Зберігаєм підключення до змінни link.</p>
  <pre id="ralo">$link-&gt;set_charset(&#x27;utf8mb4&#x27;);</pre>
  <p id="FATj">Ставимо utf8(юнікод).</p>
  <pre id="awqu">$key = $_POST[&quot;key&quot;];
$soft = $_POST[&quot;soft&quot;];
$tg_id = $_POST[&quot;tg_id&quot;];</pre>
  <p id="GHtT">Данні від софта нам кидають через POST запити тому дістаємо ці змінні.</p>
  <pre id="vk7Z">$stmt = $link-&gt;prepare(&quot;SELECT &#x60;key&#x60;,active FROM licenses WHERE &#x60;tg_id&#x60;=? and &#x60;software_name&#x60;=? LIMIT 1&quot;);</pre>
  <p id="H8KT">Пишемо код для нашого запиту в базу.<br />Якщо простою мовою тут написано таке щось таке: Вибери один ключ та одну активність_ліцензії з бази данних &quot;ліцензії&quot; де тг_айді=? та назва_софту=?.<br />Що ж таке ті знаки питання?<br />Їх ми замінимо на значення телеграм айді та софту але оскільки ми не хочемо щоб нам загнали якусь не хорошу команду(звичайно таблицю не знесуть бо серверу я дав доступ лише до команди SELECT, але можливо зможуть користуватись софтом без ліцензії) то ми будемо вставляти ключ та тг айді через спеціальну команду.</p>
  <pre id="pQYs">$stmt-&gt;bind_param(&#x27;ss&#x27;, $tg_id, $soft);</pre>
  <p id="uliY">Перший параметр пише які данні ми вставляємо замість знаків питання, в нашому випадку String,String або &quot;ss&quot;, наступними параметрами передаємо якраз значення.</p>
  <pre id="8Jkl">$stmt-&gt;execute();
$result = $stmt-&gt;get_result();
$value = $result-&gt;fetch_object();
$isactive=0;</pre>
  <p id="cex2">Виконуємо запит до бази і зберігаємо результат в змінну.<br />Також створюємо змінну в яку запишемо чи активна ліцензія.</p>
  <pre id="kFzb">if (password_verify($key, $value-&gt;key)){
	$isactive=$value-&gt;active;
	echo $isactive;
}else{
	echo 0;
}</pre>
  <p id="3VNx">Перевіряємо чи правильний ключ і записуємо в змінну чи спрацювала ліцензія.</p>
  <pre id="fSox">$log  = &quot;Tg id: &quot;.$tg_id.&#x27; - &#x27;.date(&quot;F j, Y, g:i a&quot;).PHP_EOL.
	&quot;IP: &quot;.$_SERVER[&#x27;REMOTE_ADDR&#x27;].PHP_EOL.
    &quot;Success: &quot;.$isactive.PHP_EOL.
    &quot;Soft: &quot;.$soft.PHP_EOL.
    &quot;-------------------------&quot;.PHP_EOL;
	$filename=&#x27;./log_&#x27;.date(&quot;j.n.Y&quot;).&#x27;.log&#x27;;
	file_put_contents($filename, $log, FILE_APPEND);
	chmod($filename, 0240);</pre>
  <p id="C4yi">В змінну лог зберігаєм тг айді, айпі, софт, точний час і чи успішний вхід.<br />Далі це зберігаємо в файл(його назва буде &lt;сьогоднішня дата&gt;.log) і ставимо на нього так дозволи щоб через ftp ви могли читати але не можна за посиланням було завантажити лог звисайним людям.</p>
  <pre id="123s">&lt;?php
	session_start();
	mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
	$link = new mysqli(&#x27;host&#x27;, &#x27;username&#x27;, &#x27;password&#x27;, &#x27;database&#x27;);
	$link-&gt;set_charset(&#x27;utf8mb4&#x27;); 
	$key = $_POST[&quot;key&quot;];
	$soft = $_POST[&quot;soft&quot;];
	$tg_id = $_POST[&quot;tg_id&quot;];
	$stmt = $link-&gt;prepare(&quot;SELECT &#x60;key&#x60;,active FROM licenses WHERE &#x60;tg_id&#x60;=? and &#x60;software_name&#x60;=? LIMIT 1&quot;);
	$stmt-&gt;bind_param(&#x27;ss&#x27;, $tg_id, $soft);
	$stmt-&gt;execute();
	$result = $stmt-&gt;get_result();
	$value = $result-&gt;fetch_object();
	$isactive=0;
	if (password_verify($key, $value-&gt;key)){
		$isactive=$value-&gt;active;
		echo $isactive;
	}else{
		echo 0;
	}
	$log  = &quot;Tg id: &quot;.$tg_id.&#x27; - &#x27;.date(&quot;F j, Y, g:i a&quot;).PHP_EOL.
	&quot;IP: &quot;.$_SERVER[&#x27;REMOTE_ADDR&#x27;].PHP_EOL.
    &quot;Success: &quot;.$isactive.PHP_EOL.
    &quot;Soft: &quot;.$soft.PHP_EOL.
    &quot;-------------------------&quot;.PHP_EOL;
	$filename=&#x27;./log_&#x27;.date(&quot;j.n.Y&quot;).&#x27;.log&#x27;;
	file_put_contents($filename, $log, FILE_APPEND);
	chmod($filename, 0240);
		
?&gt;
</pre>
  <p id="KDW7">Фінальний код.<br />Якщо всі перевірки пройдені то сервер верне 1.<br />Отримати логи ви можете за ftp.</p>
  <h3 id="ZPSf">Вшивання перевірки ліцензії в програму.</h3>
  <p id="ps6V">У нас є сервер але на нього мають кидати запити.<br />Цим і займеться наша програма.<br />Зараз покажу на прикладі hello world-у.</p>
  <pre id="2VMW">import requests</pre>
  <p id="2Q3k">Імпортуємо бібліотеку requests через яку і будемо кидати запити.</p>
  <pre id="qeNk">try:
    with open(&quot;license.txt&quot;) as l:
        tg_id,key=l.read().strip().split(&quot;\n&quot;)</pre>
  <p id="YQCg">Пробуємо відкрити файл і зберегти айді(з першого рядка) а також ключ(з другого рядка) в змінні tg_id та key.</p>
  <pre id="TGZ5">    a=requests.post(&quot;https://licenses.rostcraft.pp.ua/licenses.php&quot;,data={&quot;tg_id&quot;:tg_id,&quot;key&quot;:key,&quot;soft&quot;:&quot;test_bot&quot;})
except:
    exit()</pre>
  <p id="yE71">Кидаємо запит на наш сервер(замініть адресу яку вказав я на власну).<br />На сервер кидаємо тг айді, ключ і назву софта.<br />Будь-яка помилка призводить до виходу з програми.<br />Наскільки я знаю важливо мати https бо це шифрування і tls сертифікат який доводить що це справжній сайт а не його підміна.</p>
  <pre id="sGQQ">if a.status_code!=200:
    exit()
if a.text!=&quot;1&quot;:
    exit()</pre>
  <p id="4mna">Якщо код статусу не 200(200 це успіх), або повернений результат не 1 то ми виходимо з програми.</p>
  <pre id="N9Vk">print(&quot;Hello world&quot;)</pre>
  <p id="uFgL">Якщо перевірка пройдена то виходимо з програми.</p>
  <pre id="7SfQ">import requests
try:
    with open(&quot;license.txt&quot;) as l:
        tg_id,key=l.read().strip().split(&quot;\n&quot;)
    a=requests.post(&quot;https://licenses.rostcraft.pp.ua/licenses.php&quot;,data={&quot;tg_id&quot;:tg_id,&quot;key&quot;:key,&quot;soft&quot;:&quot;test_bot&quot;})
except:
    exit()
if a.status_code!=200:
    exit()
if a.text!=&quot;1&quot;:
    exit()

print(&quot;Hello world&quot;)</pre>
  <p id="Wnj7">Весь код.</p>
  <h3 id="g60p">Обфускація коду.</h3>
  <p id="tuj9">Зараз користувачу нічого не заважає просто видалити перевірку тож ми це виправимо.<br />Для цього будемо використовувати <a href="https://pypi.org/project/pyarmor/" target="_blank">pyarmor</a>.<br />Наскільки я знаю він досить безпечний.<br />Увага наскільки я знаю без використання super mode вашу програму можна вернути в вихідний код.<br />Нам треба super mode, який на момент написання статті не вийшов для python 3.10 тому я буду використовувати python 3.9, але в можете глянути в <a href="https://pyarmor.readthedocs.io/" target="_blank">документації </a>чи є super mode для вашої версії python.<br />Тепер зайдіть в папку з вашою прогою і напишіть:</p>
  <pre id="V491">pyarmor obfuscate --advanced=2 .</pre>
  <p id="mwq5">Це обфускує всі файли .py в папці і кидає їх в папку dist.<br />--advanced=2 означає super mode.<br />Також в папці dist буде файл pytransform, в нього основна програма передає обфускований код.<br />Також ви можете вказати іншу платформу оскільки ті ж файли .pyd лише для вінди.<br />Ось в документації про те як це зробити: <a href="https://pyarmor.readthedocs.io/en/latest/advanced.html#distributing-obfuscated-scripts-to-other-platform" target="_blank">https://pyarmor.readthedocs.io/en/latest/advanced.html#distributing-obfuscated-scripts-to-other-platform</a>.<br /><br />UPD: цей захист досить легко обійти як я побачив у майбутньому використовуючи підміну запитів, навіть якщо закрити цю дірку, то все одно захист можна буде обійти якщо постаратися.</p>
  <p id="ajWD">На цьому все, якщо в захисті будуть знайдені дірки я буду оновлювати статтю.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Y-Ak-robiti-veb-interfejsi-z-python-06-27</guid><link>https://teletype.in/@cryptopidval/Y-Ak-robiti-veb-interfejsi-z-python-06-27?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Y-Ak-robiti-veb-interfejsi-z-python-06-27?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Як робити веб інтерфейси з python</title><pubDate>Mon, 18 Jul 2022 19:25:37 GMT</pubDate><description><![CDATA[Ви можете знати про такі штуки як electron.
По факту це інтерфейс для програми на html, css, js.
Для пайтона є така бібліотека як eel, завдяки неї ми сьогодні напишемо простеньку програму яка показує час при натисненні на кнопку.
Поставити її можна командою:]]></description><content:encoded><![CDATA[
  <p id="nY96">Ви можете знати про такі штуки як electron.<br />По факту це інтерфейс для програми на html, css, js.<br />Для пайтона є така бібліотека як eel, завдяки неї ми сьогодні напишемо простеньку програму яка показує час при натисненні на кнопку.<br />Поставити її можна командою:</p>
  <pre id="Y3vi">pip install eel
</pre>
  <p id="iqyU">Тепер створимо в папці нашого проекта папку web, туди будемо кидати наші файли html, css, js та якісь інші файлі(наприклад картинки).<br />Сьогодні нам вистачть одного файлу html, але в більш великих проектах бажано розділяти html, css та js по різним файлам.<br />Тепер глянемо код в нашому main.py</p>
  <pre id="fIWG">import eel
from datetime import datetime
</pre>
  <p id="lCzS">Імпортуємо eel для інтерфейсу та datetime для часу.</p>
  <pre id="jluv">@eel.expose()
def what_time():
    return datetime.now().strftime(&quot;%d/%m/%Y %H:%M:%S&quot;)
</pre>
  <p id="8GRZ">Розберемо по рядкам.<br />@eel.expose це декоратор(що це таке можете глянути тут <a href="https://docs.python.org/3/glossary.html" target="_blank">https://docs.python.org/3/glossary.html</a> тут взагалі про багато штук в пайтоні і в тому числі про декоратори), цей декоратор дає можливість нам викликати цю функцію з javascript на нашій сторінці.<br />На наступному рядку ми оголушеємо функцію.<br />На наступному рядку ми одразу повертаємо дату в форматі який ми вказали в функції strftime.</p>
  <pre id="9K3c">eel.init(&quot;web&quot;)
eel.start(&quot;main.html&quot;, mode=&quot;chrome&quot;, cmdline_args=[&quot;--kiosk&quot;])
</pre>
  <p id="gMfz">Ініціалізуємо eel в папці web.<br />На настпному рядку ми відкриваємо файл main.html в режимі chrome і в аргументах вказуємо --kiosk він означає що програма буде в повноекранному режимі.</p>
  <p id="wa6C">Тепер розглянемо файл main html.</p>
  <p id="cq9j">&lt;!DOCTYPE html&gt;</p>
  <p id="W0ra">&lt;html lang=&quot;en&quot;&gt;</p>
  <p id="Jq22">На першому рядку вказуєм що це найновіше версія html.</p>
  <p id="Ow4F">а наступному відкриваєм тег html</p>
  <pre id="chP4">&lt;meta charset=&quot;UTF-8&quot;&gt;
&lt;title&gt;Crypto Pidval&lt;/title&gt;
&lt;script type=&quot;text/javascript&quot; src=&quot;eel.js&quot;&gt;&lt;/script&gt;
</pre>
  <p id="6Y3h">Вказуєм кодування utf-8 назву вікна Crypto Pidval та додаєм скрипт eel через який ми і будемо запускати функції з пайтона.</p>
  <pre id="60Qf">&lt;style&gt;
    body{
        background: linear-gradient(to right, #40E0D0, #FF8C00, #FF0080);
    }
</pre>
  <p id="hx9f">Відкриваєм тег style.<br />Для тега body(основний тег) ставимо фон градієнта(його я взяв з <a href="https://uigradients.com/" target="_blank">https://uigradients.com/</a> прикольний сайт з градієнтами).</p>
  <pre id="K7QN">button{
    background: linear-gradient(to right, #00F260, #0575E6);
    border: none;
    color: white;
    width: 190px;
    height: 108px;
    font-size: 2em;
    margin-left: 21em;
}
button:hover{
    background: linear-gradient(to right, #6EE856, #006BD2);
}
</pre>
  <p id="QS9Q">Для кнопок ставимо фон градієнтом, прибираємо контури(вони мені не подобаються), ставимо колір тексту білим, ставимо ширину та висоту, а далі ставимо розмір тексту 2em(це одиниця виміру яку часто використовують для тексту), і відступаємо 21em щоб кнопка була по цетру(так я знаю що можна відцентрувати по нормальному, але тут це не обов&#x27;язково).<br />Далі ми ставимо щоб мінявся фон при наведені на кнопку.</p>
  <pre id="N5TU">#time{
    color: #40E0D0;
    text-align: center;
}
</pre>
  <p id="iuic">Ставимо колір нашому тексту з айді time і центруємо його.</p>
  <pre id="8lh5">    &lt;/style&gt;
&lt;/head&gt;
</pre>
  <p id="xuIY">Закриваємо теги style та head.</p>
  <pre id="rkCm">&lt;body&gt;
        &lt;button onclick=&quot;what_time();&quot;&gt;What is the time?&lt;/button&gt;
        &lt;h1 id=&quot;time&quot;&gt;Result&lt;/h1&gt;
</pre>
  <p id="JUUv">Відкриваємо тег body.<br />Також створюєм кнопку яка при нажатті вкикликає функцію what_time.<br />Також текст з айді time.</p>
  <pre id="WytF">&lt;script&gt;
    async function what_time(){
        result = await eel.what_time()();
        document.getElementById(&#x27;time&#x27;).innerHTML = result;
    }
&lt;/script&gt;
</pre>
  <p id="a7EG">В цьому скрипті ми створюємо асинхронну функцію what_time(яка якраз і вкикликається при натисненні на кнопку).<br />Далі вкиликаємо функцію what_time вже із пайтона і зберігаємо результат її виконання в змінну.<br />Далі міняємо текст нашому тегу h1 на час.</p>
  <pre id="1tL5">&lt;/body&gt;
&lt;/html&gt;
</pre>
  <p id="g8r9">Закриваємо теги.</p>
  <p id="BMiv">Ось весь файл main.py:</p>
  <pre id="SRb1">import eel
from datetime import datetime

@eel.expose()
def what_time():
    return datetime.now().strftime(&quot;%d/%m/%Y %H:%M:%S&quot;)
eel.init(&quot;web&quot;)
eel.start(&quot;main.html&quot;, mode=&quot;chrome&quot;, cmdline_args=[&quot;--kiosk&quot;])
</pre>
  <p id="jTzr">І файл main.html:</p>
  <pre id="1EeI">&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;
    &lt;title&gt;Crypto Pidval&lt;/title&gt;
    &lt;script type=&quot;text/javascript&quot; src=&quot;eel.js&quot;&gt;&lt;/script&gt;
    &lt;style&gt;
        body{
            background: linear-gradient(to right, #40E0D0, #FF8C00, #FF0080);
        }
        button{
            background: linear-gradient(to right, #00F260, #0575E6);
            border: none;
            color: white;
            width: 190px;
            height: 108px;
            font-size: 2em;
            margin-left: 21em;
        }
        button:hover{
            background: linear-gradient(to right, #6EE856, #006BD2);
        }
        #time{
            color: #40E0D0;
            text-align: center;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
        &lt;button onclick=&quot;what_time();&quot;&gt;What is the time?&lt;/button&gt;
        &lt;h1 id=&quot;time&quot;&gt;Result&lt;/h1&gt;
    &lt;script&gt;
        async function what_time(){
            result = await eel.what_time()();
            document.getElementById(&#x27;time&#x27;).innerHTML = result;
        }
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
  <p id="9WMR">На цьому все, Ось тут є <a href="https://pypi.org/project/Eel/" target="_blank">документація </a>eel.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Y-Ak-zberigati-ta-zavantazhuvati-cookies-u-selenium-06-03</guid><link>https://teletype.in/@cryptopidval/Y-Ak-zberigati-ta-zavantazhuvati-cookies-u-selenium-06-03?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Y-Ak-zberigati-ta-zavantazhuvati-cookies-u-selenium-06-03?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Як зберігати та завантажувати cookies у selenium</title><pubDate>Mon, 18 Jul 2022 19:25:20 GMT</pubDate><description><![CDATA[Сьогодні ми напишемо невеличку програмку яка дозволить нам авторизуватись на сайт лише раз(ну якщо ви не забудете нажати ентер або не дочекаєтесь збереження як я під час тестів), а потім програма сама буде підвантажувати cookies.
Починаємо.]]></description><content:encoded><![CDATA[
  <p id="sm9C">Сьогодні ми напишемо невеличку програмку яка дозволить нам авторизуватись на сайт лише раз(ну якщо ви не забудете нажати ентер або не дочекаєтесь збереження як я під час тестів), а потім програма сама буде підвантажувати cookies.<br />Починаємо.</p>
  <pre id="0DqY">from selenium import webdriver
import pickle
</pre>
  <p id="rEr5">Імпортим ліби.<br />Selenium для керування браузером та pickle для зберігання/завантаження cookies.</p>
  <pre id="P7iP">driver = webdriver.Chrome()
driver.maximize_window()
driver.get(&quot;https://stackoverflow.com&quot;)
</pre>
  <p id="hRkK">Створюємо об&#x27;єкт driver для керування браузером.<br />Заходимо в повноекранний режим(просто не повноекранний режим мене бісить).<br />Відкриваєм сайт без якого програмування майже неможливе(stackoverflow).</p>
  <pre id="KMbS">try:
    with open(&quot;cookies.pkl&quot;, &quot;rb&quot;) as f:
        cookies = pickle.load(f)
    for cookie in cookies:
        driver.add_cookie(cookie)
</pre>
  <p id="VOtB">Починаєм блок try.<br />Тут ми пробуєм відкрити файл і прочитати cookies в змінну.<br />Далі завантажуєм їх в браузер.<br />Якщо що структура with просто за нас в кінці закриває файл що корисно(і так не закриті файли це погано).</p>
  <pre id="LHti">except:
    input()
    with open(&quot;cookies.pkl&quot;, &quot;wb&quot;) as f:
        pickle.dump(driver.get_cookies(), f)
</pre>
  <p id="XN4E">Якщо помилка чекаєм нажаття ентер і після цього записуєм cookies в файл.</p>
  <pre id="Is37">else:
    driver.refresh()
</pre>
  <p id="MLWu">Якщо помилки не було то перезавантажуєм сторінку щоб сайт побачив що ми авторизовані.</p>
  <pre id="7jIa">print(&quot;Done&quot;)
</pre>
  <p id="5F94">Пишем готово.<br />Це просто для мене щоб я не закрив прогу раніше збереження cookies.<br />В повноцінній програмі вам воно не треба.</p>
  <p id="5XX2">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Y-Ak-pisati-klikeri-na-pajtoni-05-27</guid><link>https://teletype.in/@cryptopidval/Y-Ak-pisati-klikeri-na-pajtoni-05-27?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Y-Ak-pisati-klikeri-na-pajtoni-05-27?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Як писати клікери на пайтоні</title><pubDate>Mon, 18 Jul 2022 19:25:05 GMT</pubDate><description><![CDATA[Сьогодні ми напишемо клікера на пайтоні з pyautogui.
В більшості випадків краще використовувати щось інше але клікери теж прикольні.]]></description><content:encoded><![CDATA[
  <p id="d7Km">Сьогодні ми напишемо клікера на пайтоні з pyautogui.<br />В більшості випадків краще використовувати щось інше але клікери теж прикольні.</p>
  <p id="P2FT">Для початку встановим pyautogui:</p>
  <pre id="Ov07">pip install pyautogui
</pre>
  <p id="L01Q">Наш клікер для тесту буде клацати значок гугла.</p>
  <pre id="xqeZ">import pyautogui
from time import sleep
</pre>
  <p id="6zIx">Імпортимо pyautogui та функцію чекання з модулю time.</p>
  <pre id="jc0b">sleep(3)
def climg(name):
    try:
        x,y=pyg.locateCenterOnScreen(f&quot;{name}.png&quot;)
    except:return
    pyg.click(x,y)
    sleep(0.3)
</pre>
  <p id="ZvLo">Для початку чекаємо 3 секунди щоб встигнути перейти в інше вікно.<br />Далі створюємо функцію, яка бере картинки png по імені яке передається в аргументі і клікає по цій картинці.<br />Для початку зберігаєм координати в змінні x,y отримані функцією locateCenterOnScreen в яку ми вказуєм нашу картинку(картинки треба щоб були в тій же папці або довведеться вказувати повний шлях).<br />Далі клікаєм і чекаєм трохи щоб все завантажилось.<br />Не забудьте кинути скріншот значка гугла в папку зі скриптом з назвою google.png</p>
  <pre id="dkka">climg(&quot;google&quot;)
</pre>
  <p id="hagH">Клікаєм по значку гугла.</p>
  <pre id="W0C3">import pyautogui as pyg
from time import sleep
sleep(3)
def climg(name):
    try:
        x,y=pyg.locateCenterOnScreen(f&quot;{name}.png&quot;)
    except:return
    pyg.click(x,y)
    sleep(0.3)
climg(&quot;google&quot;)
</pre>
  <p id="3cQK">Таким чином можна було(принаймні раніше) автоматизувати sunflower land.<br />Ось <a href="https://pyautogui.readthedocs.io/en/latest/" target="_blank">документація</a> pyautogui.<br />Як на мене клікери не найкращі для крутих проектів, але це просто, швидко і досить цікаво.</p>
  <p id="FjOF">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Znajomstvo-z-tampermonkey-05-23</guid><link>https://teletype.in/@cryptopidval/Znajomstvo-z-tampermonkey-05-23?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Znajomstvo-z-tampermonkey-05-23?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Знайомство з tampermonkey</title><pubDate>Mon, 18 Jul 2022 19:24:55 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/3b/1c/3b1c6331-4648-4126-a3dd-88e1adf7140c.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/2f/60/2f601e35-e48c-40b6-9492-86d077af1903.png"></img>Сьогодні ми будемо вчити tampermonkey.
Скачати для Chromium-ів його можна тут.
Тепер розбираєм скрипт.]]></description><content:encoded><![CDATA[
  <p id="W1Hh">Сьогодні ми будемо вчити tampermonkey.<br />Скачати для Chromium-ів його можна <a href="https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo" target="_blank">тут</a>.<br />Тепер розбираєм скрипт.</p>
  <pre id="1bT1">// ==UserScript==
// @name         Crypto Pidval test
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  Test script
// @author       https://t.me/cryptopidval
// @match        https://www.google.com/*
// @icon         https://www.google.com/s2/favicons?sz=64&amp;domain=google.com
// @grant        none
// ==/UserScript==
</pre>
  <p id="JJvE">@name це назва скрипта.<br />@description це опис.<br />@version це версія скрипта.<br />@author це автор скрипта.<br />@match це як має виглядати посилання * це будь-які посилання.<br />@icon це іконка.</p>
  <pre id="4cNf">function simulateMouseClick(targetNode) {
    function triggerMouseEvent(targetNode, eventType) {
        var clickEvent = document.createEvent(&#x27;MouseEvents&#x27;);
        clickEvent.initEvent(eventType, true, true);
        targetNode.dispatchEvent(clickEvent);
    }
    [&quot;mouseover&quot;, &quot;mousedown&quot;, &quot;mouseup&quot;, &quot;click&quot;].forEach(function(eventType) {
        triggerMouseEvent(targetNode, eventType);
    });
};
</pre>
  <p id="ZnQE">Функція що симулює кліки на javascript.<br />Вона бере просто наш об&#x27;єкт і трігерить івенти(як зміг пояснив, дякую добрим людям зі stackoverflow за код).</p>
  <pre id="O7eq">function getElementsByXPath(xpath, parent)
{
    let results = [];
    let query = document.evaluate(xpath, parent || document,
        null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (let i = 0, length = query.snapshotLength; i &lt; length; ++i) {
        results.push(query.snapshotItem(i));
    }
    return results;
};
</pre>
  <p id="1JW0">Штука яка шукає елементи за xpath(його ви можете знати з selenium).<br />По факту шукає за xpath лише document.evaluate а інше конвертує його в нормальний list.</p>
  <pre id="YPFB">function main(){
    getElementsByXPath(&quot;//h3[@class=&#x27;LC20lb MBeuO DKV0Md&#x27;]&quot;).forEach(element=&gt; {
        alert(element.textContent);
    });
};
</pre>
  <figure id="522g">
    <img src="https://img3.teletype.in/files/2f/60/2f601e35-e48c-40b6-9492-86d077af1903.png" />
    <figcaption>Клас результатів.</figcaption>
  </figure>
  <figure id="6LP1">
    <img src="https://img4.teletype.in/files/72/22/7222f3a4-f4e8-419b-9b11-c690a6b0e90d.png" />
    <figcaption>Повідомлення з текстом про результат.</figcaption>
  </figure>
  <p id="0XiH">Основна функція.<br />Шукає всі результати далі через foreach проходить по всім елементам списку і показує його текст.</p>
  <pre id="BNQc">main();
</pre>
  <p id="KJv5">Запускаєм основну функцію.</p>
  <p id="Tutq">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Otrimuyemo-cinu-kripti-z-coinmarketcap-05-21</guid><link>https://teletype.in/@cryptopidval/Otrimuyemo-cinu-kripti-z-coinmarketcap-05-21?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Otrimuyemo-cinu-kripti-z-coinmarketcap-05-21?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Отримуємо ціну крипти з coinmarketcap.</title><pubDate>Mon, 18 Jul 2022 19:24:43 GMT</pubDate><media:content medium="image" url="https://img2.teletype.in/files/d1/b2/d1b2d509-397c-4ba4-8395-b667f2b1bab2.png"></media:content><description><![CDATA[<img src="https://img3.teletype.in/files/64/aa/64aa7b03-b2fb-47e6-9796-7824888de89a.png"></img>Сьогодні я покажу два способи отримання інфи з coinmarketcap:
-Нормальний, через офіційне api.
-Колхозний через парсер, інформація не така точна(з api ви отримуєте 12 цифр після крапки в ціні біткоіна в доларах), але зате нема обмежень по використанню.]]></description><content:encoded><![CDATA[
  <p id="nm4e">Сьогодні я покажу два способи отримання інфи з coinmarketcap:<br />-Нормальний, через офіційне api.<br />-Колхозний через парсер, інформація не така точна(з api ви отримуєте 12 цифр після крапки в ціні біткоіна в доларах), але зате нема обмежень по використанню.</p>
  <h4 id="Через-api.">Через api.</h4>
  <p id="iFWH">Для початку треба отримати ключ api. Тому ідемо на цей <a href="https://pro.coinmarketcap.com/signup/" target="_blank">сайт </a>якщо ще не реєструвались на порталі для розробників, або на цей <a href="https://pro.coinmarketcap.com/login" target="_blank">сайт</a> якщо вже режструвались.</p>
  <figure id="q5C3">
    <img src="https://img3.teletype.in/files/64/aa/64aa7b03-b2fb-47e6-9796-7824888de89a.png" />
    <figcaption>Ключ api брати тут</figcaption>
  </figure>
  <p id="5rYq">Далі копіюєте свій ключ api.<br />В файлі coinfig.py пишите таке:</p>
  <pre id="oDH0">api_key = &#x27;ВАШ КЛЮЧ API&#x27;
</pre>
  <p id="nKpC">Замість ВАШ КЛЮЧ API напишіт ваш ключ api)))<br />Тепер main.py:</p>
  <pre id="3JJJ">from requests import Request, Session
import config
</pre>
  <p id="eVlJ">Імпортим requests для запитів до api та config де зберігається ключ api.</p>
  <pre id="Eynt">url = &#x27;https://pro-api.coinmarketcap.com/v1/cryptocurrency/quotes/latest&#x27;
</pre>
  <p id="vMR1">Записуєм url.<br />Там різні url для різного тому в кінці я залишу посилання на офіційну документацію.</p>
  <pre id="fOeW">parameters ={
&#x27;slug&#x27;: &#x27;bitcoin&#x27;,
&#x27;convert&#x27;: &#x27;USD&#x27;
}
</pre>
  <p id="k7hf">Ставимо &#x27;slug&#x27; в значення &#x27;bitcoin&#x27; це валюта ціна якої нам треба.<br />&#x27;convert&#x27; ставимо в значення &#x27;USD&#x27;, як ви могли здогадатись це в якій валюті нам треба ціна.</p>
  <pre id="iHDG">headers ={
    &#x27;Accepts&#x27;: &#x27;application/json&#x27;,
    &#x27;X-CMC_PRO_API_KEY&#x27;: config.api_key
}
</pre>
  <p id="wlGn">&#x27;Accepts&#x27; це по логіці в якому форматі нам буде вертати результати, але я не впевнений.<br />&#x27;X-CMC_PRO_API_KEY&#x27; це наш ключ api.</p>
  <pre id="PPVp">session = Session()
session.headers.update(headers)
</pre>
  <p id="Z84F">Створюєм сесію requests і додаєм наші заголовки.</p>
  <pre id="JxRN">response = session.get(url, params=parameters)
print(response.json()[&#x27;data&#x27;][&#x27;1&#x27;][&#x27;quote&#x27;][&#x27;USD&#x27;][&#x27;price&#x27;])
</pre>
  <p id="rLMV">Кидаєм запит до api і виводимо результати.<br />Результат такий пиблизно буде: 29320.595168474585, але сподіваюсь у вас біткоін по 100к)))</p>
  <p id="Njkr">Загалом найкращий спосіб але в безкоштовній версії 333 запита в день, але я думаю можна створити 5 акаунтів і раз в хвилину запити кидати зможете.<br />Як і обіцяв офіційна <a href="https://coinmarketcap.com/api/documentation/v1/" target="_blank">документація</a>.</p>
  <h4 id="Без-api(колхоз).">Без api(колхоз).</h4>
  <p id="U2eF">Це дійсно колхозний спосіб для порівняння результати api 29320.595168474585 та результати без api 29,320.60 (кому видаляти можна) тобто з api 12 цифр після крапки а тут 2.<br />Але зате тут скільки хочете в день(та й треба хоч десь парсери показати).</p>
  <p id="RUZ9">Для початку треба поставити BeautifulSoup.<br />pip install beautifulsoup4 це треба ввести в командний рядок/термінал.<br />Тепер код:</p>
  <pre id="3rml">import requests
from bs4 import BeautifulSoup
</pre>
  <p id="sw3N">Імпортимо requests та BeautifulSoup.</p>
  <pre id="XEvQ">page = requests.get(&quot;https://coinmarketcap.com/currencies/bitcoin/&quot;)
</pre>
  <p id="rc8y">Отримуємо сторінку про bitcoin.</p>
  <pre id="Mipv">soup=BeautifulSoup(page.content, &#x27;html.parser&#x27;)
</pre>
  <p id="E41n">Налаштовуєм парсер щоб він міг шукати по нашій сторінці данні.</p>
  <pre id="czmO">crypto_name=list(soup.find(class_=&quot;sc-1q9q90x-0 jCInrl h1&quot;).children)[0]
</pre>
  <figure id="IOza">
    <img src="https://img4.teletype.in/files/b6/a8/b6a8c107-51ab-421b-84af-4e168486584f.png" />
    <figcaption>Надпис Bitcoin</figcaption>
  </figure>
  <p id="VjIx">Знаходимо для початку елемент(завдяки пошуку за класом) який є батьківським для надпису Bitcoin(і так я знаю що в посиланні нашому і так написано біткоін, але по перше ми могли б писати в посиланні btc а по друге парсинг треба показати.<br />Потім конвертуємо список дочірніх елементів в нормальний список і беремо нульовий елемент(це і є надпис &quot;Bitcoin&quot;).</p>
  <pre id="B80y">crypto_price=soup.find(class_=&quot;priceValue&quot;).text
</pre>
  <figure id="GY3g">
    <img src="https://img2.teletype.in/files/95/f1/95f100aa-05d3-4310-b0e2-1fb416aa2ec0.png" />
    <figcaption>Надпис ціни</figcaption>
  </figure>
  <p id="F5r7">Беремо елемент ціни за класом і отримуємо з цього елемента текст.</p>
  <pre id="fE9L">print(crypto_name,crypto_price)
</pre>
  <p id="dht5">Виводимо назву крипти та ціну.</p>
  <p id="InfZ">Як бачите без api код навіть менше, але це не відміняє його колхозність.</p>
  <p id="b0XZ">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

]]></content:encoded></item><item><guid isPermaLink="true">https://teletype.in/@cryptopidval/Bo7jykbN7ET</guid><link>https://teletype.in/@cryptopidval/Bo7jykbN7ET?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval</link><comments>https://teletype.in/@cryptopidval/Bo7jykbN7ET?utm_source=teletype&amp;utm_medium=feed_rss&amp;utm_campaign=cryptopidval#comments</comments><dc:creator>cryptopidval</dc:creator><title>Як підключити Phantom Wallet через Selenium</title><pubDate>Mon, 18 Jul 2022 19:24:27 GMT</pubDate><media:content medium="image" url="https://img4.teletype.in/files/7a/72/7a72336f-5a04-445b-bb75-38b56846546c.png"></media:content><description><![CDATA[<img src="https://img2.teletype.in/files/de/e4/dee465d2-9d2a-435d-86af-a1d8bab0b175.png"></img>Для початку імортнемо ліби: selenium, time та config.]]></description><content:encoded><![CDATA[
  <pre id="wlN3">from selenium import webdriver
from time import sleep
import config
</pre>
  <p id="2Lmd">Для початку імортнемо ліби: selenium, time та config.</p>
  <p id="XO7q">Остання ліба це файл який вам треба створити &quot;config.py&quot;, в ньому буде секретна фраза від гаманця та пароль який на нього поставиться.</p>
  <p id="ppmA">З time ми імпортуємо sleep це штука яка просто нічого не робить(спить) певну кількість секунд.</p>
  <p id="QmLD"><strong>Як додати розширення? </strong></p>
  <p id="yt5B">В фаєрфоксі трохи все інакше тому ось <a href="https://www.lambdatest.com/blog/adding-firefox-extensions-with-selenium-in-python/" target="_blank">стаття</a>.</p>
  <p id="Fazt">Для хрома ось так.<br />Для початку треба отримати файл розширення на цьому <a href="https://crxextractor.com/" target="_blank">сайті </a>вставляєте посилання на розширення в chrome webstore.<br />Оце <a href="https://chrome.google.com/webstore/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa" target="_blank">Phantom</a> який ми будемо використовувати.<br />Файл crx який ми скачали кидаємо в папку з прогою.<br />Ось код:</p>
  <pre id="rjpc">EXTENSION_PATH = &#x27;extension_22_4_26_0.crx&#x27;
opt = webdriver.ChromeOptions()
opt.add_extension(EXTENSION_PATH)
driver = webdriver.Chrome(chrome_options=opt)
</pre>
  <p id="Pwnz">Цей код бере наше розширення(ім&#x27;я файлу ставите своє) і підключає його.</p>
  <pre id="nZ7V">driver.get(&quot;chrome-extension://bfnaelmomeimhlpmgjnjophhpkkoljpa/onboarding.html&quot;)
</pre>
  <figure id="lKEW" class="m_original">
    <img src="https://img2.teletype.in/files/de/e4/dee465d2-9d2a-435d-86af-a1d8bab0b175.png" width="1920" />
    <figcaption>Головна сторінка Phantom.</figcaption>
  </figure>
  <p id="yOh2">Останній шматок коду який для chrome та firefox різний. <br />Він відкриває головну сторінку Phantom. <br />Оці букви ви берете з посилання розширення <a href="https://chrome.google.com/webstore/detail/phantom/bfnaelmomeimhlpmgjnjophhpkkoljpa" target="_blank">https://chrome.google.com/webstore/detail/phantom/<strong>bfnaelmomeimhlpmgjnjophhpkkoljpa</strong></a></p>
  <pre id="Pj7r">driver.switch_to.window(driver.window_handles[-1])
sleep(3)
</pre>
  <p id="nXP2">Переключаємось на вкладку яку ми відкрили, а також чекаємо 3 секунди щоб все підвантажилось.</p>
  <pre id="ENNw">driver.find_elements_by_tag_name(&quot;button&quot;)[1].click()
sleep(3)
</pre>
  <p id="s0sJ">Клацаємо на другу кнопку яка відповідає за імпорт гаманця. І знову чекаємо)))</p>
  <pre id="QvlX">for i,j in enumerate(config.secret_phrase.split()):
</pre>
  <p id="FgCV">for цикл тут в i індекси, а в j слова фрази.<br /><strong>Наступні рядки будуть в for циклі!</strong></p>
  <pre id="QPH7">driver.execute_script(&quot;arguments[0].setAttribute(&#x27;type&#x27;,arguments[1])&quot;,driver.find_element_by_id(f&quot;word_{i}&quot;), &quot;password&quot;)
</pre>
  <p id="JcaK">Тут використовується execute_script він ставить атрибут type для інпут password.<br />Завдяки find ми беремо елемент куди ми вставимо слова фрази.<br />Отже тепер коли ми вставимо туди фразу її не буде видно що добре.</p>
  <pre id="rRue">driver.find_element_by_id(f&quot;word_{i}&quot;).send_keys(j)
</pre>
  <figure id="G8fq" class="m_original">
    <img src="https://img4.teletype.in/files/f9/4c/f94c5873-fc61-4a94-93a2-aecf657fb4a9.png" width="1920" />
    <figcaption>Поля для вводу сід фрази.</figcaption>
  </figure>
  <p id="9ZYl">Вставляємо слово фрази.<br />UPD станом на 01.03.2023 тут треба трохи змінити останні два рядки на:</p>
  <pre id="6ZIF">driver.execute_script(&quot;arguments[0].setAttribute(&#x27;type&#x27;,arguments[1])&quot;,driver.find_element(By.XPATH , f&quot;/html/body/div/main/div[2]/form/div/div[2]/div[{i+1}]/input&quot;), &quot;password&quot;)
driver.find_element(By.XPATH , f&quot;/html/body/div/main/div[2]/form/div/div[2]/div[{i+1}]/input&quot;).send_keys(j)</pre>
  <p id="GLGt"><strong>Тепер рядки вже не в for циклі!</strong></p>
  <pre id="wzyV">sleep(3)
driver.find_elements_by_tag_name(&quot;button&quot;)[0].click()
</pre>
  <figure id="rq5y" class="m_original">
    <img src="https://img2.teletype.in/files/1e/1f/1e1f2a23-8304-482c-8f7d-362fe0e679ef.png" width="1920" />
    <figcaption>Кнопка Import Wallet.</figcaption>
  </figure>
  <p id="T0sj">Чекаємо 3 секунди і клацаємо на кнопку import wallet.</p>
  <pre id="WqdH">while True:
  if &quot;Import Selected Accounts&quot; in driver.page_source:break
  else:sleep(1)
</pre>
  <p id="7W0p">Якщо з&#x27;явилось Import Selected Accounts а отже сторінка підвантажилась виходимо із циклу.<br />Якщо ні, то чекаєм секунду.</p>
  <pre id="ZjUF">sleep(3)
driver.find_elements_by_tag_name(&quot;button&quot;)[0].click()
</pre>
  <figure id="wnv2" class="m_original">
    <img src="https://img4.teletype.in/files/bf/c0/bfc0a818-2f49-4bd1-adc4-d4b9ed7f6253.png" width="1920" />
    <figcaption>Кнопка Import Selected Accounts.</figcaption>
  </figure>
  <p id="FikP">Клацаємо на кнопку Import Selected Accounts.</p>
  <pre id="xXD6">while True:
    try:
        driver.find_element_by_name(&quot;password&quot;)
    except:sleep(1)
    else:break
</pre>
  <p id="Nbce">В цьому циклі ми шукаємо елемент з атрибутом name=&quot;password&quot; якщо він є то виходимо з циклу, якщо ні, то чекаєму 1 сеукунду.</p>
  <pre id="dM8K">driver.find_element_by_name(&quot;password&quot;).send_keys(config.wallet_password)
driver.find_element_by_name(&quot;confirmPassword&quot;).send_keys(config.wallet_password)
</pre>
  <figure id="VEm8" class="m_original">
    <img src="https://img1.teletype.in/files/85/05/8505ed78-634c-4c13-adbe-c8827e8d548e.png" width="1920" />
    <figcaption>Поля ля паролів.</figcaption>
  </figure>
  <p id="AEGZ">Тут ми пишемо та підтверджуємо пароль.</p>
  <pre id="VcQ8">driver.find_elements_by_tag_name(&quot;input&quot;)[2].click()
sleep(1)
</pre>
  <figure id="cwln" class="m_original">
    <img src="https://img1.teletype.in/files/c7/f4/c7f41f76-8413-4819-80ef-6c850fe04226.png" width="1920" />
    <figcaption>Кнопка підтвердження Terms of Service.</figcaption>
  </figure>
  <p id="I55Y">Клацаємо на підтвердження Terms of Service.<br />І чекаємо 1 секунду.</p>
  <pre id="mfJl">driver.find_elements_by_tag_name(&quot;button&quot;)[0].click()
sleep(1)
</pre>
  <figure id="HJeF" class="m_original">
    <img src="https://img4.teletype.in/files/f5/1b/f51bacff-4040-449c-ab9d-a3ad0cb15b9f.png" width="1920" />
    <figcaption>Кнопка Continue.</figcaption>
  </figure>
  <p id="Sx05">Клацаємо на кнопу &quot;Continue&quot;.</p>
  <pre id="m0Bz">driver.find_elements_by_tag_name(&quot;button&quot;)[-1].click()
sleep(1)
driver.find_elements_by_tag_name(&quot;button&quot;)[-1].click()
</pre>
  <figure id="aDD2" class="m_original">
    <img src="https://img3.teletype.in/files/ab/19/ab199b8b-db85-4ada-a553-71c761978986.png" width="1920" />
    <figcaption>Кнопка Continue.</figcaption>
  </figure>
  <figure id="79IE" class="m_original">
    <img src="https://img3.teletype.in/files/ee/de/eedeeecd-7707-4b5e-854a-69a72253e4a5.png" width="1920" />
    <figcaption>Кнопка Finish.</figcaption>
  </figure>
  <p id="s18B">Клацаєм &quot;Continue&quot; знову.<br />Чекаєм 1 секунду.<br />І клацаємо &quot;Finish&quot;.<br />Фінальний main.py:</p>
  <pre id="1RuS">from selenium import webdriver
from time import sleep
import config
EXTENSION_PATH = &#x27;extension_22_4_26_0.crx&#x27;
opt = webdriver.ChromeOptions()
opt.add_extension(EXTENSION_PATH)
driver = webdriver.Chrome(chrome_options=opt)
driver.get(&quot;chrome-extension://bfnaelmomeimhlpmgjnjophhpkkoljpa/onboarding.html&quot;)
driver.switch_to.window(driver.window_handles[-1])
sleep(3)
driver.find_elements_by_tag_name(&quot;button&quot;)[1].click()
sleep(3)
for i,j in enumerate(config.secret_phrase.split()):
    driver.execute_script(&quot;arguments[0].setAttribute(&#x27;type&#x27;,arguments[1])&quot;,driver.find_element_by_id(f&quot;word_{i}&quot;), &quot;password&quot;)
    driver.find_element_by_id(f&quot;word_{i}&quot;).send_keys(j)
sleep(3)
driver.find_elements_by_tag_name(&quot;button&quot;)[0].click()
while True:
    if &quot;Import Selected Accounts&quot; in driver.page_source:break
    else:sleep(1)
sleep(3)
driver.find_elements_by_tag_name(&quot;button&quot;)[0].click()
while True:
    try:
        driver.find_element_by_name(&quot;password&quot;)
    except:sleep(1)
    else:break
driver.find_element_by_name(&quot;password&quot;).send_keys(config.wallet_password)
driver.find_element_by_name(&quot;confirmPassword&quot;).send_keys(config.wallet_password)
driver.find_elements_by_tag_name(&quot;input&quot;)[2].click()
sleep(1)
driver.find_elements_by_tag_name(&quot;button&quot;)[0].click()
sleep(1)
driver.find_elements_by_tag_name(&quot;button&quot;)[-1].click()
sleep(1)
driver.find_elements_by_tag_name(&quot;button&quot;)[-1].click()
</pre>
  <p id="kT1p">config.py:</p>
  <pre id="kqLT">secret_phrase=&quot;secret phrase&quot;
wallet_password=&quot;wallet password&quot;
</pre>
  <p id="WUtH">Сюди треба вставити фразу та пароль.</p>
  <p id="Yuu0">На цьому все.<br />Всім удачі!<br />Підготовлено каналом: <a href="https://t.me/cryptopidval" target="_blank">https://t.me/cryptopidval</a></p>

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