[ 'name' => 'Короткое имя группы или профиля (из ссылки)', 'exampleValue' => 'goblin_oper_ru', 'required' => true ], 'hide_reposts' => [ 'name' => 'Скрыть репосты', 'type' => 'checkbox', ] ] ]; const CONFIGURATION = [ 'access_token' => [ 'required' => true, ], ]; 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 $ownerNames = []; protected $pageName; private $urlRegex = '/vk\.com\/([\w.]+)/'; private $rateLimitCacheKey = 'vk2_rate_limit'; public function getURI() { if (!is_null($this->getInput('u'))) { return urljoin(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; } protected function getPostURI($post) { $r = 'https://vk.com/wall' . $post['owner_id'] . '_'; if (isset($post['reply_post_id'])) { $r .= $post['reply_post_id'] . '?reply=' . $post['id'] . '&thread=' . $post['parents_stack'][0]; } else { $r .= $post['id']; } return $r; } // This function is based on SlackCoyote's vkfeed2rss // https://github.com/em92/vkfeed2rss protected function generateContentFromPost($post) { // it's what we will return $ret = $post['text']; // html special characters convertion $ret = htmlentities($ret, ENT_QUOTES | ENT_HTML401); // change all linebreak to HTML compatible
$ret = nl2br($ret); $ret = "

$ret

"; // find URLs $ret = preg_replace( '/((https?|ftp|gopher)\:\/\/[a-zA-Z0-9\-\.]+(:[a-zA-Z0-9]*)?\/?([@\w\-\+\.\?\,\'\/&%\$#\=~\x5C])*)/', "$1", $ret ); // find [id1|Pawel Durow] form links $ret = preg_replace('/\[(\w+)\|([^\]]+)\]/', "$2", $ret); // attachments if (isset($post['attachments'])) { // level 1 foreach ($post['attachments'] as $attachment) { if ($attachment['type'] == 'video') { // VK videos $title = e($attachment['video']['title']); $photo = e($this->getImageURLWithLargestWidth($attachment['video']['image'])); $href = "https://vk.com/video{$attachment['video']['owner_id']}_{$attachment['video']['id']}"; $ret .= "

Video: {$title}
Video: {$title}

"; } elseif ($attachment['type'] == 'audio') { // VK audio $artist = e($attachment['audio']['artist']); $title = e($attachment['audio']['title']); $ret .= "

Audio: {$artist} - {$title}

"; } elseif ($attachment['type'] == 'doc' and $attachment['doc']['ext'] != 'gif') { // any doc apart of gif $doc_url = e($attachment['doc']['url']); $title = e($attachment['doc']['title']); $ret .= "

Документ: {$title}

"; } } // level 2 foreach ($post['attachments'] as $attachment) { if ($attachment['type'] == 'photo') { // JPEG, PNG photos // GIF in vk is a document, so, not handled as photo $photo = e($this->getImageURLWithLargestWidth($attachment['photo']['sizes'])); $text = e($attachment['photo']['text']); $ret .= "

{$text}

"; } elseif ($attachment['type'] == 'doc' and $attachment['doc']['ext'] == 'gif') { // GIF docs $url = e($attachment['doc']['url']); $ret .= "

"; } elseif ($attachment['type'] == 'link') { // links $url = e($attachment['link']['url']); $url = str_replace('https://m.vk.com', 'https://vk.com', $url); $title = e($attachment['link']['title']); if (isset($attachment['link']['photo'])) { $photo = $this->getImageURLWithLargestWidth($attachment['link']['photo']['sizes']); $ret .= "

{$title}
{$title}

"; } else { $ret .= "

{$title}

"; } } elseif ($attachment['type'] == 'note') { // notes $title = e($attachment['note']['title']); $url = e($attachment['note']['view_url']); $ret .= "

{$title}

"; } elseif ($attachment['type'] == 'poll') { // polls $question = e($attachment['poll']['question']); $vote_count = $attachment['poll']['votes']; $answers = $attachment['poll']['answers']; $ret .= "

Poll: {$question} ({$vote_count} votes)
"; foreach ($answers as $answer) { $text = e($answer['text']); $votes = $answer['votes']; $rate = $answer['rate']; $ret .= "* {$text}: {$votes} ({$rate}%)
"; } $ret .= '

'; } elseif ($attachment['type'] == 'album') { $album = $attachment['album']; $url = "https://vk.com/album{$album['owner_id']}_{$album['id']}"; $title = 'Альбом: ' . $album['title']; $photo = $this->getImageURLWithLargestWidth($album['thumb']['sizes']); $ret .= "

{$title}
{$title}

"; } elseif (!in_array($attachment['type'], ['video', 'audio', 'doc'])) { $ret .= "

Unknown attachment type: {$attachment['type']}

"; } } } return $ret; } protected function getImageURLWithLargestWidth($items) { usort($items, function ($a, $b) { return $b['width'] - $a['width']; }); return $items[0]['url']; } public function collectData() { if ($this->cache->get($this->rateLimitCacheKey)) { throw new RateLimitException(); } $u = $this->getInput('u'); $ownerId = null; // getting ownerId from url $r = preg_match('/^(club|public)(\d+)$/', $u, $matches); if ($r) { $ownerId = -intval($matches[2]); } else { $r = preg_match('/^(id)(\d+)$/', $u, $matches); if ($r) { $ownerId = intval($matches[2]); } } // getting owner id from API if (is_null($ownerId)) { $r = $this->api('groups.getById', [ 'group_ids' => $u, ], [100]); if (isset($r['response'][0])) { $ownerId = -$r['response'][0]['id']; } else { $r = $this->api('users.get', [ 'user_ids' => $u, ]); if (count($r['response']) > 0) { $ownerId = $r['response'][0]['id']; } } } if (is_null($ownerId)) { returnServerError('Could not detect owner id'); } $r = $this->api('wall.get', [ 'owner_id' => $ownerId, 'extended' => '1', ]); // preparing ownerNames dictionary foreach ($r['response']['profiles'] as $profile) { $this->ownerNames[$profile['id']] = $profile['first_name'] . ' ' . $profile['last_name']; } foreach ($r['response']['groups'] as $group) { $this->ownerNames[-$group['id']] = $group['name']; } $this->generateFeed($r); } protected function generateFeed($r) { $ownerId = 0; foreach ($r['response']['items'] as $post) { if (!$ownerId) { $ownerId = $post['owner_id']; } $item = []; $content = $this->generateContentFromPost($post); if (isset($post['copy_history'])) { if ($this->getInput('hide_reposts')) { continue; } $originalPost = $post['copy_history'][0]; if ($originalPost['from_id'] < 0) { $originalPostAuthorScreenName = 'club' . (-$originalPost['owner_id']); } else { $originalPostAuthorScreenName = 'id' . $originalPost['owner_id']; } $originalPostAuthorURI = 'https://vk.com/' . $originalPostAuthorScreenName; $originalPostAuthorName = $this->ownerNames[$originalPost['from_id']]; $originalPostAuthor = "$originalPostAuthorName"; $content .= '

Репост (Пост от '; $content .= $originalPostAuthor; $content .= '):

'; $content .= $this->generateContentFromPost($originalPost); } $item['content'] = $content; $item['timestamp'] = $post['date']; $item['author'] = $this->ownerNames[$post['from_id']]; $item['title'] = $this->getTitle(strip_tags($content)); $item['uri'] = $this->getPostURI($post); $this->items[] = $item; } $this->pageName = $this->ownerNames[$ownerId]; } protected 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]; } protected function api($method, array $params, $expected_error_codes = []) { $access_token = $this->getOption('access_token'); if (!$access_token) { returnServerError('You cannot run VK API methods without access_token'); } $params['v'] = '5.131'; $r = json_decode( getContents( 'https://api.vk.com/method/' . $method . '?' . http_build_query($params), ['Authorization: Bearer ' . $access_token] ), true ); if (isset($r['error']) && !in_array($r['error']['error_code'], $expected_error_codes)) { if ($r['error']['error_code'] == 6) { $this->cache->set($this->rateLimitCacheKey, true, 5); } else if ($r['error']['error_code'] == 29) { // wall.get has limit of 5000 requests per day // if that limit is hit, VK returns error 29 $this->cache->set($this->rateLimitCacheKey, true, 60 * 30); } returnServerError('API returned error: ' . $r['error']['error_msg'] . ' (' . $r['error']['error_code'] . ')'); } return $r; } }