diff --git a/QvitterPlugin.php b/QvitterPlugin.php
index 7b2278a..b9384bf 100644
--- a/QvitterPlugin.php
+++ b/QvitterPlugin.php
@@ -141,6 +141,8 @@ class QvitterPlugin extends Plugin {
public function onRouterInitialized($m)
{
+ $m->connect('api/qvitter/blocks.json',
+ array('action' => 'ApiQvitterBlocks'));
$m->connect('api/qvitter/hello.json',
array('action' => 'ApiQvitterHello'));
$m->connect('api/qvitter/mark_all_notifications_as_seen.json',
diff --git a/actions/apiqvitterblocks.php b/actions/apiqvitterblocks.php
new file mode 100644
index 0000000..8cfa735
--- /dev/null
+++ b/actions/apiqvitterblocks.php
@@ -0,0 +1,188 @@
+ \\\\_\ ·
+ · \\) \____) ·
+ · ·
+ · ·
+ · ·
+ · Qvitter is free software: you can redistribute it and / or modify it ·
+ · under the terms of the GNU Affero General Public License as published by ·
+ · the Free Software Foundation, either version three of the License or (at ·
+ · your option) any later version. ·
+ · ·
+ · Qvitter is distributed in hope that it will be useful but WITHOUT ANY ·
+ · WARRANTY; without even the implied warranty of MERCHANTABILTY or FITNESS ·
+ · FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for ·
+ · more details. ·
+ · ·
+ · You should have received a copy of the GNU Affero General Public License ·
+ · along with Qvitter. If not, see . ·
+ · ·
+ · Contact h@nnesmannerhe.im if you have any questions. ·
+ · ·
+ · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
+
+if (!defined('STATUSNET')) {
+ exit(1);
+}
+
+
+class ApiQvitterBlocksAction extends ApiBareAuthAction
+{
+ var $profiles = null;
+
+ /**
+ * Take arguments for running
+ *
+ * @param array $args $_REQUEST args
+ *
+ * @return boolean success flag
+ */
+ protected function prepare(array $args=array())
+ {
+ parent::prepare($args);
+
+ // If called as a social graph method, show 5000 per page, otherwise 100
+
+ $this->count = (int)$this->arg('count', 100);
+
+ $this->target = $this->getTargetProfile($this->arg('id'));
+
+ if (!($this->target instanceof Profile)) {
+ // TRANS: Client error displayed when requesting a list of followers for a non-existing user.
+ $this->clientError(_('No such user.'), 404);
+ }
+
+ $this->profiles = $this->getProfiles();
+
+ return true;
+ }
+
+ /**
+ * Handle the request
+ *
+ * Show the profiles
+ *
+ * @return void
+ */
+ protected function handle()
+ {
+ parent::handle();
+
+ $this->initDocument('json');
+ print json_encode($this->showProfiles());
+ $this->endDocument('json');
+ }
+
+ /**
+ * Get the user's subscribers (followers) as an array of profiles
+ *
+ * @return array Profiles
+ */
+ protected function getProfiles()
+ {
+ $offset = ($this->page - 1) * $this->count;
+ $limit = $this->count + 1;
+
+ $blocks = null;
+
+ $blocks = QvitterBlocked::getBlocked($this->target->id, $offset, $limit);
+
+ $profiles = array();
+
+ while ($blocks->fetch()) {
+ $this_profile_block = clone($blocks);
+ $profiles[] = $this->getTargetProfile($this_profile_block->blocked);
+ }
+
+ return $profiles;
+ }
+
+ /**
+ * Is this action read only?
+ *
+ * @param array $args other arguments
+ *
+ * @return boolean true
+ */
+ function isReadOnly($args)
+ {
+ return true;
+ }
+
+ /**
+ * When was this feed last modified?
+ *
+ * @return string datestamp of the latest profile in the stream
+ */
+ function lastModified()
+ {
+ if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+ return strtotime($this->profiles[0]->modified);
+ }
+
+ return null;
+ }
+
+ /**
+ * An entity tag for this action
+ *
+ * Returns an Etag based on the action name, language, user ID, and
+ * timestamps of the first and last profiles in the subscriptions list
+ * There's also an indicator to show whether this action is being called
+ * as /api/statuses/(friends|followers) or /api/(friends|followers)/ids
+ *
+ * @return string etag
+ */
+ function etag()
+ {
+ if (!empty($this->profiles) && (count($this->profiles) > 0)) {
+
+ $last = count($this->profiles) - 1;
+
+ return '"' . implode(
+ ':',
+ array($this->arg('action'),
+ common_user_cache_hash($this->auth_user),
+ common_language(),
+ $this->target->id,
+ 'Profiles',
+ strtotime($this->profiles[0]->modified),
+ strtotime($this->profiles[$last]->modified))
+ )
+ . '"';
+ }
+
+ return null;
+ }
+
+ /**
+ * Show the profiles as Twitter-style useres and statuses
+ *
+ * @return void
+ */
+ function showProfiles()
+ {
+ $user_arrays = array();
+ foreach ($this->profiles as $profile) {
+ $user_arrays[] = $this->twitterUserArray($profile, false );
+ }
+ return $user_arrays;
+ }
+}
diff --git a/classes/QvitterBlocked.php b/classes/QvitterBlocked.php
new file mode 100644
index 0000000..0affb1c
--- /dev/null
+++ b/classes/QvitterBlocked.php
@@ -0,0 +1,112 @@
+ \\\\_\ ·
+ · \\) \____) ·
+ · ·
+ · ·
+ · ·
+ · Qvitter is free software: you can redistribute it and / or modify it ·
+ · under the terms of the GNU Affero General Public License as published by ·
+ · the Free Software Foundation, either version three of the License or (at ·
+ · your option) any later version. ·
+ · ·
+ · Qvitter is distributed in hope that it will be useful but WITHOUT ANY ·
+ · WARRANTY; without even the implied warranty of MERCHANTABILTY or FITNESS ·
+ · FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for ·
+ · more details. ·
+ · ·
+ · You should have received a copy of the GNU Affero General Public License ·
+ · along with Qvitter. If not, see . ·
+ · ·
+ · Contact h@nnesmannerhe.im if you have any questions. ·
+ · ·
+ · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */
+
+
+if (!defined('GNUSOCIAL')) { exit(1); }
+
+class QvitterBlocked extends Profile_block
+{
+
+ const CACHE_WINDOW = 201;
+
+ public static function getBlocked($profile_id, $offset = 0, $limit = PROFILES_PER_PAGE)
+ {
+ $ids = self::getBlockedIDs($profile_id, $offset, $limit);
+ return Profile_block::listFind('blocked', $ids);
+ }
+
+
+ private static function getBlockedIDs($profile_id, $offset, $limit)
+ {
+ $cacheKey = 'qvitterblocked:'.$profile_id;
+
+ $queryoffset = $offset;
+ $querylimit = $limit;
+
+ if ($offset + $limit <= self::CACHE_WINDOW) {
+ // Oh, it seems it should be cached
+ $ids = self::cacheGet($cacheKey);
+ if (is_array($ids)) {
+ return array_slice($ids, $offset, $limit);
+ }
+ // Being here indicates we didn't find anything cached
+ // so we'll have to fill it up simultaneously
+ $queryoffset = 0;
+ $querylimit = self::CACHE_WINDOW;
+ }
+
+ $blocks = new Profile_block();
+ $blocks->blocker = $profile_id;
+ $blocks->selectAdd('blocked');
+ $blocks->whereAdd("blocked != {$profile_id}");
+ $blocks->orderBy('modified DESC');
+ $blocks->limit($queryoffset, $querylimit);
+
+ if (!$blocks->find()) {
+ return array();
+ }
+
+ $ids = $blocks->fetchAll('blocked');
+
+ // If we're simultaneously filling up cache, remember to slice
+ if ($queryoffset === 0 && $querylimit === self::CACHE_WINDOW) {
+ self::cacheSet($cacheKey, $ids);
+ return array_slice($ids, $offset, $limit);
+ }
+
+ return $ids;
+ }
+
+ /**
+ * Flush cached blocks when blocks are updated
+ *
+ * @param mixed $dataObject Original version of object
+ *
+ * @return boolean success flag.
+ */
+ function update($dataObject=false)
+ {
+ self::blow('qvitterblocked:'.$this->blocker);
+ self::blow('qvitterblocked:'.$this->blocked);
+
+ return parent::update($dataObject);
+ }
+
+}