z3nIO - миграция на .net10 без боли. + Скромный тулсет для комфортной разработки ;)

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
Всем привет господа автоматизаторы :bt: ,
и дамы коненечно :ax:, если таковые читают эту статью




140727


Сидел я как-то значит и починял свой примус шаблон с твиттером который внезапно перестал логиниться через креденшиалы. И так попробовал и по другому, и с одним уровнем эмуляции и с другим и браузеры поменял и даже через js навайбкодил дополнительной эмуляции — не логинится и всё. В Zennoposter не логинится, а в ZennoBrowser логинится — никаких проблем. Ладно, думаю, вот и момент, когда переход стал необходим. Прокрастинацию на отдых — шаблоны на адаптацию. Внедрил работу с ZB себе на ферму и... получил от твиттера то же самое шило в то же самое место. Выпытывание причин у Grok, Claude, Gemini и пачки других LLM помогло детерминировать момент когда API возвращает мне бибу, но так и не дало понимания о причинах. Разница была лишь в механизме запуска — профиль ZB запущенный через PM или ZennoPoster не логинится (даже если всё делать ручками), профиль из UI ZB — логинится. Вот только запуская ZB из UI не автоматизируешь ничего — не руками же мне профили греть.

Очевидно нужно было найти способ запуска и автоматизации в обход. Так я узнал, что ZB по API выдаёт строку для подключения к CDP — класс, вот только для того чтобы управлять браузером таким образом нужна какая-то другая библиотека, а у этой библиотеки свои методы, свои зависимости и свои принципы работы. И что более существенно — свой подход к написанию.

140728


Написанное в ZennoPoster остаётся в ZennoPoster?
Я попробовал наваять что-то в Playwright и мне это показалось много трудозатратнее чем в PM — селекторы подбирать неудобно, писать всё сразу тоже, юзер экспириенс по написанию хуже чем в ZP. Вследствие чего переделывать то что уже существует и переучиваться кажется подобным стрельбе по собственным коленям.

Значит надо сделать хотя бы так, чтобы оно работало так как мне удобно и привычно — так я добавил несколько обёрток для методов Playwright и понял что выглядят они ровно так же как те, что и у меня в Zenno, и сигнатуры у них такие же, и логика в общем-то та же. Тут само собой напрашивается — почему не скопировать код из своих шаблонов и не запустить его в среде с этими методами? Я попробовал и сходу у меня конечно ничего не вышло. Пользовался-то я, при написании, куда большим количеством стандартных методов чем просто клики.

Очевидное решение — продолжать. Продолжать реализовывать сущности библиотек Zenno пока скрипт не заведётся. С точки зрения человека задача не очень рентабельная — но нам-то с такой точки зрения смотреть вовсе не с руки. Тут на помощь приходит Клод.

Чтобы дать ему меньше пространства для воображения и побольше данных — через рефлексию я спарсил сигнатуры всех методов и перегрузок + подкинул XML-доки из папки ZennoPoster. Меня порой забавляет как он проводит оценку — получает задачу, думает и пишет "реализация такого займёт примерно неделю"... :bl: после чего получает подтверждение и пишет решение минут 3-5 :dq:

В итоге ZP-скрипт копируется в .csx файл — и запускается напрямую, с теми же переменными project, instance и браузерным API что он ожидает в ZP. Прослойка реализует тот же интерфейс. Скрипт работает не подозревая что он уже не в ZennoPoster.

Пример импорта кошелька с PW
C#:
// RabbyWallet.csx
// executor:     csx-internal

string HeClick((string tag, string attribute, string value, string typeOfSearch, int index) he, int maxRetries = 30)
{
    while (instance.ActiveTab.FindElementByAttribute(he.tag, he.attribute, he.value, he.typeOfSearch, he.index).IsVoid && maxRetries > 0)
    {
        maxRetries--;
        Thread.Sleep(1000);
    }

    if (instance.ActiveTab.FindElementByAttribute(he.tag, he.attribute, he.value, he.typeOfSearch, he.index).IsVoid)
        throw new Exception($"no btn by parameters ({he.tag}, {he.attribute}, {he.value}, {he.typeOfSearch}, {he.index})");

    instance.ActiveTab.FindElementByAttribute(he.tag, he.attribute, he.value, he.typeOfSearch, he.index).RiseEvent("click", "Full");
    return $"[CLICK SUCCESS] ({he.tag}, {he.attribute}, {he.value}, {he.typeOfSearch}, {he.index})";
}

void ImportRabbyWallet(string filePath)
{
    //instance.InstallCrxExtension(filePath); // no-op — расширение уже в профиле для ZB

    var tab = instance.NewTab("1");
    Thread.Sleep(5000);
    instance.ActiveTab.Navigate("chrome-extension://acmacodkjbdgmoleebolmdjonilkdbch/index.html#/new-user/guide", "");
    HeClick(("button", "innertext", "I\\ already\\ have\\ an\\ address", "regexp", 0));


    HeClick(("img", "src", "aa199324eab773e560e1e3cc56a2a841.svg", "regexp", 0));

    var inputs = instance.ActiveTab.FindElementsByAttribute("input:password", "fulltagname", "input:password", "regexp");

    var seed = "few produce rose excuse three wage harvest stadium balance useful acoustic uncover";//project.Variables["seed"].Value;
    var seedParts = seed.Split(' ');
    for (int i = 0; i < 12; i++)
        inputs[i].SetValue(seedParts[i], "Full", false);
    Thread.Sleep(2000);
    HeClick(("button", "innertext", "Confirm", "regexp", 0));
    Thread.Sleep(2000);

    instance.UseFullMouseEmulation = true;
    instance.ActiveTab.FindElementById("password").SetValue("test_password", "Full", false);
    instance.ActiveTab.FindElementById("confirmPassword").SetValue("test_password", "Full", false);
    instance.UseFullMouseEmulation = false;

    HeClick(("button", "innertext", "Confirm", "regexp", 0));
    HeClick(("button", "innertext", "Get\\ Started", "regexp", 0));
    HeClick(("button", "innertext", "Done", "regexp", 0));
}

try{
ImportRabbyWallet(@"W:\work_hard\zenoposter\CURRENT_JOBS\.crx\Rabby-Wallet-Chrome-Web-Store.crx");
}
catch (Exception ex)
     {
         log.Warn($"[FULL ERROR] {ex}");
         throw;
     }
 
Последнее редактирование:

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
140731

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

Хотелки мои приняли примерно такой вид (не сразу конечно)
140730



Осознав как это все интересно на .csx я конечно не ограничился - зачем запирать себя в C#, если есть нативные решения под нишевые задачи - что-то лучше делается в питоне, а для чего то другого вообше достаточно команды в терминал (все равно писать их будет LLM - а мне чтобы хорошо им командовать неплохо было бы разобратсья в архитектуре) - поэтому я добавил екзекуторы для py, js, ts, bash, powershell и прочих .
Стало гораздо удобнее чем кормить репо с js проектом LLM и ребилдить его под C# + отдельная папочка с папочками под скрипты, оркестрируемые из одного места с stdout и расписанием в одном месте - красота рабочий способ откупиться от внутреннего перфекциониста (хотя бы на время).
 
  • Спасибо
Реакции: Dmitriy Ka

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
140732


Всё это было сделано за одну сессию — ~12 часов + пара литров кофе + недельные лимиты Claude за 20$. Классно, весело, задорно — но по факту я получил мьют от LLM на неделю с прилагающимся хрестоматийным "нужно больше золота".

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

Тут началось самое интересное — нет ничего лучше для обучения промптингу, чем аккаунт на бесплатных лимитах.
Фактически в условиях задачи для тебя всегда жёсткие рамки трёх-пяти запросов на 5 часов — никаких формулировок допускающих множество трактовок, никаких недосказанностей и неопределённостей, никаких шансов на уточнение. Промпты должны быть как катана самурая, а если нет — то есть следующие 5 часов на заточку ... :dz:

В процессе агент любезно написал мне расширение чтобы я мог следить за его лимитами которая напоминает полоску с healthpoints - немного геймификации в рабочий процесс :cc:)
140733


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

Вложения

Последнее редактирование:
  • Спасибо
Реакции: Dmitriy Ka и smartmail

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
140735


Где первое окно там и второе — подумал я. Хорошо бы управлять всем из одного места как в ZennoPoster, только ZennoPoster у меня не один и не на одной машине. Можно конечно удалённые рабочие столы использовать — но мне такое не показалось удобным. А Zenno может и сам собой управлять например из БД — прочитал команду, выполнил.

Но через БД некрасиво, а душа требует прекрасного. Например места в дашборде, откуда я смогу рулить задачами на всех своих машинах и лицензиях с полным переносом управления + сводной визуальной и AI аналитикой по всем проектам на ферме, и каждому из них отдельно
140737


И даже управлением настройками каждого из проектов с возможностью экспорта их же в json для основного оркестратора
140736




140738


Бывают такие ситуации, что UI инструментов не всегда вывозит :bk: ...
... зато работает API :bo:.
140739



Берём API и просим Клауда написать нам UI — и через некоторое время получаем консистентный интерфейс к ZennoBrowser , позволяющий нам работать как ни в чем ни бывало
140740

Instances — все запущенные браузеры с uptime, можно остановить через API или Process.Kill напрямую если API не отвечает.
Profiles — профили с поиском, фильтром по папкам и сортировкой по последнему запуску, запуск одной кнопкой.
Proxies — список с URI и статусом проверки.
Threads — в штатном интерфейсе я не нашел кнопки я очистки threads: Сам ZB чистит их корректно далеко не всегда , я обнаруживал у себя потоки висящие сутками - при этом ZB стоял (майнинг :do: ?) - нет просто неккорректная обработка упавшой задачи). Эти записи копятся и занимают слоты. В API к счастью есть метод для очистки - нам осталось лишь прикрутить нужные кнопки к интерфейсу.
 
Последнее редактирование:
  • Спасибо
Реакции: Dmitriy Ka

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
140742




Чтобы за всем следить из одного места — логи тоже нужны в одном месте. Для этого я использовал кастомный логгер зеркаля логи на локалхост.
logger:
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Linq;
using System.Runtime.CompilerServices;
using ZennoLab.InterfacesLibrary.Enums.Log;
using ZennoLab.InterfacesLibrary.ProjectModel;
using System.Text;
using System.Net.Http;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Newtonsoft.Json;

using ZennoLab.CommandCenter;

namespace z3n7
{
    public enum LogLevel
    {
        Debug   = 0,
        Info    = 1,
        Warning = 2,
        Error   = 3,
        Off     = 99
    }

    public class Logger
    {

        [ThreadStatic]
        private static Logger _threadLogger;

        public static Logger Current => _threadLogger;

        public static Logger Init(IZennoPosterProjectModel project, Instance instance = null)
        {
            _threadLogger = Get(project, instance);
            return _threadLogger;
        }

        public static void log(string msg,  [CallerMemberName] string caller = "")
            => _threadLogger?.Info(msg, caller);
        public static void warn(string msg, [CallerMemberName] string caller = "")
            => _threadLogger?.Warn(msg, caller);
        public static void err(string msg,  [CallerMemberName] string caller = "")
            => _threadLogger?.Error(msg, caller);

        private static readonly ConcurrentDictionary<string, Logger> _loggerCache
            = new ConcurrentDictionary<string, Logger>();

        public static Logger Get(IZennoPosterProjectModel project, Instance instance = null)
        {
            string key = project.Var("acc0");

            // Если инстанс передан — обновляем (мог смениться)
            if (instance != null && _loggerCache.TryGetValue(key, out var existing))
            {
                // пересоздаём только если инстанс другой
                if (existing._instance != instance)
                    return _loggerCache[key] = new Logger(project, instance:instance);
                return existing;
            }

            return _loggerCache.GetOrAdd(key, _ => new Logger(project, instance:instance));
        }

        public static void ClearCache(IZennoPosterProjectModel project)
        {
            _loggerCache.TryRemove(project.Var("acc0"), out _);
            _threadLogger = null; // добавить
        }

        public Logger WithInstance(Instance instance)
        {
            string key = _project?.Var("acc0") ?? "";
            var updated = new Logger(_project, instance,
                logLevel: _minLevel, logHost: _logHost, http: _http);
            if (!string.IsNullOrEmpty(key))
                Logger._loggerCache[key] = updated;
            return updated;
        }

        // ── Config ────────────────────────────────────────────────────────────
        private readonly IZennoPosterProjectModel _project;
        private Instance _instance;
        private readonly bool     _persistent;
        private readonly Stopwatch _stopwatch;
        private readonly string   _logHost;
        private readonly int      _timezone;
        private readonly bool     _http;
        private readonly LogLevel _minLevel;

        private string Port { set; get; } = "";
        private string Pid { set; get; } = "";
        public string Emoji { set; get; }


        // cfgLog flags
        private readonly bool _fAcc, _fPort, _fTime, _fCaller, _fWrap, _fForce;

        private static readonly HttpClient _httpClient = new HttpClient { Timeout = TimeSpan.FromSeconds(5) };

        // ── Constructors ──────────────────────────────────────────────────────

        /// <summary>Full constructor — with ZennoPoster project context.</summary>
        public Logger(
            IZennoPosterProjectModel project,
            Instance instance = null,
            string     classEmoji     = null,
            bool       persistent     = true,
            LogLevel   logLevel       = LogLevel.Info,
            string     logHost        = null,
            bool       http           = true,
            int        timezoneOffset = -5)
        {
            _project    = project;
            _instance    = instance;
            Emoji      = classEmoji;
            _persistent = persistent;
            _stopwatch  = persistent ? Stopwatch.StartNew() : null;
            _http       = http;
            _timezone   = timezoneOffset;
            _minLevel   = logLevel;
            _logHost = !string.IsNullOrEmpty(logHost)                        ? logHost
                     : !string.IsNullOrEmpty(_project?.GVar("logHost"))      ? _project.GVar("logHost")
                     : "http://localhost:10993/log";

            bool debugVar = _project?.Var("debug") == "True";
            if (debugVar) _minLevel = LogLevel.Debug;
            _minLevel = (LogLevel)Math.Min((int)_minLevel, (int)LogLevel.Info);

            string cfg = _project?.Var("cfgLog") ?? "";
            _fAcc    = cfg.Contains("acc");
            _fPort   = cfg.Contains("port");
            _fTime   = cfg.Contains("time");
            _fCaller = cfg.Contains("caller");
            _fWrap   = cfg.Contains("wrap");
            _fForce  = cfg.Contains("force");

            if (_instance != null)
            {
                var m = Regex.Match(_instance.FormTitle ?? "", @"Port:(\d+); Pid:(\d+)");
                Port = m.Groups[1].Value;
                Pid  = m.Groups[2].Value;
            }
        }

        /// <summary>Standalone constructor — no ZennoPoster project.</summary>
        public Logger(
            string     classEmoji     = null,
            bool       persistent     = true,
            LogLevel   logLevel       = LogLevel.Info,
            string     logHost        = null,
            bool       http           = true,
            int        timezoneOffset = -5)
        {
            _project    = null;
            Emoji      = classEmoji;
            _persistent = persistent;
            _stopwatch  = persistent ? Stopwatch.StartNew() : null;
            _http       = http;
            _timezone   = timezoneOffset;
            _minLevel   =  logLevel;

            _logHost = logHost ?? "http://localhost:10993/log";

            _fCaller = true;
            _fWrap   = true;
            // rest are false
        }

        // ── Public API ────────────────────────────────────────────────────────

        public void Send(
            object   toLog,
            [CallerMemberName] string callerName = "",
            bool     show   = false,
            bool     thrw   = false,
            bool     toZp   = true,
            int      cut    = 0,
            LogLevel level  = LogLevel.Info,
            LogType  type   = LogType.Info,
            LogColor color  = LogColor.Default)
        {
            if (_fForce) show = true;
            if (!show && level < _minLevel) return;

            string body = BuildBody(toLog?.ToString() ?? "null", callerName, cut);

            // Map LogLevel → LogType if not overridden
            if (level == LogLevel.Warning) type = LogType.Warning;
            if (level == LogLevel.Error)   type = LogType.Error;

            // Override by message markers (legacy support)
            if (body.Contains("!W")) type = LogType.Warning;
            if (body.Contains("!E")) type = LogType.Error;

            string header = _fWrap ? BuildHeader(callerName) : string.Empty;
            string full   = header + body;

            if (_project != null && toZp)
            {
                _project.SendToLog(full, type, toZp, color);
                if (thrw) throw new Exception(full);
            }

            if (_http)
                SendHttp(body, type, callerName, level);
        }

        public void Debug(object toLog, [CallerMemberName] string callerName = "")
            => Send(toLog, callerName, level: LogLevel.Debug, type: LogType.Info);

        public void Info(object toLog, [CallerMemberName] string callerName = "")
            => Send(toLog, callerName, level: LogLevel.Info);

        public void Warn(
            object   toLog,
            [CallerMemberName] string callerName = "",
            bool     show  = false,
            bool     thrw  = false,
            bool     toZp  = true,
            int      cut   = 0,
            LogColor color = LogColor.Default)
            => Send(toLog, callerName, show, thrw, toZp, cut, LogLevel.Warning, LogType.Warning, color);

        public void Error(
            object   toLog,
            [CallerMemberName] string callerName = "",
            bool     thrw  = false)
            => Send(toLog, callerName, show: true, thrw: thrw, level: LogLevel.Error, type: LogType.Error);

        // ── Private helpers ───────────────────────────────────────────────────

        private string BuildHeader(string callerName)
        {
            var sb = new StringBuilder();
            if (_project != null)
            {
                if (_fAcc)  sb.Append($"   [{_project.Var("acc0")}]");
                if (_fTime) sb.Append($"  ⏱ [{_project.Age<string>()}]");
                if (_fPort) sb.Append($"   [{_project.Var("instancePort")}]");
            }
            if (_fCaller) sb.Append($"   [{callerName}]");
            return sb.ToString();
        }

        private string BuildBody(string text, string callerName, int cut)
        {
            if (cut > 0 && text.Count(c => c == '\n') > cut)
                text = text.Replace("\r\n", " ").Replace('\n', ' ');

            string prefix = !string.IsNullOrEmpty(Emoji) ? $"[ {Emoji} ] " : "";
            return $"\n          {prefix}{text.Trim()}";
        }

        private void SendHttp(string body, LogType type, string caller, LogLevel level)
        {


            // Присвоение остальных переменных
            string prj     = _project?.Name.Replace(".zp", "") ?? "";
            string acc     = _project?.Var("acc0")             ?? "";
            string session = _project?.Var("varSessionId")     ?? "";
            string taskId  = _project?.TaskId                  ?? "";

            _ = Task.Run(async () =>
            {
                try
                {
                    var payload = new
                    {
                        machine   = Environment.MachineName,
                        project   = prj,
                        timestamp = DateTime.UtcNow.AddHours(_timezone).ToString("yyyy-MM-dd HH:mm:ss"),
                        level     = level.ToString().ToUpper(),
                        account   = acc,
                        session   = session,
                        port      = Port,
                        pid       = Pid,
                        task_id   = taskId,
                        caller    = caller,
                        message   = body.Trim(),
                        origin = "z3n7"
                    };

                    string json = JsonConvert.SerializeObject(payload);
                    using (var cts = new System.Threading.CancellationTokenSource(1000))
                    using (var content = new StringContent(json, Encoding.UTF8, "application/json"))
                    {
                        await _httpClient.PostAsync(_logHost, content, cts.Token);
                    }
                }
                catch { }
            });
        }
    }
}

// ── Extension methods ─────────────────────────────────────────────────────────
namespace z3n7
{
    public static partial class ProjectExtensions
    {
        public static void log(
            this IZennoPosterProjectModel project,
            object toLog,
            [CallerMemberName] string callerName = "",
            bool show = true,
            bool thrw = false,
            bool toZp = true)
        {
            if (Regex.IsMatch(callerName, @"^M[a-f0-9]{32}$"))
                callerName = project.Name;
            Logger.Get(project).Send(toLog, callerName, show: show, thrw: thrw, toZp: toZp);
        }

        public static void warn(
            this IZennoPosterProjectModel project,
            string toLog,
            bool thrw = false,
            bool show = true,
            bool toZp = true,
            [CallerMemberName] string callerName = "")
            => Logger.Get(project).Warn(toLog, callerName, show: show, thrw: thrw, toZp: toZp);

        public static void warn(
            this IZennoPosterProjectModel project,
            Exception ex,
            bool thrw = false,
            bool withStack = false,
            bool toZp = true,
            [CallerMemberName] string callerName = "")
        {
            var msg = withStack ? ex.Message + "\n" + ex.StackTrace : ex.Message;
            Logger.Get(project).Warn(msg, callerName, show: true, thrw: thrw, toZp: toZp);
        }

        internal static void ObsoleteCode(this IZennoPosterProjectModel project, string newName = "unknown")
        {
            try
            {
                var sb = new StringBuilder();
                var trace = new StackTrace(1, true);
                string oldName = "", callerName = "";

                for (int i = 0; i < trace.FrameCount; i++)
                {
                    var frame = trace.GetFrame(i);
                    var method = frame?.GetMethod();
                    if (method?.DeclaringType == null) continue;

                    string typeName = method.DeclaringType.FullName ?? "";
                    if (typeName.StartsWith("System.") || typeName.StartsWith("ZennoLab.")) continue;

                    string methodName = $"{typeName}.{method.Name}";
                    if (i == 0) oldName = methodName;
                    else
                    {
                        callerName = methodName;
                        break;
                    }
                }

                if (string.IsNullOrEmpty(callerName) || callerName == "z3nCore.Init.RunProject")
                    callerName = System.IO.Path.Combine(project.Path, project.Name);

                sb.Append($"![OBSOLETE CODE]. Method: [{oldName}] called from: [{callerName}]");
                if (!string.IsNullOrEmpty(newName)) sb.Append($". Use: [{newName}] instead");

                project.SendWarningToLog(sb.ToString().Trim(), true);
            }
            catch
            {
            }
        }
    }
}
Получаем возможность смотреть логи по проектам на всех машинах и сортировать и фильтровать их как нам нравится.
140743


С трафиком я решил поступить так же. Обычно мне не нужно смотреть весь трафик — для диагностики чаще всего достаточно смотреть те запросы что отправляют твои боты. Так же из всего траффика бывает полезно получить что-то типа HAR только не в 50к элементов а например хэшсет из URL — чтобы составить "карту API" для дальнейшей работы. Можно "получить весь траффик" и отправить его сюда например из ZP, а страница выдаст скелет с таким хешсетом по фильтру при нажатии соответствующей кнопки в котором будут все типы или даже примеры возвращаемых значений

140744

Мне нравится в ZennoPoster "создать запрос из элемента трафика" — это довольно удобно, клацнул правой мышкой и проверил работает или нет. Но если вы пишете на C# — у запроса функции конвертировать в код уже нет.
В общем пораскинув воображением я попросил Клавдия добавить генератор запросов для типичных клиентов на популярных языках, а также для самого ZennoPoster плюс конвертор из cURL — потому что usage examples под curl есть везде, а под всё остальное как повезёт. Также можно одной кнопкой получить скелет API с типами или хэшсет с примерами под каждый уникальный URL.

Генератор хорошо — но нужен клиент куда мы этот запрос вставим - какая-то точка входа. В PM нас это не заботит — фактически мы всё время в состоянии паузы, и запрос мы отправляем из любого места хоть повесив кубик с ним в воздухе.

Я подумал что для тестирования и кубики можно никуда не добавлять а кнопкой собрать весь запрос из тех данных что были сохранены в траффике и там же этот запрос и отпрравить и там же увидеть и результат
140745
 
Последнее редактирование:
  • Спасибо
Реакции: Dmitriy Ka

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
140746

JSON
Раз уж мы упомянули "карту апи" - она экспортирутся в JSON, который хотелось бы как-то удобно обработать.
Разложить по уровням посмотреть авторизации , прикинуть как мы будем с этим работать.
Или скопировать C# селектор в формате [""][""] (dinamic конечно мне нравиться, но не тогда когда в json зарезервированные слова string json = "{\"class\":\"premium\",\"name\":\"Иван\"}";
Или экспортировать отдельный JToken как JSON
Ну и как же не помочь себе в аналитике с помощью AI.
140747

На самом деле AI тут появился в самый последний момент и промпт на скрине для анализа не очень удачный, но это нюансы

TEXT
К JSON в комплект напрашивается небольшой сетап для работы с текстом. Если вы работаете с трафиком и web3 — возможно вам он так же зайдёт. Можно быстро конвертировать текст в C#-съедобный литерал, делать читаемыми длинные URL, кодировать и декодировать base64, а также бить на чанки data от ethereum-транзакций
140748


CLIPS
Вероятно некоторые знакомы с программой ClipDiary — на форуме была тема с примерами использования и юзер экспириенс мне зашёл. Раньше я хранил там сниппеты, а теперь промпты, решил добавить нечто подобное. Например когда расширение для слежения за лимитами сигналит мне что лимиты на исходе — мне не нужно искать где же я использовал промпт для экспорта в последний раз.
140749



SYSTEM
Если вы являетесь вайбкодером разработчиком — вероятно вы сталкивались с ситуациями когда в коде не закрыто соединение там, где оно должно быть закрыто, нет finally или ещё какая-нибудь дичь, приводящая к сбоям на уровне системы, утечкам памяти, переполнениям пула соединений и прочим приколам, ранее требовавших владения шаманскими музыкальными инструментами для поиска корня проблемы - сегодня такую аналитику можно собрать одной кнопкой — System Snapshot снимает состояние Windows и отдаёт AI для разбора. Как минимум получаешь вектор для поиска а иногда и проблему на блюдечке.
140750
 
  • Спасибо
Реакции: Dmitriy Ka

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
140751

Если вы работаете в криптовалюте + у вас есть зеннопостер, то количество кошельков с большим шансом у вас переваливает за сотню а то и тысячу. Есть множество удобных портфолио менеджеров типа Debank/Jumper/Arkham - но они больше все же для индивидуального использования или вообще трейсинга как Arkham . В них не запихнешь всю ферму, а если и запихнешь то результат получается громоздким и трудноперевариваемым.

Незамениимый Claude помог мне зареверсить API DeBank (беЗплано сэр). Дело за малым - выгружаем результаты в SQL откуда их в свою очередь забирает фронтенд. Получаем хитмап с расбросом балансов по всем чейнам а так же аналитуку от AI о узких местах или рекомендуемых экшенах.
140752

Например я учавствовал в активити QuackAI - дроп вышел честноговоря бомжовым - <1$ на аккаунт и мне даже клеймер было писать лень. Потом я про него просто забыл. В какой то момент такой анализ помог мне найти слегка подросший $Q равномерно размазаный по ферме и в сумме состовляющий > 500$, смотря на единичный баланс я бы вряд ли обратил внимание на токен внизу сортировке по вэлью
 
  • Спасибо
Реакции: Dmitriy Ka

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
141080


Если вдруг вам нравятся графы, так же как они нравятся мне, то вы можете загрузить папку или путь к dll в Grapher и насладиться видом на архитектуру. Рисует состав и связи C# библиотек через рефлексию
141081




А если вы еще и храните данные в таком виде (документацию/ заметки / сессии с llm ) то подобный граф можно получить из любого vault имеющего структуру obsidian
141085
 
Последнее редактирование:
  • Спасибо
Реакции: Dmitriy Ka

web3grep

Client
Регистрация
27.11.2023
Сообщения
96
Благодарностей
191
Баллы
33
141076


Репозиторий: https://github.com/w3bgr3p/z3nIO
Инсталлятор: https://github.com/w3bgr3p/z3nIO/tree/master/installer_output
Зависимости для ZP7: https://github.com/w3bgr3p/z3nIO/tree/master/z3n7dll
Код z3n7(ex z3nCore): https://github.com/w3bgr3p/z3n7

141077


Программа находится в стадии активной разработки может содержать ошибки и распространяется как есть.
Код открыт на GitHub, а LLM открыты к общению ;-)\

Еще есть https://github.com/w3bgr3p/z3nIO/issues для оповещения о оных

Для того чтобы AI функционал работал нужны токены https://id.io.net/ в таблице _aiio - подробнее о всех зависимостях можно почитавть вовстроенной справке программы

141078



- ZennoPoster из моего воркфлоу никуда не делся - большую часть браузерных задач по прежнему выполняет он + первичную логику я по-прежнему пишу в PM

- z3nIO умеет управлять ZP, но не обязан — он может взять любой другой инструмент если задача того требует, ZP так же не обязан управляться z3nIO он по прежнему может крутить свои задачи на этой же машине стендэлон со своими задачами если это потребуется

- Я не планировал писать оркестратор. Я планировал починить твиттер... :bw:

На плечах гигантов: В работе я опирался на виденные мной на форуме работы и идеи. Было бы нечестно не поблагодарить:
  • @zennolab— за вдохновение - многие воплощенные клавдием в этом кейсе являются выросшими из работы с продуктами Zenno
  • @rimen— статья про ClipDiary удобная штука, пользовался несколько лет
  • @wizardстатья откуда я взял сервис с токенами, это бесценно (исходя из моих юзингов так точно)
Спасибо :cv:
Комфортного вам рабочего процесса :bt:

141079
 
Последнее редактирование:
Регистрация
26.05.2020
Сообщения
581
Благодарностей
200
Баллы
43
Нихера не понял, но очень интересно :D :D:D
 

n0n3mi1y

Client
Регистрация
08.03.2017
Сообщения
1 449
Благодарностей
764
Баллы
113
o_O

ты че, псих?
 

Кто просматривает тему: (Всего: 0, Пользователи: 0, Гости: 0)