diff --git a/bridges/Vk2Bridge.php b/bridges/Vk2Bridge.php
new file mode 100644
index 00000000..0bc0879f
--- /dev/null
+++ b/bridges/Vk2Bridge.php
@@ -0,0 +1,323 @@
+ [
+ '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 .= ""; + } 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 .= ""; + } + } + // 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 .= "Poll: {$question} ({$vote_count} votes)
";
+ foreach ($answers as $answer) {
+ $text = e($answer['text']);
+ $votes = $answer['votes'];
+ $rate = $answer['rate'];
+ $ret .= "* {$text}: {$votes} ({$rate}%)
";
+ }
+ $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 HttpException('429 Too Many Requests', 429); + } + + $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 = new FeedItem(); + $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->setContent($content); + $item->setTimestamp($post['date']); + $item->setAuthor($this->ownerNames[$post['from_id']]); + $item->setTitle($this->getTitle(strip_tags($content))); + $item->setURI($this->getPostURI($post)); + + $this->items[] = $item; + } + + $this->pageName = $this->ownerNames[$ownerId]; + } + + protected function getTitle($content) + { + $content = explode('