[ 'name' => 'Group or user name', 'exampleValue' => 'elonmusk_tech', 'required' => true ], 'hide_reposts' => [ 'name' => 'Hide reposts', 'type' => 'checkbox', ] ] ]; const TEST_DETECT_PARAMETERS = [ 'https://vk.com/id1' => ['u' => 'id1'], 'https://vk.com/groupname' => ['u' => 'groupname'], 'https://m.vk.com/groupname' => ['u' => 'groupname'], 'https://vk.com/groupname/anythingelse' => ['u' => 'groupname'], 'https://vk.com/groupname?w=somethingelse' => ['u' => 'groupname'], 'https://vk.com/with_underscore' => ['u' => 'with_underscore'], 'https://vk.com/vk.cats' => ['u' => 'vk.cats'], ]; protected $pageName; protected $tz = 0; private $urlRegex = '/vk\.com\/([\w.]+)/'; public function getURI() { if (!is_null($this->getInput('u'))) { return static::URI . urlencode($this->getInput('u')); } return parent::getURI(); } public function getName() { if ($this->pageName) { return $this->pageName; } return parent::getName(); } public function detectParameters($url) { if (preg_match($this->urlRegex, $url, $matches)) { return ['u' => $matches[1]]; } return null; } public function collectData() { $text_html = $this->getContents(); $text_html = iconv('windows-1251', 'utf-8//ignore', $text_html); $html = str_get_html($text_html); foreach ($html->find('script') as $script) { preg_match('/tz: ([0-9]+)/', $script->outertext, $matches); if (count($matches) > 0) { $this->tz = intval($matches[1]); break; } } $pageName = $html->find('meta[property="og:title"]', 0); if (is_object($pageName)) { $pageName = $pageName->getAttribute('content'); $this->pageName = $pageName; } foreach ($html->find('div.replies') as $comment_block) { $comment_block->outertext = ''; } // expensive operation $save = $html->save(); $html->load($save); $pinned_post_item = null; $last_post_id = 0; foreach ($html->find('.post') as $post) { if ($post->find('.wall_post_text_deleted')) { // repost of deleted post continue; } defaultLinkTo($post, self::URI); $is_pinned_post = false; if (strpos($post->getAttribute('class'), 'post_fixed') !== false) { $is_pinned_post = true; } // Remove 'Show more' button foreach ($post->find('button.PostTextMore') as $junk) { $junk->outertext = ''; } $content_suffix = ''; // looking for external links $external_link_selectors = [ 'a.page_media_link_title', 'div.page_media_link_title > a', 'div.media_desc > a.lnk', ]; foreach ($external_link_selectors as $sel) { if (is_object($post->find($sel, 0))) { $a = $post->find($sel, 0); $innertext = $a->innertext; $parsed_url = parse_url($a->getAttribute('href')); if (strpos($parsed_url['path'], '/away.php') !== 0) { continue; } parse_str($parsed_url['query'], $parsed_query); $content_suffix .= "
External link: $innertext"; } } // remove external link from content $external_link_selectors_to_remove = [ 'div.page_media_thumbed_link', 'div.page_media_link_desc_wrap', 'div.media_desc > a.lnk', ]; foreach ($external_link_selectors_to_remove as $sel) { if (is_object($post->find($sel, 0))) { $post->find($sel, 0)->outertext = ''; } } // looking for article $article = $post->find('a.article_snippet', 0); if (is_object($article)) { if (strpos($article->getAttribute('class'), 'article_snippet_mini') !== false) { $article_title_selector = 'div.article_snippet_mini_title'; $article_author_selector = 'div.article_snippet_mini_info > .mem_link, div.article_snippet_mini_info > .group_link'; $article_thumb_selector = 'div.article_snippet_mini_thumb'; } else { $article_title_selector = 'div.article_snippet__title'; $article_author_selector = 'div.article_snippet__author'; $article_thumb_selector = 'div.article_snippet__image'; } $article_title = $article->find($article_title_selector, 0)->innertext ?? ''; $article_author = $article->find($article_author_selector, 0)->innertext ?? ''; $article_link = $article->getAttribute('href'); $article_img_element_style = $article->find($article_thumb_selector, 0)->getAttribute('style'); preg_match('/background-image: url\((.*)\)/', $article_img_element_style, $matches); if (count($matches) > 0) { $content_suffix .= "
"; } $content_suffix .= "
Article: $article_title ($article_author)"; $article->outertext = ''; } // get all videos foreach ($post->find('a.page_post_thumb_video') as $a) { $video_title = htmlspecialchars_decode($a->getAttribute('aria-label')); $video_title_split_pos = strrpos($video_title, ' is '); if ($video_title_split_pos !== false) { $video_title = substr($video_title, 0, $video_title_split_pos); } $video_link = $a->getAttribute('href'); $this->appendVideo($video_title, $video_link, backgroundToImg($a), $content_suffix); $a->outertext = ''; } // get all photos foreach ($post->find('div.wall_text a.page_post_thumb_wrap') as $a) { $result = $this->getPhoto($a); if ($result == null) { continue; } $a->outertext = ''; $content_suffix .= "
$result"; } // get albums foreach ($post->find('.page_album_wrap') as $el) { $a = $el->find('.page_album_link', 0); $album_title = $a->find('.page_album_title_text', 0)->getAttribute('title'); $album_link = $a->getAttribute('href'); $el->outertext = ''; $content_suffix .= "
Album: $album_title"; } // get photo documents foreach ($post->find('a.page_doc_photo_href') as $a) { $doc_link = $a->getAttribute('href'); $doc_gif_label_element = $a->find('.page_gif_label', 0); $doc_title_element = $a->find('.doc_label', 0); if (is_object($doc_gif_label_element)) { $gif_preview_img = backgroundToImg($a->find('.page_doc_photo', 0)); $content_suffix .= "
Gif: $gif_preview_img"; } elseif (is_object($doc_title_element)) { $doc_title = $doc_title_element->innertext; $content_suffix .= "
Doc: $doc_title"; } else { continue; } $a->outertext = ''; } // get other documents foreach ($post->find('div.page_doc_row') as $div) { $doc_title_element = $div->find('a.page_doc_title', 0); if (is_object($doc_title_element)) { $doc_title = $doc_title_element->innertext; $doc_link = $doc_title_element->getAttribute('href'); $content_suffix .= "
Doc: $doc_title"; } else { continue; } $div->outertext = ''; } // get polls foreach ($post->find('div.page_media_poll_wrap') as $div) { $poll_title = $div->find('.page_media_poll_title', 0)->innertext; $content_suffix .= "
Poll: $poll_title"; foreach ($div->find('div.page_poll_text') as $poll_stat_title) { $content_suffix .= '
- ' . $poll_stat_title->innertext; } $div->outertext = ''; } // get sign / post author $post_author = $pageName; $author_selectors = ['a.wall_signed_by', 'a.author']; foreach ($author_selectors as $author_selector) { $a = $post->find($author_selector, 0); if (is_object($a)) { $post_author = $a->innertext; $a->outertext = ''; break; } } // fix links and get post hashtags $hashtags = []; foreach ($post->find('a') as $a) { $href = $a->getAttribute('href'); $innertext = $a->innertext; $hashtag_prefix = '/feed?section=search&q=%23'; $hashtag = null; if ($href && substr($href, 0, strlen($hashtag_prefix)) === $hashtag_prefix) { $hashtag = urldecode(substr($href, strlen($hashtag_prefix))); } elseif (substr($innertext, 0, 1) == '#') { $hashtag = $innertext; } if ($hashtag) { $a->outertext = $innertext; $hashtags[] = $hashtag; continue; } $parsed_url = parse_url($href); if (array_key_exists('path', $parsed_url) === false) { continue; } if (strpos($parsed_url['path'], '/away.php') === 0) { parse_str($parsed_url['query'], $parsed_query); $a->setAttribute('href', iconv( 'windows-1251', 'utf-8//ignore', $parsed_query['to'] )); } } $copy_quote = $post->find('div.copy_quote', 0); if (is_object($copy_quote)) { if ($this->getInput('hide_reposts') === true) { continue; } if ($copy_post_header = $copy_quote->find('div.copy_post_header', 0)) { $copy_post_header->outertext = ''; } $second_copy_quote = $copy_quote->find('div.published_sec_quote', 0); if (is_object($second_copy_quote)) { $second_copy_quote_author = $second_copy_quote->find('a.copy_author', 0)->outertext; $second_copy_quote_content = $second_copy_quote->find('div.copy_post_date', 0)->outertext; $second_copy_quote->outertext = "
Reposted ($second_copy_quote_author): $second_copy_quote_content"; } $copy_quote_author = $copy_quote->find('a.copy_author', 0)->outertext; $copy_quote_content = $copy_quote->innertext; $copy_quote->outertext = "
Reposted ($copy_quote_author):
$copy_quote_content"; } foreach ($post->find('.PrimaryAttachment .PhotoPrimaryAttachment') as $pa) { $img = $pa->find('.PhotoPrimaryAttachment__imageElement', 0); if (is_object($img)) { $pa->outertext = $img->outertext; } } foreach ($post->find('.SecondaryAttachment') as $sa) { $sa_href = $sa->getAttribute('href'); if (!$sa_href) { $sa_href = ''; } $sa_task_click = $sa->getAttribute('data-task-click'); if (str_starts_with($sa_href, 'https://vk.com/doc')) { // document $doc_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext; $doc_size = $sa->find('.SecondaryAttachmentSubhead', 0)->innertext; $doc_link = $sa_href; $content_suffix .= "
Doc: $doc_title ($doc_size)"; $sa->outertext = ''; } else if (str_starts_with($sa_href, 'https://vk.com/@')) { // article $article_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext; $article_author = explode('Article · from ', $sa->find('.SecondaryAttachmentSubhead', 0)->innertext)[1]; $article_link = $sa_href; $content_suffix .= "
Article: $article_title ($article_author)"; $sa->outertext = ''; } else if ($sa_task_click == 'SecondaryAttachment/playAudio') { // audio $audio_json = json_decode(html_entity_decode($sa->getAttribute('data-audio'))); $audio_link = $audio_json->url; $audio_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext; $audio_author = $sa->find('.SecondaryAttachmentSubhead', 0)->innertext; $content_suffix .= "
Audio: $audio_title ($audio_author)"; $sa->outertext = ''; } else if ($sa_task_click == 'SecondaryAttachment/playPlaylist') { // playlist link $playlist_title = $sa->find('.SecondaryAttachment__childrenText', 0)->innertext; $playlist_link = $sa->find('.SecondaryAttachment__link', 0)->getAttribute('href'); $content_suffix .= "
Playlist: $playlist_title"; $sa->outertext = ''; } } $item = []; $content = strip_tags(backgroundToImg($post->find('div.wall_text', 0)->innertext), '
'); $content .= $content_suffix; if (!$content) { $content = '(empty post)'; } $content = str_get_html($content); foreach ($content->find('img') as $img) { $parsed_src = parse_url($img->getAttribute('src')); // unblur images (case of impf) // get original images instead of thumbnails (case of impg) $imgPrefix = array_reduce(['/impf/', '/impg/'], function ($a, $c) use ($parsed_src) { if ($a) { return $a; } if (str_starts_with($parsed_src['path'], $c)) { return $c; } return $a; }, ''); if ($imgPrefix) { $new_src = $parsed_src['scheme'] . '://' . $parsed_src['host']; $new_src .= substr($parsed_src['path'], strlen($imgPrefix) - 1); $img->setAttribute('src', $new_src); } } $item['content'] = $content->outertext; $item['categories'] = $hashtags; // get post link $post_link = $post->find('a.PostHeaderSubtitle__link', 0)->getAttribute('href'); preg_match('/wall-?\d+_(\d+)/', $post_link, $preg_match_result); $item['post_id'] = intval($preg_match_result[1]); $item['uri'] = $post_link; $item['timestamp'] = $this->getTime($post); $item['title'] = $this->getTitle($item['content']); $item['author'] = $post_author; if ($is_pinned_post) { // do not append it now $pinned_post_item = $item; } else { $last_post_id = $item['post_id']; $this->items[] = $item; } } if (!is_null($pinned_post_item)) { if (count($this->items) == 0) { $this->items[] = $pinned_post_item; } elseif ($last_post_id < $pinned_post_item['post_id']) { $this->items[] = $pinned_post_item; usort($this->items, function ($item1, $item2) { return $item2['post_id'] - $item1['post_id']; }); } } } private function getPhoto($a) { $onclick = $a->getAttribute('onclick'); preg_match('/return showPhoto\(.+?({.*})/', $onclick, $preg_match_result); if (count($preg_match_result) == 0) { return; } $arg = htmlspecialchars_decode(str_replace('queue:1', '"queue":1', $preg_match_result[1])); $data = json_decode($arg, true); if ($data == null) { return; } $thumb = $data['temp']['base'] . $data['temp']['x_'][0]; $original = ''; foreach (['y_', 'z_', 'w_'] as $key) { if (!isset($data['temp'][$key])) { continue; } if (!isset($data['temp'][$key][0])) { continue; } if (substr($data['temp'][$key][0], 0, 4) == 'http') { $base = ''; } else { $base = $data['temp']['base']; } $original = $base . $data['temp'][$key][0]; } if ($original) { return "
"; } else { return backgroundToImg($a); } } private function getTitle($content) { $content = explode('
', $content)[0]; $content = strip_tags($content); preg_match('/^[:\,"\w\ \p{L}\(\)\?#«»\-\–\—||&\.%\\₽\/+\;\!]+/mu', htmlspecialchars_decode($content), $result); if (count($result) == 0) { return 'untitled'; } return $result[0]; } private function getTime($post) { $accurateDateElement = $post->find('span.rel_date', 0); if ($accurateDateElement) { return $accurateDateElement->getAttribute('time'); } else { $strdate = $post->find('time.PostHeaderSubtitle__item', 0)->plaintext; $strdate = preg_replace('/[\x00-\x1F\x7F-\xFF]/', ' ', $strdate); $date = date_parse($strdate); if (!$date['year']) { if (strstr($strdate, 'today') !== false) { $strdate = date('d-m-Y') . ' ' . $strdate; } elseif (strstr($strdate, 'yesterday ') !== false) { $time = time() - 60 * 60 * 24; $strdate = date('d-m-Y', $time) . ' ' . $strdate; } elseif ($date['month'] && intval(date('m')) < $date['month']) { $strdate = $strdate . ' ' . (date('Y') - 1); } else { $strdate = $strdate . ' ' . date('Y'); } $date = date_parse($strdate); } elseif ($date['hour'] === false) { $date['hour'] = $date['minute'] = '00'; } return strtotime($date['day'] . '-' . $date['month'] . '-' . $date['year'] . ' ' . $date['hour'] . ':' . $date['minute']) - $this->tz; } } private function getContents() { $httpHeaders = [ 'Accept-language: en', 'Cookie: remixlang=3', ]; $redirects = 0; $uri = $this->getURI(); while ($redirects < 2) { $response = getContents($uri, $httpHeaders, [CURLOPT_FOLLOWLOCATION => false], true); if (in_array($response->getCode(), [200, 304])) { return $response->getBody(); } $headers = $response->getHeaders(); $uri = urljoin(self::URI, $headers['location'][0]); if (str_contains($uri, '/429.html')) { throw new RateLimitException(); } if (!preg_match('#^https?://vk.com/#', $uri)) { returnServerError('Unexpected redirect location: ' . $uri); } $redirects++; } returnServerError('Too many redirects, while retreving content from VK'); } protected function appendVideo($video_title, $video_link, $previewImg, &$content_suffix) { if (!$video_title) { $video_title = '(empty)'; } $content_suffix .= '
' . $previewImg; $content_suffix .= 'Video: ' . $video_title . ''; } }