diff --git a/bridges/YouTubeCommunityTabBridge.php b/bridges/YouTubeCommunityTabBridge.php
new file mode 100644
index 00000000..39ef46de
--- /dev/null
+++ b/bridges/YouTubeCommunityTabBridge.php
@@ -0,0 +1,267 @@
+ array(
+ 'channel' => array(
+ 'name' => 'Channel ID',
+ 'type' => 'text',
+ 'required' => true,
+ 'exampleValue' => 'UCULkRHBdLC5ZcEQBaL0oYHQ'
+ )
+ ),
+ 'By username' => array(
+ 'username' => array(
+ 'name' => 'Username',
+ 'type' => 'text',
+ 'required' => true,
+ 'exampleValue' => 'YouTubeUK'
+ ),
+ )
+ );
+
+ const CACHE_TIMEOUT = 3600; // 1 hour
+
+ private $feedUrl = '';
+ private $feedName = '';
+ private $itemTitle = '';
+
+ private $urlRegex = '/youtube\.com\/(channel|user|c)\/([\w]+)\/community/';
+ private $jsonRegex = '/var ytInitialData = (.*);<\/script>urlRegex, $url, $matches)) {
+ if ($matches[1] === 'channel') {
+ $params['context'] = 'By channel ID';
+ $params['channel'] = $matches[2];
+ }
+
+ if ($matches[1] === 'user') {
+ $params['context'] = 'By username';
+ $params['username'] = $matches[2];
+ }
+
+ return $params;
+ }
+
+ return null;
+ }
+
+ public function collectData() {
+
+ if (is_null($this->getInput('username')) === false) {
+ try {
+ $this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'c');
+ $html = getSimpleHTMLDOM($this->feedUrl);
+
+ } catch (Exception $e) {
+ $this->feedUrl = $this->buildCommunityUri($this->getInput('username'), 'user');
+ $html = getSimpleHTMLDOM($this->feedUrl);
+ }
+ } else {
+ $this->feedUrl = $this->buildCommunityUri($this->getInput('channel'), 'channel');
+ $html = getSimpleHTMLDOM($this->feedUrl);
+ }
+
+ $json = $this->extractJson($html->find('body', 0)->innertext);
+
+ $this->feedName = $json->header->c4TabbedHeaderRenderer->title;
+
+ if ($this->hasCommunityTab($json) === false) {
+ returnServerError('Channel does not have a community tab');
+ }
+
+ foreach ($this->getCommunityPosts($json) as $post) {
+ $this->itemTitle = '';
+
+ if (!isset($post->backstagePostThreadRenderer)) {
+ continue;
+ }
+
+ $details = $post->backstagePostThreadRenderer->post->backstagePostRenderer;
+
+ $item = array();
+ $item['uri'] = self::URI . '/post/' . $details->postId;
+ $item['author'] = $details->authorText->runs[0]->text;
+ $item['content'] = '';
+
+ if (isset($details->contentText)) {
+ $text = $this->getText($details->contentText->runs);
+
+ $this->itemTitle = $this->ellipsisTitle($text);
+ $item['content'] = $text;
+ }
+
+ $item['content'] .= $this->getAttachments($details);
+ $item['title'] = $this->itemTitle;
+
+ $this->items[] = $item;
+ }
+ }
+
+ public function getURI() {
+
+ if (!empty($this->feedUri)) {
+ return $this->feedUri;
+ }
+
+ return parent::getURI();
+ }
+
+ public function getName() {
+
+ if (!empty($this->feedName)) {
+ return $this->feedName . ' - YouTube Community Tab';
+ }
+
+ return parent::getName();
+ }
+
+ /**
+ * Build Community URI
+ */
+ private function buildCommunityUri($value, $type) {
+ return self::URI . '/' . $type . '/' . $value . '/community';
+ }
+
+ /**
+ * Extract JSON from page
+ */
+ private function extractJson($html) {
+
+ if (!preg_match($this->jsonRegex, $html, $parts)) {
+ returnServerError('Failed to extract data from page');
+ }
+
+ $data = json_decode($parts[1]);
+
+ if ($data === false) {
+ returnServerError('Failed to decode extracted data');
+ }
+
+ return $data;
+ }
+
+ /**
+ * Check if channel has a community tab
+ */
+ private function hasCommunityTab($json) {
+
+ foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) {
+ if (isset($tab->tabRenderer) && $tab->tabRenderer->title === 'Community') {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Get community tab posts
+ */
+ private function getCommunityPosts($json) {
+
+ foreach ($json->contents->twoColumnBrowseResultsRenderer->tabs as $tab) {
+ if ($tab->tabRenderer->title === 'Community') {
+ return $tab->tabRenderer->content->sectionListRenderer->contents[0]->itemSectionRenderer->contents;
+ }
+ }
+ }
+
+ /**
+ * Get text content for a post
+ */
+ private function getText($runs) {
+ $text = '';
+
+ foreach ($runs as $part) {
+ $text .= $this->formatUrls($part->text);
+ }
+
+ return nl2br($text);
+ }
+
+ /**
+ * Get attachments for posts
+ */
+ private function getAttachments($details) {
+ $content = '';
+
+ if (isset($details->backstageAttachment)) {
+ $attachments = $details->backstageAttachment;
+
+ // Video
+ if (isset($attachments->videoRenderer) && isset($attachments->videoRenderer->videoId)) {
+ if (empty($this->itemTitle)) {
+ $this->itemTitle = $this->feedName . ' posted a video';
+ }
+
+ $content = <<
Poll ({$attachments->pollRenderer->totalVotes->simpleText})
+EOD;
+ }
+ }
+
+ return $content;
+ }
+
+ /*
+ Ellipsis text for title
+ */
+ private function ellipsisTitle($text) {
+ $length = 100;
+
+ if (strlen($text) > $length) {
+ $text = explode('
', wordwrap($text, $length, '
'));
+ return $text[0] . '...';
+ }
+
+ return $text;
+ }
+
+ private function formatUrls($content) {
+ return preg_replace(
+ '/(http[s]{0,1}\:\/\/[a-zA-Z0-9.\/\?\&=\-_]{4,})/ims',
+ '$1 ',
+ $content
+ );
+ }
+}