, ,

12 января, 2025

Обход защиты сайтов от парсинга данных или anti-anti-scraping

Допустим, есть задача написать парсер, который бы получал данные сайта и складывал их в локальную базу данных. На первый взгляд это очень простая и тривиальная задача, но при реализации оказывается, что многие сайты используют механизмы защиты от прямого парсинга. Такие защиты могут включать:

  • Формирование HTML-страницы с помощью JavaScript.
  • Ожидание специальных заголовков или куки.
  • Блокировку «подозрительных» запросов.

Посмотрев на это, я немного приуныл и начал искать способ быстрее решить задачу. Мне нужно было решение, которое бы запускало парсер так, как если бы запрос выполнял обычный браузерный пользователь. В голову сразу пришла идея использовать Chromium как сервис внутри Docker. Я мог бы передавать ссылку страницы в этот сервис, а в ответ получать HTML уже после отработки JavaScript.

Выбор инструментов

Парсить я планирую на PHP. В качестве браузерного сервиса я выбрал Selenium. Также можно использовать Puppeteer, но он у меня не запустился под Docker, поэтому я отказался от него.

Selenium — это инструмент для тестировщиков, позволяющий автоматизировать действия на сайте с помощью эмуляции браузера. Мне достаточно одного браузера — Chrome, но Selenium поддерживает и другие браузеры. Подробнее можно прочитать на официальном сайте: https://www.selenium.dev

version: '3.8'

services:

  selenium:
    image: selenium/hub:4.8.0  # Selenium Hub
    container_name: selenium_parser
    environment:
      - GRID_MAX_SESSION=5
      - GRID_BROWSER_TIMEOUT=300
      - GRID_TIMEOUT=300
    networks:
      - network

  chrome:
    image: selenium/node-chrome:4.8.0  # Браузер Chrome
    shm_size: 2gb
    depends_on:
      - selenium
    environment:
      - SE_EVENT_BUS_HOST=selenium
      - SE_EVENT_BUS_PUBLISH_PORT=4442
      - SE_EVENT_BUS_SUBSCRIBE_PORT=4443
    networks:
      - network

  php:
    build:
      context: .
      dockerfile: Dockerfile
    container_name: parser_php
    volumes:
      - ./:/var/www/html
    depends_on:
      - selenium
    networks:
      - network

networks:
  network:
    driver: bridge
    ipam:
      driver: default
      config:
        - subnet: 192.168.210.0/28

selenium — контейнер с нашим Selenium, который будет использоваться как сервис для запуска сайтов из браузера Chrome.

chrome — контейнер с браузером (можно добавить контейнеры с другими браузерами, если нужно).

php — контейнер с PHP-скриптом.

Поскольку я буду использовать стороннюю библиотеку Composer для взаимодействия с API Selenium, мне нужно, чтобы Composer был установлен в моем контейнере PHP. Для этого создаем дополнительный Dockerfile для PHP.

# Используем базовый образ PHP 8.3
FROM php:8.3-fpm

# Установка необходимых пакетов
RUN apt-get update && apt-get install -y \
    git \
    unzip \
    libpq-dev \
    && docker-php-ext-install bcmath

# Установка Composer
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer

# Установка Symfony CLI
RUN curl -sS https://get.symfony.com/cli/installer | bash \
    && mv /root/.symfony*/bin/symfony /usr/local/bin/symfony

# Установка прав доступа к директории
WORKDIR /var/www/html

Запуск Docker Compose

Теперь все готово для запуска docker-compose:

$ docker-compose up -f docker-compose.yaml

Обратите внимание, что я использую минимальный набор контейнеров. В реальном проекте для полноценного парсера потребуются контейнеры с HTTP-сервером, базой данных, Redis и т. д. Поскольку написание полноценного парсера выходит за рамки этого поста, здесь я привожу пример упрощенной конфигурации docker-compose.

Заходим в контейнер PHP и инициализируем Composer:

$ docker exec -it parser_php bash

docker# composer require php-webdriver/webdriver 

После успешной инициализации Docker пишем небольшой скрипт на PHP для проверки работоспособности Selenium:

<?php

require_once 'vendor/autoload.php';

use Facebook\WebDriver\Remote\RemoteWebDriver;
use Facebook\WebDriver\Remote\DesiredCapabilities;

// Подключение к Selenium Hub
$host = 'http://selenium:4444/wd/hub'; // URL Selenium Hub
$capabilities = DesiredCapabilities::chrome(); // Используем Chrome
$driver = RemoteWebDriver::create($host, $capabilities);

// Открываем страницу
$driver->get('https://example.com');

// Получаем HTML страницы
$html = $driver->getPageSource();
echo $html;

// Закрываем браузер
$driver->quit();

Все теперь можно из контейнера с php обращаться к любым сайтам через selenium как полноценный браузер Chrome, выполнять JS, можно эмулировать обычные действия пользователя на сайте, все зависит от вашей фантазии, задачи.

Теперь можно из контейнера PHP обращаться к любым сайтам через Selenium как полноценный браузер Chrome. Вы можете выполнять JavaScript, эмулировать действия пользователя на сайте — все зависит от ваших задач и фантазии.

, , ,

6 января, 2025

Разворачиваем git хранилище на своем сервере

Всем привет! Сегодня хочу рассказать про установку такого замечательного программного обеспечения как Gitea. Данный продукт предназначен для удобного доступа к git хранилищу, напоминает по своим возможностям gitlab, github, bitbucket.

Сейчас особенно полезно задуматься о создании своих независимых git хранилищ потому что пидарская протекционистская политика государств нарастает и очень вероятно, что в будущем пользователей из России могут лишить доступа к github или bitbucket, а код где-то хранить необходимо и так как мудаки гениальные стратеги отвечающие за импортузамещение до сих пор не создали ничего более менее альтернативного в России, то лучше создавать свои хранилища на своих выделенных серверах. Gitea прекрасно подходит в качестве альтернативы, а так же он гораздо проще в установке и настройке чем тот же самый GitLab.

Для установки я выбрал способ развертывания через docker compose, так что требуется установить docker если это еще не сделано, затем создать директорию в котором будет находится файловая система gitea и создать файл docker-compose.yaml

$ sudo apt update
$ sudo apt install -y docker.io
$ sudo systemctl start docker
$ sudo systemctl enable docker
$ sudo apt install -y docker-compose
$ mkdir ~/gitea && cd ~/gitea
$ vim docker-compose.yml

Соответственно, на хостинге у меня Ubuntu ( если у вас что-то другое, используйте свои команды для установки Docker ). В качестве редактора само собой я использую православный vim

version: '3.7'

services:
  db:
    image: postgres:15
    container_name: gitea-db
    environment:
      POSTGRES_USER: gitea
      POSTGRES_PASSWORD: gitea_password
      POSTGRES_DB: gitea
    volumes:
      - ./db:/var/lib/postgresql/data
    restart: always

  gitea:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
    volumes:
      - ./gitea:/data
    ports:
      - "127.0.0.1:3000:3000" # Веб-интерфейс Gitea
      - "2222:22"   # SSH для клонирования репозиториев
    depends_on:
      - db
    restart: always

В качестве базы данных я решил использовать postgres. Поменяйте доступы к базе данных. В принципе gitea поддерживает разные СУБД, можете использовать свой любимый. Описание портов есть в комментариях.

Так как мне нужен был отдельный домен для git я указал проброс порта 3000 только для 127.0.0.1 поэтому из вне к нему нельзя обратится ( далее я покажу как разрулить порт через настройку nginx )

Теперь создаем любымый Makefile для удобства

.PHONY: up down restart logs ps clean help

# Переменные
COMPOSE_FILE = docker-compose.yml
PROJECT_NAME = gitea_project

# Команды
up: ## Запускает все контейнеры в фоне
	docker-compose -p $(PROJECT_NAME) -f $(COMPOSE_FILE) up -d

down: ## Останавливает контейнеры и освобождает ресурсы
	docker-compose -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down

restart: ## Перезапускает все контейнеры
	$(MAKE) down
	$(MAKE) up

logs: ## Показывает логи контейнеров в реальном времени
	docker-compose -p $(PROJECT_NAME) -f $(COMPOSE_FILE) logs -f

ps: ## Отображает состояние запущенных контейнеров
	docker-compose -p $(PROJECT_NAME) -f $(COMPOSE_FILE) ps

clean: ## Удаляет все контейнеры, тома и связанные ресурсы
	docker-compose -p $(PROJECT_NAME) -f $(COMPOSE_FILE) down --volumes --remove-orphans

help: ## Показывает это сообщение
	@echo "Доступные команды:"
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "  %-15s %s\n", $$1, $$2}'

.DEFAULT_GOAL := help

В Makefile можно вынести необходимые команды для работы с докером

В процессе запуска у меня возникли некоторые сложности при запуске докера на хостинге поэтому пришлось установить питон

$ sudo apt update 
$ sudo apt install python3 -y
$ sudo apt install python3-pip -y

Запускать docker-compose up от рута плохая идея поэтому необходимо добавить пользователя в группу docker и запускать docker-compose от обычного пользователя, для этого добавляем пользователя <your-user> в группу docker

$ sudo usermod -a -G docker <your-user>

Ну или так:

$ sudo usermod -aG docker $USER

После добавления пользователя в группу требуется перезапустить shell ( без этого make up ругался на то что у пользователя не хватает прав )

Теперь требуется привязать домен к нашему локальному порту 3000 на котором запущен gitea, для этого нужно перейти к настройкам nginx и создать конфиг для сервиса gittea

$ cd /etc/nginx/sites-available
$ vim gitea

Добавляем подобное содержимое

server {
        server_name domain.ru www.domain.ru;
        location / {
                proxy_pass http://localhost:3000;
        }
}

Тут все понятно — все внешние запросы проксируются на локальный порт 3000 на котором запущен наш gitea

Далее создаем линк на /etc/nginx/sites-available/gitea в
/etc/nginx/sites-enabled и перезапускаем nginx

$ sudo ln -s /etc/nginx/sites-enabled/gitea /etc/nginx/sites-available/gitea
$ sudo nginx reload

После этого я рекомендую так же настроить SSL сертификаты для нашего хранилища, я использую для этой цели certbot

$ cerbot --nginx -d domain.ru -d www.domain.ru

Чуть не забыл важный момент, необходимо создать службу в Ubuntu, которая позволит автоматически поднимать docker-compose с gitea при перезагрузки сервера, для этого создаем новую пользовательскую службу systemd

$ cd /etc/systemd/system/
$ sudo vim gitea.service

Добавляем код нового сервиса в gitea.service

[Unit]
Discription=Gitea
After=docker.service
Requires=docker.service

[Service]
WorkerDirectory=/var/www/gitea/
ExecStart=docker-compose -p gitea_project -f /var/www/gitea/docker-compose.yml up
ExecStartPost=service nginx reload
ExecStop=docker-compose -p gitea_project -f /var/www/gitea/docker-compose.yml down
Restart=on-failure

[Install]
WantedBy=multi-user.target

Тут особо пояснять нечего. Указываем, что служба запускается только после запуска docker.service и для нее обязательно должен быть запущен docker.service

Запускаем

$ sudo systemctl daemon-reload
$ sudo systemctl enable gitea

Все! На этом настройки сервера закончены и можно приступать к установке gitea.

Открываем браузер и переходим на страницу domain.ru ( тот домен который мы указали в настройках nginx )

Откроется окно со множеством настроек, я не буду приводить все настройки, остановлюсь только на настройках базы данных и админского доступа.

Тут необходимо заполнить настройки данными, которые были указаны в docker-compose.yml

POSTGRES_USER: gitea
POSTGRES_PASSWORD: gitea_password
POSTGRES_DB: gitea

Собственно имя пользователя базы данных, имя базы данных и ваш пароль. В качестве СУБД выбираем postgreSQL

Так же можно сразу же настроить тут порт для ssh соединения, в нашем случае это будет порт 2222

И настроить доступы для администратора Gitea:

После этого нажимаем на кнопку Устанивать Gitea. Ждем пока система установится и можно приступать к работе с Gitea. Интерфейс системы интуитивно понятен, а по функционалу не сильно отличается от привычного Gitlab. Работает достаточно шустро.

На что стоит обратить внимание, конечно настройки базы данных лучше вынести в .env файл из docker-compose.yml а так же может еще имеет смысл настроить работу своего git хранилища внутри вашего собственного VPN, но это уже немного выходит за рамки темы статьи.

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

, , ,

15 декабря, 2024

Поднимаем Chat GPT у себя на компьютере

Привет! Сегодня хотел бы рассказать как можно развернуть на своей локальной машине аналог чата GPT. Особых знаний для этого иметь не нужно. Все достаточно просто.

Для тех кто не знает что такое chatGTP, то это  чат-бот с генеративным искусственным интеллектом, разработанный компанией OpenAI и способный работать в диалоговом режиме, поддерживающий запросы на естественных языках. ( кусок из Викепедии )

Но возможно не все знают, что существуют альтернативные чат боты, платные и бесплатные, централизованные и децентрализованные

Существует несколько популярных моделей с открытым исходным кодом, которые можно развернуть локально:

Эти модели предоставляют функциональность, схожую с ChatGPT, но менее мощные.

Так же существуют децентрализованные аналоги чата GPT но тут я не буду об этом писать, хотя тема децентрализации подобных систем мне кажется достаточно интересной.

По-своей сути подобные чаты представляют из себя ML-модели

ML (Machine Learning) — это технология, позволяющая компьютерам обучаться и принимать решения на основе данных, не будучи явно запрограммированными. ML-модель — это математическое представление данных и логики, которое позволяет системе делать предсказания или выполнять задачи, анализируя входные данные.

Основные этапы работы ML-модели:

  1. Сбор данных: Системе нужны данные для обучения.
  2. Обработка данных: Данные очищаются, нормализуются и подготавливаются для анализа.
  3. Обучение: Модель обучается на данных, выявляя закономерности и зависимости.
  4. Валидация: Проверяется, как хорошо модель работает на новых данных.
  5. Применение: Обученная модель используется для анализа новых данных.

Типы ML-моделей:

  1. Супервизорное обучение (Supervised Learning): Обучение на размеченных данных (например, классификация писем как «спам» или «не спам»).
  2. Без учителя (Unsupervised Learning): Выявление скрытых структур в неразмеченных данных (например, кластеризация клиентов).
  3. Обучение с подкреплением (Reinforcement Learning): Модель учится через награды за правильные действия (например, обучение робота).

Для чего это вообще может пригодится в быту? Да для чего угодно :) На базе генеративного чата ИИ вы можете создать своего персонального помощника, который сможет предоставлять вам те данные, которые вам необходим, например, в работе. При этом он будет работать автономно на вашем компьютере и абсолютно бесплатно, так он вполне работает без подключения к интернету.

В данной статье я бы хотел продемонстрировать как достаточно просто можно развернуть чат ollana, пару слов о том, что из себя представляет ollama

Ollama — это платформа и инструмент для локальной работы с большими языковыми моделями (LLM, Large Language Models). Она позволяет запускать и использовать LLM прямо на вашем устройстве, не требуя обращения к внешним серверам.

Основные особенности Ollama:

  • Локальная работа: Запуск моделей происходит локально, что повышает конфиденциальность и безопасность.
  • Оптимизация: Использует аппаратные ресурсы устройства максимально эффективно.
  • Поддержка популярных моделей: Ollama работает с различными открытыми и специализированными языковыми моделями, включая модели вроде Llama 2, Alpaca, или GPT.

Модели: Ollama предоставляет доступ к языковым моделям, которые работают с текстами. Это может быть генерация текста, обработка запросов, написание кода и многое другое.

Технология:

  • Использует LLM, которые обучены на огромных текстовых датасетах.
  • Поддерживает работу с моделями, оптимизированными для локального запуска.
  • Совместима с архитектурами, такими как Transformer, лежащими в основе современных LLM.

Применение:

  • Генерация текста (автоматизация переписки, написание статей).
  • Классификация (определение тематики текста).
  • Анализ (суммаризация текста, ответ на вопросы).

Ollama может использовать любые модели, доступные в формате GGML, подходящем для локального запуска.

По управлению ollama немного напоминает docker

Для установки ollama нужно перейти на официальный сайт и скачать версию для вашей операционной системы

После установке можно запустить ollama выполнив команду в консоле:

$ ollama run llama3.2

Данная команда скачает и запустит модель llama3.2

Список моделей доступных для ollama можно посмотреть тут модели могут быть мощными 90B и выше, так и менее мощными 2B

Можно установить любое количество моделей ( если у вас хватает ресурсов ), для просмотра уже скаченных моделей можно воспользоваться командой:

$ ollama list

NAME               ID              SIZE      MODIFIED     
codestral:22b      0898a8b286d5    12 GB     12 hours ago    
phi:latest         e2fd6321a5fe    1.6 GB    14 hours ago    
llama3.2:latest    a80c4f17acd5    2.0 GB    4 days ago 

Так же хотелось бы отметить, что такое 2B, 90B. Это обозначения, которые указывают на количество параметров в машинной модели (в миллиардах, где B означает billion — миллиард). Параметры — это числовые значения, которые модель настраивает во время обучения для выполнения задач, таких как обработка текста или генерация ответов. К примеру, GPT-4 использует 1.8 триллиона параметров (или 1,800 миллиардов)

Большие и мощные модели потребуют больших ресурсов компьютера для работы. Они потребляют больше процессорной мощности, памяти и занимают достаточно большой объем жесткого диска.

И так, после выполнения команды, мы уже сможем работать с чатом из командной строки.

так же после запуска чат доступен по адресу http://localhost:11434, по которому можно обратится, сформировав HTTP запрос. Вот пример как это сделать:

На curl из командной строки:

curl -X POST http://localhost:11434/api/generate \
-H "Content-Type: application/json" \
-d '{
  "model": "llama3.2",
  "prompt": "Привет! Как дела?",
  "stream": false
}'

Получаем ответ, что-то вроде:

{
  "model": "llama3.2",
  "created_at": "2024-12-14T22:15:02.891574Z",
  "response": "Здравствуйте! Я работаю нормально, gracias за вопрос. Как youre?",
  "done": true,
  "done_reason": "stop",
  "context": [
    ...
  ],
  "total_duration": 1162500333,
  "load_duration": 50502583,
  "prompt_eval_count": 32,
  "prompt_eval_duration": 618000000,
  "eval_count": 20,
  "eval_duration": 491000000
}

Тут важно отметить, что если не передать в теле запроса «stream»: false то ответ будет возвращаться в виде так называемых чанков, на каждое генерацию ответа:

{"model":"llama3.2","created_at":"2024-12-14T22:12:32.01656Z","response":"Пр","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.032929Z","response":"ив","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.054271Z","response":"ет","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.076969Z","response":"!","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.098469Z","response":" Как","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.122245Z","response":" я","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.144477Z","response":" могу","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.165088Z","response":" пом","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.186886Z","response":"очь","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.207423Z","response":" вам","done":false}
{"model":"llama3.2","created_at":"2024-12-14T22:12:32.229528Z","response":"?","done":false}
...

Так же к чату можно обращаться из своих скриптов, вот простой пример скрипта на PHP:

<?php

$url = 'http://localhost:11434/api/generate';
$data = [
    "model" => "llama3.2",
    "prompt" => "Привет! Как дела?",
    "stream" => true // включение потокового режима
];

$ch = curl_init($url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
curl_setopt($ch, CURLOPT_WRITEFUNCTION, function($curl, $chunk) {
    // Обрабатываем чанки текста
    $response = json_decode($chunk, true);
    if (isset($response['response'])) {
        echo $response['response'];
    }
    return strlen($chunk);
});

curl_exec($ch);
curl_close($ch);

Далее не помешало бы настроить какой-то удобный интерфейс для взаимодействия с ИИ чатом. Для этого можно воспользоваться готовым решением — open-webui

Open-webui — это графический интерфейс при помощи которого можно подключаться как к локальным так и к удаленным генеративным моделя наподобие chatGPT или LLaMa

open-webui можно установить локально ( для этого потребуется установить Paython ) или воспользоваться образом из докера. Мне больше нравится работать с докером, поэтому я выбрал вариант с контейнером:

$ docker run -d -p 3000:8080 -e OLLAMA_BASE_URL=http://host.docker.internal:11434 -v open-webui:/app/backend/data --name open-webui --restart always ghcr.io/open-webui/open-webui:main

Тут важно отметить, что я указал в команде значение переменной -e OLLAMA_BASE_URL=http://host.docker.internal:11434

Она указывает на адрес на котором ollama запускается локально. В моем случае подобный вариант запуска привел к тому, что UI web интерфейса очень сильно тормозил, поэтому в команде я указал вместо http://host.docker.internal:11434 — http://localhost:11434 а уже после запуска изменил адрес на http://host.docker.internal:11434 из панеле управления: Админ Панель -> настройки -> соедниение

После запуска, open-webui будет доступен по адресу http://localhost:3000

Нужно будет зарегистрироваться и можно общаться с чатом:

Ну вот и все! Надеюсь данная информация будет полезна кому-нибудь.

, , , ,

10 декабря, 2024

Работа с Tron ( TRX ) на PHP

Сегодня хотел бы рассказать про такую замечательную криптовалюту как TRON ( TRX ), а так же показать как достаточно просто можно создать сервис процессинга оплаты при помощи АПИ — https://api.trongrid.io на любимом многими РHP

Почему я считаю, что TRX замечательным:

  • Сеть Tron работает очень быстро с минимальными затратами. TRON способен обрабатывать до 2000 транзакций в секунду, что значительно больше, чем у Ethereum (15-30 транзакций/секунда) или Bitcoin (5-7 транзакций/секунда).
  • Транзакции в TRON практически бесплатны. Это делает его отличным выбором для частых и мелких переводов, особенно в DeFi-приложениях и микротранзакциях.
  • Огромная популярность стэйблкоина в сети TRON (TRC20) из-за скорости и минимальных комиссий

Теперь я хотел бы продемонстрировать простой способ как можно процессить проведение платежей на примере реализации простого API на PHP с использованием Symfony.

Каким образом настроить докер для работы Symfony а так же как развернуть на Symfony API я углоблятся не буду ( может напишу про это отдельный пост чуть позже ), а вот как установить библиотеку и подключить ее к проекту на Symfony расскажу.

Для установки библиотеки для работы с АПИ TRX потребуется composer

Устанавливаем необходимый пакет:

# composer require iexbase/tron-api

Я хочу создать API который будет реализовывать 2 метода REST API:

  1. метод создания кошелька
  2. метод получения баланса кошелька

В целом думаю этого будет достаточно для того чтобы создать кошелек TRON для оплаты чего-либо и далее регулярно выполняя запросы на метод получения данных о балансе можно будет производить проверку проведения платежа, если сумма платежа будет соответствовать той под которую создавался кошелек, тогда оплату можно считать успешной.

<?php

namespace App\Controller;

use IEXBase\TronAPI\Exception\TronException;
use IEXBase\TronAPI\Provider\HttpProvider;
use IEXBase\TronAPI\Tron;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Attribute\Route;

class CreateWalletController extends AbstractController
{
    /**
     * @throws TronException
     */
    #[Route('/wallet', name: 'create_wallet', methods: ['POST'])]
    public function __invoke(Request $request): JsonResponse
    {
        $fullNode = new HttpProvider('https://api.trongrid.io'); // Нода Tron
        $solidityNode = new HttpProvider('https://api.trongrid.io');
        $eventServer = new HttpProvider('https://api.trongrid.io');
        $tron = new Tron($fullNode, $solidityNode, $eventServer);
        $wallet = $tron->generateAddress();
        $data = $wallet->getRawData();

        return new JsonResponse([
            'wallet' => $data['address_base58']
        ]);
    }
}

Это простой метод POST при вызове которого будет создан кошелек.
Тут важно отметить, что приватный ключ не будет нигде сохранен и передан, поэтому с кошельком далее нельзя будет выполнить какие-либо действия ( тут приводится упрощенный код для того чтобы показать как это можно сделать на PHP )

После получения публичного адреса нового кошелька можно запустить воркер, который бы проверял поступление денежных средств на данный кошелек, для этого потребуется создать метод GET с передачей параметра — адреса кошелька для проверки его баланса.

<?php

namespace App\Controller;

use IEXBase\TronAPI\Exception\TronException;
use IEXBase\TronAPI\Provider\HttpProvider;
use IEXBase\TronAPI\Tron;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Attribute\Route;

class GetAddressInfo
{
    #[Route('/address/{address}', name: 'get_address_info')]
    public function __invoke(string $address): JsonResponse {
        $fullNode = new HttpProvider('https://api.trongrid.io');
        $solidityNode = new HttpProvider('https://api.trongrid.io');
        $eventServer = new HttpProvider('https://api.trongrid.io');

        try {
            $tron = new Tron($fullNode, $solidityNode, $eventServer);
            $tron->setAddress($address);
            $balance = $tron->getBalance();
            $info = $tron->getAccount();
        } catch (TronException $e) {
            exit($e->getMessage());
        }
        return new JsonResponse([
            'address' => $address,
            'create_date' => date('Y-m-d H:i:s', $info['create_time']/1000),
            'balance' => $balance,
        ]);
    }
}

Таким образом мы можем получить баланс кошелька и сопоставить его с ожидаемой суммой в TRX для оплаты того под что создавался кошелек. Тут важно понимать, что подобная система не является полностью работоспособной. Чтобы далее что-то делать со средствами которые поступили на счет нового кошелька, потребуется как минимум приватный ключ, но я думаю эти методы вполне могут подойти для каркаса будущего приложения в парадигме web3

Продолжение следует.

,

6 декабря, 2024

Репостинг wordpress постов в VK в 2024 году

Всем привет! Когда-то давно уже делал подобное для других сайтов на вордпрессе, решил сделать так же для данного блога подобную систему репостинга в социальную сеть вконтакте.

Думал, что сложностей не возникнет, но оказывается, что сейчас что-то стало проще, а что-то труднее.

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

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

Поэтому я решил делать репостинг через токен группы, чтобы его подключить необходимо в своей группе перейти в управление и создать ключ доступа ( он потребуется для выполнения дальнейших запросов от WP к нашей группе )

Перед тем как писать код на php я бы хотел так же отметить, что выбранный мной формат репоста будет являться ссылка на пост сайта. Сначала были предприняты попытки добавление полноценной записи со своим изображением и текстом, но проблемой оказалось так сделать потому что для загрузки изображение в группу а так же получения данных альбомов группы треубется пользовательский токен, а его теперь стало труднее получить поэтому я отказался от создания полноценного поста в ВК.

Не знаю по какой причине по токену группы нельзя получить список фотографий этой же группы и почему нельзя загрузить фотографию в эту же группу, возможно это бага, возможно фича. Общаться на эту тему в группе VK API у меня не было особого желания поэтому я отказался от создания полноценных записей на стену сообщества и остановился на варианте передачи attachments в запрос к АПИ со ссылкой на запись поста.

И тут хотелось бы так же сказать еще, что для красивого отображения прикрепленных ссылок требуется чтобы на сайте на который вы оставляете ссылку были прописаны Open Graph теги, как это можно сделать я писал тут
Добавить Open Graph в WordPress необходимо, вот, например как будет выглядеть репост в социальную сеть с Open Graph и без него:

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

Теперь расширяем функционал репостинга для ВК

Открываем на редактирование functions.php нашей темы и создаем функцию, которая будет отправлять наш пост на стену сообщества

// Добавляем функцию авторипостинга в хуки смены статуса поста

// Если пост переходит из автосохраненной в публикацию 
add_action('auto-draft_to_publish', 'send_post_vk', 20, 1);

// Если пост переходит из запланированного поста в публикацию
add_action('future_to_publish', 'send_post_vk', 20, 1);

// Если пост переходит из черновика в публикацию
add_action('draft_to_publish', 'send_post_vk', 20, 1);

function send_post_vk($post_id)
{
    // Получаем пост 
    $post = get_post($post_id);

    // Добавляем к тексту сообщения теги поста
    $text = get_tags_to_message($post->ID);
    if (!empty($text)) {
        $text = PHP_EOL.$text.PHP_EOL;
    }

    // Убираем из текста лишнее и обрезаем его до 500 символов
    $text .= preg_replace('/[\n\r]+/s', "\n\n", strip_tags($post->post_content));
    $text = strip_tags($text);
    if (strlen($text) > 500) {
        $text = mb_substr($text, 0, 500) . '...';
    }

    // Закидываем в текст прямую ссылку на пост ( иногда почему-то аттачмент не срабатывает, поэтому я решил дополнительно еще добавить ссылку поста к тексту
    $text .= PHP_EOL.get_permalink($post->ID).PHP_EOL;
    $data = [
        'message' => $text,
        'link' => get_permalink($post->ID)
    ];

    // Вызываем функцию обращения к АПИ и передаем ее сформированный массив с данными
    vk_send_message_to_channel($data);
}

function vk_send_message_to_channel($data) {
    $url = 'https://api.vk.com/method/wall.post';
    $params = [
        'owner_id' => VK_GROUP, // ВК сообщество ( не забываейте что это должно быть отрицательное целое с минусом в начале ID сообщества)
        'message' => $data['message'], // Текст сообщения на стене
        'attachments' =>  $data['link'], // В атачменте передаем прикрепленную ссылку с необходимым постом
        'access_token' => VK_TOKEN, // Тут должен передаваться токен АПИ вашего сообщества, я показывал где его можно создать на скриншоте выше
        'from_group' => 1, // Сообщаем, что пост будет от автора сообщества
        'v' => '5.131'
    ];

    // Вызываем метод отправки данных через curl
    curl_sender_exec($url, $params);
}

// Функция отправки данных через curl, вынес его чтобы не дублировать в других местах, где необходимо также отправка данных через curl
function curl_sender_exec($url, $params) {
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($params));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_exec($ch);
    curl_close($ch);
}

Ну вот и все :) Теперь автопостинг будет автоматически отправлять при публикации в WordPress вашу статью в сообщество. Вот тут можно посмотреть как выглядет репостинг, за одно подписаться

4 декабря, 2024

Очистка кэша Open Graph для VK и Telegram

Всем привет! Ранее я писал про Open Graph и как его поддержку можно просто реализовать на WordPress

В этой небольшой статье хочу поделится как можно быстро очистить кэш Open Graph в VK и Телеграмме. Такое может потребоваться, если ранее страница уже репостилась в телеграмм или Вконтакте и после внесения изменений в Open Graph мета теги это информация при будущих репостах не подтягивается в социальные сети.

Итак, для того чтобы очистить кэш для Вконтакте нужно запустить простой скрипт на PHP, который обратится к методу utils.resolveScreenName и передать в качестве параметра URL кэш которого нужно очистить. В качестве токена можно использовать токен сообщества, как его получить я писал тут.

Очистка кэша Open Graph для VK

<?php
// Укажите ваш токен доступа
$token = '***';

// URL страницы, которую нужно обновить
$url_to_clear = 'https://killercoder.ru';

// URL API ВКонтакте
$api_url = 'https://api.vk.com/method/';

// Функция для очистки кэша
function clearVkCache($url, $token)
{
    $api_url = 'https://api.vk.com/method/utils.resolveScreenName';
    $params = [
        'screen_name' => $url,
        'access_token' => $token,
        'v' => '5.131'
    ];

    // Отправка запроса
    $response = file_get_contents($api_url . '?' . http_build_query($params));
    $result = json_decode($response, true);

    // Проверка результата
    if (isset($result['error'])) {
        die('Ошибка: ' . $result['error']['error_msg']);
    }

    return $result;
}

// Очистка кэша
$response = clearVkCache($url_to_clear, $token);
if ($response) {
    echo "Кэш для ссылки обновлен успешно!";
} else {
    echo "Не удалось обновить кэш ссылки.";
}

Теперь этот скрипт нужно выполнить. Это можно сделать запустив консольный php-cli или обратившись HTTP запросом к веб серверу, который будет выполнять этот код.

Очистка кэша Open Graph для Telegram

Для очистки кэша Open Graph для Telegram необходимо запустить телеграмм бота — @WebpageBot

После чего скормить ему адрес ссылки для которой требуется сбросить кэш.

Ну вот и все. Следите за обновлением сайта в телеграм канале, а также присоединяйтесь к сообществу в ВК

1 декабря, 2024

Добавление Open Graph в блог на WordPress

Всем привет! Сегодня хочу рассказать о том, что такое Open Graph, для чего нужен и как его можно добавить в свой блог на Wodpress простым способом.

Open Graph — это специальный протокол, который был разработан в Facebook. В последствие на него стали использовать большинство популярных месенджеров и социальных сетей для отображения ссылок.

Для того чтобы добавить Open Graph на свой сайт, достаточно в html внутри тега <head> разместить блок с таким содержанием:

<meta property="og:title" content="Название страницы">
<meta property="og:description" content="Описание страницы">
<meta property="og:image" content="https://example.com/image.jpg">
<meta property="og:url" content="https://example.com">
<meta property="og:type" content="website">

По-своей сути это обычные мета теги, но с дополнительной микроразметкой og: которую понимаю социальные сети вроде вконтакте и месенджеры вроде телеграмм

Наверное есть множество готовых плагинов для редактирование open graph тегов в WordPress, но плагины это не мой путь, тем более доработка для Wodpress не просто простая, а очень простая.

Для того, чтобы сделать вывод мета тегов og нужно отредактировать файл темы functions.php

Для начала давайте пропишем событие при котором будет вызываться функция которая будет автоматически заполнять content мета тегов Open Graph и возвращать блок с заполненной разметкой.

Используем для этой цели событие wp_head(). Это событие вызывается при загрузки шапки сайта, как раз то что нужно.

add_action( 'wp_head', 'load_og_meta' );

А теперь описываем функцию load_og_meta():

function load_og_meta() {
    $og_meta = '';

    // Выводим только для страниц и постов
    if ( is_page() || is_single() ) {
        
        // Получаем пост 
        $post = get_post(get_queried_object_id());

        // Эта функция получает метки нашего поста, может быть полезна для социальных сетей вроде VK
        $tags = get_tags_to_message($post->ID);
        if (!empty($text)) {
            $tags .= PHP_EOL.$text.PHP_EOL;
        }

        // Получаем изображение поста, если оно есть
        if (has_post_thumbnail($post->ID)) {
            $image = wp_get_attachment_image_src( get_post_thumbnail_id( $post->ID ), 'single-post-thumbnail' );
        }

        // Тут немного приводим в порядок текст для описания
        $text = $tags . preg_replace('/[\n\r]+/s', "\n\n", strip_tags($post->post_content));
        if (strlen($text) > 500) {
            $text = mb_substr($text, 0, 500) . '...';
        }

        // Заполняем мета теги данными
        $og_meta .= '<meta property="og:title" content="' . $post->post_title. '" />' . PHP_EOL;
        $og_meta .= '<meta property="og:description" content="' . $text . '" />' . PHP_EOL;
        $og_meta .= '<meta property="og:url" content="' . get_permalink($post->ID) . '" />' . PHP_EOL;
        $og_meta .= '<meta property="og:type" content="URL" />' . PHP_EOL;
        $og_meta .= '<meta property="og:site_name" content="' . get_bloginfo('name') . '" />' . PHP_EOL;
        $og_meta .= '<meta property="og:locale" content="' . get_bloginfo('language') . '" />' . PHP_EOL;
        if (isset($image[0])) {
            $og_meta .= '<meta property="og:image" content="' . $image[0] . '" />' . PHP_EOL;
        }
    }
    echo $og_meta;
}

// функция получения меток
function get_tags_to_message($post_id) {
    $post_tags = get_the_tags( $post_id );
    $text = '';
    if (!empty($post_tags)) {
        foreach ($post_tags as $tag) {
            $text .= sprintf('#%s ', $tag->name);
        }
    }

    return $text;
}

Вот этот нехитрый и простой код добавит немного красоты, если вдруг ссылка поста окажется в сообщении телеграма или на стене сообщества в вк, вот примерно такая разница получается:

Если у вас есть какие-нибудь вопросы, то всегда можете написать мне в телеграм
Так же подписывайтесь на телеграм канал и если пользуетесь вконтакте, недавно была запущено сообщество, так что так же можете присоединятся.

,

19 октября, 2024

Пишем крестики нолики на javascript :)

Ради развлечения решил вспомнить немного JS и вот что в итоге получилось! Простая игра на Javascript — крестики нолики

Вот шаблон html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="x0.css">
    <script src="x0.js"></script>
</head>
<body>
<div class="status"></div>
<div class="gamespace"></div>
</body>
</html>

Код стилей x0.css

.gamespace .row * {
    box-sizing: border-box;
}
.gamespace .row {
    display: flex;
    flex-wrap: wrap;
}
.gamespace .col {
    width: 33%;
    position: relative;
    overflow: hidden;
    border: 3px solid #000;
}
.gamespace .col:after {
    content: "";
    padding-top: 100%;
    float: left;
}
.gamespace .item {
    position: absolute;
    width: 100%;
    height: 100%;
    display: flex;
    flex-flow: column;
    align-items: center;
    justify-content: center;
    color: #000;
    font-size: 8em;
}
.gamespace {
    width: 30%;
    margin: 0 auto;
}
.status {
    text-align: center;
    font-size: 2em;
}

И собственно сама бизнес логика приложения

window.addEventListener('load', function () {
    window.game = {
        finishGame: false,
        cells: 3,
        players: {
            zero: {
                value: 0,
                symbol: '0',
            },
            crosse: {
                value: 1,
                symbol: 'X',
            }
        },
        activePlayer: null,
        result: [
            [null, null, null],
            [null, null, null],
            [null, null, null]
        ],
        init: function() {
            this.activePlayer = this.players.crosse;
            this.createGameSpace();
            document.querySelector('.status').innerHTML = 'Ходит игрок: ' + this.activePlayer.symbol;
        },
        createGameSpace: function() {
            let gamespace = '';
            for(let i = 0; i < this.cells; i++) {
                gamespace += this.createRow(i);
            }
            document.querySelector('.gamespace').innerHTML = gamespace;
        },
        createRow: function(i) {
            let row = document.createElement('div');
            row.classList.add('row');
            for(let j =0; j < this.cells; j++) {
                let col = document.createElement('div');
                col.classList.add('col');
                col.insertAdjacentHTML('beforeend', this.createItem(i, j));
                row.insertAdjacentHTML('beforeend', col.outerHTML);
            }

            return row.outerHTML;
        },
        createItem: function(i, j) {
            let item = document.createElement('div');
            item.classList.add('item');
            item.dataset.row = i;
            item.dataset.col = j;

            return item.outerHTML;
        },
        switchPlayer: function() {
            this.activePlayer = this.activePlayer === this.players.zero ? this.players.crosse : this.players.zero;
        },
        finish: function() {
            this.finishGame = true;
        },
        checkWin: function() {
            let isWin = false;
            loop: for (let i = 0; i < this.result.length; i++) {
                for(let j =0; j < this.result[i].length; j++) {
                    if(this.winRow(i,j) || this.winCol(i,j)) {
                        isWin = true;
                        break loop;
                    }
                }
            }
            return isWin || this.winDia();
        },
        winRow: function(row,col) {
            return null !== this.result[row][col] && this.result[row][col] === this.result[row][col+1] && this.result[row][col] === this.result[row][col+2];
        },
        winCol: function(row,col) {
            return null !== this.result[row][col] && this.result[row][col] === this.result[row+1][col] && this.result[row][col] === this.result[row+2][col];
        },
        winDia: function() {
            return this.winDia1() || this.winDia2();
        },
        winDia1: function() {
            return this.result[0][0] === this.result[1][1] && this.result[0][0]  === this.result[2][2] && null !== this.result[0][0]
        },
        winDia2: function() {
            return this.result[0][2] === this.result[1][1] && this.result[0][2] === this.result[2][0] && null !== this.result[0][2];
        },
        showWinLine: function(isWin) {
            if (this.finishGame) {
                window.game.switchPlayer();
                document.querySelector('.status').innerHTML = 'Победил: ' + this.activePlayer.symbol;
            } else {
                document.querySelector('.status').innerHTML = this.isStandoff() ? 'Ничья!' : 'Ходит игрок: ' + this.activePlayer.symbol;
            }
        },
        isStandoff: function() {
            let standoff = true;
            for(let i = 0; i < this.result.length; i++) {
                for(let j = 0; j < this.result[i].length; j++) {
                    if (null === this.result[i][j]) {
                        standoff = false;
                    }
                }
            }
            return standoff;
        }
    }
    window.game.init();
    document.querySelectorAll('.item').forEach(function (e) {
        e.addEventListener('click', function () {
            if ('' === e.innerText && !window.game.finishGame) {
                e.innerText = window.game.activePlayer.symbol;
                window.game.result[parseInt(e.dataset.row)][parseInt(e.dataset.col)] = window.game.activePlayer.value;
                window.game.switchPlayer();
                window.game.showWinLine();
                let finishGame = window.game.checkWin();
                if (finishGame) {
                    window.game.finish();
                    window.game.showWinLine();
                }
            }
        });
    });
});

Код игры можно скачать отсюда — https://github.com/wikide/x0.git

,

6 октября, 2024

Создаем систему для партнерских программ в WordPress

Всем привет! В рамках реализации задачи по монетизации блога на wordpress мне пришла в голову мысль добавить партнерских программ на свой сайт, пертнерок тех компаний услагами которых я пользуюсь самостоятельно, но для реализации этой задачи нужно немного допилить wordpress, от сторонних плагинов по традиции решил отказаться потому что не хочу =) и к тому же для реализации такой простой задачи достаточно будет возможностей wordpress. Итак, поехали. Далее я опишу процесс создания системы партнерских программ для блога.

Для создания партнерок потребуется создать отдельный справочник записей, те зарегистрировать в системе еще один тип постов:

Открываем functions.php темы и добавляем туда что-то вроде:

add_action('init', 'register_post_types');

function register_post_types()
{
    register_post_type('partner', [
        'label' => null,
        'labels' => [
            'name' => 'Партнерки',
            'singular_name' => 'partner',
            'add_new' => 'Добавить партнерку',
            'add_new_item' => 'Добавление партнерки',
            'edit_item' => 'Редактирование пертнерки',
            'new_item' => 'Новая партнерка',
            'view_item' => 'Смотреть партнерку',
            'search_items' => 'Искать патнерку',
            'not_found' => 'Не найдено',
            'not_found_in_trash' => 'Не найдено в корзине',
            'parent_item_colon' => '',
            'menu_name' => 'Партнерка',
        ],
        'description' => '',
        'public' => true,
        'show_in_menu' => null,
        'show_in_rest' => null,
        'rest_base' => null,
        'menu_position' => null,
        'menu_icon' => null,
        'hierarchical' => false,
        'supports' => ['title', 'thumbnail'], // 'title','editor','author','thumbnail','excerpt','trackbacks','custom-fields','comments','revisions','page-attributes','post-formats'
        'taxonomies' => [],
        'has_archive' => false,
        'rewrite' => true,
        'query_var' => true,
    ]);
}

После сохранения в админке WP должен появится новый пункт ниже пунтка меню Записи с названием Партнерки. Теперь у нас есть новый зарегистрированный тип записей с минимальным набором полей ( название и изображение ) для добавления наших партнеров.

Но по-мимо изображения и название так же требуется добавления реферальной ссылки, для этого потребуется для нового типа с партнерами зарегистрировать кастомезированые поля записей, поэтому добавляем следующий код в functions.php

add_action('add_meta_boxes', 'my_extra_fields_meta_box', 1);

function my_extra_fields_meta_box()
{
    $post_type = 'partner';
    add_meta_box('extra_fields', 'Дополнительные поля', 'extra_fields_box_func', $post_type, 'normal', 'high');
}

function extra_fields_box_func($post)
{
    ?>
    <p>
        Ссылка:
        <label>
            <input style="width: 100%;" type="text" name="extra[link]"
                   value="<?= get_post_meta($post->ID, 'link', 1) ?>"/>
        </label>
    </p>
    <p>
        Комментарий:
        <textarea type="text" name="extra[description]"
                  style="width:100%; height:50px;"><?= get_post_meta($post->ID, 'description', 1) ?></textarea>
    </p>
    <p>
        Количество переходов:
        <input type="text" name="extra[count]" value="<?= get_post_meta($post->ID, 'count', 1) ?>" disabled="disabled">
    </p>
    <input type="hidden" name="extra_fields_nonce" value="<?= wp_create_nonce('extra_fields_nonce_id') ?>"/>
    <?php
}

Я решил для своей системы добавить еще 2 свойства партнера, комментарий — для внутреннего использования и подсчета количества переходов по моей реферальной ссылке.

Для того чтобы дополнительные поля сохранялись так же требуется добавить следующий хук

add_action('save_post', 'my_extra_fields_save_on_update', 0);

function my_extra_fields_save_on_update($post_id)
{
    // базовая проверка
    if (
        empty($_POST['extra'])
        || !wp_verify_nonce($_POST['extra_fields_nonce'], 'extra_fields_nonce_id')
        || wp_is_post_autosave($post_id) || wp_is_post_revision($post_id)
    ) {
        return false;
    }

    $extra = $_POST['extra'];

    // Все ОК! Теперь, нужно сохранить/удалить данные

    // Очищаем все данные
    $extra = array_map('sanitize_text_field', $extra);
    foreach ($extra as $key => $value) {
        // удаляем поле если значение пустое
        if (!$value) {
            delete_post_meta($post_id, $key);
        } else {
            update_post_meta($post_id, $key, $value); // add_post_meta() работает автоматически
        }
    }

    return $post_id;
}

Админка готова! Теперь необходимо доработать шаблоны темы, чтобы партнерские программ начали выводится на сайт.

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

function get_partner_random() {
    $query = new WP_Query([]);
    $query->query([
        'post_type' => 'partner',
        'post_status' => 'publish',
        'orderby' => 'rand',
        'posts_per_page' => '1'
    ]);

    return $query->posts[0] ?? null;
}

Так как я хочу увеличивать количество переходов на 1 каждый раз при переходе по моей реферальной ссылки требуется добавить функцию проксирования партнерских ссылок. Это можно сделать довольно просто добавив следующий хук в functions.php

add_action( 'init', 'init_proxy' );

function init_proxy() {
    if (
            isset($_GET['action']) &&
            $_GET['action'] == 'partner' &&
            isset($_GET['id']) &&
            is_numeric($_GET['id'])
    ) {
        $query = new WP_Query([]);
        $query->query([
            'post_type' => 'partner',
            'ID' => (int)$_GET['id'],
            'post_status' => 'publish',
            'posts_per_page' => '1'
        ]);
        $posts = $query->get_posts();
        if (!empty($posts[0]->ID)) {
            $count = (int)get_post_meta($posts[0]->ID, 'count', true);
            $count++;
            update_post_meta($posts[0]->ID, 'count', $count);
            header('Location: ' . get_post_meta($posts[0]->ID, 'link', true));
            exit;
        }
    }
}

Теперь уже точно можно перейти к шаблону сайта. Тут все зависит от сложности шаблона. В моем случае шаблон очень простой поэтому править потребуется не так много

Добавляем новый шаблон для партнерок:

<?php
/** @var WP_Post $partner */
$partner = get_partner_random();
?>
<?php if ($partner): ?>
<article>
    <header class="entry-header">
        <header class="entry-header">
            <h2 class="entry-title"><a href="<?php echo sprintf('/?action=partner&id=%d', $partner->ID) ?>" rel="bookmark"><?php echo $partner->post_title?></a></h2>
        </header>
        <div class="post-thumbnail">
                <a href="<?php echo sprintf('/?action=partner&id=%d', $partner->ID) ?>">
                    <?php echo get_the_post_thumbnail( $partner->ID, 'large'); ?>
                </a>
        </div><!-- .post-thumbnail -->
        <div class="nav-links">
            <div class="nav-next kc-nav-links">
                <a href="<?php echo sprintf('/?action=partner&id=%d', $partner->ID) ?>">Взглянуть →</a>
            </div>
        </div>
    </header>
</article>
<?php endif ?>

И далее добавляем логику вывода этого шаблона в index.php темы

get_template_part( 'header' );

				if ( have_posts() ) :

                    $i = 1;
					while ( have_posts() ) :

						the_post();

						get_template_part( 'content' );

						// If comments are open or we have at least one comment, load up the comment template.
						if ( comments_open() || get_comments_number() ) :
							comments_template();
						endif;

                        if ($i % 5 == 0) {
                            get_template_part( 'partner' );
                        }

                    $i++;
					endwhile;

				else :

					get_template_part( 'content', 'none' );

				endif;
				?>

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

                        if ($i % 5 == 0) {
                            get_template_part( 'partner' );
                        }

Этот код будет отображать рандомную партнерку после каждых 5 постов

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

А если вам нужна разработка под WP или любая другая помощь по php, можете написать мне в любое время в мой телеграмм на странице профайла

,

4 августа, 2024

Автопубликация постов WordPress в Telegram

Продолжаю развивать свой сайт. Мне захотелось, чтобы мои новые посты публиковались на канале сайта в телеграм, кстати, кто еще не подписался, подписывайтесь, чтобы следить за обновлениями на сайте.

Скорее всего для WordPress существует куча разных решений для репоста постов с сайта в телеграм, но зачем нам это если мы сами ж программисты, поэтому я сразу решил сделать все своими силами и не прибегать к какому либо готовому решению.

Итак, для репоста в свой телеграм канал, нам потребуется сайт на WordPress, телеграм канал и токен бота, который потребуется получить через папу ботов телеграмма — @BotFather

Открываем @BotFather пишим ему команду

/newbot

В ответ от вас потребуется придумать и ввести имя бота и имя бота с постфексом _bot
После этого папа ботов сгенерирует для вас токен для HTTP API

Этот токен мы и будем использовать для нашей системы репостинга обновлений на сайте

после этого требуется отредактировать wp-config.php и добавить туда константу:

define( 'TELEGRAM_BOT_TOKEN', '<TOKEN>' );

<TOKEN> заменить на тот токен, который мы получили при создании нового бота через @BotFather

После этого нам потребуется определить ID канала для того, чтобы передать нашему боту информацию о том в какой чат требуется отправлять сообщения. В моем случае, я использовал закрытый канал, поэтому мне потребовалось выполнить следующие действия для получения ID канала

  1. Добавляем в качестве администратора нашего новорожденного бота на наш канал
  2. Публикуем тестовый пост ( который потом будет не жалко удалить )
  3. Выполняем элементарный запрос curl
    curl —location ‘https://api.telegram.org/bot<TOKEN>/getUpdates’
  4. В ответ получаем ID нашего чата, который вставляем в wp-config.php
define( 'TELEGRAM_CHANNEL_ID',  <ID_CHANNEL> );

Теперь все готово к написанию нашего основного функционала!

Открываем нашу текущую тему WordPress ( предварительно скопировав ее в новую директорию, чтобы обновления темы не затерли наши изменения ) и находим там файл functions.php, в него мы и будем вносить изменения.

В данном случае нас интересуют только новые посты, поэтому мы будем использовать событие WordPress — save_post

Регистрируем новую функцию, которая будет выполнятся каждый раз, когда на сайте будет опубликован новый пост

add_action( 'save_post', 'send_telegram' );

Теперь, напишем код функции send_telegram

function send_telegram( $post_id ) {
    $post = get_post($post_id);
    $is_new = $post->post_date === $post->post_modified;
    if (  !$is_new || wp_is_post_revision( $post_id ) || $post->post_status != 'publish')
        return;
    message_to_telegram( $post );
}

Эта функция выполняет проверяет является ли пост новым публичным и не ревизией поста и если эти условия выполняются, то вызывается функция message_to_telegram

function message_to_telegram($post)
{
    $url = get_permalink($post->ID);
    $text = preg_replace('/[\n\r]+/s', "\n\n", strip_tags($post->post_content));
    if (strlen($text) > 500) {
        $text = substr($text, 0, 500) . '...';
    }
    $ch = curl_init();
    $ch_post = [
        CURLOPT_URL => sprintf('https://api.telegram.org/bot%s/sendMessage', TELEGRAM_BOT_TOKEN),
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 10,
        CURLOPT_POSTFIELDS => [
            'chat_id' => TELEGRAM_CHANNEL_ID,
            'parse_mode' => 'HTML',
            'text' => sprintf("<a href=\"%s\">%s</a> %s \n\n<a href=\"%s\">Взглянуть ?</a>", $url, $post->post_title, $text, $url),
        ]
    ];

    curl_setopt_array($ch, $ch_post);
    curl_exec($ch);
}

Эта функция выполняет отправку ссылку и первые 500 символов нового поста в канал телеграм

Ну вот и все, быстрое и минималистичное решение для уведомления о новых постах в телеграм канале. Как это работает можете увидеть на канале, обязательно подпишитесь на него, чтобы не пропустить обновление на сайте.

А если вас интересует создание ботов телеграм или создание сайта на движке wordpress то всегда можете написать мне в телеграм.

Docker + Wordpress

, ,

31 июля, 2024

Поднимаем локально докер для WordPress

Так как появилась задача развивать дальше этот блог, то пришлось решить вопрос с локальным развертыванием сайта локально, поэтому открываю новую рубрику на сайте под названием яждевопс.

Когда-то давно еще во допотоные времена до ядерной войны в 18 веке лет 5 назад мы делали это при помощи вирутальной машины, которая работала через vagrant или вообще разворачивали всю экосистему локально, но сейчас времена наступили другие и появилась докеризация

Если кратко, то докер — это такая прикрасная штука, которая позволяет делать все что позволяла делать виртуальная машина, но более гибко и с меньшими ресурсными затратами. Например можно собрать себе экосистему для запуска определенного программного обеспечения и даже организовать свою изолированную сеть внутри докер системы на вашей локальной машине. О возможностях докера каждый может найти в любой поисковой системе, информации достаточно.

Тут же я хотел продемонстрировать простое решение на докере, которое позволяет развернуть локально такой простой блок на WordPress как этот блог. Итак поехали.

Для начала создаем файл .env с таким содержимым

# префикс, который мы будем использовать для наименования контейнеров в докере
PREFIX=wp

# пароль root для локального контейнера с mysql
MYSQL_ROOT_PASSWORD=somewordpress

# название базы данных root для локального контейнера с mysql
MYSQL_DATABASE=wordpress

# имя пользователя базы данных для локального контейнера с mysql
MYSQL_USER=wordpress

# пароль пользователя базы данных для локального контейнера с mysql
MYSQL_PASSWORD=wordpress

# хоста базы данных на боевом сервере
MYSQL_REMOTE_HOST=remote_host_address

# название базы данных на боевом сервере
MYSQL_REMOTE_DATABASE=remote_wp

# имя пользователя базы данных на боевом сервере
MYSQL_REMOTE_USER=user_db_read

# пароль пользователя базы данных на боевом сервере
MYSQL_REMOTE_PASSWORD=password_db_read

Тут все понятно по комментариям, единственное, что может вызвать вопрос это данные удаленного сервер mysql. Эти данные понадобятся для синхронизации с рабочей базой данных, о котором я напишу ниже. Стоит только отметить, что я рекомендую создать отдельную учетную запись на стороне боевого сервера у которой будет возможность только чтения из базы данных, чтобы в случае утечки этих данных эти данные не могли бы использовать для изменений данных на боевом сервере.

Далее создаем docker-compose.yml

services:
  db:
    container_name: ${PREFIX}-db
    # We use a mariadb image which supports both amd64 & arm64 architecture
    image: mariadb:10.6.4-focal
    # If you really want to use MySQL, uncomment the following line
    #image: mysql:8.0.27
    command: '--default-authentication-plugin=mysql_native_password'
    volumes:
      - db_data:/var/lib/mysql
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
      - MYSQL_DATABASE=${MYSQL_DATABASE}
      - MYSQL_USER=${MYSQL_USER}
      - MYSQL_PASSWORD=${MYSQL_PASSWORD}
    expose:
      - 3306
      - 33060
    ports:
      - 3306:3306
      - 33060:33060
  wordpress:
    container_name: ${PREFIX}-wp
    image: wordpress:latest
    volumes:
      - ./www:/var/www/html
    ports:
      - 8082:80
    restart: always
    environment:
      - WORDPRESS_DB_HOST=db
      - WORDPRESS_DB_USER=${MYSQL_DATABASE}
      - WORDPRESS_DB_PASSWORD=${MYSQL_PASSWORD}
      - WORDPRESS_DB_NAME=${MYSQL_DATABASE}
      - WORDPRESS_DEBUG=false
volumes:
  db_data:

Это основной файл нашей докер системы при использовании которого будет запущено 2 контейнера, контейнер с базой данных mysql и контейнер с образом wordpress

Все настройки перечислять не буду ( почитаете документацию если будет кому нужно ) отмечу только то что в ports: мы биндим порты на локальную машину http будет доступен на порте 8082, а mysql на порту 3306 так же хочу отметить еще что в секции valumes: контейнера wordpress мы биндим локальную директорию ./www в которой будут хранится файлы wordpress. В нее нужно будет развенуть архив с wordpress или скопировать файлы wordpress с удаленного хостинга.

В итоге у нас получается вот такая директория проекта с файлом .env, docker-compose.yml и директорией www с файлами wordpress, если не хотите прописывать данные доступов к базе данных вручную, то перед запуском сборки докера удалите файл wp-config.php. Контейнер wordpress создаст его вручную и пропишет в него доступы из docker-compose.yml

Далее запускаем вот такую простую команду

$ docker compose up -d

Наслаждаемся процессом скачки образов и сборки контейнеров

Если вам повезло, то после этого можете зайти по адресу http://localhost:8082 откроется ваш wordpress

В принципе на этом можно было бы и закончить, но я настолько ленив, что мне захотелось создать несколько удобных команд для моего проекта, чтобы автоматизировать запуск, пересборку и установку докера, а так же синхронизацию удаленной базы данных ( не зря же я добавил в .env файл параметры MYSQL_REMOTE_*

Итак, чтобы автоматзировать рутинные задачи создадим Makefile.

Makefile — это такая крутая штука, которая была придумана еще в далкие времена, когда программы компилировали, а программисты еще знали как очищать в своих программах память от мусора и что такое язык С. В Makefile можно перечислять инструции которые потом можно выполнять в той директории в которой присутствует этот файл, вот такой Makefile у меня получился

include .env
export
.DEFAULT_GOAL = help
.PHONY: help

help:
	@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'

up:
	@docker compose up -d

build:
	@docker compose up --build -d

down:
	@docker stop `docker ps -a -q`

shell:
	@docker exec -it $(PREFIX)-wp bash

drop-db:
	@docker exec -it $(PREFIX)-db mysql -e 'DROP DATABASE $(MYSQL_DATABASE) ; CREATE DATABASE $(MYSQL_DATABASE)' -uroot -p$(MYSQL_ROOT_PASSWORD)

sync: drop-db
	@ssh $(MYSQL_REMOTE_HOST) mysqldump -u$(MYSQL_REMOTE_USER) -p$(MYSQL_REMOTE_PASSWORD) $(MYSQL_REMOTE_DATABASE) | docker exec -i $(PREFIX)-db mysql -u$(MYSQL_USER) -p$(MYSQL_PASSWORD) $(MYSQL_DATABASE)

После этого чтобы запустить наш докер с wordpress достаточно будет ввести команду

$ make up

Для того чтобы пересобрать контейнеры

$ make down

Остановить докер

$ make down

И самая интересная команд, команда синхронизации базы данных докера с базой данных боевого сервера

$ make sync

Она удаляет полностью базу данных в докере и полностью забирает ее с боевого хостинга, после этой операции у вас в контейнере базы данных будет полная копия базы боевого сайта с wordpress

В целом это все. Если вдруг возникнут какие-то вопросы и замечания, то можете писать в комменатрии или мне в телеграм, так же если кому-то требуется разработка сайта на wordpress пишите сюда :)

,

29 июля, 2024

Запиливаем удобный блок донатов в WordPress

Итак, решил реанимировать свой сайт и заодно подправить страницу профайл и добавил туда блок помощи, кстати, если есть возможность, то можете воспользоваться этой возможностью и поддержать меня.

Сначала добавил обычный текстовый блок, но вряд ли кто-то станет выделять и копировать текст для того чтобы отправить куда-то деньги, тогда я захотел немного упросить процесс и решил подключить javascript для решения этой задачи. От поиска готового решения для WP отказался так как для такой ерунды это не целесообразно, а всевозможные расширения и специальные плагины еще и слишком ресурсоемкие, к тому же я вспомнил что Я Ж программист и решил самостоятельно на коленки сделать свое решение. Как по мне так получилось достаточно неплохо, результат можете посмотреть тут ( а заодно и воспользоваться функционалом =) )

Итак, вот код:

Добавляем в стили шаблона css

.term {
	cursor: pointer;
	color: #2f8eff;
	text-decoration: none;
	border-bottom: 1px dashed #2f8eff;
}
.term:after {
	padding-left: 3px;
	content: url('/uploads/donate/copy.svg');
	display: inline-block;
	width: 20px;
	height: 20px;
}
.tooltiped {
	position: relative;
}
.tooltiped .tooltip {
	position: absolute;
	right: 0;
	top: 0;
	visibility: hidden;
	opacity: 0;
	transition: ease 1s;
	background-color: #FFF;
	z-index: 999;
}
.tooltiped:hover .tooltip {
	position: absolute;
	top: 20px;
	visibility: visible;
	opacity: 1;
}
.tooltiped .tooltip .tooltip-content {
	max-width: 150px;
}
.tooltiped .tooltip .tooltip-content img {
	width: 200px;
}
.tooltiped .tooltip .tooltip-content,
.tooltiped .tooltip .tooltip-content a {
	color: #fff;
}
.promp {
	border-radius: 3px;
	background: rgba(47, 142, 255, 0.5);
	position: absolute;
	top: -40px;
	right: -100px;
	padding: 3px;
}

Добавляем в шаблон небольшой javascript:

    window.onload = () => {
        document.querySelectorAll(".term").forEach(function(e) {
            e.addEventListener("click", function() {
                navigator.clipboard.writeText(e.innerText).then(function() {
                    e.insertAdjacentHTML('beforebegin', '<div class="promp">скопировано</div>');
                    setTimeout(function(){
                        document.querySelectorAll(".tooltiped .promp").forEach(function(p) {
                            p.remove();
                        });
                    },1000);
                }).catch(function (error) {
                    console.error('Error:', error);
                });
            });
            e.addEventListener("mouseover", () => {
                e.insertAdjacentHTML('afterEnd', '<div class="tooltip"><div class="tooltip-content"><img src="' + e.dataset.url + '" /></div></div>');
            });
        });
    }

Ну и собственно блок с реквизитами:

<p>
    <span class="tooltiped">
        <strong>Карта ( Т-банк ):</strong> <span class="term" data-url="">5536913774062992</span><br>
    </span>
    <span class="tooltiped">
        <strong>USDT (Сеть Solana):</strong> <span class="term" data-url="/uploads/donate/USDT.jpg">HEzoskh3Gmehztu578DWFzq3PBmsoCZSVkzM25AYtCmQ</span> <br>
    </span>
    <span class="tooltiped">
        <strong>BTC:</strong> <span class="term" data-url="/uploads/donate/BTC.jpg">bc1qmnze7qda4eutpzyzjesyr05p0cf4myfrppqz2f</span><br>
    </span>
    <span class="tooltiped">
        <strong>TON:</strong> <span class="term" data-url="/uploads/donate/TON.jpg">UQC7ZauWx4qlSknEd-Ag9NEF5jXq3QT7sd8nJtWRPgWdWBj0</span><br>
    </span>
    <span class="tooltiped">
        <strong>Litecoin:</strong> <span class="term" data-url="/uploads/donate/LTC.jpg">ltc1qlphwcvh0lzmewuupjas3k2uh6d5sjdpcnxyvp0</span><br>
    </span>
    <span class="tooltiped">
        <strong>Ethereum:</strong> <span class="term" data-url="/uploads/donate/ETH.jpg">0x834C17E4bd362AAd6Ba85A665197DE3111f9b7D2</span><br>
    </span>
    <span class="tooltiped">
        <strong>DASH:</strong> <span class="term" data-url="/uploads/donate/DASH.jpg">XgkG5z3zoUATHCKWAP9W76qMTR8938J5ev</span><br>
    </span>
    <span class="tooltiped">
        <strong>XRP:</strong> <span class="term" data-url="/uploads/donate/XRP.jpg">rLnU6E5DSpWNeo2xEDfARpo2jDR1ymWRkh</span><br>
    </span>
    <span class="tooltiped">
        <strong>XLM:</strong> <span class="term" data-url="/uploads/donate/XLM.jpg">GDV3OMVJYL6LJI2HKXNBKQYYVTBQ3LY2LOX2DZVNCYPXV26JI4BZJNLX</span><br>
    </span>
    <span class="tooltiped">
        <strong>HBAR:</strong> <span class="term" data-url="/uploads/donate/HBAR.jpg">0.0.5028861</span><br>
    </span>
    <span class="tooltiped">
        <strong>Solana:</strong> <span class="term" data-url="/uploads/donate/SOL.jpg">HEzoskh3Gmehztu578DWFzq3PBmsoCZSVkzM25AYtCmQ</span><br>
    </span>
    <span class="tooltiped">
        <strong>Cardano:</strong> <span class="term" data-url="/uploads/donate/ADA.jpg">addr1v9pkq5weeqz33kc8aljul5wj4yslr8zxe6flwfan2psvquszy73dg</span>
    </span>
</p>

Готово! =)

31 мая, 2015

Мониторим домены

Небольшой скрипт на php для мониторинга освободившихся доменов в помощь лентяям.

<?php

$domains = array(
    'domain1.ru',
    'domain2.ru'
);

$email = 'my@email.com';

foreach($domains as $domain)
{
    $inf = check_domain($domain);
    if(!isset($inf['free-date']))
    {
        $text = 'DOMAIN ' . $domain . ' FREE';
        mail($email,$text, $text);
    }
}

function check_domain( $domain )
{
    $curl = curl_init();
    curl_setopt( $curl, CURLOPT_URL,'r01.ru/domain/whois/check-domain.php?domain=' . $domain);
    curl_setopt($curl,CURLOPT_RETURNTRANSFER,true);
    curl_setopt($curl,CURLOPT_FOLLOWLOCATION,true);
    $result = curl_exec($curl);
    $info = array();
    $infoField = array(
        'free-date',
        // 'domain','type','descr','state','nic-hdl','person','phone','fax-no','e-mail','reg-till','created','changed', 'registrar'
    );
    foreach($infoField as $field)
    {
        preg_match('#' . $field . ': (.*)#',$result,$m);
        $info[$field] = isset($m[1]) ? strip_tags($m[1]) : '';
    }
    return $info;
}

Запускать скрипт по крону, раз в 5-10 минут.

PS Письма по-умолчанию будут падать в спам.

,

9 апреля, 2014

URL без завершающих слэшей и www

Часто бывает нужно убрать www и завершающие слэши из всех URL. Данную операцию достаточно просто выполнить при помощи кода в .htaccess

#Если вы хотите, чтобы всегда был домен с www
RewriteCond %{HTTP_HOST} ^domen\.ru
RewriteRule ^(.*)$ http://www.domen.ru/$1 [R=301,L]
#Если вы хотите, чтобы всегда был домен без www
RewriteCond %{HTTP_HOST} ^www\.domen\.ru
RewriteRule ^(.*)$ http://domen.ru/$1 [R=301,L]
#Добавить завершающий слэш
RewriteBase /
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_URI} !(.*)/$
RewriteRule ^(.*[^/])$ $1/ [L,R=301]
#Удалить завершающий слэш
RewriteBase /
RewriteCond %{HTTP_HOST} (.*)
RewriteCond %{REQUEST_URI} /$ [NC]
RewriteRule ^(.*)(/)$ $1 [L,R=301]

12 мая, 2013

Расширение всех файлов в XML формате

Не нашел нигде список всех расширений файлов, но зато наткнулся на сайт http://open-file.ru в котором приведен весь список расширений. Решил написать небольшой скрипт, который вытянет все расширения с описаниями и типами в XML файл.

<?php

set_time_limit(0);
$url = 'http://open-file.ru';
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HEADER, 0);
$result = curl_exec($ch);
preg_match('~<p>(.*)</p>~',$result,$match);
preg_match_all('~<a href="(/types/.*/)">.*</a>~Us',$match[1],$match);
$extension = array();
$xml = '<?xml version="1.0" encoding="UTF-8"?>
<extensions>';
foreach( $match[1] as $link )
{
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, ( $url . $link ));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($ch, CURLOPT_HEADER, 0);
    $result = curl_exec($ch);
    preg_match( '~<table>(.*)</table>~Us', $result, $table );

    $reg = array(
        '~<tr><td>.*<a href="/types/.*">(.*)</a>.*</td>',
        '<td>.*<a href="/types/.*/">(.*)</a>.*</td><td>',
        '.*<a href="/types/.*">(.*)</a>.*</td></tr>~'
    );

    preg_match_all(implode($reg),$table[1],$data);
    foreach( $data[1] as $key => $ext )
    {
        $xml .= '
            <extension>
                <name><![CDATA[' . $ext . ']]></name>
                <type><![CDATA[' . $data[2][$key] . ']]></type>
                <description><![CDATA[' . $data[3][$key] . ']]></description>
           </extension>
';
   }
}

$xml .= '</extensions>';

file_put_contents('tmp/extensions.xml' , iconv( 'cp1251', 'utf8', $xml ) );

,

17 февраля, 2013

Скрипт для отправки обновлений на почту

Для одного проекта понадобилось высылать заказчику обновления ( pack ), так как это рутинная задача, я решил написать простой скрипт на shell:

 

#!/bin/sh 

# Мыльник куда отправляем пакет с обновлениями 
EMAIL=$1 

# Директория с проектом 
PROJECT=/var/www/my_project 

# Дата ( для заголовка письма и названия файла архива с обновлением ) 
DATE=`date +%d-%m-%Y_%H-%M` 

# Название файла архива с обновлением 
TARNAME=pack_my_project_$DATE.tgz 

# Локальная директория в которой сохраняются архивы с обновлениями 
PACK=pack/$TARNAME 

# Изменения в проекте ( git ) 
CHANGE=`cd $PROJECT && git diff --stat | awk 'NF != 7 {print $1}' && cd - > /dev/null` 

# Тема письма с архивом обновления 
SUBJECT='Обновление project от $DATE' 

# Тело письма, опционально 
BODY='' 

# Создаем архив с изменениями и отправляем письмо
cd $PROJECT && \
tar czf $PACK $CHANGE > /dev/null && \
( ( echo $BODY $CHANGE ); uuencode $PACK $TARNAME ) \ 
| mail -s "Обновление project от $DATE" $EMAIL && \
cd - > /dev/null 

в .zshrc прописываем путь к директории к скрипту и ставим разрешение на выполнение файла

Отправляем:

 $ my_project_update.sh test@gmail.com 

Сообщение с паком уйдет на почту.

TODO: Мою задачу скрипт решает полностью, но его можно доработать:
1) проверка наличия параметра $1
2) проверка, что в проекте реально есть изменения

, ,

Быстрое добавление множества директорий в .gitignore

Часто в проекте есть директория uploads/ с вложениями, содержимое в которых не нуждаются в поддержки версионности, для того чтобы не утомлять себя ручным вводом каждой директории можно воспользоваться командой:

 $ ls -l uploads | awk {'print "/uploads/"$9"/*"'} > ../.gitignore 

Дело сделано, нужные директории теперь не будут включены в репозитарий.

19 декабря, 2010

Шелл на PHP

Небольшая тулза на PHP+Ajax, которая позволяет выполнять shell команды.

<?php
if (isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH'] == 'XMLHttpRequest') {
    if (!empty($_POST['command'])) {
        exec($_POST['command'], $result);
        if (is_array($result)) {
            foreach ($result as $str)
                $res[] = htmlspecialchars($str);
            $res = isset($res) ? implode("<br />", $res) : '';
        } else {
            $res = $result;
        }
        echo $res;
    }
    exit();
}
?>

<!DOCTYPE HTML>
<html>
<head>
    <title>Shell</title>
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
    <script type="text/javascript">
        function clear(c) {
            c.attr("value", "");
        }

        $(document).ready(function () {
            var c = $("input[name='command']");
            clear(c);
            c.focus(function () {
                $("span").hide();
            });
            c.blur(function () {
                $("span").show();
            });
            c.keyup(function (event) {
                if (event.keyCode == 13)
                    $.post('sh.php', {
                        command: $(this).val()
                    }, function (data) {
                        $("div").html(data);
                        clear(c);
                    });
            });
        });
    </script>
    <style type="text/css">
        * {
            background: #000;
            border: 0;
            color: green;
            font: 62.5% Arial, Helvetica, sans-serif;
            width: 100%;
        }

        body {
            font-size: 1.5em;
            margin: 0;
            padding: 0;
        }

        input {
            background-color: #000;
            font-size: 1em;
            width: 80%;
        }

        div {
            background: #000;
            font-size: 1em;
            height: 100%;
            min-height: 20px;
            width: 90%;
        }

        strong {
            color: #fff;
        }

        hr {
            color: #fff;
            background-color: #fff;
            height: 1px;
            margin-top: 0;
            width: 100%;
        }

        blink {
            font-size: 1.5em;
        }

        p {
            background: #fff url(http://www.catb.org/~esr/hacker-emblem/glider.png) no-repeat top right;
            display: block;
            height: 55px;
            right: 20px;
            position: absolute;
            width: 55px;
        }
    </style>
</head>
<body>
<?php system('hostname')?>&gt;<span><blink>_</blink></span>
<input name="command"/>
<hr/>
<p></p>
<div></div>
</body>
</html>

28 июля, 2010

Скрипт добавления нового виртуального хоста

Как-то поднадоело мне постоянно редактировать вручную httpd.conf и /etc/hosts и я решил написать скрипт, который бы позволил автоматизировать этот процесс
Вот такой скрипт:

#!/bin/sh

# Константы:
HOSTS='/tc/hosts'; # Путь к файлу hosts
HTTPD_CONF='/etc/httpd/conf/extra/httpd-vhosts.conf';

# Путь к файлу с настройками виртуальных хостов
DIR_HTTP='/srv/http/'; # Путь к директориям с хостами
IP_MASK='127.0.0.'; # Маска по которой будет формироваться новый IP
INDEX_FILE='index.php'; # Index файл в корневой папке нового виртуального хоста
DIR_LOG='/var/log/httpd/'; # Путь к папке с логами
EMAIL_ADMIN='wikide@gmail.com'; # Email админестратора

# Функция вывода ошибок
get_error() {
    echo $1;
    exit 0;
}

# Функция проверки ip
check_ip() {
    if [ "`cat $HOSTS | grep \"$ip\"`" ]; then
        echo 1;
    fi
}

# Проверяем наличея параметра с именем хоста
if [ "$1" ]; then
    if [ "$2" ]; then
        # Если задан свой IP
        new_ip=$2;
        if [ "`check_ip $new_ip`" ]; then # Проверка на наличее в файле
            get_error 'Такой ip адрес - '$new_ip' уже занят. Введите другой или пусть скрипт '$0' сам сгенерирует ip адресс для нового хоста.';
        fi;
    else
        # Получаем IP автоматически
        new_ip=$IP_MASK$((`tail -1 $HOSTS | awk '{ split($1, ip, "."); print ip[4]}'`+1));
    fi

    # Если корневая папка нового хоста уже существует
    if [ -d $DIR_HTTP$1/ ]; then
        get_error 'Директория '$1' уже есть в '$DIR_HTTP'. Подберите другое имя.';
    else
        # Создаем корневую директорию виртуального хоста
        mkdir $DIR_HTTP$1;
        echo -e "
    <html>
     <head>
      <title>New host</title>
     </head>
     <body>
        <h1>Created host '$1'!</h1>
     </body>
    </html>" > $DIR_HTTP$1'/index.php';
  
    # Запись в hosts
        sudo echo $new_ip  $1 | sudo tee -a $HOSTS 1>/dev/null;

    # Запись в конфиг Apache
        sudo echo -e "#Create date: `date '+%F %T'`
          DirectoryIndex $INDEX_FILE
          ServerAdmin $EMAIL_ADMIN
          DocumentRoot \"$DIR_HTTP$1/\"
          ServerName $1
          ServerAlias $1
          ErrorLog \"$DIR_LOG$1.log\"
          CustomLog \"$DIR_LOG$1\" common" | sudo tee -a $HTTPD_CONF 1>/dev/null;

        # Перезапускаем сервер
        sudo apachectl restart;

        # Выводими сообщение

        echo 'Виртуальный хост "'$1'" успешно создан'
    fi
else
    get_error 'Нужно обязательно указать название нового хоста $0 NAME_HOST [IP_HOST]';
fi

exit 0;

,

27 июля, 2010

Скрипт запуска wifi в ARCH Linux

Небольшой скрипт для подключения к сетям wifi в ARCH linux

#!/bin/bash

# Определяем интрфейс ( в моем случае это - wlan0)
if [ "$2" ]; then 
	dev=$2;
else 
	dev="wlan0";
fi

# Действие
case "$1" in
	start)
		sudo ifconfig $dev up;
		sudo wpa_supplicant -B -i$dev -c/etc/wpa_supplicant.conf;
		sleep 3;
		sudo dhcpcd $dev 2&gt;/dev/null;
	;;
	stop)	
		sudo killall -9 dhcpcd 3&gt;/dev/null;
		sudo killall -9 wpa_supplicant 3&gt;/dev/null;
		sudo ifconfig $dev down;
	;;
	list)
		sudo ifconfig $dev up;
		sleep 1;
		sudo iwlist $dev scanning;
		sudo ifconfig $dev down;
	;;
	restart)
		$0 stop;
		$0 start;
	;;
	status)
		echo -n 'IP arrdess: '; ifconfig wlan0 | grep "inet addr" | awk '{print $2}' | sed 's/addr://'
		echo -n "Essid: "; iwconfig wlan0 | grep "Ralink STA" | awk '{print $4}' | sed 's/ESSID://'
	;;
	*)

	if [ "$1"  ]; then
		echo "usage: $0 {start|stop|list|restart}"; 
	else 
		echo "Restarting";
		$0 restart;
	fi

esac

exit 0

Вот такой вот скрипт :)