From b189c9500c3ea5362edfcbf818c2f847b4892da5 Mon Sep 17 00:00:00 2001 From: Brion Vibber Date: Fri, 20 Nov 2009 09:42:19 -0800 Subject: Initial functional version of feed subscription plugin, currently supporting only PuSH-enabled feeds. --- plugins/FeedSub/feedmunger.php | 238 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 plugins/FeedSub/feedmunger.php (limited to 'plugins/FeedSub/feedmunger.php') diff --git a/plugins/FeedSub/feedmunger.php b/plugins/FeedSub/feedmunger.php new file mode 100644 index 000000000..bb8075da9 --- /dev/null +++ b/plugins/FeedSub/feedmunger.php @@ -0,0 +1,238 @@ +. + */ + +/** + * @package FeedSubPlugin + * @maintainer Brion Vibber + */ + +if (!defined('STATUSNET') && !defined('LACONICA')) { exit(1); } + +class FeedSubPreviewNotice extends Notice +{ + protected $fetched = true; + + function __construct($profile) + { + //parent::__construct(); // uhhh? + $this->profile = $profile; + } + + function getProfile() + { + return $this->profile; + } + + function find() + { + return true; + } + + function fetch() + { + $got = $this->fetched; + $this->fetched = false; + return $got; + } +} + +class FeedSubPreviewProfile extends Profile +{ + function getAvatar($width, $height=null) + { + return new FeedSubPreviewAvatar($width, $height); + } +} + +class FeedSubPreviewAvatar extends Avatar +{ + function displayUrl() { + return common_path('plugins/FeedSub/images/48px-Feed-icon.svg.png'); + } +} + +class FeedMunger +{ + /** + * @param XML_Feed_Parser $feed + */ + function __construct($feed, $url=null) + { + $this->feed = $feed; + $this->url = $url; + } + + function feedinfo() + { + $feedinfo = new Feedinfo(); + $feedinfo->feeduri = $this->url; + $feedinfo->homeuri = $this->feed->link; + $feedinfo->huburi = $this->getHubLink(); + return $feedinfo; + } + + function getAtomLink($item, $attribs=array()) + { + // XML_Feed_Parser gets confused by multiple elements. + $dom = $item->model; + + // Note that RSS feeds would embed an so this should work for both. + /// http://code.google.com/p/pubsubhubbub/wiki/RssFeeds + // + $links = $dom->getElementsByTagNameNS('http://www.w3.org/2005/Atom', 'link'); + for ($i = 0; $i < $links->length; $i++) { + $node = $links->item($i); + if ($node->hasAttributes()) { + $href = $node->attributes->getNamedItem('href'); + if ($href) { + $matches = 0; + foreach ($attribs as $name => $val) { + $attrib = $node->attributes->getNamedItem($name); + if ($attrib && $attrib->value == $val) { + $matches++; + } + } + if ($matches == count($attribs)) { + return $href->value; + } + } + } + } + return false; + } + + function getRssLink($item) + { + // XML_Feed_Parser gets confused by multiple elements. + $dom = $item->model; + + // Note that RSS feeds would embed an so this should work for both. + /// http://code.google.com/p/pubsubhubbub/wiki/RssFeeds + // + $links = $dom->getElementsByTagName('link'); + for ($i = 0; $i < $links->length; $i++) { + $node = $links->item($i); + if (!$node->hasAttributes()) { + return $node->textContent; + } + } + return false; + } + + function getAltLink($item) + { + // Check for an atom link... + $link = $this->getAtomLink($item, array('rel' => 'alternate', 'type' => 'text/html')); + if (!$link) { + $link = $this->getRssLink($item); + } + return $link; + } + + function getHubLink() + { + return $this->getAtomLink($this->feed, array('rel' => 'hub')); + } + + function profile($preview=false) + { + if ($preview) { + $profile = new FeedSubPreviewProfile(); + } else { + $profile = new Profile(); + } + + // @todo validate/normalize nick? + $profile->nickname = $this->feed->title; + $profile->fullname = $this->feed->title; + $profile->homepage = $this->getAltLink($this->feed); + $profile->bio = $this->feed->description; + $profile->profileurl = $this->getAltLink($this->feed); + + // @todo tags from categories + // @todo lat/lon/location? + + return $profile; + } + + function notice($index=1, $preview=false) + { + $entry = $this->feed->getEntryByOffset($index); + if (!$entry) { + return null; + } + + if ($preview) { + $notice = new FeedSubPreviewNotice($this->profile(true)); + $notice->id = -1; + } else { + $notice = new Notice(); + } + + $link = $this->getAltLink($entry); + $notice->uri = $link; + $notice->url = $link; + $notice->content = $this->noticeFromEntry($entry); + $notice->rendered = common_render_content($notice->content, $notice); + $notice->created = common_sql_date($entry->updated); // @fixme + $notice->is_local = Notice::GATEWAY; + $notice->source = 'feed'; + + return $notice; + } + + /** + * @param XML_Feed_Type $entry + * @return string notice text, within post size limit + */ + function noticeFromEntry($entry) + { + $title = $entry->title; + $link = $entry->link; + + // @todo We can get entries like this: + // $cats = $entry->getCategory('category', array(0, true)); + // but it feels like an awful hack. If it's accessible cleanly, + // try adding #hashtags from the categories/tags on a post. + + // @todo Should we force a language here? + $format = dgettext("FeedSubPlugin", 'New post: "%1$s" %2$s'); + $title = $entry->title; + $link = $this->getAltLink($entry); + $out = sprintf($format, $title, $link); + + // Trim link if needed... + $max = Notice::maxContent(); + if (mb_strlen($out) > $max) { + $link = common_shorten_url($link); + $out = sprintf($format, $title, $link); + } + + // Trim title if needed... + if (mb_strlen($out) > $max) { + $ellipsis = "\xe2\x80\xa6"; // U+2026 HORIZONTAL ELLIPSIS + $used = mb_strlen($out) - mb_strlen($title); + $available = $max - $used - mb_strlen($ellipsis); + $title = mb_substr($title, 0, $available) . $ellipsis; + $out = sprintf($format, $title, $link); + } + + return $out; + } +} -- cgit v1.2.3-54-g00ecf