Как автоматизировать генерацию изображений с помощью Puppeteer
Table Of Content
- Парсинг Discord
- Искусство искусственного интеллекта: использование ChatGPT и Midjourney для создания вдохновляющих визуальных образов
- Исследование красоты случайности.
- rasbign/getImageUrl.js в основной ветке · alekslario/rasbign
- Внесите свой вклад в разработку alekslario/rasbign, создав учетную запись на GitHub.
- Проверьте свою электронную почту
- Вот и все.
Парсинг Discord
Это вторая статья в серии. Первую статью, сравнивающую различные генераторы изображений/искусства на основе искусственного интеллекта, можно найти здесь.
Искусство искусственного интеллекта: использование ChatGPT и Midjourney для создания вдохновляющих визуальных образов
Исследование красоты случайности.
medium.com
Midjourney запрещает автоматизацию в своих условиях использования. Поэтому будьте осторожны и избегайте злоупотребления.
В статье предполагается, что у вас есть подписка на Midjourney, но код можно адаптировать и для бесплатной пробной версии.
Существуют разные способы автоматизации генерации изображений с помощью Midjourney.
Вот статья, описывающая, как это сделать с помощью pyautogui
на Python, написанная Майклом Кингом. Есть способы сделать это в безголовом режиме, предоставив путь к виртуальному дисплею для pyautogui
или использовав плагин Dummy display, который можно приобрести за несколько долларов на Amazon.
Это подходящее решение для pyautogui
, работающего на Raspberry Pi. Однако в статье выше предполагается, что вы используете свой собственный компьютер и, возможно, оставляете скрипт работать на ночь, и не объясняется, как использовать pyautogui
в безголовом режиме.
Мы попробуем другой подход.
Мы будем использовать Puppeteer, библиотеку Node.js, которая позволяет управлять Chromium или Chrome через протокол DevTools. Эта библиотека может помочь вам автоматизировать тестирование веб-сайтов, парсить веб-сайты, создавать скриншоты и PDF-файлы и выполнять другие задачи. Если вы не знакомы с Puppeteer, вы можете изучить его основы, обратившись к этому руководству.
Мы начинаем с создания нового проекта и импорта Puppeteer. Нам также нужно определить переменные среды, которые нам понадобятся.
import puppeteer from "puppeteer";
const password = process.env.DISCORD_PASSWORD;
const email = process.env.DISCORD_EMAIL;
const server_name = process.env.DISCORD_SERVER_NAME;
Вы можете заменить переменные среды на захардкоженные значения, если хотите, или внедрить их по-своему. Нам нужны учетные данные и имя вашего сервера. Нам нужно настроить отдельный сервер Midjourney, который будет служить вашим каналом для общения с ботом Midjourney.
export default async (prompt) => {
const browser = await puppeteer.launch({
headless: true,
ignoreHTTPSErrors: true,
slowMo: 0,
args: [
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-setuid-sandbox",
"--no-first-run",
"--no-sandbox",
"--no-zygote",
],
});
const page = await browser.newPage();
await page.setUserAgent(
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36"
);
Вы можете заменить user agent на любой другой. Обратите внимание, что headless
установлен в true
. В фазе тестирования, локально, я бы предложил установить его в false
.
Давайте определим функцию wait
. Она позволит нам ждать выполнения JavaScript. Даже если элемент может появиться в дереве DOM, он все равно может быть нереагирующим.
const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
Затем мы попытаемся войти в систему.
await page.goto("https://discord.com/login");
// Set screen size
await page.setViewport({ width: 1080, height: 1024 });
await page.waitForSelector('input[name="email"]');
//wait for js
await wait(1000);
await page.type('input[name="email"]', email);
await wait(1000);
await page.type('input[name="password"]', password);
await wait(2000);
Если вы запускаете скрипт на своем персональном компьютере или на близкой машине, у вас не возникнет проблем. Однако, если вы используете удаленный сервер с новым IP-адресом, механизм защиты от ботов Discord вступит в действие и потребует от пользователей решить hCaptcha-задачи, такие как головоломки и флажки.
В этом руководстве мы полагаемся на сторонние сервисы для решения hCaptcha, так как в настоящее время нет алгоритмов, которые могут надежно их решать. Хотя некоторые проекты искусственного интеллекта показывают хорошие результаты, например hcaptcha-challenger, они обычно специализируются на одном типе капчи. Для наших целей мы будем использовать 2captcha. Однако есть и другие доступные сервисы, поэтому стоит провести некоторое исследование.
Я потратил 3 фунта стерлингов, что позволит получить около 1000 решений. Мы передаем им URL и site-key
, который является уникальным UUID, найденным где-то в HTML. После предоставления этих входных данных 2captcha возвращает хэш решения.
import captcha from "2captcha";
const solver = new captcha.Solver(process.env.CAPTCHA_API_KEY);
export default async (siteKey, url) => {
const obj = await solver.hcaptcha(siteKey, url);
const { data } = obj;
return data;
};
Но это только половина работы. Затем нам нужно найти функцию обратного вызова где-то на веб-сайте Discord и вручную вызвать ее, предоставив хэш в качестве аргумента. Это сложная часть. Ниже приведен код, который это делает.
try {
await page.click('button[type="submit"]');
let foundElement = await page.waitForSelector(
`iframe[data-hcaptcha-widget-id], div[data-dnd-name="${server_name}"]`
);
//let's determine what we found
const tagName = await foundElement.evaluate((el) => el.tagName);
if (tagName === "IFRAME") {
const srcString = await foundElement.evaluate((el) => el.src);
const siteKey = srcString.split("sitekey=")[1].split("&")[0];
const data = await solver(siteKey, "https://discord.com/login");
await page.evaluate((token) => {
const node =
document.querySelector("iframe").parentElement.parentElement;
const properties = Object.getOwnPropertyDescriptors(node);
const keys = Object.keys(properties);
const reactProp = keys[1];
document
.querySelector("iframe")
.parentElement.parentElement[reactProp].children.props.onVerify(
token
);
}, data);
foundElement = await page.waitForSelector(
`div[data-dnd-name="${server_name}"]`
);
}
Позвольте мне рассказать подробнее о коде выше. Мы нажимаем submit
и ждем, пока Discord загрузится, затем где-то слева появится div
с именем вашего сервера, или будет запущен hCaptcha. В последнем случае мы ожидаем увидеть iframe
.
await page.click('button[type="submit"]');
let foundElement = await page.waitForSelector(
`iframe[data-hcaptcha-widget-id], div[data-dnd-name="${server_name}"]`
);
Получив элемент, в зависимости от того, что загрузится первым, нам нужно определить, что мы получили по имени тега.
const tagName = await foundElement.evaluate((el) => el.tagName);
Если это iframe
, мы получаем site-key
, который является одним из свойств iframe.
const srcString = await foundElement.evaluate((el) => el.src);
const siteKey = srcString.split("sitekey=")[1].split("&")[0];
Мы вызываем наш решатель из предыдущего кода и ждем, пока человек, где-то далеко, решит его за нас и вернет хэш.
const data = await solver(siteKey, "https://discord.com/login");
Теперь нам нужно найти обратный вызов и вызвать его. Немного рытья в исходном коде и у нас есть имя.
Мы также знаем, что Discord создан с использованием React, и onVerify
, вместе с другими аргументами, передается в качестве свойства. В принципе, поиск любого свойства по имени не так уж сложен. Нам нужно пройти через каждый узел и проверить его.
//__reactProps + случайная строка
// будет отличаться каждый раз, когда мы перезагружаем страницу
let react_p = "__reactProps$ksy66ebrux";
const search = (el) => {
if (!el) return;
if (el[react_p]?.children?.props) {
if (Object.keys(el[react_p].children.props).find((el) => el === "onVerify"))
console.log("Got it!", el.className);
}
if (el.children) {
if (HTMLCollection.prototype.isPrototypeOf(el.children)) {
for (let i = 0; i < el.children.length; i++) {
if (el.children[i]) search(el.children[i]);
}
} else {
search(el.children);
}
}
};
search(document);
Мы получаем доступ к виртуальному DOM React с помощью react_p
и к свойствам узла с помощью .children.props
. Как только мы получили имя класса, мы можем напрямую нацеливаться на него. Вернемся к скрипту Puppeteer.
if (tagName === "iframe") {
const srcString = await foundElement.evaluate((el) => el.src);
const siteKey = srcString.split("sitekey=")[1].split("&")[0];
const data = await solver(siteKey, "https://discord.com/login");
await page.evaluate((token) => {
const node =
document.querySelector("iframe").parentElement.parentElement;
const properties = Object.getOwnPropertyDescriptors(node);
const keys = Object.keys(properties);
const reactProp = keys[1];
document
.querySelector("iframe")
.parentElement.parentElement[reactProp].children.props.onVerify(
token
);
}, data);
foundElement = await page.waitForSelector(
`div[data-dnd-name="${server_name}"]`
);
}
После того, как мы победили hCaptcha, остальное дело техники.
//wait for js
await wait(1000);
await foundElement.click();
await page.waitForSelector("form");
await page.type("form", `/imagine`);
await wait(1000);
await page.type("form", ` `);
await wait(1000);
await page.type("form", `${prompt}`);
await page.keyboard.press("Enter");
// wait for js to update html tree
await wait(3000);
// waiting for the 4ximage to load
await page.waitForSelector('ol li:last-of-type img[alt="🔄"]', {
timeout: 1000 * 60 * 4,
});
const button = await page.waitForSelector("ol li:last-of-type button");
//wait for javascript, buttons are not responsive at first
await wait(3000);
await button.click();
//wait for upscale, upscaling the first image
await page.waitForSelector('ol li:last-of-type img[alt="❤️"]', {
timeout: 1000 * 60 * 4,
});
await wait(1000);
const image = await page.$('ol li:last-of-type img[alt="Image"]');
const imageSrc = await image.evaluate((img) => img.src);
await browser.close();
Код выше также увеличивает первое изображение.
Время ожидания для селекторов может быть значительным, до 5 минут или даже больше. Если вы планируете развернуть его в облаке и активно использовать скрипт, имеет смысл разделить его на три части: запись запроса, увеличение и извлечение URL. Таким образом, вы можете избежать оплаты за время ожидания между процессами и можете вызывать каждый скрипт отдельно. Это ваше решение.
Полный исходный код находится здесь.
rasbign/getImageUrl.js в основной ветке · alekslario/rasbign
Внесите свой вклад в разработку alekslario/rasbign, создав учетную запись на GitHub.
github.com
Проверьте свою электронную почту
Если вы запускаете скрипт с нового IP-адреса, Discord отправит вам письмо с подтверждением, которое вы должны будете немедленно подтвердить.
Есть несколько способов автоматизировать это. Я расскажу об этом в будущих статьях.
Вот и все.
В следующей статье будет рассмотрено, как развернуть его в облаке и настроить Raspberry Pi с дисплеем E-Ink для вызова конечной точки на регулярной основе.
До свидания, друзья.