initial
This commit is contained in:
commit
1f919eaef0
132
README.md
Normal file
132
README.md
Normal file
@ -0,0 +1,132 @@
|
||||
# vk-messages-post-archive
|
||||
|
||||
> **Дисклеймер:** информация актуальна на осень 2020 года. Может, во ВКонтакте уже всё поменялось и скрипт более
|
||||
> не нужен, а может, поменялся формат отдаваемого архива и скрипт не сработает. Смотрите сами.
|
||||
|
||||
Во ВКонтакте существует [функция](https://vk.com/data_protection?section=rules&scroll_to_archive=1) выгрузки информации
|
||||
в ZIP-архиве, в т.ч. всех сообщений. Сделана она как попало: история сообщений, полученная таким образом, содержит
|
||||
только текст самих сообщений (там даже нет пересланных! сложно было положить, что ли?), ссылки на фото и нерабочие (!)
|
||||
ссылки на документы. Но лучше так, чем никак.
|
||||
|
||||
Этот репозиторий содержит простенькие инструменты и инструкции, с помощью которых можно дополнительно выкачать все
|
||||
фотографии из истории, а также получить и сохранить объекты сообщений с вложениями из API (рекомендуется как минимум
|
||||
для получения рабочих ссылок на документы).
|
||||
|
||||
Это проще, чем выкачивать всё через API с нуля. Пусть ВКонтактик соберёт нам свой архив, там уже есть более-менее
|
||||
нормальная навигация и оформление, а мы потом просто скачаем все, чего не хватает. Все-таки простых текстовых сообщений
|
||||
без вложений должно быть на порядок больше.
|
||||
|
||||
# Системные требования
|
||||
|
||||
- bash
|
||||
- PHP >= 7.1
|
||||
- composer
|
||||
- php-iconv
|
||||
- php-curl
|
||||
- наверное, неплохой идеей будет включить opcache для cli в `php.ini` (путь которого можно узнать через `php --ini`):
|
||||
```
|
||||
opcache.enable=1
|
||||
opcache.enable_cli=1
|
||||
opcache.file_cache=/tmp/php-opcache
|
||||
```
|
||||
- iconv
|
||||
- wget
|
||||
- sqlite3
|
||||
- стандартные утилиты типа cat, find, grep, xargs и т.д.
|
||||
|
||||
# "Поехали", как говорил Юра
|
||||
|
||||
## Скачиваем фоточки
|
||||
|
||||
Склонили репозиторий, устанавливаем зависимости:
|
||||
```
|
||||
git clone https://github.com/gch1p/vk-messages-post-archive
|
||||
cd vk-messages-post-archive
|
||||
composer install
|
||||
```
|
||||
|
||||
Перейти в папку с распакованным архивом от ВК:
|
||||
```
|
||||
cd path/to/Archive
|
||||
```
|
||||
|
||||
Запускаем нехитрый парсер:
|
||||
```
|
||||
for f in $(find messages -type f -name "*.html"); do
|
||||
cat "$f" | iconv -f windows-1251 | php path/to/get-attaches.php >> to-download.txt
|
||||
done
|
||||
```
|
||||
В файле `to-download.txt` будут ID сообщений с аттачами вперемешку со ссылками на фотки.
|
||||
|
||||
Выкачиваем фотки. `-P8` означает, что будет параллельно запущено 8 процессов wget. Можете написать другое число.
|
||||
```
|
||||
cat to-download.txt | grep https | xargs -P8 wget -x -i
|
||||
```
|
||||
|
||||
Заменяем ссылки (`<a>`) на изображения (`<img>`). Тут в `-P` разумно передать количество ядер.
|
||||
```
|
||||
find messages -type f -name "*.html" | xargs -I{} -P4 php path/to/replace-photos.php "{}"
|
||||
```
|
||||
|
||||
## Достаём остальную информацию из API
|
||||
|
||||
Теперь давайте залезем в API. ВК недавно отключил сторонним приложениям доступ к сообщениям, но кого это остановит?
|
||||
Скачайте [десктопный мессенджер](https://vk.com/messenger), авторизуйтесь в нём, потом закройте и зайдите в папку, где
|
||||
он хранит конфиг (`~/.config/VK` на Linux, `~/Library/Application Support/VK` на маке, на винде по логике должно быть
|
||||
`%APPDATA%\Roaming\VK`), там будет sqlite3 база `vk.db`. Зайдём в неё:
|
||||
```
|
||||
sqlite3 vk.db
|
||||
```
|
||||
|
||||
Осмотримся.
|
||||
```
|
||||
sqlite> .tables
|
||||
auth debug_api_fails recents users
|
||||
communities friends settings
|
||||
```
|
||||
|
||||
Хм, табличка `auth` выглядит многообещающе. Посмотрим структуру:
|
||||
```
|
||||
sqlite> .schema auth
|
||||
CREATE TABLE auth (
|
||||
id INTEGER PRIMARY KEY,
|
||||
user_id INTEGER,
|
||||
ts INTEGER,
|
||||
access_token TEXT
|
||||
, is_encrypted INTEGER);
|
||||
```
|
||||
|
||||
О! То, что нужно. Нам нужен `access_token`.
|
||||
```
|
||||
sqlite> SELECT access_token FROM auth;
|
||||
```
|
||||
|
||||
Скопируйте токен и можно выходить (`Ctrl+D`) отсюда.
|
||||
|
||||
Теперь откройте скрипт `common.php` и пропишите этот токен в константу `ACCESS_TOKEN`, а так же путь к папке
|
||||
`Archive` в константу `ARCHIVE_DIR`.
|
||||
|
||||
После этого можно запустить скрипт `fetch-messages.php` для слива объектов сообщений. Они будут сохраняться в папку `api`
|
||||
с именами `{dir}/{id}.txt`, где `{id}` – это ID сообщения, а `{dir}` – это остаток от деления ID сообщения на 100.
|
||||
|
||||
Метод [messages.getById](https://vk.com/dev.php?method=messages.getById) вконтактовского API может вернуть до 100
|
||||
сообщений за 1 запрос, плюс нужно учитывать стандартное ограничение на 3 запроса в секунду, иначе нам прилетит капча.
|
||||
`xargs` поможет запустить не более 3-х инстансов скрипта за раз и передать не более 100 идентификаторов в каждый, а
|
||||
`sleep(1)` сделает сам скрипт в конце.
|
||||
|
||||
```
|
||||
cat to-download.txt | grep -v https | xargs -n100 -P3 php path/to/fetch-messages.php
|
||||
```
|
||||
|
||||
Теперь добавим возможность просмотра этих объектов на страничках истории.
|
||||
```
|
||||
find messages -type f -name "*.html" | xargs -I{} -P4 php path/to/insert-api-objects.php "{}"
|
||||
```
|
||||
|
||||
## Скачиваем документы
|
||||
|
||||
```
|
||||
php fetch-documents.php
|
||||
```
|
||||
|
||||
Ну вот, вроде, и всё.
|
95
common.php
Normal file
95
common.php
Normal file
@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/vendor/autoload.php';
|
||||
|
||||
error_reporting(E_ALL);
|
||||
ini_set('display_errors', 1);
|
||||
|
||||
define('FAKE_USER_AGENT', 'User-Agent: VKDesktopMessenger/5.0.1 (darwin; 19.6.0; x64)');
|
||||
define('ACCESS_TOKEN', '');
|
||||
define('ARCHIVE_DIR', '');
|
||||
|
||||
function fatalError(string $message) {
|
||||
fprintf(STDERR, "error: ".$message."\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $str
|
||||
* @param callable|null $message_callback
|
||||
* @param callable|null $photo_callback
|
||||
* @return simplehtmldom\HtmlDocument
|
||||
* @throws Exception
|
||||
*/
|
||||
function onEachMessageOrAttachment(string $str, ?callable $message_callback, ?callable $photo_callback) {
|
||||
$doc = new simplehtmldom\HtmlDocument($str,
|
||||
/* $lowercase */ true,
|
||||
/* $forceTagsClosed */ true,
|
||||
/* $target_charset */ simplehtmldom\DEFAULT_TARGET_CHARSET,
|
||||
/* $stripRN */ false
|
||||
);
|
||||
if (!$doc)
|
||||
throw new Exception('failed to parse html');
|
||||
|
||||
$nodes = $doc->find('.message');
|
||||
if (!count($nodes))
|
||||
throw new Exception('no message nodes found');
|
||||
|
||||
foreach ($nodes as $node) {
|
||||
$kludges = $node->find('.kludges');
|
||||
if (empty($kludges))
|
||||
continue;
|
||||
|
||||
$attachments = $kludges[0]->find('.attachment');
|
||||
if (empty($attachments))
|
||||
continue;
|
||||
|
||||
$message_id = $node->getAttribute('data-id');
|
||||
if (!is_null($message_callback))
|
||||
$message_callback($doc, $message_id, $node);
|
||||
|
||||
foreach ($attachments as $attachment) {
|
||||
$desc = $attachment->find('.attachment__description');
|
||||
if (empty($desc) || $desc[0]->innertext != 'Фотография')
|
||||
continue;
|
||||
|
||||
$link_node = $attachment->find('a.attachment__link');
|
||||
if (!$link_node)
|
||||
continue;
|
||||
|
||||
$href = $link_node[0]->href;
|
||||
if (strpos($href, 'https://vk.com/im?sel') !== false)
|
||||
continue;
|
||||
|
||||
if (!is_null($photo_callback))
|
||||
$photo_callback($doc, $href, $link_node[0]);
|
||||
}
|
||||
}
|
||||
|
||||
return $doc;
|
||||
}
|
||||
|
||||
function httpPost(string $url, array $fields = []): array {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, true);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $fields);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, FAKE_USER_AGENT);
|
||||
$body = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return [$code, $body];
|
||||
}
|
||||
|
||||
function httpGet(string $url): array {
|
||||
$ch = curl_init();
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, FAKE_USER_AGENT);
|
||||
$body = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
return [$code, $body];
|
||||
}
|
15
composer.json
Normal file
15
composer.json
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
"minimum-stability": "dev",
|
||||
"prefer-stable": true,
|
||||
"require": {
|
||||
"simplehtmldom/simplehtmldom": "^2.0@RC",
|
||||
"ext-curl": "*",
|
||||
"ext-json": "*",
|
||||
"ext-iconv": "*"
|
||||
},
|
||||
"config": {
|
||||
"platform": {
|
||||
"php": "7.1"
|
||||
}
|
||||
}
|
||||
}
|
96
composer.lock
generated
Normal file
96
composer.lock
generated
Normal file
@ -0,0 +1,96 @@
|
||||
{
|
||||
"_readme": [
|
||||
"This file locks the dependencies of your project to a known state",
|
||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||
"This file is @generated automatically"
|
||||
],
|
||||
"content-hash": "6e4d59c1ab45b11a7233ab1e77f9f0df",
|
||||
"packages": [
|
||||
{
|
||||
"name": "simplehtmldom/simplehtmldom",
|
||||
"version": "2.0-RC2",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/simplehtmldom/simplehtmldom.git",
|
||||
"reference": "3c87726400e59d8e1bc4709cfe82353abeb0f4d1"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/simplehtmldom/simplehtmldom/zipball/3c87726400e59d8e1bc4709cfe82353abeb0f4d1",
|
||||
"reference": "3c87726400e59d8e1bc4709cfe82353abeb0f4d1",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
"ext-iconv": "*",
|
||||
"php": ">=5.6"
|
||||
},
|
||||
"require-dev": {
|
||||
"phpunit/phpunit": "^6 || ^7"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-curl": "Needed to support cURL downloads in class HtmlWeb",
|
||||
"ext-mbstring": "Allows better decoding for multi-byte documents",
|
||||
"ext-openssl": "Allows loading HTTPS pages when using cURL"
|
||||
},
|
||||
"type": "library",
|
||||
"autoload": {
|
||||
"classmap": [
|
||||
"./"
|
||||
],
|
||||
"exclude-from-classmap": [
|
||||
"/example/",
|
||||
"/manual/",
|
||||
"/testcase/",
|
||||
"/tests/",
|
||||
"simple_html_dom.php"
|
||||
]
|
||||
},
|
||||
"notification-url": "https://packagist.org/downloads/",
|
||||
"license": [
|
||||
"MIT"
|
||||
],
|
||||
"authors": [
|
||||
{
|
||||
"name": "S.C. Chen",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "John Schlick",
|
||||
"role": "Developer"
|
||||
},
|
||||
{
|
||||
"name": "logmanoriginal",
|
||||
"role": "Developer"
|
||||
}
|
||||
],
|
||||
"description": "A fast, simple and reliable HTML document parser for PHP.",
|
||||
"homepage": "https://simplehtmldom.sourceforge.io/",
|
||||
"keywords": [
|
||||
"Simple",
|
||||
"dom",
|
||||
"html",
|
||||
"parser",
|
||||
"php",
|
||||
"simplehtmldom"
|
||||
],
|
||||
"support": {
|
||||
"issues": "https://sourceforge.net/p/simplehtmldom/bugs/",
|
||||
"rss": "https://sourceforge.net/p/simplehtmldom/news/feed.rss",
|
||||
"source": "https://sourceforge.net/p/simplehtmldom/repository/",
|
||||
"wiki": "https://simplehtmldom.sourceforge.io/docs/"
|
||||
},
|
||||
"time": "2019-11-09T15:42:50+00:00"
|
||||
}
|
||||
],
|
||||
"packages-dev": [],
|
||||
"aliases": [],
|
||||
"minimum-stability": "dev",
|
||||
"stability-flags": {
|
||||
"simplehtmldom/simplehtmldom": 5
|
||||
},
|
||||
"prefer-stable": true,
|
||||
"prefer-lowest": false,
|
||||
"platform": [],
|
||||
"platform-dev": [],
|
||||
"plugin-api-version": "2.0.0"
|
||||
}
|
99
fetch-documents.php
Normal file
99
fetch-documents.php
Normal file
@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/common.php';
|
||||
|
||||
ini_set('memory_limit', '3072M');
|
||||
|
||||
function findAllAttachments(array $obj): array {
|
||||
$list = [];
|
||||
if (!empty($obj['attachments'])) {
|
||||
foreach ($obj['attachments'] as $attachment) {
|
||||
$list[] = $attachment;
|
||||
if ($attachment['type'] == 'wall' || $attachment['type'] == 'wall_reply') {
|
||||
$list = array_merge($list, findAllAttachments($attachment));
|
||||
}
|
||||
}
|
||||
$list = array_merge($list, $obj['attachments']);
|
||||
}
|
||||
if (!empty($obj['fwd_messages'])) {
|
||||
foreach ($obj['fwd_messages'] as $fwd_message) {
|
||||
$list = array_merge($list, findAllAttachments($fwd_message));
|
||||
}
|
||||
}
|
||||
$list = array_filter($list, function($attachment) {
|
||||
static $ids = [];
|
||||
|
||||
$type = $attachment['type'];
|
||||
if (!isset($attachment[$type]))
|
||||
// weird
|
||||
return false;
|
||||
|
||||
$attach = $attachment[$type];
|
||||
|
||||
$id = $type;
|
||||
if (isset($attach['owner_id']))
|
||||
$id .= $attach['owner_id'].'_';
|
||||
if (isset($attach['id']))
|
||||
$id .= isset($attach['id']);
|
||||
|
||||
if (isset($ids[$id]))
|
||||
return false;
|
||||
|
||||
$ids[$id] = true;
|
||||
return true;
|
||||
});
|
||||
return $list;
|
||||
}
|
||||
|
||||
$api_dir = ARCHIVE_DIR.'/messages/api';
|
||||
foreach (scandir($api_dir) as $n) {
|
||||
if ($n == '.' || $n == '..')
|
||||
continue;
|
||||
|
||||
foreach (scandir($api_dir.'/'.$n) as $file) {
|
||||
if (!preg_match('/^\d+\.txt$/', $file))
|
||||
continue;
|
||||
|
||||
$obj = json_decode(file_get_contents($api_dir.'/'.$n.'/'.$file), true);
|
||||
$attachments = findAllAttachments($obj);
|
||||
|
||||
$docs = array_filter($attachments, function($a) {
|
||||
return $a['type'] == 'doc';
|
||||
});
|
||||
if (empty($docs))
|
||||
continue;
|
||||
|
||||
foreach ($docs as $doc) {
|
||||
$doc = $doc['doc']; // seriously?!
|
||||
$doc_id = $doc['owner_id'].'_'.$doc['id'];
|
||||
|
||||
$doc_dir = ARCHIVE_DIR.'/messages/docs/'.$doc_id;
|
||||
if (!file_exists($doc_dir)) {
|
||||
if (!mkdir($doc_dir, 0755, true))
|
||||
fatalError("failed to mkdir({$doc_dir})");
|
||||
}
|
||||
|
||||
// TODO sanitize filename
|
||||
$doc_file = $doc_dir.'/'.$doc['title'];
|
||||
if (file_exists($doc_file)) {
|
||||
if (filesize($doc_file) == 56655)
|
||||
unlink($doc_file);
|
||||
else {
|
||||
echo "$doc_id already exists\n";
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
list($code, $body) = httpGet($doc['url']);
|
||||
if ($code != 200) {
|
||||
fprintf(STDERR, "failed to download {$doc_id} ({$doc['url']})\n");
|
||||
rmdir($doc_dir);
|
||||
continue;
|
||||
}
|
||||
|
||||
file_put_contents($doc_file, $body);
|
||||
echo "$doc_id saved, ".filesize($doc_file)." bytes\n";
|
||||
unset($body);
|
||||
}
|
||||
}
|
||||
}
|
38
fetch-messages.php
Normal file
38
fetch-messages.php
Normal file
@ -0,0 +1,38 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/common.php';
|
||||
|
||||
$message_ids = array_slice($argv, 1);
|
||||
if (empty($message_ids))
|
||||
fatalError('no message ids');
|
||||
|
||||
$url = 'https://api.vk.com/method/messages.getById';
|
||||
$fields = [
|
||||
'message_ids' => implode(',', $message_ids),
|
||||
'access_token' => ACCESS_TOKEN,
|
||||
'v' => '5.109'
|
||||
];
|
||||
list($code, $body) = httpPost($url, $fields);
|
||||
|
||||
if ($code != 200)
|
||||
fatalError('api returned '.$code);
|
||||
|
||||
$response = json_decode($body, true);
|
||||
if (!empty($response['error']))
|
||||
fatalError('api error: '.$response['error']['error_msg']);
|
||||
|
||||
foreach ($response['response']['items'] as $item) {
|
||||
$id = (int)$item['id'];
|
||||
|
||||
$dir_n = $id % 100;
|
||||
$cur_dir = ARCHIVE_DIR.'/messages/api/'.$dir_n;
|
||||
|
||||
if (!file_exists($cur_dir)) {
|
||||
if (!mkdir($cur_dir, 0755, true))
|
||||
fatalError('failed to mkdir('.$cur_dir.')');
|
||||
}
|
||||
|
||||
file_put_contents($cur_dir.'/'.$id.'.txt', json_encode($item, JSON_UNESCAPED_UNICODE|JSON_PRETTY_PRINT));
|
||||
}
|
||||
|
||||
sleep(1);
|
31
get-attaches.php
Normal file
31
get-attaches.php
Normal file
@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/common.php';
|
||||
|
||||
$str = file_get_contents('php://stdin');
|
||||
if (!$str)
|
||||
fatalError("no input");
|
||||
|
||||
$message_ids = [];
|
||||
$photo_urls = [];
|
||||
|
||||
$on_message = function($doc, $message_id) {
|
||||
global $message_ids;
|
||||
$message_ids[] = $message_id;
|
||||
};
|
||||
$on_photo = function($doc, $href, $link_node) {
|
||||
global $photo_urls;
|
||||
$photo_urls[] = $href;
|
||||
};
|
||||
|
||||
try {
|
||||
onEachMessageOrAttachment($str, $on_message, $on_photo);
|
||||
} catch (Exception $e) {
|
||||
fatalError($e->getMessage());
|
||||
}
|
||||
|
||||
if (!empty($message_ids))
|
||||
echo implode("\n", $message_ids)."\n";
|
||||
|
||||
if (!empty($photo_urls))
|
||||
echo implode("\n", $photo_urls)."\n";
|
44
insert-api-objects.php
Normal file
44
insert-api-objects.php
Normal file
@ -0,0 +1,44 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/common.php';
|
||||
|
||||
$file = $argv[1] ?? '';
|
||||
if (!$file)
|
||||
fatalError("no file provided");
|
||||
|
||||
$str = file_get_contents($file);
|
||||
$str = iconv('windows-1251', 'utf-8//IGNORE', $str);
|
||||
|
||||
$is_modified = false;
|
||||
|
||||
try {
|
||||
$doc = onEachMessageOrAttachment($str, function (simplehtmldom\HtmlDocument $doc, int $id, simplehtmldom\HtmlNode $node) {
|
||||
global $is_modified;
|
||||
|
||||
$file = ARCHIVE_DIR.'/messages/api/'.($id % 100).'/'.$id.'.txt';
|
||||
if (!file_exists($file))
|
||||
return;
|
||||
|
||||
$obj = file_get_contents($file);
|
||||
|
||||
$a = $doc->createElement('a');
|
||||
$a->setAttribute('href', 'javascript:void(0)');
|
||||
$a->setAttribute('onclick', "this.nextSibling.style.display = (this.nextSibling.style.display === 'none' ? 'block' : 'none')");
|
||||
$a->appendChild($doc->createTextNode('Показать/скрыть объект API'));
|
||||
|
||||
$div = $doc->createElement('div');
|
||||
$div->setAttribute('style', 'display: none; font-size: 11px; font-family: monospace; background-color: #edeef0; padding: 10px; white-space: pre; overflow: auto;');
|
||||
$div->appendChild($doc->createTextNode($obj));
|
||||
|
||||
$node->appendChild($doc->createElement('br'));
|
||||
$node->appendChild($a);
|
||||
$node->appendChild($div);
|
||||
|
||||
$is_modified = true;
|
||||
}, null);
|
||||
} catch (Exception $e) {
|
||||
fatalError($e->getMessage());
|
||||
}
|
||||
|
||||
if ($is_modified)
|
||||
file_put_contents($file, iconv('utf-8', 'windows-1251//IGNORE', $doc->outertext));
|
33
replace-photos.php
Normal file
33
replace-photos.php
Normal file
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
require_once __DIR__.'/common.php';
|
||||
|
||||
$file = $argv[1] ?? '';
|
||||
if (!$file)
|
||||
fatalError("no file provided");
|
||||
|
||||
$str = file_get_contents($file);
|
||||
$str = iconv('windows-1251', 'utf-8//IGNORE', $str);
|
||||
|
||||
try {
|
||||
$doc = onEachMessageOrAttachment($str,
|
||||
null,
|
||||
function (simplehtmldom\HtmlDocument $doc, string $href, simplehtmldom\HtmlNode $link_node) {
|
||||
$local_href = '../../'.preg_replace('#^https?://#', '', $href);
|
||||
|
||||
/** @var simplehtmldom\HtmlNode $parent */
|
||||
$parent = $link_node->parent();
|
||||
$link_node->remove();
|
||||
|
||||
$img = $doc->createElement('img');
|
||||
$img->setAttribute('src', $local_href);
|
||||
$img->setAttribute('alt', $href);
|
||||
|
||||
$parent->appendChild($doc->createElement('br'));
|
||||
$parent->appendChild($img);
|
||||
});
|
||||
} catch (Exception $e) {
|
||||
fatalError($e->getMessage());
|
||||
}
|
||||
|
||||
file_put_contents($file, iconv('utf-8', 'windows-1251//IGNORE', $doc->outertext));
|
Loading…
x
Reference in New Issue
Block a user