code:
# "Dusk" for pekwm 0.1.6
#  by lyrae (thrynk.deviantart.com)
#  Date: 02.01.2015
#  Update: 08.04.2016

IMPORT = "$HOME/.pekwm/pekwm-global-decor/EmptyDecor"

$font = "Conforta-8#Left#XFT"

PDecor {
	Decor = "Default" {
		Title {
			Height = "20"
			Pad = "2 6 6 6"

			Focused = "Image title.png"
			Unfocused = "Image title.png"

			Tab {
				Focused = "Image title.png"
				FocusedSelected = "Image title.png"
				Unfocused = "Image title.png"
				UnfocusedSelected = "Image title.png"
			}
			Separator {
				Focused = "#FFB03E"
				Unfocused = "#FFB03E"
			}
			Font {
				Focused           = "TitilliumWeb-8#Center#XFT"
			}
			FontColor {
				Focused = "#353535"
				FocusedSelected = "#DADADA"
				Unfocused = "#7E7E7E"
				UnfocusedSelected = "#7E7E7E"

			}
	               Buttons {
			Right {
				Focused = "Image close-active.png"
				Unfocused = "Image close-inactive.png"
				Pressed = "Image close-pressed.png"
				Button = "1" { Actions = "Close" }
				Button = "3" { Actions = "Kill" }
			}

			Right {
				Focused = "Image maximize-active.png"
				Unfocused = "Image maximize-inactive.png"
				Pressed = "Image maximize-pressed.png"
				Button = "1" { Actions = "Toggle Maximized 1 1" }
			}

			Right {
				Focused = "Image hide-active.png"
				Unfocused = "Image hide-inactive.png"
				Pressed = "Image hide-pressed.png"
				Button = "1" { Actions = "Set Iconified" }
			}
		}
			Border {
				Focused {
					TopLeft = "Image top-left.png"
					Top = "Image top-border.png"
					TopRight = "Image top-right.png"
					Left =  "Image left-border.png"
					Right = "Image right-border.png"
					BottomLeft = "Image bottom-left.png"
					Bottom = "Image bottom-border.png"
					BottomRight = "Image bottom-right.png"
				}
				Unfocused {
					TopLeft = "Image top-left.png"
					Top = "Image top-border.png"
					TopRight = "Image top-right.png"
					Left =  "Image left-border.png"
					Right = "Image right-border.png"
					BottomLeft = "Image bottom-left.png"
					Bottom = "Image bottom-border.png"
					BottomRight = "Image bottom-right.png"
				}
			}
		}
	}

	Decor = "Menu" {
		Title {
			Height = "0"
			Pad = "0 0 0 0"
			Focused = "Empty"
			Unfocused = "Empty"

	                Tab {
	                        Focused = "Empty"
	       	                FocusedSelected = "Empty"
	                        Unfocused = "Empty"
	                        UnfocusedSelected = "Empty"
	                }
                        Separator {
                                Focused = "Empty"
                                Unfocused = "Empty"
                        }
	                Font {
	                        Focused = "$font"
	                }
	                FontColor {
	                        Focused = "Empty"
	                        FocusedSelected = "Empty"
	                        Unfocused = "Empty"
	                        UnfocusedSelected = "Empty"
	                }
	                Buttons {
			}
			Border {
				Focused {
					TopLeft = "Image top-left2.png"
					Top = "Image menu-title1.png"
					TopRight = "Image top-right2.png"
					Left =  "Image left-border2.png"
					Right = "Image right-border2.png"
					BottomLeft = "Image bottom-left2.png"
					Bottom = "Image menu-bottom1.png"
					BottomRight = "Image bottom-right2.png"
				}
				Unfocused {
					TopLeft = "Image top-left2.png"
					Top = "Image menu-title1.png"
					TopRight = "Image top-right2.png"
					Left =  "Image left-border2.png"
					Right = "Image right-border2.png"
					BottomLeft = "Image bottom-left2.png"
					Bottom = "Image menu-bottom1.png"
					BottomRight = "Image bottom-right2.png"
				}
			}
		}
	}

	Decor = "Titlebarless" {
		Title {
			Focused = "Empty"
			Unfocused = "Empty"

                        Tab {
                                Focused = "Empty"
                                FocusedSelected = "Empty"
                                Unfocused = "Empty"
                                UnfocusedSelected = "Empty"
                        }

	                Separator {
	                        Focused = "Empty"
	                        Unfocused = "Empty"
        	        }
	                Font {
	                        Focused = "Empty"
	                }
	                FontColor {
	                        Focused = "Empty"
	                        FocusedSelected = "Empty"
	                        Unfocused = "Empty"
	                        UnfocusedSelected = "Empty"
	                }
			Border {
				Focused {
					TopLeft = "Empty"
					Top = "Empty"
					TopRight = "Empty"
					Left =  "Empty"
					Right = "Empty"
					BottomLeft = "Empty"
					Bottom = "Empty"
					BottomRight = "Empty"
				}
				Unfocused {
	                                TopLeft = "Empty"
	                                Top = "Empty"
        	                        TopRight = "Empty"
	                                Left =  "Empty"
	                                Right = "Empty"
	                                BottomLeft = "Empty"
	                                Bottom = "Empty"
	                                BottomRight = "Empty"
				}
			}
		}
	}

	Decor = "Statuswindow" {
		Title {
			Height = "0"
			Pad = "0 0 0 0"

			Focused = "Empty"
			Unfocused = "Empty"

			Tab {
				Focused = "Empty"
				FocusedSelected = "Empty"
				Unfocused = "Empty"
				UnfocusedSelected = "Empty"
			}
			Separator {
				Focused = "Empty"
				Unfocused = "Empty"
			}
			Font {
				Focused = "Empty"
			}
			FontColor {
				Focused = "Empty"
				FocusedSelected = "Empty"
				Unfocused = "Empty"
				UnfocusedSelected = "Empty"
			}
			Buttons {
			}
	                Border {
	                      Focused {
				TopLeft = "Empty"
				Top = "Empty"
				TopRight = "Empty"
				Left =  "Empty"
				Right = "Empty"
				BottomLeft = "Empty"
				Bottom = "Empty"
				BottomRight = "Empty"
			}
			Unfocused {
				TopLeft = "Empty"
				Top = "Empty"
				TopRight = "Empty"
				Left = "Empty"
				Right = "Empty"
				BottomLeft = "Empty"
				Bottom = "Empty"
				BottomRight = "Empty"
	                        }
	                }
		}
	}
}

Harbour {
        Texture = "Solid #262626"
	}
Menu {
        Pad = "0 0 5 5"

	Focused {
    		Font = "$font"
		Background = "Solid #111111"
    		Item = "Empty"
		Text = "#BEBEBE"
				Separator = "Image menuline1.png#Scaled"
                Arrow = "Empty"
  	}
  	Unfocused {
    		Font = "$font"
                Background = "Solid #111111"
    		Item = "Empty"
		Text = "#707070"
				Separator = "Image menuline1.png#Scaled"
                Arrow = "Empty"
  	}
  	Selected {
    		Font = "$font"
    		Background = "Empty"
			Item ="SolidRaised #191919 #404040 #333333 1  0"
    		Text = "#FFFFFF"
                Arrow = "Empty"
  	}
}
CmdDialog {
 	Font = "$font"
	Texture = "Solid #262626"
	Text = "#BFBFBF"
        Pad = "3 0 1 10"
}
Status {
	Font = "$font"
	Texture = "Solid #262626"
        Text = "#707070"
	Pad = "2 2 10 10"
}
WorkspaceIndicator {
	Font = "$FONT"
	Background = "Solid #1a1a1a"
	Workspace = "Image workspace-u.png"
	WorkspaceActive = "Image workspace-f.png"
	Text = "#ffffff"
	EdgePadding = "3"
	WorkspacePadding = "2"
}

path: Air-black/theme


code:
# Air-Black

Port do tema KDE Air Black para o gerenciador de janelas Pekwm.

## Instalando
Baixe o arquivo, descompacte e mova para `~/.pekwm/themes`

## Imagens

![menu](https://notabug.org/pekwm-themes/Air-Black/raw/master/scrots/168196-1.png)

![Janela sem foco](https://notabug.org/pekwm-themes/Air-Black/raw/master/scrots/168196-2.png)

![Janela com foco](https://notabug.org/pekwm-themes/Air-Black/raw/master/scrots/168196-3.png)

repo_name: pekwm/themes

license: mit


code:
<?php

 /* · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
  ·                                                                             ·
  ·                                                                             ·
  ·                             Q V I T T E R                                   ·
  ·                                                                             ·
  ·                      https://git.gnu.io/h2p/Qvitter                         ·
  ·                                                                             ·
  ·                                                                             ·
  ·                                 <o)                                         ·
  ·                                  /_////                                     ·
  ·                                 (____/                                      ·
  ·                                          (o<                                ·
  ·                                   o> \\\\_\                                 ·
  ·                                 \\)   \____)                                ·
  ·                                                                             ·
  ·                                                                             ·
  ·                                                                             ·
  ·  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 <http://www.gnu.org/licenses/>.            ·
  ·                                                                             ·
  ·  Contact h@nnesmannerhe.im if you have any questions.                       ·
  ·                                                                             ·
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */

const QVITTERDIR = __DIR__;

class QvitterPlugin extends Plugin {

    protected $hijack_ui = false;
    protected $qvitter_hide_replies = false;

	static function settings($setting)
	{

 	/* · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
         ·        							     ·
         ·                          S E T T I N G S                          ·
         ·         							     ·
         · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */

		// THESE SETTINGS CAN BE OVERRIDDEN IN CONFIG.PHP
		// e.g. $config['site']['qvitter']['enabledbydefault'] = false;

		// ENABLED BY DEFAULT (true/false)
		$settings['enabledbydefault'] = true;

		// DEFAULT BACKGROUND COLOR
		$settings['defaultbackgroundcolor'] = '#f4f4f4';

		// DEFAULT BACKGROUND IMAGE
		$settings['sitebackground'] = 'img/vagnsmossen.jpg';

		// FAVICON PATH (we've used realfavicongenerator.net to generate the icons)
		$settings['favicon_path'] = Plugin::staticPath('Qvitter', '').'img/gnusocial-favicons/';

		// DEFAULT SPRITE
		$settings['sprite'] = Plugin::staticPath('Qvitter', '').'img/sprite.png?v=41';

		// DEFAULT LINK COLOR
		$settings['defaultlinkcolor'] = '#0084B4';

		// ENABLE DEFAULT WELCOME TEXT
		$settings['enablewelcometext'] = true;

		// CUSTOM WELCOME TEXT (overrides the previous setting)
		$settings['customwelcometext'] = false;

// 		Example:
// 		$settings['customwelcometext']['sv'] = '
Välkommen till Quitter.se – en federerad1 mikrobloggsallmänning!

Etc etc...
';
// 		$settings['customwelcometext']['en'] = '
Welcome to Quitter.se – a federated microblog common!

Etc etc...
';

		// TIME BETWEEN POLLING
		$settings['timebetweenpolling'] = 5000; // ms

		// URL SHORTENER
		$settings['urlshortenerapiurl'] = 'http://qttr.at/yourls-api.php';
		$settings['urlshortenersignature'] = 'b6afeec983';
        $settings['urlshortenerformat'] = 'jsonp'; // if you're using shortener.php you can set this to 'json', which enables you to use YOURLS versions below 1.5.1

		// CUSTOM TERMS OF USE
		$settings['customtermsofuse'] = false;

		// IP ADDRESSES BLOCKED FROM REGISTRATION
		$settings['blocked_ips'] = array();

        // LINKIFY DOMAINS WITHOUT PROTOCOL AS DEFAULT
        $settings['linkify_bare_domains'] = true;

		 /* · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · ·
		  ·                                                                   ·
		  ·                (o>                                  >o)           ·
		  ·            \\\\_\                                    /_////       .
		  ·             \____)                                  (____/        ·
		  ·                                                                   ·
		  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */

		// config.php settings override the settings in this file
		$configphpsettings = common_config('site','qvitter') ?: array();
		foreach($configphpsettings as $configphpsetting=>$value) {
			$settings[$configphpsetting] = $value;
		}

        // set linkify setting
        common_config_set('linkify', 'bare_domains', $settings['linkify_bare_domains']);

		if(isset($settings[$setting])) {
			return $settings[$setting];
		}
		else {
			return false;
		}
	}

    public function initialize()
    {
        // show qvitter link in the admin panel
        common_config_append('admin', 'panels', 'qvitteradm');
    }

    function onCheckSchema()
    {
        $schema = Schema::get();

        // make sure we have a notifications table
        $schema->ensureTable('qvitternotification', QvitterNotification::schemaDef());

        // index the url column in the notice table
        $notice_schemadef = $schema->getTableDef('notice');
        if(!isset($notice_schemadef['indexes']['notice_url_idx'])) {
            try {
                $schema->createIndex('notice', 'url');
            } catch (Exception $e) {
                common_log(LOG_ERR, __METHOD__ . ': ' . $e->getMessage());
            }
        }

        return true;
    }

    public function onBeforePluginCheckSchema()
    {
        QvitterNotification::beforeSchemaUpdate();
        return true;
    }

	// route/reroute urls
    public function onRouterInitialized($m)
    {
		$m->connect(':nickname/mutes',
					array('action' => 'qvitter',
						  'nickname' => Nickname::INPUT_FMT));
        $m->connect('api/qvitter/mutes.json',
					array('action' => 'ApiQvitterMutes'));
        $m->connect('api/qvitter/sandboxed.:format',
                    array('action' => 'ApiQvitterSandboxed',
                          'format' => '(xml|json)'));
        $m->connect('api/qvitter/silenced.:format',
                    array('action' => 'ApiQvitterSilenced',
                          'format' => '(xml|json)'));
        $m->connect('api/qvitter/sandbox/create.json',
					array('action' => 'ApiQvitterSandboxCreate'));
        $m->connect('api/qvitter/sandbox/destroy.json',
					array('action' => 'ApiQvitterSandboxDestroy'));
        $m->connect('api/qvitter/silence/create.json',
					array('action' => 'ApiQvitterSilenceCreate'));
        $m->connect('api/qvitter/silence/destroy.json',
					array('action' => 'ApiQvitterSilenceDestroy'));
        $m->connect('services/oembed.:format',
                    array('action' => 'apiqvitteroembednotice',
                          'format' => '(xml|json)'));
        $m->connect('api/qvitter/check_email.json',
					array('action' => 'ApiQvitterCheckEmail'));
        $m->connect('api/qvitter/:nickname/lists/:id/subscribers.json',
                    array('action' => 'ApiQvitterListSubscribers',
                          'nickname' => '[a-zA-Z0-9]+',
                          'id' => '[a-zA-Z0-9]+'));
        $m->connect('api/qvitter/:nickname/lists/:id/members.json',
                    array('action' => 'ApiQvitterListMembers',
                          'nickname' => '[a-zA-Z0-9]+',
                          'id' => '[a-zA-Z0-9]+'));
        $m->connect('api/qvitter/:nickname/lists/:id/statuses.json',
                    array('action' => 'ApiQvitterTimelineList',
                          'nickname' => '[a-zA-Z0-9]+',
                          'id' => '[a-zA-Z0-9]+'));
        $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',
					array('action' => 'ApiQvitterMarkAllNotificationsAsSeen'));
		$m->connect('api/qvitter/favs_and_repeats/:notice_id.json',
					array('action' => 'ApiFavsAndRepeats'),
					array('notice_id' => '[0-9]+'));
		$m->connect('api/statuses/public_and_external_timeline.:format',
					array('action' => 'ApiTimelinePublicAndExternal',
						  'format' => '(xml|json|rss|atom|as)'));
        $m->connect('api/qvitter/update_bookmarks.json',
					array('action' => 'ApiQvitterUpdateBookmarks'));
        $m->connect('api/qvitter/set_profile_pref.json',
					array('action' => 'ApiQvitterSetProfilePref'));
        $m->connect('api/qvitter/update_link_color.json',
					array('action' => 'apiqvitterupdatelinkcolor'));
		$m->connect('api/qvitter/update_background_color.json',
					array('action' => 'apiqvitterupdatebackgroundcolor'));
		$m->connect('api/qvitter/checklogin.json',
					array('action' => 'apiqvitterchecklogin'));
		$m->connect('api/qvitter/allfollowing/:id.json',
					array('action' => 'apiqvitterallfollowing',
						  'id' => Nickname::INPUT_FMT));
		$m->connect('api/account/update_profile_banner.json',
					array('action' => 'ApiAccountUpdateProfileBanner'));
		$m->connect('api/saved_searches/list.json',
					array('action' => 'ApiSavedSearchesList'));
		$m->connect('api/trends/place.json',
					array('action' => 'ApiTrendsPlace'));
		$m->connect('api/activity/about_me/unread.json',
					array('action' => 'ApiActivityAboutMeUnread'));
		$m->connect('api/qvitter/update_background_image.json',
					array('action' => 'ApiUpdateBackgroundImage'));
		$m->connect('api/qvitter/update_avatar.json',
					array('action' => 'ApiUpdateAvatar'));
		$m->connect('api/qvitter/upload_image.json',
					array('action' => 'ApiUploadImage'));
		$m->connect('api/qvitter/external_user_show.json',
					array('action' => 'ApiExternalUserShow'));
		$m->connect('api/qvitter/toggle_qvitter.json',
					array('action' => 'ApiToggleQvitter'));
		$m->connect('api/qvitter/statuses/notifications.json',
					array('action' => 'apiqvitternotifications'));
		$m->connect(':nickname/notifications',
					array('action' => 'qvitter',
						  'nickname' => Nickname::INPUT_FMT));
		$m->connect(':nickname/blocks',
					array('action' => 'qvitter',
						  'nickname' => Nickname::INPUT_FMT));
        $m->connect('settings/qvitter',
                    array('action' => 'qvittersettings'));
        $m->connect('panel/qvitter',
                    array('action' => 'qvitteradminsettings'));
        $m->connect('main/qlogin',
                    array('action' => 'qvitterlogin'));
        $m->connect('api/qvitter/statuses/update.:format',
					array('action' => 'ApiQvitterStatusesUpdate'),
					array('format' => '(xml|json)'));

		// check if we should reroute UI to qvitter, and which home-stream the user wants (hide-replies or normal)
		$scoped = Profile::current();
		$qvitter_enabled_by_user = 0;
		$qvitter_disabled_by_user = 0;
		if ($scoped instanceof Profile) {
			$qvitter_enabled_by_user = (int)$scoped->getPref('qvitter', 'enable_qvitter', false);
			$qvitter_disabled_by_user = (int)$scoped->getPref('qvitter', 'disable_qvitter', false);
            $this->qvitter_hide_replies = $scoped->getPref('qvitter', 'hide_replies', false);
		}

        // reroute to qvitter if we're logged out and qvitter is enabled by default
        if(static::settings('enabledbydefault') === true && is_null($scoped)) {
            $this->hijack_ui = true;
        }
        // if we're logged in and qvitter is enabled by default, reroute if the user has not disabled qvitter
        elseif(static::settings('enabledbydefault') === true && $qvitter_disabled_by_user == 0){
            $this->hijack_ui = true;
        }
        // if we're logged in, and qvitter is _not_ enabled by default, reroute if the user enabled qvitter
        elseif(static::settings('enabledbydefault') === false && $qvitter_enabled_by_user == 1) {
            $this->hijack_ui = true;
        }

        if ($this->hijack_ui === true) {

            // other plugins might want to reroute to qvitter
            Event::handle('QvitterHijackUI', array($m));

            $m->connect('', array('action' => 'qvitter'));
			$m->connect('main/all', array('action' => 'qvitter'));
            $m->connect('main/public', array('action' => 'qvitter'));
			$m->connect('main/silenced', array('action' => 'qvitter'));
            $m->connect('main/sandboxed', array('action' => 'qvitter'));
			$m->connect('search/notice', array('action' => 'qvitter'));

            // if the user wants the twitter style home stream with hidden replies to non-friends
            if ($this->qvitter_hide_replies == 1) {
			URLMapperOverwrite::overwrite_variable($m, 'api/statuses/friends_timeline.:format',
									array('action' => 'ApiTimelineFriends'),
									array('format' => '(xml|json|rss|atom|as)'),
									'ApiTimelineFriendsHiddenReplies');
                }

			URLMapperOverwrite::overwrite_variable($m, ':nickname',
									array('action' => 'showstream'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/',
									array('action' => 'showstream'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/all',
									array('action' => 'all'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/subscriptions',
									array('action' => 'subscriptions'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/subscribers',
									array('action' => 'subscribers'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/groups',
									array('action' => 'usergroups'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/replies',
									array('action' => 'replies'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/favorites',
									array('action' => 'showfavorites'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, 'group/:nickname',
									array('action' => 'showgroup'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, 'group/:nickname/members',
									array('action' => 'groupmembers'),
									array('nickname' => Nickname::DISPLAY_FMT),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':nickname/all/:tag',
									array('action' => 'showprofiletag'),
									array('nickname' => Nickname::DISPLAY_FMT,
                                          'tag' => Router::REGEX_TAG),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':tagger/all/:tag/tagged',
									array('action' => 'peopletagged'),
									array('tagger' => Nickname::DISPLAY_FMT,
                                          'tag' => Router::REGEX_TAG),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, ':tagger/all/:tag/subscribers',
									array('action' => 'peopletagsubscribers'),
									array('tagger' => Nickname::DISPLAY_FMT,
                                          'tag' => Router::REGEX_TAG),
									'qvitter');

			$m->connect('group/:nickname/admins',
						array('action' => 'qvitter'),
						array('nickname' => Nickname::DISPLAY_FMT));

			URLMapperOverwrite::overwrite_variable($m, 'tag/:tag',
									array('action' => 'showstream'),
									array('tag' => Router::REGEX_TAG),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, 'notice/:notice',
									array('action' => 'shownotice'),
									array('notice' => '[0-9]+'),
									'qvitter');
			URLMapperOverwrite::overwrite_variable($m, 'conversation/:id',
									array('action' => 'conversation'),
									array('id' => '[0-9]+'),
									'qvitter');
		}

		// if qvitter is opt-out, disable the default register page (if we don't have a valid invitation code)
        $valid_code = isset($_POST['code'])
                        ? Invitation::getKV('code', $_POST['code'])
                        : null;
		if(self::settings('enabledbydefault') && empty($valid_code)) {
			$m->connect('main/register',
						array('action' => 'qvitter'));
			}

		// add user arrays for some urls, to use to build profile cards
		// this way we don't have to request this in a separate http request
		if(isset($_GET['withuserarray'])) switch (getPath($_REQUEST)) {
		case 'api/statuses/followers.json':
		case 'api/statuses/friends.json':
		case 'api/statusnet/groups/list.json':
		case 'api/statuses/mentions.json':
		case 'api/favorites.json':
		case 'api/statuses/friends_timeline.json':
		case 'api/statuses/user_timeline.json':

			// add logged in user's user array
			if (common_logged_in() && !isset($_GET['screen_name']) && !isset($_GET['id'])) {
				$profilecurrent = Profile::current();
				header('Qvitter-User-Array: '.json_encode($this->qvitterTwitterUserArray($profilecurrent)));
				}

			// add screen_name's user array
			elseif(isset($_GET['screen_name']) || isset($_GET['id'])){

                if(isset($_GET['screen_name'])) {
                    $user = User::getKV('nickname', $_GET['screen_name']);
                    }
                elseif(isset($_GET['id'])) {
                    $user = User::getKV('id', $_GET['id']);
                    }

                if($user instanceof User) {
					header('Qvitter-User-Array: '.json_encode($this->qvitterTwitterUserArray($user->getProfile())));
					}
				}
			break;
		 	}

    }

    /**
     * Remove CSRF cookie on logout
     *
     */

    function onEndLogout($action) {
        common_set_cookie('Qvitter-CSRF', '', 0);
        return true;
    }

    /**
     * Add script to default ui, to be able to toggle Qvitter with one click
     *
     * @return boolean hook return
     */

    function onEndShowScripts($action){

        if (common_logged_in()) {

			$user = common_current_user();
			$profile = $user->getProfile();
			$qvitter_enabled='false';

			// if qvitter is enabled by default but _not_ disabled by the user,
			if(QvitterPlugin::settings('enabledbydefault')) {
				$disabled = Profile_prefs::getConfigData($profile, 'qvitter', 'disable_qvitter');
				if($disabled == 0) {
					$qvitter_enabled='true';
					}
				}
			// if qvitter is disabled by default and _enabled_ by the user,
			else {
				$enabled = Profile_prefs::getConfigData($profile, 'qvitter', 'enable_qvitter');
				if($enabled == 1) {
					$qvitter_enabled='true';
					}
				}

            $action->inlineScript(' var toggleQvitterAPIURL = \''.common_path('', true).'api/qvitter/toggle_qvitter.json\';
            						var toggleText = \''._('New').' '.str_replace("'","\'",common_config('site','name')).'\';
            						var qvitterEnabled = '.$qvitter_enabled.';
            						var qvitterAllLink = \''.common_local_url('all', array('nickname' => $user->nickname)).'\';
            						');
            $action->script($this->path('js/toggleqvitter.js').'?changed='.date('YmdHis',filemtime(QVITTERDIR.'/js/toggleqvitter.js')));
        }
    }

    /**
     * Menu item for Qvitter
     *
     * @param Action $action action being executed
     *
     * @return boolean hook return
     */

    function onEndAccountSettingsNav($action)
    {
        $action_name = $action->trimmed('action');

        $action->menuItem(common_local_url('qvittersettings'),
                          // TRANS: Poll plugin menu item on user settings page.
                          _m('MENU', 'Qvitter'),
                          // TRANS: Poll plugin tooltip for user settings menu item.
                          _m('Qvitter Settings'),
                          $action_name === 'qvittersettings');

        return true;
    }

    /**
     * Menu item for admin panel
     *
     * @param Action $action action being executed
     *
     * @return boolean hook return
     */

    function onEndAdminPanelNav($action)
    {

        $action_name = $action->trimmed('action');

        $action->out->menuItem(common_local_url('qvitteradminsettings'),
                          // TRANS: Poll plugin menu item on user settings page.
                          _m('MENU', 'Qvitter'),
                          // TRANS: Poll plugin tooltip for user settings menu item.
                          _m('Qvitter Sidebar Notice'),
                          $action_name === 'qvitteradminsettings');

        return true;
    }

    /**
     * Add stuff to notices in API responses
     *
     * @param Action $action action being executed
     *
     * @return boolean hook return
     */

    function onNoticeSimpleStatusArray($notice, &$twitter_status, $scoped)
    {

        // strip tags from source, we can't trust html here, because of gs bug
        $twitter_status['source'] = htmlspecialchars(strip_tags($twitter_status['source']));

    	// groups
		$notice_groups = $notice->getGroups();
		$group_addressees = false;
		foreach($notice_groups as $g) {
			$group_addressees[] = array('nickname'=>$g->nickname,'url'=>$g->mainpage);
			}
		$twitter_status['statusnet_in_groups'] = $group_addressees;

		// for older verions of gnu social: include the repeat-id, which we need when unrepeating later
		if(array_key_exists('repeated', $twitter_status) && $twitter_status['repeated'] === true) {
            $repeated = Notice::pkeyGet(array('profile_id' => $scoped->id,
                                        	'repeat_of' => $notice->id,
                                            'verb' => 'http://activitystrea.ms/schema/1.0/share'));
			$twitter_status['repeated_id'] = $repeated->id;

			}

		// more metadata about attachments

        // get all attachments first, and put all the extra meta data in an array
        $attachments = $notice->attachments();
        $attachment_url_to_id = array();
        if (!empty($attachments)) {
            foreach ($attachments as $attachment) {
				if(is_object($attachment)) {
                    try {
						$enclosure_o = $attachment->getEnclosure();

                        // Oembed
                        if(array_key_exists('Oembed', StatusNet::getActivePlugins())) {
                            $oembed = File_oembed::getKV('file_id',$attachment->id);
                            if($oembed instanceof File_oembed) {
                                $oembed_html = str_replace('<!--//-->','',$oembed->html); // trash left of wordpress' javascript after htmLawed removed the tags
                                if($oembed->provider == 'Twitter' && strstr($oembed_html, '>— '.$oembed->author_name)) {
                                    $oembed_html = substr($oembed_html,0,strpos($oembed_html, '>— '.$oembed->author_name)+1); // remove user data from twitter oembed html (we have it in )
                                    $twitter_username = substr($oembed->html,strpos($oembed->html, '>— '.$oembed->author_name)+strlen('>— '.$oembed->author_name));
                                    $twitter_username = substr($twitter_username, strpos($twitter_username,'(@')+1);
                                    $twitter_username = substr($twitter_username, 0,strpos($twitter_username,')'));
                                    $oembed->title = $twitter_username;
                                    }
                                $oembed_html = str_replace('…','...',$oembed_html); // ellipsis is sometimes stored as html in db, for some reason
                                $oembed_html = mb_substr(trim(strip_tags($oembed_html)),0,250);
                                $oembed_title = trim(strip_tags(html_entity_decode($oembed->title,ENT_QUOTES))); // sometimes we have html charachters that we want to decode and then strip
                                $oembed_provider = trim(strip_tags(html_entity_decode($oembed->provider,ENT_QUOTES)));
                                $oembed_author_name = trim(strip_tags(html_entity_decode($oembed->author_name,ENT_QUOTES)));
                                $attachment_url_to_id[$enclosure_o->url]['oembed'] = array(
                                    'type'=> $oembed->type,
                                    'provider'=> $oembed_provider,
                                    'provider_url'=> $oembed->provider_url,
                                    'oembedHTML'=> $oembed_html,
                                    'title'=> $oembed_title,
                                    'author_name'=> $oembed_author_name,
                                    'author_url'=> $oembed->author_url
                                );
                            } else {
                                $attachment_url_to_id[$enclosure_o->url]['oembed'] = false;
                            }
                        }

                        // add id to all attachments
                        $attachment_url_to_id[$enclosure_o->url]['id'] = $attachment->id;

                        // add an attachment version to all attachments
                        // this means we can force all cached attachments to update, if we change this
                        $attachment_url_to_id[$enclosure_o->url]['version'] = '1.2';

                        // add data about thumbnails
                        $thumb = $attachment->getThumbnail();
						$large_thumb = $attachment->getThumbnail(1000,3000,false);
                        if(method_exists('File_thumbnail','url')) {
                            $thumb_url = File_thumbnail::url($thumb->filename);
                            $large_thumb_url = File_thumbnail::url($large_thumb->filename);
                        } else {
                            $thumb_url = $thumb->getUrl();
                            $large_thumb_url = $large_thumb->getUrl();
                        }
						$attachment_url_to_id[$enclosure_o->url]['thumb_url'] = $thumb_url;
                        $attachment_url_to_id[$enclosure_o->url]['large_thumb_url'] = $large_thumb_url;
						$attachment_url_to_id[$enclosure_o->url]['width'] = $attachment->width;
						$attachment_url_to_id[$enclosure_o->url]['height'] = $attachment->height;

						// animated gif?
						if($attachment->mimetype == 'image/gif') {
							$image = ImageFile::fromFileObject($attachment);
							if($image->animated == 1) {
								$attachment_url_to_id[$enclosure_o->url]['animated'] = true;
							} else {
								$attachment_url_to_id[$enclosure_o->url]['animated'] = false;
							}
                        }

                    // this applies to older versions of gnu social, i think
					} catch (Exception $e) {
                        $twitter_status['attachment_error'] = array('code'=>$e->getCode(),'message'=>$e->getMessage(),'file'=>$e->getFile(),'line'=>$e->getLine(),'trace'=>$e->getTraceAsString());
						$thumb = File_thumbnail::getKV('file_id', $attachment->id);
						if ($thumb instanceof File_thumbnail) {
                            $thumb_url = $thumb->getUrl();
							$attachment_url_to_id[$enclosure_o->url]['id'] = $attachment->id;
							$attachment_url_to_id[$enclosure_o->url]['thumb_url'] = $thumb_url;
                            $attachment_url_to_id[$enclosure_o->url]['large_thumb_url'] = $thumb_url;
							$attachment_url_to_id[$enclosure_o->url]['width'] = $attachment->width;
							$attachment_url_to_id[$enclosure_o->url]['height'] = $attachment->height;

							// animated gif?
							if($attachment->mimetype == 'image/gif') {
								$image = ImageFile::fromFileObject($attachment);
								if($image->animated == 1) {
									$attachment_url_to_id[$enclosure_o->url]['animated'] = true;
								}
								else {
									$attachment_url_to_id[$enclosure_o->url]['animated'] = false;
								}
							}
						}
					}
            	}
            }
        }

		// add the extra meta data to $twitter_status
        if (!empty($twitter_status['attachments'])) {
            foreach ($twitter_status['attachments'] as &$attachment) {
                if (!empty($attachment_url_to_id[$attachment['url']])) {
                    $attachment = array_merge($attachment,$attachment_url_to_id[$attachment['url']]);
                }
            }
        }

        // quoted notices
        if (!empty($twitter_status['attachments'])) {
            foreach ($twitter_status['attachments'] as &$attachment) {

                $quoted_notice = false;

                // if this attachment has an url this might be a notice url
                if (isset($attachment['url'])) {
                    $noticeurl = common_path('notice/', StatusNet::isHTTPS());
                    $instanceurl = common_path('', StatusNet::isHTTPS());

                    // remove protocol for the comparison below
                    $noticeurl_wo_protocol = preg_replace('(^https?://)', '', $noticeurl);
                    $instanceurl_wo_protocol = preg_replace('(^https?://)', '', $instanceurl);
                    $attachment_url_wo_protocol = preg_replace('(^https?://)', '', $attachment['url']);

                    // local notice urls
                    if(strpos($attachment_url_wo_protocol, $noticeurl_wo_protocol) === 0) {
                        $possible_notice_id = str_replace($noticeurl_wo_protocol,'',$attachment_url_wo_protocol);
                        if(ctype_digit($possible_notice_id)) {
                            $quoted_notice = Notice::getKV('id',$possible_notice_id);;
                        }
                    }
                    // remote. but we don't want to lookup every url in the db,
                    // so only do this if we have reason to believe this might
                    // be a remote notice url
                    elseif(strpos($attachment_url_wo_protocol, $instanceurl_wo_protocol) !== 0 && stristr($attachment_url_wo_protocol,'/notice/')) {
                        $quoted_notice = Notice::getKV('url',$attachment['url']);
                        // try with http<->https if no match. applies to quitter.se notices mostly
                        if(!$quoted_notice instanceof Notice) {
                            if(strpos($attachment['url'],'https://') === 0) {
                                $quoted_notice = Notice::getKV('url',str_replace('https://','http://', $attachment['url']));
                            } else {
                                $quoted_notice = Notice::getKV('url',str_replace('http://','https://', $attachment['url']));
                            }
                        }
                    }

                    // include the quoted notice in the attachment
                    if($quoted_notice instanceof Notice) {
                        $quoted_notice_author = Profile::getKV('id',$quoted_notice->profile_id);
                        if($quoted_notice_author instanceof Profile) {
                            $attachment['quoted_notice']['id'] = $quoted_notice->id;
                            $attachment['quoted_notice']['content'] = $quoted_notice->content;
                            $attachment['quoted_notice']['nickname'] = $quoted_notice_author->nickname;
                            $attachment['quoted_notice']['fullname'] = $quoted_notice_author->fullname;
                            $attachment['quoted_notice']['is_local'] = $quoted_notice_author->isLocal();
                            $quoted_notice_attachments = $quoted_notice->attachments();
                            foreach($quoted_notice_attachments as $q_attach) {
                                if(is_object($q_attach)) {
                                    try {
                                        $qthumb = $q_attach->getThumbnail();
                                        if(method_exists('File_thumbnail','url')) {
                                            $thumb_url = File_thumbnail::url($qthumb->filename);
                                        } else {
                                            $thumb_url = $qthumb->getUrl();
                                        }
                                        $attachment['quoted_notice']['attachments'][] = array('thumb_url'=>$thumb_url,
                                                                                'attachment_id'=>$q_attach->id);
                                    } catch (Exception $e) {
                                        common_debug('Qvitter: could not get thumbnail for attachment id='.$q_attach->id.' in quoted notice id='.$quoted_notice->id);
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        try {
            $twitter_status['external_url'] = $notice->getUrl(true);
        } catch (InvalidUrlException $e) {
		    common_debug('Qvitter: No URL available for external notice: id='.$notice->id);
        }

		// reply-to profile url
        try {
            $reply = $notice->getParent();
            $reply_profile = $reply->getProfile();
			$twitter_status['in_reply_to_profileurl'] = $reply_profile->getUrl();
            $twitter_status['in_reply_to_ostatus_uri'] = $reply_profile->getUri();
        } catch (ServerException $e) {
		    $twitter_status['in_reply_to_profileurl'] = null;
            $twitter_status['in_reply_to_ostatus_uri'] = null;
        }

        // attentions
        try {
            $attentions = $notice->getAttentionProfiles();
            $attentions_array = array();
            foreach ($attentions as $attn) {
                if(!$attn->isGroup()) {
                    $attentions_array[] = array(
                        'id' => $attn->getID(),
                        'screen_name' => $attn->getNickname(),
                        'fullname' => $attn->getStreamName(),
                        'profileurl' => $attn->getUrl(),
                        'ostatus_uri' => $attn->getUri(),
                    );
                }
            }
        $twitter_status['attentions'] = $attentions_array;
        } catch (Exception $e) {
            //
        }

		// fave number
		$faves = Fave::byNotice($notice);
		$favenum = count($faves);
		$twitter_status['fave_num'] = $favenum;

		// repeat number
		$repeats = $notice->repeatStream();
        $repeatnum=0;
        while ($repeats->fetch()) {
            if($repeats->verb == ActivityVerb::SHARE) { // i.e. not deleted repeats
                $repeatnum++;
                }
        	}
		$twitter_status['repeat_num'] = $repeatnum;

        // is this a post? (previously is_activity)
        if(method_exists('ActivityUtils','compareVerbs')) {
            $twitter_status['is_post_verb'] = ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST));
            }
        else {
            $twitter_status['is_post_verb'] = ($notice->verb == ActivityVerb::POST ? true : false);
            }

        // some more metadata about notice
		if($notice->is_local == '1') {
			$twitter_status['is_local'] = true;
			}
		else {
			$twitter_status['is_local'] = false;
			if ($twitter_status['is_post_verb'] === true) {
                try {
                    $twitter_status['external_url'] = $notice->getUrl(true);
                } catch (InvalidUrlException $e) {
        		    common_debug('Qvitter: No URL available for external notice: id='.$notice->id);
                }
				}
			}

		if(ActivityUtils::compareTypes($notice->verb, array('qvitter-delete-notice', 'delete'))) {
			$twitter_status['qvitter_delete_notice'] = true;
			}

        return true;
    }

    /**
     * Cover photo in API response, also follows_you, etc
     *
     * @return boolean hook return
     */

    function onTwitterUserArray($profile, &$twitter_user, $scoped)
    {

        $twitter_user['cover_photo'] = Profile_prefs::getConfigData($profile, 'qvitter', 'cover_photo');
        $twitter_user['background_image'] = Profile_prefs::getConfigData($profile, 'qvitter', 'background_image');

        // twitter compatible
        $twitter_user['profile_link_color'] = Profile_prefs::getConfigData($profile, 'theme', 'linkcolor');
        $twitter_user['profile_background_color'] = Profile_prefs::getConfigData($profile, 'theme', 'backgroundcolor');
        $twitter_user['profile_banner_url'] = Profile_prefs::getConfigData($profile, 'qvitter', 'cover_photo');

		if ($scoped) {
            // follows me?
            $twitter_user['follows_you'] = $profile->isSubscribed($scoped);

            // blocks me?
            $twitter_user['blocks_you']  = $profile->hasBlocked($scoped);
        }

		// local user?
		$twitter_user['is_local'] = $profile->isLocal();

		// silenced?
		$twitter_user['is_silenced'] = $profile->isSilenced();

        // rights
        $twitter_user['rights'] = array();
        $twitter_user['rights']['delete_user'] = $profile->hasRight(Right::DELETEUSER);
        $twitter_user['rights']['delete_others_notice'] = $profile->hasRight(Right::DELETEOTHERSNOTICE);
        $twitter_user['rights']['silence'] = $profile->hasRight(Right::SILENCEUSER);
        $twitter_user['rights']['sandbox'] = $profile->hasRight(Right::SANDBOXUSER);

		// sandboxed?
		$twitter_user['is_sandboxed'] = $profile->isSandboxed();

        // ostatus uri
        if($twitter_user['is_local']) {
            $user = $profile->getUser();
            if($user instanceof User) {
                $twitter_user['ostatus_uri'] = $user->uri;
            }
        } else {
            $ostatus_profile = Ostatus_profile::getKV('profile_id',$profile->id);
            if($ostatus_profile instanceof Ostatus_profile) {
                $twitter_user['ostatus_uri'] = $ostatus_profile->uri;
            }
        }

        return true;
    }

   /**
     * Insert into notification table
     */
    function insertNotification($to_profile_id, $from_profile_id, $ntype, $notice_id=false)
    {

		$to_user = User::getKV('id', $to_profile_id);
		$from_profile = Profile::getKV($from_profile_id);

		// don't notify remote profiles
		if (!$to_user instanceof User) {
			return false;
			}

		// no notifications from blocked profiles
		if ($to_user->hasBlocked($from_profile)) {
			return false;
			}

		// never notify myself
		if($to_profile_id == $from_profile_id) {
			return false;
			}

		// insert
		$notif = new QvitterNotification();
		$notif->to_profile_id = $to_profile_id;
		$notif->from_profile_id = $from_profile_id;
		$notif->ntype = $ntype;
		$notif->notice_id = $notice_id;
		$notif->created = common_sql_now();
		if (!$notif->insert()) {
			common_log_db_error($notif, 'INSERT', __FILE__);
			return false;
			}

        return true;
    }

    /**
     * Insert likes in notification table
     */
    public function onEndFavorNotice($profile, $notice)
    {

		// don't notify people favoriting their own notices
 		if($notice->profile_id != $profile->id) {
            $this->insertNotification($notice->profile_id, $profile->id, 'like', $notice->id);
 			}

        // mark reply and mention notifications as seen if i'm liking a notice i'm notified about
        self::markNotificationAsSeen($notice->id,$profile->id,'mention');
        self::markNotificationAsSeen($notice->id,$profile->id,'reply');
    }

    /**
     * Mark single notification as seen
     */
    public function markNotificationAsSeen($notice_id, $to_profile_id, $ntype)
    {
    $notification_to_mark_as_seen = QvitterNotification::pkeyGet(array(
        'is_seen' => 0,
        'notice_id' => $notice_id,
        'to_profile_id' => $to_profile_id,
        'ntype' => $ntype
    ));
    if($notification_to_mark_as_seen instanceof QvitterNotification) {
        $orig = clone($notification_to_mark_as_seen);
        $notification_to_mark_as_seen->is_seen = 1;
        $notification_to_mark_as_seen->update($orig);
    	}
    }

    /**
     * Remove likes in notification table on dislike
     */
    public function onEndDisfavorNotice($profile, $notice)
    {
		$notif = new QvitterNotification();
		$notif->from_profile_id = $profile->id;
		$notif->notice_id = $notice->id;
		$notif->ntype = 'like';
		$notif->delete();
        return true;
    }

    /**
     * Insert notifications for replies, mentions and repeats
     *
     * @return boolean hook flag
     */
    function onStartNoticeDistribute($notice) {

        assert($notice->id > 0);    // since we removed tests below

		// repeats
        if ($notice->isRepeat()) {
			$repeated_notice = Notice::getKV('id', $notice->repeat_of);
			if ($repeated_notice instanceof Notice) {
	            $this->insertNotification($repeated_notice->profile_id, $notice->profile_id, 'repeat', $repeated_notice->id);

                // mark reply/mention-notifications as read if we're repeating to a notice we're notified about
                self::markNotificationAsSeen($repeated_notice->id,$notice->profile_id,'mention');
                self::markNotificationAsSeen($repeated_notice->id,$notice->profile_id,'reply');

                // (no other notifications repeats)
                return true;
                }
 			}

		// don't add notifications for activity/non-post-verb notices
        if(method_exists('ActivityUtils','compareVerbs')) {
            $is_post_verb = ActivityUtils::compareVerbs($notice->verb, array(ActivityVerb::POST));
            }
        else {
            $is_post_verb = ($notice->verb == ActivityVerb::POST ? true : false);
            }
        if($notice->source == 'activity' || !$is_post_verb) {
			return true;
			}

		// mark reply/mention-notifications as read if we're replying to a notice we're notified about
		if($notice->reply_to) {
            self::markNotificationAsSeen($notice->reply_to,$notice->profile_id,'mention');
            self::markNotificationAsSeen($notice->reply_to,$notice->profile_id,'reply');
			}

		// replies and mentions
 		$reply_notification_to = false;
		// check for reply to insert in notifications
		if($notice->reply_to) {
			try {
				$replyauthor = $notice->getParent()->getProfile();
				$reply_notification_to = $replyauthor->id;
				$this->insertNotification($replyauthor->id, $notice->profile_id, 'reply', $notice->id);
			//} catch (NoParentNoticeException $e) {	// TODO: catch this when everyone runs latest GNU social!
				// This is not a reply to something (has no parent)
			} catch (NoResultException $e) {
				// Parent author's profile not found! Complain louder?
				common_log(LOG_ERR, "Parent notice's author not found: ".$e->getMessage());
			}
		}

		// check for mentions to insert in notifications
		$mentions = $notice->getReplies();
		$sender = Profile::getKV($notice->profile_id);
		$all_mentioned_user_ids = array();
		foreach ($mentions as $mentioned) {

			// no duplicate mentions
			if(in_array($mentioned, $all_mentioned_user_ids)) {
				continue;
				}
			$all_mentioned_user_ids[] = $mentioned;

			// only notify if mentioned user is not already notified for reply
			if($reply_notification_to != $mentioned) {
				$this->insertNotification($mentioned, $notice->profile_id, 'mention', $notice->id);
				}
			}

        return true;
    	}

   /**
     * Delete any notifications tied to deleted notices and un-repeats
     *
     * @return boolean hook flag
     */
    public function onNoticeDeleteRelated($notice)
    {

		$notif = new QvitterNotification();

		// unrepeats
        if ($notice->isRepeat()) {
			$repeated_notice = Notice::getKV('id', $notice->repeat_of);
			$notif->notice_id = $repeated_notice->id;
			$notif->from_profile_id = $notice->profile_id;
 			}
		// notices
		else {
			$notif->notice_id = $notice->id;
			}

		$notif->delete();

        // don't delete if this is a user is being deleted
        // because that creates an infinite loop of deleting and creating notices...
        $user_is_deleted = false;
        try {
            // outputs an activity notice that this notice was deleted
            $profile = $notice->getProfile();
            $user = User::getKV('id',$profile->id);
            if($user instanceof User && $user->hasRole(Profile_role::DELETED)) {
                $user_is_deleted = true;
            }
        } catch (NoProfileException $e) {
            $user_is_deleted = true;
        }

        if(!$user_is_deleted && class_exists('StatusNet') && !array_key_exists('ActivityModeration', StatusNet::getActivePlugins())) {
            $rendered = sprintf(_m('%2$s deleted notice {{%4$s}}.'),
                                htmlspecialchars($profile->getUrl()),
                                htmlspecialchars($profile->getBestName()),
                                htmlspecialchars($notice->getUrl()),
                                htmlspecialchars($notice->uri));
            $text = sprintf(_m('%1$s deleted notice {{%2$s}}.'),
                                $profile->getBestName(),
                                $notice->uri);
            $uri = TagURI::mint('delete-notice:%d:%d:%s',
                                $notice->profile_id,
                                $notice->id,
                                common_date_iso8601(common_sql_now()));
            $notice = Notice::saveNew($notice->profile_id,
                                      $text,
                                      ActivityPlugin::SOURCE,
                                      array('rendered' => $rendered,
                                            'urls' => array(),
                                            'uri' => $uri,
                                            'verb' => 'qvitter-delete-notice',
                                            'object_type' => ActivityObject::ACTIVITY));
        }

        return true;
    }

   /**
     * Checks for deleted remote notices and deleted the locally
     * A local qvitter-delete-notice is outputted in the onNoticeDeleteRelated event above
     *
     * @return boolean hook flag
     */

    public function onEndHandleFeedEntry($activity) {

		if($activity->verb == 'qvitter-delete-notice' && class_exists('StatusNet') && !array_key_exists('ActivityModeration', StatusNet::getActivePlugins())) {

			$deleter_profile_uri = $activity->actor->id;
			$deleted_notice_uri = $activity->objects[0]->objects[0]->content;
			$deleted_notice_uri = substr($deleted_notice_uri,strpos($deleted_notice_uri,'{{')+2);
			$deleted_notice_uri = substr($deleted_notice_uri,0,strpos($deleted_notice_uri,'}}'));

			$deleter_ostatus_profile = Ostatus_profile::getKV('uri', $deleter_profile_uri);

			if(!$deleter_ostatus_profile instanceof Ostatus_profile) {
				return true;
				}

			$deleter_profile = Profile::getKV('id', $deleter_ostatus_profile->profile_id);
			$deleted_notice = Notice::getKV('uri', $deleted_notice_uri);

			if(!($deleter_profile instanceof Profile) || !($deleted_notice instanceof Notice)) {
				return true;
				}

			if($deleter_profile->id != $deleted_notice->profile_id) {
				return true;
				}

			$deleted_notice->delete();
			}

        return true;
    }

   /**
     * Add notification on subscription, remove on unsubscribe
     *
     * @return boolean hook flag
     */
    public function onEndSubscribe($subscriber, $other)
    {
		if(Subscription::exists($subscriber, $other)) {
			$this->insertNotification($other->id, $subscriber->id, 'follow', 1);
			}

        return true;
    }
    public function onEndUnsubscribe($subscriber, $other)
    {
		if(!Subscription::exists($subscriber, $other)) {
			$notif = new QvitterNotification();
			$notif->to_profile_id = $other->id;
			$notif->from_profile_id = $subscriber->id;
			$notif->ntype = 'follow';
			$notif->delete();
			}

        return true;
    }

   /**
     * Replace GNU Social's default FAQ with Qvitter's
     *
     * @return boolean hook flag
     */
    public function onEndLoadDoc($title, &$output)
    {

    	if($title == 'faq') {

	    	$faq = file_get_contents(QVITTERDIR.'/doc/en/faq.html');
	    	$faq = str_replace('{instance-name}',common_config('site','name'),$faq);
	    	$faq = str_replace('{instance-url}',common_config('site','server'),$faq);
	    	$faq = str_replace('{instance-url-with-protocol}',common_path('', true),$faq);

	        if (common_logged_in()) {
				$user = common_current_user();
		    	$faq = str_replace('{nickname}',$user->nickname,$faq);
		    	}
	    	$output = $faq;
    		}

        return true;
    }

   /**
     * Add menu items to top header in Classic
     *
     * @return boolean hook flag
     */
    public function onStartPrimaryNav($action)
    {

                $action->menuItem(common_local_url('doc', array('title' => 'faq')),
                                // TRANS: Menu item in primary navigation panel.
                                _m('MENU','FAQ'),
                                // TRANS: Menu item title in primary navigation panel.
                                _('Frequently asked questions'),
                                false,
                                'top_nav_doc_faq');

        return true;
    }

   /**
     * No registration for blocked ips
     *
     * @return boolean hook flag
     */
    public function onStartUserRegister($profile)
    {

		if(is_array(self::settings("blocked_ips"))) {
			if(in_array($_SERVER['REMOTE_ADDR'], self::settings("blocked_ips"))) {
				return false;
				}
			}

        return true;
    }

   /**
     * Correct group mentions
     *
     * We get the correct group ids in a $_POST var called "post_to_groups", formatted as a string with ids separated by colon, e.g. 4:5
     *
     * @return boolean hook flag
     */
    public function onEndFindMentions($sender, $text, &$mentions) {

        // get the correct group profiles
        if(isset($_POST['post_to_groups'])) {
            $correct_group_mentions = explode(':',$_POST['post_to_groups']);
            foreach($correct_group_mentions as $group_id) {
                $correct_groups[] = User_group::getKV('id',$group_id);
                }

            // loop through the groups guessed by gnu social's common_find_mentions() and correct them
            foreach($mentions as $mention_array_id=>$mention) {
                foreach($correct_groups as $correct_groups_array_id=>$correct_group) {
                    if(isset($mention['mentioned'][0]->nickname)
                    && isset($correct_group->nickname)
                    && $mention['mentioned'][0]->nickname == $correct_group->nickname
                    && !isset($mentions[$mention_array_id]['corrected'])) {
                        $user_group_profile = Profile::getKV('id',$correct_group->profile_id);
                        $mentions[$mention_array_id]['mentioned'][0] = $user_group_profile;
                        $mentions[$mention_array_id]['url'] = $correct_group->permalink();
                        $mentions[$mention_array_id]['title'] = $correct_group->getFancyName();
                        $mentions[$mention_array_id]['corrected'] = true;
                        // now we've used this
                        unset($correct_groups[$correct_groups_array_id]);
                        }
                    }
                }
            }

        return true;
        }

   /**
     * Add unread notification count to all API responses, when logged in
     *
     * @return boolean hook flag
     */
    public function onEndSetApiUser($user) {

        // if we're POST:ing and are logged in using a regular session (i.e. not basic auth or oauth)
        // check that we have a correct csrf cookie and header, otherwise deny
        if(common_logged_in() && $_SERVER['REQUEST_METHOD'] === 'POST') {
            if(!isset($_COOKIE['Qvitter-CSRF'])) {
                throw new ServerException(_('Error setting user. Missing authorization cookie data. Please logout and login again.'));
            }
            $csrf_token = sha1(common_config('qvitter', 'appid').session_id());
            if($_COOKIE['Qvitter-CSRF'] != $csrf_token) {
                throw new ServerException(_('Error setting user. Invalid authorization cookie data. Please logout and login again.'));
            }
            if(!isset($_SERVER['HTTP_X_QVITTER_CSRF'])) {
                throw new ServerException(_('Error setting user. Missing authorization header data. Please logout and login again.'));
            }
            if($_SERVER['HTTP_X_QVITTER_CSRF'] != $csrf_token) {
                throw new ServerException(_('Error setting user. Invalid authorization header data. Please logout and login again.'));
            }
        }

        // cleanup sessions, to allow for simultaneous http-requests,
        // e.g. if posting a notice takes a very long time
        Session::cleanup();

        if (!$user instanceof User) {
            return true;
        }

		$user_id = $user->id;
        $profile = $user->getProfile();
		$notification = new QvitterNotification();

		$notification->selectAdd();
		$notification->selectAdd('ntype');
		$notification->selectAdd('count(id) as count');
		$notification->whereAdd("(to_profile_id = '".$user_id."')");

        // if the user only want notifications from users they follow
        $only_show_notifications_from_users_i_follow = Profile_prefs::getConfigData($profile, 'qvitter', 'only_show_notifications_from_users_i_follow');
        if($only_show_notifications_from_users_i_follow == '1') {
            $notification->whereAdd(sprintf('qvitternotification.from_profile_id IN (SELECT subscribed FROM subscription WHERE subscriber = %u)', $user_id));
            }

        // the user might have opted out from notifications from profiles they have muted
        $hide_notifications_from_muted_users = Profile_prefs::getConfigData($profile, 'qvitter', 'hide_notifications_from_muted_users');
        if($hide_notifications_from_muted_users == '1') {
            $muted_ids = QvitterMuted::getMutedIDs($profile->id,0,10000); // get all (hopefully not more than 10 000...)
            if($muted_ids !== false && count($muted_ids) > 0) {
                $ids_imploded = implode(',',$muted_ids);
                $notification->whereAdd('qvitternotification.from_profile_id NOT IN ('.$ids_imploded.')');
                }
            }

        // the user might have opted out from certain notification types
        $current_profile = $user->getProfile();
        $disable_notify_replies_and_mentions = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_replies_and_mentions');
        $disable_notify_favs = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_favs');
        $disable_notify_repeats = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_repeats');
        $disable_notify_follows = Profile_prefs::getConfigData($current_profile, 'qvitter', 'disable_notify_follows');
        if($disable_notify_replies_and_mentions == '1') {
            $notification->whereAdd('qvitternotification.ntype != "mention"');
            $notification->whereAdd('qvitternotification.ntype != "reply"');
            }
        if($disable_notify_favs == '1') {
            $notification->whereAdd('qvitternotification.ntype != "like"');
            }
        if($disable_notify_repeats == '1') {
            $notification->whereAdd('qvitternotification.ntype != "repeat"');
            }
        if($disable_notify_follows == '1') {
            $notification->whereAdd('qvitternotification.ntype != "follow"');
            }

		$notification->groupBy('ntype');
		$notification->whereAdd("(is_seen = '0')");
		$notification->whereAdd("(notice_id IS NOT NULL)");	// sometimes notice_id is NULL, those notifications are corrupt and should be discarded
		$notification->find();

		$new_notifications = array();
		while ($notification->fetch()) {
			$new_notifications[$notification->ntype] = $notification->count;
			}

		header('Qvitter-Notifications: '.json_encode($new_notifications));

    	return true;
    	}

    function onPluginVersion(array &$versions)
    {
        $versions[] = array('name' => 'Qvitter',
                            'version' => '5-alpha',
                            'author' => 'Hannes Mannerheim',
                            'homepage' => 'https://git.gnu.io/h2p/Qvitter',
                            'rawdescription' => _m('User interface'));
        return true;
    }

    function qvitterTwitterUserArray($profile)
    {
        $twitter_user = array();

        try {
            $user = $profile->getUser();
        } catch (NoSuchUserException $e) {
            $user = null;
        }

        $twitter_user['id'] = intval($profile->id);
        $twitter_user['name'] = $profile->getBestName();
        $twitter_user['screen_name'] = $profile->nickname;
        $twitter_user['location'] = ($profile->location) ? $profile->location : null;
        $twitter_user['description'] = ($profile->bio) ? $profile->bio : null;

        // TODO: avatar url template (example.com/user/avatar?size={x}x{y})
        $twitter_user['profile_image_url'] = Avatar::urlByProfile($profile, AVATAR_STREAM_SIZE);
        $twitter_user['profile_image_url_https'] = $twitter_user['profile_image_url'];

        // START introduced by qvitter API, not necessary for StatusNet API
        $twitter_user['profile_image_url_profile_size'] = Avatar::urlByProfile($profile, AVATAR_PROFILE_SIZE);
        try {
            $avatar  = Avatar::getUploaded($profile);
            $origurl = $avatar->displayUrl();
        } catch (Exception $e) {

            // ugly fix if avatar is missing in the db but exists on the server
            $largest_avatar = array('name'=>false,'size'=>0);
            foreach (glob('avatar/'.$profile->id.'-*') as $filename) {
                $size = filesize($filename);
                if($size > $largest_avatar['size']) {
                    $largest_avatar['size'] = $size;
                    $largest_avatar['name'] = $filename;
                }
            }
            if($largest_avatar['size']>0) {
                $origurl = common_path('', StatusNet::isHTTPS()).$largest_avatar['name'];
            } else {
                $origurl = $twitter_user['profile_image_url_profile_size'];
            }

        }
        $twitter_user['profile_image_url_original'] = $origurl;

        $twitter_user['groups_count'] = $profile->getGroupCount();
        foreach (array('linkcolor', 'backgroundcolor') as $key) {
            $twitter_user[$key] = Profile_prefs::getConfigData($profile, 'theme', $key);
        }
        // END introduced by qvitter API, not necessary for StatusNet API

        $twitter_user['url'] = ($profile->homepage) ? $profile->homepage : null;
        $twitter_user['protected'] = (!empty($user) && $user->private_stream) ? true : false;
        $twitter_user['followers_count'] = $profile->subscriberCount();

        // Note: some profiles don't have an associated user

        $twitter_user['friends_count'] = $profile->subscriptionCount();

        $twitter_user['created_at'] = ApiAction::dateTwitter($profile->created);

        $timezone = 'UTC';

        if (!empty($user) && $user->timezone) {
            $timezone = $user->timezone;
        }

        $t = new DateTime;
        $t->setTimezone(new DateTimeZone($timezone));

        $twitter_user['utc_offset'] = $t->format('Z');
        $twitter_user['time_zone'] = $timezone;
        $twitter_user['statuses_count'] = $profile->noticeCount();

        // Is the requesting user following this user?
        $twitter_user['following'] = false;
        $twitter_user['statusnet_blocking'] = false;

		$logged_in_profile = null;

        if(common_logged_in()) {

            $logged_in_profile = Profile::current();

            $twitter_user['following'] = $logged_in_profile->isSubscribed($profile);
            $twitter_user['statusnet_blocking']  = $logged_in_profile->hasBlocked($profile);

        }

        // StatusNet-specific

        $twitter_user['statusnet_profile_url'] = $profile->profileurl;

        Event::handle('TwitterUserArray', array($profile, &$twitter_user, $logged_in_profile, array()));

        return $twitter_user;
    }

}

/**
 * Overwrites variables in URL-mapping
 *
 */
class URLMapperOverwrite extends URLMapper
{
    static function overwrite_variable($m, $path, $args, $paramPatterns, $newaction)
    {

        $m->connect($path, array('action' => $newaction), $paramPatterns);

		$regex = self::makeRegex($path, $paramPatterns);

		foreach($m->variables as $n=>$v)
			if($v[1] == $regex)
				$m->variables[$n][0]['action'] = $newaction;
    }
}


code:
Qvitter
==========================================

* Author:    Hannes Mannerheim (<h@nnesmannerhe.im>)
* Last mod.: Jan, 2016
* Version:   5-alpha
* Homepage:  <https://git.gnu.io/h2p/Qvitter>

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 <http://www.gnu.org/licenses/>.

Setup
-----

1. Install GNU social _directly under a domain_ (Qvitter is not compatible with
subdirectory installs) with _fancy urls_ enabled (if your instance has
"index.php" in its URLs, Qvitter will not work properly) _and HTTPS enabled_ (it's
always best to use HTTPS from the beginning, there will be issues with federation
if you choose to enable HTTPS later)

2. Put all files in local/plugins/Qvitter

3. Add `addPlugin('Qvitter');` to your /config.php file.

4. It's highly recommended to use the StoreRemoteMedia plugin. It will cache attachments from remote instances locally and make them appear in the streams. Add `addPlugin('StoreRemoteMedia');` to your /config.php file. (Only available in newer GNU social)

5. There are settings in QvitterPlugin.php, but for easy updates, put them in config.php instead. Example:

````
// Qvitter-settings
$config['site']['qvitter']['enabledbydefault'] = true;
$config['site']['qvitter']['defaultbackgroundcolor'] = '#f4f4f4';
$config['site']['qvitter']['defaultlinkcolor'] = '#0084B4';
$config['site']['qvitter']['timebetweenpolling'] = 5000;
$config['site']['qvitter']['urlshortenerapiurl'] = 'http://qttr.at/yourls-api.php'; // if your site is on HTTPS, use url to shortener.php here
$config['site']['qvitter']['urlshortenersignature'] = 'b6afeec983';
$config['site']['qvitter']['sitebackground'] = 'img/vagnsmossen.jpg';
$config['site']['qvitter']['favicon_path'] = Plugin::staticPath('Qvitter', '').'img/gnusocial-favicons/';
$config['site']['qvitter']['sprite'] = Plugin::staticPath('Qvitter', '').'img/sprite.png?v=40';
$config['site']['qvitter']['enablewelcometext'] = true;
// $config['site']['qvitter']['customwelcometext']['sv'] = '<h1>Välkommen till Quitter.se – en federerad<sup>1</sup> mikrobloggsallmänning!</h1><p>Etc etc...</p>';
// $config['site']['qvitter']['customwelcometext']['en'] = '<h1>Welcome to Quitter.se – a federated microblog common!</h1><p>Etc etc...</p>';
// $config['site']['qvitter']['customtermsofuse'] = 'Our custom terms of use';
$config['site']['qvitter']['blocked_ips'] = array();

// Recommended GNU social settings
$config['thumbnail']['maxsize'] = 3000; // recommended setting to get more high-res image previews
$config['profile']['delete'] = true; // twitter users are used to being able to remove their accounts
$config['profile']['changenick'] = true; // twitter users are used to being able to change their nicknames
$config['public']['localonly'] = true; // only local users in the public timeline (qvitter always has a timeline for the whole known network)
addPlugin('StoreRemoteMedia'); // makes remote images appear in the feed

````

The settings should be self-explanatory. In doubt, ask in the !qvitter group on quitter.se, or email h@nnesmannerhe.im

6. For better performance, disable checkschema (instructions in GNU social's config.php),
but don't forget to run it when updating plugins (including Qvitter).

7. To change the logo, edit /img/sprite.png. For easier git-updates, put it in GNU social's locale folder, and edit the sprite path setting above.

Translation
-----

1. Translation files reside in /locale and /doc folders.

2. When translating .json files, don't translate the placeholder strings inside curly brackets {}

API
-----

Qvitter uses GNU social's built in "twitter compatible" API. The API was designed to be
compatible with Twitter's API v1.0. Some documentation is here: <http://skilledtests.com/wiki/Twitter-compatible_API>
Twitter's API v1.1 documentation is also useful, but may be partly incorrect.

Qvitter extends this API in a few undocumented ways. See onRouterInitialized() in QvitterPlugin.php
for ideas about paths for the API extensions.

Notes
-----

Qvitter is tested with the latest GNU social nightly


code:
<?php
 /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  ·                                                                             ·
  ·  Update the profile banner                                                  ·
  ·                                                                             ·
  - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  ·                                                                             ·
  ·                                                                             ·
  ·                             Q V I T T E R                                   ·
  ·                                                                             ·
  ·                      https://git.gnu.io/h2p/Qvitter                         ·
  ·                                                                             ·
  ·                                                                             ·
  ·                                                                             ·
  ·                                 <o)                                         ·
  ·                                  /_////                                     ·
  ·                                 (____/                                      ·
  ·                                          (o<                                ·
  ·                                   o> \\\\_\                                 ·
  ·                                 \\)   \____)                                ·
  ·                                                                             ·
  ·                                                                             ·
  ·  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 <http://www.gnu.org/licenses/>.            ·
  ·                                                                             ·
  ·  Contact h@nnesmannerhe.im if you have any questions.                       ·
  ·                                                                             ·
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */

if (!defined('GNUSOCIAL')) {
    exit(1);
}

class ApiAccountUpdateProfileBannerAction extends ApiAuthAction
{
    protected $needPost = true;

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        $this->format = 'json';

        $this->user = $this->auth_user;

        $this->cropW = $this->trimmed('width');
        $this->cropH = $this->trimmed('height');
        $this->cropX = $this->trimmed('offset_left');
        $this->cropY = $this->trimmed('offset_top');
        $this->img   = $this->trimmed('banner');

        return true;
    }

    /**
     * Handle the request
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        // see if we have regular uploaded image data
        try {

            $mediafile = MediaFile::fromUpload('banner', $this->scoped);

        } catch (NoUploadedMediaException $e) {

            // if not we may have base64 data

            $this->img = str_replace('data:image/jpeg;base64,', '', $this->img);
            $this->img = str_replace('data:image/png;base64,', '', $this->img);
            $this->img = str_replace(' ', '+', $this->img);
            $this->img = base64_decode($this->img, true);

            $fh = tmpfile();
            fwrite($fh, $this->img);
            unset($this->img);
            fseek($fh, 0);

            $mediafile = MediaFile::fromFilehandle($fh, $this->scoped);
        }

        // maybe resize
        $width = $this->cropW;
        $height = $this->cropH;
        $scale = 1;
        if($width > 1200) {
            $scale = 1200/$width;
        } elseif($height > 600) {
            $scale = 600/$height;
        }
        $width = round($width*$scale);
        $height = round($height*$scale);

        // crop
        try {
            $imagefile = ImageFile::fromFileObject($mediafile->fileRecord);
            unset($mediafile);

            // We're just using the Avatar function to build a filename here
            // but we don't save it _as_ an avatar below... but in the same dir!
            $filename = Avatar::filename(
                $this->scoped->getID(),
                image_type_to_extension($imagefile->preferredType()),
                null,
                'banner-'.common_timestamp()
            );

            $imagefile->resizeTo(Avatar::path($filename), array('width'=>$width, 'height'=>$height, 'x'=>$this->cropX, 'y'=>$this->cropY, 'w'=>$this->cropW, 'h'=>$this->cropH));
            $result['url'] = Avatar::url($filename);
        } catch (Exception $e) {
            $this->clientError(_('The image could not be resized and cropped. '.$e), 422);
        }

        // save in profile_prefs
        try {
		    Profile_prefs::setData($this->scoped, 'qvitter', 'cover_photo', $result['url']);
        } catch (ServerException $e) {
            $this->clientError(_('The image could not be resized and cropped. '.$e), 422);
        }

        // return json
        $this->initDocument('json');
        $this->showJsonObjects($result);
        $this->endDocument('json');
    }
}


code:
<?php

  /* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
   ·                                                                            ·
   ·  Undocumented Twitter API used by Twidere (not implemented yet)            ·
   ·                                                                            ·
   - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  ·                                                                             ·
  ·                                                                             ·
  ·                             Q V I T T E R                                   ·
  ·                                                                             ·
  ·                      https://git.gnu.io/h2p/Qvitter                         ·
  ·                                                                             ·
  ·                                                                             ·
  ·                                 <o)                                         ·
  ·                                  /_////                                     ·
  ·                                 (____/                                      ·
  ·                                          (o<                                ·
  ·                                   o> \\\\_\                                 ·
  ·                                 \\)   \____)                                ·
  ·                                                                             ·
  ·                                                                             ·
  ·                                                                             ·
  ·  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 <http://www.gnu.org/licenses/>.            ·
  ·                                                                             ·
  ·  Contact h@nnesmannerhe.im if you have any questions.                       ·
  ·                                                                             ·
  · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · · */

if (!defined('GNUSOCIAL')) { exit(1); }

class ApiActivityAboutMeUnreadAction extends ApiAuthAction
{

    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        return true;
    }

    /**
     * Handle the request
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        $json_obects = array();

        $this->initDocument('json');
        $this->showJsonObjects($json_obects);
        $this->endDocument('json');
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show an external user's profile information
 *
 * PHP version 5
 *
 * LICENCE: This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   GNUsocial
 * @author    Hannes Mannerheim <h@nnesmannerhe.im>
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://www.gnu.org/software/social/
 */

if (!defined('GNUSOCIAL')) { exit(1); }

/**
 * Ouputs information for a user, specified by ID or screen name.
 * The user's most recent status will be returned inline.
 */
class ApiExternalUserShowAction extends ApiPrivateAuthAction
{
    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     *
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        $this->format = 'json';

        $profileurl = urldecode($this->arg('profileurl'));
        $nickname = urldecode($this->arg('nickname'));

		$this->profile = new stdClass();
		$this->profile->external = null;
		$this->profile->local = null;

		// we can get urls of two types of urls 	(1) ://instance/nickname
		//						    				(2) ://instance/user/1234
		//
		// in case (1) we have the problem that the html can be outdated,
		// i.e. the user can have changed her nickname. we also have no idea
		// if it is a multi or single user instance, which forces us to
		// guess the api root url.
		//
		// in case (2) we have another problem: we can't use that url to find
		// the local profile for the external user, we need url:s of type (2)
		// for that. so we have to try getting the nickname from the external
		// instance first

		// case (2)
		if(strstr($profileurl, '/user/')) {

			$external_user_id = substr($profileurl,strpos($profileurl,'/user/')+6);
			$external_instance_url = substr($profileurl,0,strpos($profileurl,'/user/')+1);

			if(!is_numeric($external_user_id)) {
				return true;
				}

			$external_profile = $this->getProfileFromExternalInstance($external_instance_url,$external_user_id);

			if(!isset($external_profile->statusnet_profile_url)) {
				return true;
				}

			$this->profile->external = $external_profile;
			$local_profile = Profile::getKV('profileurl',$external_profile->statusnet_profile_url);

			if(!$local_profile instanceof Profile) {
				return true;
				}

			$this->profile->local = $this->twitterUserArray($local_profile);
			return true;
			}

		// case (1)
		$local_profile = Profile::getKV('profileurl',$profileurl);

		if($local_profile instanceof Profile) {

			// if profile url is not ending with nickname, this is probably a single user instance
			if(!substr($local_profile->profileurl, -strlen($local_profile->nickname))===$local_profile->nickname) {
				$external_instance_url = $local_profile->profileurl;
				}
			// multi user instance
			else {
				$external_instance_url = substr($local_profile->profileurl, 0, strrpos($local_profile->profileurl, '/'));
				}

			$external_profile = $this->getProfileFromExternalInstance($external_instance_url,$local_profile->nickname);

			if(!isset($external_profile->statusnet_profile_url)) {
				return true;
				}

			$this->profile->external = $external_profile;
			$this->profile->local = $this->twitterUserArray($local_profile);
			return true;
			}

		return true;

    }

    /**
     * Handle the request
     *
     * Check the format and show the user info
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        if(is_null($this->profile->local) && is_null($this->profile->external)) {
            $this->clientError(_('List not found.'), 404);
        } else {
            $this->initDocument('json');
        	$this->showJsonObjects($this->profile);
            $this->endDocument('json');
        }
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return true;
    }

    /**
     * Get profile from external instance
     *
     * @return null or profile object
     */
	function getProfileFromExternalInstance($instance_url,$user_id_or_nickname)
	{
		$apicall = $instance_url.'/api/users/show.json?id='.$user_id_or_nickname;
        $client = new HTTPClient();
        $response = $client->get($apicall);
        // json_decode returns null if it fails to decode
        return $response->isOk() ? json_decode($response->getBody()) : null;
	}

}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Returns both favs and repeats for a notice
 *
 * PHP version 5
 *
 * LICENCE: This program 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 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY 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 this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @category  API
 * @package   GNUsocial
 * @author    Hannes Mannerheim <h@nnesmannerhe.im>
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://www.gnu.org/software/social/
 */

if (!defined('GNUSOCIAL')) { exit(1); }

/**
 * Ouputs information for a user, specified by ID or screen name.
 * The user's most recent status will be returned inline.
 */
class ApiFavsAndRepeatsAction extends ApiPrivateAuthAction
{
    /**
     * Take arguments for running
     *
     * @param array $args $_REQUEST args
     *
     * @return boolean success flag
     *
     */
    protected function prepare(array $args=array())
    {
        parent::prepare($args);

        $this->format = 'json';

        $this->notice_id = $this->trimmed('notice_id');

        $this->original = Notice::getKV('id', $this->notice_id);

        if (empty($this->original)) {
            // TRANS: Client error displayed trying to display redents of a non-exiting notice.
            $this->clientError(_('No such notice.'), 400);
            return false;
        }

        $cnt = $this->trimmed('count');

        if (empty($cnt) || !is_integer($cnt)) {
            $this->cnt = 100;
        } else {
            $this->cnt = min((int)$cnt, self::MAXCOUNT);
        }

        return true;
    }

    /**
     * Handle the request
     *
     * Check the format and show the user info
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        // since this api method is in practice only used when expanding a
        // notice, we can assume the user has seen the notice in question,
        // an no longer need a notification about it. mark reply/mention-
        // notifications tied to this notice and the current profile as read
        if($this->auth_user) {
            QvitterPlugin::markNotificationAsSeen($this->notice_id,$this->auth_user->id,'mention');
            QvitterPlugin::markNotificationAsSeen($this->notice_id,$this->auth_user->id,'reply');
        }

        // favs
        $fave = new Fave();
        $fave->selectAdd();
        $fave->selectAdd('user_id');
        $fave->selectAdd('modified');
        $fave->notice_id = $this->original->id;
        $fave->orderBy('modified');
        if (!is_null($this->cnt)) {
            $fave->limit(0, $this->cnt);
        }
		$fav_ids = $fave->fetchAll('user_id', 'modified');

		// get nickname and profile image
		$fav_ids_with_profile_data = array();
		$i=0;
		foreach($fav_ids as $id=>$time) {
			$profile = Profile::getKV('id', $id);
			$fav_ids_with_profile_data[$i]['user_id'] = $id;
			$fav_ids_with_profile_data[$i]['nickname'] = $profile->nickname;
			$fav_ids_with_profile_data[$i]['fullname'] = $profile->fullname;
			$fav_ids_with_profile_data[$i]['profileurl'] = $profile->profileurl;
			$fav_ids_with_profile_data[$i]['time'] = strtotime($time);
			$profile = new Profile();
			$profile->id = $id;
			$avatarurl = $profile->avatarUrl(48);
			$fav_ids_with_profile_data[$i]['avatarurl'] = $avatarurl;
			$i++;
		}

        // repeats
        $notice = new Notice();
        $notice->selectAdd(); // clears it
        $notice->selectAdd('profile_id');
        $notice->selectAdd('created');
        $notice->repeat_of = $this->original->id;
        $notice->verb =  ActivityVerb::SHARE;
        $notice->orderBy('created, id'); // NB: asc!
        if (!is_null($this->cnt)) {
            $notice->limit(0, $this->cnt);
        }
        $repeat_ids = $notice->fetchAll('profile_id','created');

		// get nickname and profile image
		$repeat_ids_with_profile_data = array();
		$i=0;
		foreach($repeat_ids as $id=>$time) {
			$profile = Profile::getKV('id', $id);
			$repeat_ids_with_profile_data[$i]['user_id'] = $id;
			$repeat_ids_with_profile_data[$i]['nickname'] = $profile->nickname;
			$repeat_ids_with_profile_data[$i]['fullname'] = $profile->fullname;
			$repeat_ids_with_profile_data[$i]['profileurl'] = $profile->profileurl;
			$repeat_ids_with_profile_data[$i]['time'] = strtotime($time);
			$profile = new Profile();
			$profile->id = $id;
			$avatarurl = $profile->avatarUrl(48);
			$repeat_ids_with_profile_data[$i]['avatarurl'] = $avatarurl;
			$i++;
		}

        $favs_and_repeats = array('favs'=>$fav_ids_with_profile_data,'repeats'=>$repeat_ids_with_profile_data);

        $this->initDocument('json');
		$this->showJsonObjects($favs_and_repeats);
        $this->endDocument('json');
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */
    function isReadOnly($args)
    {
        return true;
    }

}