code:
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-05 16:33+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (http://www.transifex.com/gnu-social/gnu-social/language/zh/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Form legend.
#: forms/searchunsub.php:90
msgid "Unsubscribe from this search"
msgstr ""

#. TRANS: Button text for unsubscribing from a text search.
#: forms/searchunsub.php:102
msgctxt "BUTTON"
msgid "Unsubscribe"
msgstr ""

#. TRANS: Button title for unsubscribing from a text search.
#: forms/searchunsub.php:106
msgid "Unsubscribe from this search."
msgstr ""

#. TRANS: Form legend.
#: forms/searchsub.php:110
msgid "Subscribe to this search"
msgstr ""

#. TRANS: Button text for subscribing to a search.
#: forms/searchsub.php:134
msgctxt "BUTTON"
msgid "Subscribe"
msgstr ""

#. TRANS: Button title for subscribing to a search.
#: forms/searchsub.php:138
msgid "Subscribe to this search."
msgstr ""

#. TRANS: Plugin description.
#: SearchSubPlugin.php:99
msgid "Plugin to allow following all messages with a given search."
msgstr ""

#. TRANS: SearchSub plugin menu item on user settings page.
#. TRANS: Sub menu for searches.
#: SearchSubPlugin.php:202 SearchSubPlugin.php:265
msgctxt "MENU"
msgid "Searches"
msgstr ""

#. TRANS: SearchSub plugin tooltip for user settings menu item.
#: SearchSubPlugin.php:204
msgid "Configure search subscriptions"
msgstr ""

#. TRANS: Help message for IM/SMS command "track <word>"
#: SearchSubPlugin.php:242
msgctxt "COMMANDHELP"
msgid "Start following notices matching the given search query."
msgstr ""

#. TRANS: Help message for IM/SMS command "untrack <word>"
#: SearchSubPlugin.php:244
msgctxt "COMMANDHELP"
msgid "Stop following notices matching the given search query."
msgstr ""

#. TRANS: Help message for IM/SMS command "track off"
#. TRANS: Help message for IM/SMS command "untrack all"
#: SearchSubPlugin.php:246 SearchSubPlugin.php:248
msgctxt "COMMANDHELP"
msgid "Disable all tracked search subscriptions."
msgstr ""

#. TRANS: Help message for IM/SMS command "tracks"
#. TRANS: Help message for IM/SMS command "tracking"
#: SearchSubPlugin.php:250 SearchSubPlugin.php:252
msgctxt "COMMANDHELP"
msgid "List all your search subscriptions."
msgstr ""

#. TRANS: Header for subscriptions overview for a user (first page).
#. TRANS: %s is a user nickname.
#: actions/searchsubs.php:51
#, php-format
msgid "%s's search subscriptions"
msgstr ""

#. TRANS: Header for subscriptions overview for a user (not first page).
#. TRANS: %1$s is a user nickname, %2$d is the page number.
#: actions/searchsubs.php:55
#, php-format
msgid "%1$s's search subscriptions, page %2$d"
msgstr ""

#. TRANS: Page notice for page with an overview of all search subscriptions
#. TRANS: of the logged in user's own profile.
#: actions/searchsubs.php:68
msgid ""
"You have subscribed to receive all notices on this site matching the "
"following searches:"
msgstr ""

#. TRANS: Page notice for page with an overview of all subscriptions of a user
#. other
#. TRANS: than the logged in user. %s is the user nickname.
#: actions/searchsubs.php:73
#, php-format
msgid ""
"%s has subscribed to receive all notices on this site matching the following"
" searches:"
msgstr ""

#. TRANS: Search subscription list text when the logged in user has no search
#. subscriptions.
#: actions/searchsubs.php:118
msgid ""
"You are not subscribed to any text searches right now. You can push the "
"\"Subscribe\" button on any notice text search to automatically receive any "
"public messages on this site that match that search, even if you are not "
"subscribed to the poster."
msgstr ""

#. TRANS: Search subscription list text when looking at the subscriptions for
#. a of a user other
#. TRANS: than the logged in user that has no search subscriptions. %s is the
#. user nickname.
#. TRANS: Subscription list text when looking at the subscriptions for a of a
#. user that has none
#. TRANS: as an anonymous user. %s is the user nickname.
#: actions/searchsubs.php:124 actions/searchsubs.php:130
#, php-format
msgid "%s is not subscribed to any searches."
msgstr ""

#. TRANS: Search subscription list item. %1$s is a URL to a notice search,
#. TRANS: %2$s are the search criteria, %3$s is a datestring.
#: actions/searchsubs.php:170
#, php-format
msgid "\"%2$s\" since %3$s"
msgstr ""

#. TRANS: Page title when search unsubscription succeeded.
#: actions/searchunsub.php:76
msgid "Unsubscribed"
msgstr ""

#. TRANS: Client error displayed trying to perform any request method other
#. than POST.
#. TRANS: Do not translate POST.
#: actions/searchsub.php:78
msgid "This action only accepts POST requests."
msgstr ""

#. TRANS: Client error displayed when the session token is not okay.
#: actions/searchsub.php:87
msgid "There was a problem with your session token. Try again, please."
msgstr ""

#. TRANS: Error message displayed when trying to perform an action that
#. requires a logged in user.
#: actions/searchsub.php:97
msgid "Not logged in."
msgstr ""

#. TRANS: Client error displayed trying to subscribe to a non-existing
#. profile.
#: actions/searchsub.php:106
msgid "No such profile."
msgstr ""

#. TRANS: Page title when search subscription succeeded.
#: actions/searchsub.php:132
msgid "Subscribed"
msgstr ""

#. TRANS: Error text shown a user tries to disable all a search subscriptions
#. with track off command, but has none.
#: lib/searchsubtrackingcommand.php:14 lib/searchsubtrackoffcommand.php:14
msgid "You are not tracking any searches."
msgstr ""

#. TRANS: Separator for list of tracked searches.
#: lib/searchsubtrackingcommand.php:24
msgctxt "SEPARATOR"
msgid "\", \""
msgstr ""

#. TRANS: Message given having disabled all search subscriptions with 'track
#. off'.
#. TRANS: %s is a list of searches. Separator default is '", "'.
#: lib/searchsubtrackingcommand.php:28
#, php-format
msgid "You are tracking searches for: \"%s\"."
msgstr ""

#. TRANS: Error text shown a user tries to track a search query they're
#. already subscribed to.
#: lib/searchsubtrackcommand.php:21
#, php-format
msgid "You are already tracking the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to set up a search subscription by track
#. command.
#: lib/searchsubtrackcommand.php:29
#, php-format
msgid "Could not start a search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having added a search subscription by track command.
#: lib/searchsubtrackcommand.php:35
#, php-format
msgid "You are subscribed to the search \"%s\"."
msgstr ""

#. TRANS: Error text shown a user tries to untrack a search query they're not
#. subscribed to.
#. TRANS: %s is the keyword for the search.
#: lib/searchsubuntrackcommand.php:22
#, php-format
msgid "You are not tracking the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to cancel a search subscription by
#. untrack command.
#. TRANS: %s is the keyword for the query.
#: lib/searchsubuntrackcommand.php:31
#, php-format
msgid "Could not end a search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having removed a search subscription by untrack
#. command.
#. TRANS: %s is the keyword for the search.
#: lib/searchsubuntrackcommand.php:38
#, php-format
msgid "You are no longer subscribed to the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to cancel one of the search subs with
#. 'track off' command.
#. TRANS: %s is the search for which the subscription removal failed.
#: lib/searchsubtrackoffcommand.php:25
#, php-format
msgid "Error disabling search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having disabled all search subscriptions with 'track
#. off'.
#: lib/searchsubtrackoffcommand.php:32
msgid "Disabled all your search subscriptions."
msgstr ""


code:
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-05 16:33+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (China) (http://www.transifex.com/gnu-social/gnu-social/language/zh_CN/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_CN\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Form legend.
#: forms/searchunsub.php:90
msgid "Unsubscribe from this search"
msgstr ""

#. TRANS: Button text for unsubscribing from a text search.
#: forms/searchunsub.php:102
msgctxt "BUTTON"
msgid "Unsubscribe"
msgstr "取消关注"

#. TRANS: Button title for unsubscribing from a text search.
#: forms/searchunsub.php:106
msgid "Unsubscribe from this search."
msgstr ""

#. TRANS: Form legend.
#: forms/searchsub.php:110
msgid "Subscribe to this search"
msgstr ""

#. TRANS: Button text for subscribing to a search.
#: forms/searchsub.php:134
msgctxt "BUTTON"
msgid "Subscribe"
msgstr "关注"

#. TRANS: Button title for subscribing to a search.
#: forms/searchsub.php:138
msgid "Subscribe to this search."
msgstr ""

#. TRANS: Plugin description.
#: SearchSubPlugin.php:99
msgid "Plugin to allow following all messages with a given search."
msgstr ""

#. TRANS: SearchSub plugin menu item on user settings page.
#. TRANS: Sub menu for searches.
#: SearchSubPlugin.php:202 SearchSubPlugin.php:265
msgctxt "MENU"
msgid "Searches"
msgstr ""

#. TRANS: SearchSub plugin tooltip for user settings menu item.
#: SearchSubPlugin.php:204
msgid "Configure search subscriptions"
msgstr ""

#. TRANS: Help message for IM/SMS command "track <word>"
#: SearchSubPlugin.php:242
msgctxt "COMMANDHELP"
msgid "Start following notices matching the given search query."
msgstr ""

#. TRANS: Help message for IM/SMS command "untrack <word>"
#: SearchSubPlugin.php:244
msgctxt "COMMANDHELP"
msgid "Stop following notices matching the given search query."
msgstr ""

#. TRANS: Help message for IM/SMS command "track off"
#. TRANS: Help message for IM/SMS command "untrack all"
#: SearchSubPlugin.php:246 SearchSubPlugin.php:248
msgctxt "COMMANDHELP"
msgid "Disable all tracked search subscriptions."
msgstr ""

#. TRANS: Help message for IM/SMS command "tracks"
#. TRANS: Help message for IM/SMS command "tracking"
#: SearchSubPlugin.php:250 SearchSubPlugin.php:252
msgctxt "COMMANDHELP"
msgid "List all your search subscriptions."
msgstr ""

#. TRANS: Header for subscriptions overview for a user (first page).
#. TRANS: %s is a user nickname.
#: actions/searchsubs.php:51
#, php-format
msgid "%s's search subscriptions"
msgstr ""

#. TRANS: Header for subscriptions overview for a user (not first page).
#. TRANS: %1$s is a user nickname, %2$d is the page number.
#: actions/searchsubs.php:55
#, php-format
msgid "%1$s's search subscriptions, page %2$d"
msgstr ""

#. TRANS: Page notice for page with an overview of all search subscriptions
#. TRANS: of the logged in user's own profile.
#: actions/searchsubs.php:68
msgid ""
"You have subscribed to receive all notices on this site matching the "
"following searches:"
msgstr ""

#. TRANS: Page notice for page with an overview of all subscriptions of a user
#. other
#. TRANS: than the logged in user. %s is the user nickname.
#: actions/searchsubs.php:73
#, php-format
msgid ""
"%s has subscribed to receive all notices on this site matching the following"
" searches:"
msgstr ""

#. TRANS: Search subscription list text when the logged in user has no search
#. subscriptions.
#: actions/searchsubs.php:118
msgid ""
"You are not subscribed to any text searches right now. You can push the "
"\"Subscribe\" button on any notice text search to automatically receive any "
"public messages on this site that match that search, even if you are not "
"subscribed to the poster."
msgstr ""

#. TRANS: Search subscription list text when looking at the subscriptions for
#. a of a user other
#. TRANS: than the logged in user that has no search subscriptions. %s is the
#. user nickname.
#. TRANS: Subscription list text when looking at the subscriptions for a of a
#. user that has none
#. TRANS: as an anonymous user. %s is the user nickname.
#: actions/searchsubs.php:124 actions/searchsubs.php:130
#, php-format
msgid "%s is not subscribed to any searches."
msgstr ""

#. TRANS: Search subscription list item. %1$s is a URL to a notice search,
#. TRANS: %2$s are the search criteria, %3$s is a datestring.
#: actions/searchsubs.php:170
#, php-format
msgid "\"%2$s\" since %3$s"
msgstr ""

#. TRANS: Page title when search unsubscription succeeded.
#: actions/searchunsub.php:76
msgid "Unsubscribed"
msgstr "已取消关注"

#. TRANS: Client error displayed trying to perform any request method other
#. than POST.
#. TRANS: Do not translate POST.
#: actions/searchsub.php:78
msgid "This action only accepts POST requests."
msgstr "这个动作只接受 POST 请求"

#. TRANS: Client error displayed when the session token is not okay.
#: actions/searchsub.php:87
msgid "There was a problem with your session token. Try again, please."
msgstr "你的 session 出现了一个问题，请重试。"

#. TRANS: Error message displayed when trying to perform an action that
#. requires a logged in user.
#: actions/searchsub.php:97
msgid "Not logged in."
msgstr "未登录。"

#. TRANS: Client error displayed trying to subscribe to a non-existing
#. profile.
#: actions/searchsub.php:106
msgid "No such profile."
msgstr "没有这个文件。"

#. TRANS: Page title when search subscription succeeded.
#: actions/searchsub.php:132
msgid "Subscribed"
msgstr "已关注"

#. TRANS: Error text shown a user tries to disable all a search subscriptions
#. with track off command, but has none.
#: lib/searchsubtrackingcommand.php:14 lib/searchsubtrackoffcommand.php:14
msgid "You are not tracking any searches."
msgstr ""

#. TRANS: Separator for list of tracked searches.
#: lib/searchsubtrackingcommand.php:24
msgctxt "SEPARATOR"
msgid "\", \""
msgstr ""

#. TRANS: Message given having disabled all search subscriptions with 'track
#. off'.
#. TRANS: %s is a list of searches. Separator default is '", "'.
#: lib/searchsubtrackingcommand.php:28
#, php-format
msgid "You are tracking searches for: \"%s\"."
msgstr ""

#. TRANS: Error text shown a user tries to track a search query they're
#. already subscribed to.
#: lib/searchsubtrackcommand.php:21
#, php-format
msgid "You are already tracking the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to set up a search subscription by track
#. command.
#: lib/searchsubtrackcommand.php:29
#, php-format
msgid "Could not start a search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having added a search subscription by track command.
#: lib/searchsubtrackcommand.php:35
#, php-format
msgid "You are subscribed to the search \"%s\"."
msgstr ""

#. TRANS: Error text shown a user tries to untrack a search query they're not
#. subscribed to.
#. TRANS: %s is the keyword for the search.
#: lib/searchsubuntrackcommand.php:22
#, php-format
msgid "You are not tracking the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to cancel a search subscription by
#. untrack command.
#. TRANS: %s is the keyword for the query.
#: lib/searchsubuntrackcommand.php:31
#, php-format
msgid "Could not end a search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having removed a search subscription by untrack
#. command.
#. TRANS: %s is the keyword for the search.
#: lib/searchsubuntrackcommand.php:38
#, php-format
msgid "You are no longer subscribed to the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to cancel one of the search subs with
#. 'track off' command.
#. TRANS: %s is the search for which the subscription removal failed.
#: lib/searchsubtrackoffcommand.php:25
#, php-format
msgid "Error disabling search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having disabled all search subscriptions with 'track
#. off'.
#: lib/searchsubtrackoffcommand.php:32
msgid "Disabled all your search subscriptions."
msgstr ""

license: mit


code:
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-05 16:33+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Chinese (Taiwan) (http://www.transifex.com/gnu-social/gnu-social/language/zh_TW/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: zh_TW\n"
"Plural-Forms: nplurals=1; plural=0;\n"

#. TRANS: Form legend.
#: forms/searchunsub.php:90
msgid "Unsubscribe from this search"
msgstr ""

#. TRANS: Button text for unsubscribing from a text search.
#: forms/searchunsub.php:102
msgctxt "BUTTON"
msgid "Unsubscribe"
msgstr ""

#. TRANS: Button title for unsubscribing from a text search.
#: forms/searchunsub.php:106
msgid "Unsubscribe from this search."
msgstr ""

#. TRANS: Form legend.
#: forms/searchsub.php:110
msgid "Subscribe to this search"
msgstr ""

#. TRANS: Button text for subscribing to a search.
#: forms/searchsub.php:134
msgctxt "BUTTON"
msgid "Subscribe"
msgstr ""

#. TRANS: Button title for subscribing to a search.
#: forms/searchsub.php:138
msgid "Subscribe to this search."
msgstr ""

#. TRANS: Plugin description.
#: SearchSubPlugin.php:99
msgid "Plugin to allow following all messages with a given search."
msgstr ""

#. TRANS: SearchSub plugin menu item on user settings page.
#. TRANS: Sub menu for searches.
#: SearchSubPlugin.php:202 SearchSubPlugin.php:265
msgctxt "MENU"
msgid "Searches"
msgstr ""

#. TRANS: SearchSub plugin tooltip for user settings menu item.
#: SearchSubPlugin.php:204
msgid "Configure search subscriptions"
msgstr ""

#. TRANS: Help message for IM/SMS command "track <word>"
#: SearchSubPlugin.php:242
msgctxt "COMMANDHELP"
msgid "Start following notices matching the given search query."
msgstr ""

#. TRANS: Help message for IM/SMS command "untrack <word>"
#: SearchSubPlugin.php:244
msgctxt "COMMANDHELP"
msgid "Stop following notices matching the given search query."
msgstr ""

#. TRANS: Help message for IM/SMS command "track off"
#. TRANS: Help message for IM/SMS command "untrack all"
#: SearchSubPlugin.php:246 SearchSubPlugin.php:248
msgctxt "COMMANDHELP"
msgid "Disable all tracked search subscriptions."
msgstr ""

#. TRANS: Help message for IM/SMS command "tracks"
#. TRANS: Help message for IM/SMS command "tracking"
#: SearchSubPlugin.php:250 SearchSubPlugin.php:252
msgctxt "COMMANDHELP"
msgid "List all your search subscriptions."
msgstr ""

#. TRANS: Header for subscriptions overview for a user (first page).
#. TRANS: %s is a user nickname.
#: actions/searchsubs.php:51
#, php-format
msgid "%s's search subscriptions"
msgstr ""

#. TRANS: Header for subscriptions overview for a user (not first page).
#. TRANS: %1$s is a user nickname, %2$d is the page number.
#: actions/searchsubs.php:55
#, php-format
msgid "%1$s's search subscriptions, page %2$d"
msgstr ""

#. TRANS: Page notice for page with an overview of all search subscriptions
#. TRANS: of the logged in user's own profile.
#: actions/searchsubs.php:68
msgid ""
"You have subscribed to receive all notices on this site matching the "
"following searches:"
msgstr ""

#. TRANS: Page notice for page with an overview of all subscriptions of a user
#. other
#. TRANS: than the logged in user. %s is the user nickname.
#: actions/searchsubs.php:73
#, php-format
msgid ""
"%s has subscribed to receive all notices on this site matching the following"
" searches:"
msgstr ""

#. TRANS: Search subscription list text when the logged in user has no search
#. subscriptions.
#: actions/searchsubs.php:118
msgid ""
"You are not subscribed to any text searches right now. You can push the "
"\"Subscribe\" button on any notice text search to automatically receive any "
"public messages on this site that match that search, even if you are not "
"subscribed to the poster."
msgstr ""

#. TRANS: Search subscription list text when looking at the subscriptions for
#. a of a user other
#. TRANS: than the logged in user that has no search subscriptions. %s is the
#. user nickname.
#. TRANS: Subscription list text when looking at the subscriptions for a of a
#. user that has none
#. TRANS: as an anonymous user. %s is the user nickname.
#: actions/searchsubs.php:124 actions/searchsubs.php:130
#, php-format
msgid "%s is not subscribed to any searches."
msgstr ""

#. TRANS: Search subscription list item. %1$s is a URL to a notice search,
#. TRANS: %2$s are the search criteria, %3$s is a datestring.
#: actions/searchsubs.php:170
#, php-format
msgid "\"%2$s\" since %3$s"
msgstr ""

#. TRANS: Page title when search unsubscription succeeded.
#: actions/searchunsub.php:76
msgid "Unsubscribed"
msgstr ""

#. TRANS: Client error displayed trying to perform any request method other
#. than POST.
#. TRANS: Do not translate POST.
#: actions/searchsub.php:78
msgid "This action only accepts POST requests."
msgstr ""

#. TRANS: Client error displayed when the session token is not okay.
#: actions/searchsub.php:87
msgid "There was a problem with your session token. Try again, please."
msgstr ""

#. TRANS: Error message displayed when trying to perform an action that
#. requires a logged in user.
#: actions/searchsub.php:97
msgid "Not logged in."
msgstr "未登錄。"

#. TRANS: Client error displayed trying to subscribe to a non-existing
#. profile.
#: actions/searchsub.php:106
msgid "No such profile."
msgstr ""

#. TRANS: Page title when search subscription succeeded.
#: actions/searchsub.php:132
msgid "Subscribed"
msgstr ""

#. TRANS: Error text shown a user tries to disable all a search subscriptions
#. with track off command, but has none.
#: lib/searchsubtrackingcommand.php:14 lib/searchsubtrackoffcommand.php:14
msgid "You are not tracking any searches."
msgstr ""

#. TRANS: Separator for list of tracked searches.
#: lib/searchsubtrackingcommand.php:24
msgctxt "SEPARATOR"
msgid "\", \""
msgstr ""

#. TRANS: Message given having disabled all search subscriptions with 'track
#. off'.
#. TRANS: %s is a list of searches. Separator default is '", "'.
#: lib/searchsubtrackingcommand.php:28
#, php-format
msgid "You are tracking searches for: \"%s\"."
msgstr ""

#. TRANS: Error text shown a user tries to track a search query they're
#. already subscribed to.
#: lib/searchsubtrackcommand.php:21
#, php-format
msgid "You are already tracking the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to set up a search subscription by track
#. command.
#: lib/searchsubtrackcommand.php:29
#, php-format
msgid "Could not start a search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having added a search subscription by track command.
#: lib/searchsubtrackcommand.php:35
#, php-format
msgid "You are subscribed to the search \"%s\"."
msgstr ""

#. TRANS: Error text shown a user tries to untrack a search query they're not
#. subscribed to.
#. TRANS: %s is the keyword for the search.
#: lib/searchsubuntrackcommand.php:22
#, php-format
msgid "You are not tracking the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to cancel a search subscription by
#. untrack command.
#. TRANS: %s is the keyword for the query.
#: lib/searchsubuntrackcommand.php:31
#, php-format
msgid "Could not end a search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having removed a search subscription by untrack
#. command.
#. TRANS: %s is the keyword for the search.
#: lib/searchsubuntrackcommand.php:38
#, php-format
msgid "You are no longer subscribed to the search \"%s\"."
msgstr ""

#. TRANS: Message given having failed to cancel one of the search subs with
#. 'track off' command.
#. TRANS: %s is the search for which the subscription removal failed.
#: lib/searchsubtrackoffcommand.php:25
#, php-format
msgid "Error disabling search subscription for query \"%s\"."
msgstr ""

#. TRANS: Message given having disabled all search subscriptions with 'track
#. off'.
#: lib/searchsubtrackoffcommand.php:32
msgid "Disabled all your search subscriptions."
msgstr ""


code:
# "Sensitive" Content Plugin for GNU Social

## About

WARNING: THIS IS ALPHA CODE, IT IS PRERELEASE AND SHOULD ONLY BE INSTALLED TO
HELP TEST OR YOU ARE WILLING TO TAKE RISKS.

Create user option to allow a user to hide #NSFW-hashtagged notices behind a
blocker image until clicked.

Works for both vanilla GNUSocial and with the Qvitter plugin.

## Install

- Move the project directory to ${GNU_SOCIAL}/plugins
- Add addPlugin('SensitiveContent'); to your config.php

if you want to customize the blocker image, add a line to your config.php:

  $config['site']['sensitivecontent']['blockerimage'] = "/path/to/image.jpg";

## Usage

Individual users must go to their Settings page. A new sidebar menu item "Sensitive Content"
will be available. User checks or unchecks the checkbox on this page, and presses save.

If you have GNU Social open in other browser tabs, refresh them. If you are using Qvitter, also
refresh, but because Qvitter caches notices on the client side, only new sensitive images will
be hidden, it will not apply to notices retroactively unless you clear your browser cache.

## License

GNU Affero License

## Thanks

Thanks in particular to Hannes and Qvitter because looking at his code helped me a lot.

A tiny bit of content was taken from Qvitter to enhance Qvitter with this functionality.


code:
<?php

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

class SensitiveContentPlugin extends Plugin
{
	const PLUGIN_VERSION = '0.0.1';

	function onPluginVersion(array &$versions)
	{
		$versions[] = array('name' => 'Sensitive Content',
			'version' => self::PLUGIN_VERSION,
			'author' => 'MoonMan',
			'homepage' => 'https://gitgud.io/ShitposterClub/SensitiveContent/',
			'description' =>
				_m('Mark, hide/show sensitive notices like on Twitter.'));
		return true;
	}

	static function settings($setting)
	{
		$settings['blockerimage'] = Plugin::staticPath('SensitiveContent', '').'img/blocker.png';

		$configphpsettings = common_config('site','sensitivecontent') ?: array();
 		foreach($configphpsettings as $configphpsetting=>$value) {
			$settings[$configphpsetting] = $value;
		}

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

	function onNoticeSimpleStatusArray($notice, &$twitter_status, $scoped)
	{
		$twitter_status['tags'] = $notice->getTags();
	}

	function onTwitterUserArray($profile, &$twitter_user, $scoped)
	{
		if ($scoped instanceof Profile  && $scoped->sameAs($profile)) {
			$twitter_user['hide_sensitive'] = $this->getHideSensitive($scoped);
		}
	}

	public function onRouterInitialized(URLMapper $m)
	{
		$m->connect('settings/sensitivecontent',
                    ['action' => 'sensitivecontentsettings']);
	}

	function onEndAccountSettingsNav($action)
	{
		$action->menuItem(common_local_url('sensitivecontentsettings'),
			_m('MENU', 'Sensitive Content'),
			_m('Settings for display of sensitive content.'));

		return true;
	}

	public function onQvitterEndShowHeadElements(Action $action)
	{
		$blocker = static::settings('blockerimage');
		common_log( LOG_DEBUG, "SENSITIVECONTENT " . $blocker );

		$styles = <<<EOB

.sensitive-blocker {
  display: none;
}

div.stream-item.notice.sensitive-notice .sensitive-blocker {
display: block;
width: 100%;
height: 100%;
position: absolute;
z-index: 100;
background-color: #d4baba;
background-image: url($blocker);
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
transition: opacity 1s ease-in-out;
}

.sensitive-blocker:hover {
  opacity: .5;
}

div.stream-item.notice.expanded.sensitive-notice .sensitive-blocker {
display: none;
background-color: transparent;
background-image: none;
}

EOB;

		$action->style($styles);
	}

	function onQvitterEndShowScripts(Action $action)
	{
		$action->script( Plugin::staticPath('SensitiveContent', '').'js/sensitivecontent.js' );
	}

	function onEndShowStyles(Action $action)
	{
		$blocker = static::settings('blockerimage');

		$styles =  <<<EOB

/* default no show */
html .tagcontainer > footer > .attachments > .inline-attachment > .attachment-wrapper > .sensitive-blocker {
	display: none;
}

html[data-hidesensitive='true'] .tagcontainer.data-tag-nsfw > footer > .attachments > .inline-attachment > .attachment-wrapper > .sensitive-blocker {
display: block;
width: 100%;
height: 100%;
position: absolute;
z-index: 100;
/*background-color: #d4baba;*/
background-color: black;
background-image: url($blocker);
background-repeat: no-repeat;
background-position: center center;
background-size: contain;
transition: opacity 1s ease-in-out;
}

html[data-hidesensitive='true'] .tagcontainer.data-tag-nsfw > footer > .attachments > .inline-attachment > .attachment-wrapper > .sensitive-blocker.reveal {
	opacity: 0;
}

EOB;

		$action->style($styles);
	}

	function onStartShowAttachmentRepresentation($out, $file)
	{
		$profile = Profile::current();

                if (!is_null($profile) && $profile instanceof Profile)
                {
                        $hidesensitive = $this->getHideSensitive($profile);
                }
                else
                {
                        $hidesensitive = false;
                }

		$classes = "sensitive-blocker"; //'sensitive-blocker';

		$out->elementStart('div', array(
			'class'=>'attachment-wrapper',
			'style'=>'height: ' . $file->getThumbnail()->height . 'px; width: ' . $file->getThumbnail()->width . 'px;'
		)); /*needs height of thumb*/
		$out->elementStart('div', array(
			'class'=>$classes,
			'onclick'=>'toggleSpoiler(event)',
			'style'=>'height: ' . $file->getThumbnail()->height . 'px; width: ' . $file->getThumbnail()->width . 'px;'
		));
		$out->raw(' ');
		$out->elementEnd('div');
	}

	function onEndShowAttachmentRepresentation($out, $file)
	{
		$out->elementEnd('div');
	}

	function onEndShowScripts(Action $action)
	{
		$profile = $action->getScoped();
		if (!is_null($profile) && $profile instanceof Profile)
		{
			$hidesensitive = $this->getHideSensitive($profile) ? "true" : "false";
		}
		else
		{
			$hidesensitive = "false";
		}

		$inline = <<<EOB

window.hidesensitive = $hidesensitive ;

function toggleSpoiler(evt) {
	if (window.hidesensitive) evt.target.classList.toggle('reveal');
}
EOB;
		$action->inlineScript($inline);
	}

	function onEndOpenNoticeListItemElement(NoticeListItem $nli)
	{
		$rawtags = $nli->getNotice()->getTags();
		$classes = "tagcontainer";

		foreach($rawtags as $tag)
		{
			$classes = $classes . ' data-tag-' . $tag;
                }

		$nli->elementStart('span', array('class' => $classes));
		//$nli->elementEnd('span');
	}

	function onStartCloseNoticeListItemElement(NoticeListItem $nli)
	{
		$nli->elementEnd('span');
	}

	function onStartHtmlElement($action, &$attrs) {
		$profile = Profile::current();

                if (!is_null($profile) && $profile instanceof Profile)
                {
                        $hidesensitive = $this->getHideSensitive($profile);
                }
                else
                {
                        $hidesensitive = false;
                }

		$attrs = array_merge($attrs,
			array('data-hidesensitive' => ($hidesensitive ? "true" : "false"))
		);
	}

	function getHideSensitive($profile) {
		$c = Cache::instance();

		/*
		if (!empty($c)) {
			$hidesensitive = $c->get(Cache::key('profile:hide_sensitive:'.$profile->id));
			if (is_numeric($hidesensitive)) {
				return (boolean) $hidesensitive;
			}
			else return FALSE;
		}
		*/

		$hidesensitive = $profile->getPref('MoonMan', 'hide_sensitive', '0');

		if (!empty($c)) {
			//not using it yet.
			$c->set(Cache::key('profile:hide_sensitive:'.$profile->id), $hidesensitive);
		}

		//common_log(LOG_DEBUG, "SENSITIVECONTENT hidesensitive? id " . $profile->id . " value " . (boolean)$hidesensitive );

		if (is_null($hidesensitive)) {
			return FALSE;
		} else
		if (is_numeric($hidesensitive)) {
			return (boolean) $hidesensitive;
		}
		else return FALSE;
	}

}


code:
<?php

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

class SensitiveContentSettingsAction extends SettingsAction
{
	function title()
	{
		return _m('Sensitive content settings');
	}

	function getInstructions()
	{
		return _m('Set preferences for display of "sensitive" content');
	}

	function showContent()
	{

		$user = $this->scoped->getUser();

		$this->elementStart('form', array('method' => 'post',
			'id' => 'sensitivecontent',
			'class' => 'form_settings',
			'action' => common_local_url('sensitivecontentsettings')));

		$this->elementStart('fieldset');
		$this->hidden('token', common_session_token());
		$this->elementStart('ul', 'form_data');

		$this->elementStart('li');
			$this->checkbox('hidesensitive', _('Hide attachments in posts hashtagged #NSFW'),
				($this->arg('hidesensitive')) ?
				$this->boolean('hidesensitive') : $this->scoped->getPref('MoonMan','hide_sensitive',0));
		$this->elementEnd('li');

		$this->elementEnd('ul');
		$this->submit('save', _m('BUTTON','Save'));

		$this->elementEnd('fieldset');
		$this->elementEnd('form');
	}

	function doPost()
	{
		$hidesensitive = $this->booleanintstring('hidesensitive');
		$this->scoped->setPref('MoonMan','hide_sensitive', $hidesensitive);
		return _('Settings saved.');
	}
}


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-08 18:20+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"

#: SensitiveContentPlugin.php:18
msgid "Mark, hide/show sensitive notices like on Twitter."
msgstr ""

#: SensitiveContentPlugin.php:59
msgctxt "MENU"
msgid "Sensitive Content"
msgstr ""

#: SensitiveContentPlugin.php:60
msgid "Settings for display of sensitive content."
msgstr ""

#: actions/sensitivecontentsettings.php:9
msgid "Sensitive content settings"
msgstr ""

#: actions/sensitivecontentsettings.php:14
msgid "Set preferences for display of \"sensitive\" content"
msgstr ""

#: actions/sensitivecontentsettings.php:39
msgctxt "BUTTON"
msgid "Save"
msgstr ""


code:
The Share plugin implements "Shares" (repeats) type of notices

Installation
============
This plugin is enabled by default

Settings
========
none

path: plugins/Share/README


code:
<?php
/*
 * GNU Social - a federating social network
 * Copyright (C) 2014, Free Software Foundation, Inc.
 *
 * 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/>.
 */

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

/**
 * @package     Activity
 * @maintainer  Mikael Nordfeldth <mmn@hethane.se>
 */
class SharePlugin extends ActivityVerbHandlerPlugin
{
    const PLUGIN_VERSION = '2.0.0';

    public function tag()
    {
        return 'share';
    }

    public function types()
    {
        return array();
    }

    public function verbs()
    {
        return array(ActivityVerb::SHARE);
    }

    // Share is a bit special and $act->objects[0] should be an Activity
    // instead of ActivityObject! Therefore also $act->objects[0]->type is not set.
    public function isMyActivity(Activity $act) {
        return (count($act->objects) == 1
            && ($act->objects[0] instanceof Activity)
            && $this->isMyVerb($act->verb));
    }

    public function onRouterInitialized(URLMapper $m)
    {
        // Web UI actions
        $m->connect('main/repeat', ['action' => 'repeat']);

        // Share for Twitter API ("Retweet")
        $m->connect('api/statuses/retweeted_by_me.:format',
                    ['action' => 'ApiTimelineRetweetedByMe'],
                    ['format' => '(xml|json|atom|as)']);

        $m->connect('api/statuses/retweeted_to_me.:format',
                    ['action' => 'ApiTimelineRetweetedToMe'],
                    ['format' => '(xml|json|atom|as)']);

        $m->connect('api/statuses/retweets_of_me.:format',
                    ['action' => 'ApiTimelineRetweetsOfMe'],
                    ['format' => '(xml|json|atom|as)']);

        $m->connect('api/statuses/retweet/:id.:format',
                    ['action' => 'ApiStatusesRetweet'],
                    ['id'     => '[0-9]+',
                     'format' => '(xml|json)']);

        $m->connect('api/statuses/retweets/:id.:format',
                    ['action' => 'ApiStatusesRetweets'],
                    ['id'     => '[0-9]+',
                     'format' => '(xml|json)']);
    }

    // FIXME: Set this to abstract public in lib/activityhandlerplugin.php when all plugins have migrated!
    protected function saveObjectFromActivity(Activity $act, Notice $stored, array $options=array())
    {
        assert($this->isMyActivity($act));

        // The below algorithm is mainly copied from the previous Ostatus_profile->processShare()

        if (count($act->objects) !== 1) {
            // TRANS: Client exception thrown when trying to share multiple activities at once.
            throw new ClientException(_m('Can only handle share activities with exactly one object.'));
        }

        $shared = $act->objects[0];
        if (!$shared instanceof Activity) {
            // TRANS: Client exception thrown when trying to share a non-activity object.
            throw new ClientException(_m('Can only handle shared activities.'));
        }

        $sharedUri = $shared->id;
        if (!empty($shared->objects[0]->id)) {
            // Because StatusNet since commit 8cc4660 sets $shared->id to a TagURI which
            // fucks up federation, because the URI is no longer recognised by the origin.
            // So we set it to the object ID if it exists, otherwise we trust $shared->id
            $sharedUri = $shared->objects[0]->id;
        }
        if (empty($sharedUri)) {
            throw new ClientException(_m('Shared activity does not have an id'));
        }

        try {
            // First check if we have the shared activity. This has to be done first, because
            // we can't use these functions to "ensureActivityObjectProfile" of a local user,
            // who might be the creator of the shared activity in question.
            $sharedNotice = Notice::getByUri($sharedUri);
        } catch (NoResultException $e) {
            // If no locally stored notice is found, process it!
            // TODO: Remember to check Deleted_notice!
            // TODO: If a post is shared that we can't retrieve - what to do?
            $other = Ostatus_profile::ensureActivityObjectProfile($shared->actor);
            $sharedNotice = Notice::saveActivity($shared, $other->localProfile(), array('source'=>'share'));
        } catch (FeedSubException $e) {
            // Remote feed could not be found or verified, should we
            // transform this into an "RT @user Blah, blah, blah..."?
            common_log(LOG_INFO, __METHOD__ . ' got a ' . get_class($e) . ': ' . $e->getMessage());
            return false;
        }

        // Setting this here because when the algorithm gets back to
        // Notice::saveActivity it will update the Notice object.
        $stored->repeat_of = $sharedNotice->getID();
        $stored->conversation = $sharedNotice->conversation;

        // We don't have to save a repeat in a separate table, we can
        // find repeats by just looking at the notice.repeat_of field.

        // By returning true here instead of something that evaluates
        // to false, we show that we have processed everything properly.
        return true;
    }

    // FIXME: Put this in lib/activityhandlerplugin.php when we're ready
    //          with the other microapps/activityhandlers as well.
    //          Also it should be StartNoticeAsActivity (with a prepped Activity, including ->context etc.)
    public function onEndNoticeAsActivity(Notice $stored, Activity $act, Profile $scoped=null)
    {
        if (!$this->isMyNotice($stored)) {
            return true;
        }

        $this->extendActivity($stored, $act, $scoped);
        return false;
    }

    public function extendActivity(Notice $stored, Activity $act, Profile $scoped=null)
    {
        // TODO: How to handle repeats of deleted notices?
        $target = Notice::getByID($stored->repeat_of);
        // TRANS: A repeat activity's title. %1$s is repeater's nickname
        //        and %2$s is the repeated user's nickname.
        $act->title = sprintf(_('%1$s repeated a notice by %2$s'),
                              $stored->getProfile()->getNickname(),
                              $target->getProfile()->getNickname());
        $act->objects[] = $target->asActivity($scoped);
    }

    public function activityObjectFromNotice(Notice $stored)
    {
        // Repeat is a little bit special. As it's an activity, our
        // ActivityObject is instead turned into an Activity
        $object          = new Activity();
        $object->actor   = $stored->getProfile()->asActivityObject();
        $object->verb    = ActivityVerb::SHARE;
        $object->content = $stored->getRendered();
        $this->extendActivity($stored, $object);

        return $object;
    }

    public function deleteRelated(Notice $notice)
    {
        // No action needed as we don't have a separate table for share objects.
        return true;
    }

    // Layout stuff

    /**
     * show a link to the author of repeat
     *
     * FIXME: Some repeat stuff still in lib/noticelistitem.php! ($nli->repeat etc.)
     */
    public function onEndShowNoticeInfo(NoticeListItem $nli)
    {
        if (!empty($nli->repeat)) {
            $repeater = $nli->repeat->getProfile();

            $attrs = array('href' => $repeater->getUrl(),
                           'class' => 'h-card p-author',
                           'title' => $repeater->getFancyName());

            $nli->out->elementStart('span', 'repeat');

            // TRANS: Addition in notice list item if notice was repeated. Followed by a span with a nickname.
            $nli->out->raw(_('Repeated by').' ');

            $nli->out->element('a', $attrs, $repeater->getNickname());

            $nli->out->elementEnd('span');
        }
    }

    public function onEndShowThreadedNoticeTailItems(NoticeListItem $nli, Notice $notice, &$threadActive)
    {
        if ($nli instanceof ThreadedNoticeListSubItem) {
            // The sub-items are replies to a conversation, thus we use different HTML elements etc.
            $item = new ThreadedNoticeListInlineRepeatsItem($notice, $nli->out);
        } else {
            $item = new ThreadedNoticeListRepeatsItem($notice, $nli->out);
        }
        $threadActive = $item->show() || $threadActive;
        return true;
    }

    /**
     * show the "repeat" form in the notice options element
     * FIXME: Don't let a NoticeListItemAdapter slip in here (or extend that from NoticeListItem)
     *
     * @return void
     */
    public function onEndShowNoticeOptionItems($nli)
    {
        // FIXME: Use bitmasks (but be aware that PUBLIC_SCOPE is 0!)
        // Also: AHHH, $scope and $scoped are scarily similar looking.
        $scope = $nli->notice->getScope();
        if ($scope === Notice::PUBLIC_SCOPE || $scope === Notice::SITE_SCOPE) {
            $scoped = Profile::current();
            if ($scoped instanceof Profile &&
                    $scoped->getID() !== $nli->notice->getProfile()->getID()) {

                if ($scoped->hasRepeated($nli->notice)) {
                    $nli->out->element('span', array('class' => 'repeated',
                                                      // TRANS: Title for repeat form status in notice list when a notice has been repeated.
                                                      'title' => _('Notice repeated.')),
                                        // TRANS: Repeat form status in notice list when a notice has been repeated.
                                        _('Repeated'));
                } else {
                    $repeat = new RepeatForm($nli->out, $nli->notice);
                    $repeat->show();
                }
            }
        }
    }

    protected function showNoticeListItem(NoticeListItem $nli)
    {
        // pass
    }
    public function openNoticeListItemElement(NoticeListItem $nli)
    {
        // pass
    }
    public function closeNoticeListItemElement(NoticeListItem $nli)
    {
        // pass
    }

    // API stuff

    /**
     * Typically just used to fill out Twitter-compatible API status data.
     *
     * FIXME: Make all the calls before this end up with a Notice instead of ArrayWrapper please...
     */
    public function onNoticeSimpleStatusArray($notice, array &$status, Profile $scoped=null, array $args=array())
    {
        $status['repeated'] = $scoped instanceof Profile
                            ? $scoped->hasRepeated($notice)
                            : false;

        if ($status['repeated'] === true) {
            // Qvitter API wants the "repeated_id" value set too.
            $repeated = Notice::pkeyGet(array('profile_id' => $scoped->getID(),
                                              'repeat_of' => $notice->getID(),
                                              'verb' => ActivityVerb::SHARE));
            $status['repeated_id'] = $repeated->getID();
        }
    }

    public function onTwitterUserArray(Profile $profile, array &$userdata, Profile $scoped=null, array $args=array())
    {
        $userdata['favourites_count'] = Fave::countByProfile($profile);
    }

    // Command stuff

    /**
     * EndInterpretCommand for RepeatPlugin will handle the 'repeat' command
     * using the class RepeatCommand.
     *
     * @param string  $cmd     Command being run
     * @param string  $arg     Rest of the message (including address)
     * @param User    $user    User sending the message
     * @param Command &$result The resulting command object to be run.
     *
     * @return boolean hook value
     */
    public function onStartInterpretCommand($cmd, $arg, $user, &$result)
    {
        if ($result === false && in_array($cmd, array('repeat', 'rp', 'rt', 'rd'))) {
            if (empty($arg)) {
                $result = null;
            } else {
                list($other, $extra) = CommandInterpreter::split_arg($arg);
                if (!empty($extra)) {
                    $result = null;
                } else {
                    $result = new RepeatCommand($user, $other);
                }
            }
            return false;
        }
        return true;
    }

    public function onHelpCommandMessages(HelpCommand $help, array &$commands)
    {
        // TRANS: Help message for IM/SMS command "repeat #<notice_id>".
        $commands['repeat #<notice_id>'] = _m('COMMANDHELP', "repeat a notice with a given id");
        // TRANS: Help message for IM/SMS command "repeat <nickname>".
        $commands['repeat <nickname>']   = _m('COMMANDHELP', "repeat the last notice from user");
    }

    /**
     * Are we allowed to perform a certain command over the API?
     */
    public function onCommandSupportedAPI(Command $cmd, &$supported)
    {
        $supported = $supported || $cmd instanceof RepeatCommand;
    }

    protected function getActionTitle(ManagedAction $action, $verb, Notice $target, Profile $scoped)
    {
        // return page title
    }

    protected function doActionPreparation(ManagedAction $action, $verb, Notice $target, Profile $scoped)
    {
        // prepare Action?
    }

    protected function doActionPost(ManagedAction $action, $verb, Notice $target, Profile $scoped)
    {
        // handle repeat POST
    }

    protected function getActivityForm(ManagedAction $action, $verb, Notice $target, Profile $scoped)
    {
        return new RepeatForm($action, $target);
    }

    public function onPluginVersion(array &$versions)
    {
        $versions[] = array('name' => 'Share verb',
                            'version' => self::PLUGIN_VERSION,
                            'author' => 'Mikael Nordfeldth',
                            'homepage' => 'https://gnu.io/',
                            'rawdescription' =>
                            // TRANS: Plugin description.
                            _m('Shares (repeats) using ActivityStreams.'));

        return true;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Repeat a notice through the API
 *
 * 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   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Repeat a notice through the API
 *
 * @category API
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiStatusesRetweetAction extends ApiAuthAction
{
    protected $needPost = true;

    var $original = null;

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

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

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

        if (!$this->original instanceof Notice) {
            // TRANS: Client error displayed trying to repeat a non-existing notice through the API.
            $this->clientError(_('No such notice.'), 400, $this->format);
        }

        return true;
    }

    /**
     * Handle the request
     *
     * Make a new notice for the update, save it, and show it
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        $repeat = $this->original->repeat($this->scoped, $this->source);

        $this->showNotice($repeat);
    }

    /**
     * Show the resulting notice
     *
     * @return void
     */
    function showNotice($notice)
    {
        if (!empty($notice)) {
            if ($this->format == 'xml') {
                $this->showSingleXmlStatus($notice);
            } elseif ($this->format == 'json') {
                $this->show_single_json_status($notice);
            }
        }
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show up to 100 repeats of 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   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Show up to 100 repeats of a notice
 *
 * @category API
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiStatusesRetweetsAction extends ApiAuthAction
{
    const MAXCOUNT = 100;

    var $original = null;
    var $cnt      = self::MAXCOUNT;

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

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

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

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

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

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

        return true;
    }

    /**
     * Handle the request
     *
     * Make a new notice for the update, save it, and show it
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        $strm = $this->original->repeatStream($this->cnt);

        switch ($this->format) {
        case 'xml':
            $this->showXmlTimeline($strm);
            break;
        case 'json':
            $this->showJsonTimeline($strm);
            break;
        default:
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), $code = 404);
            break;
        }
    }

    /**
     * Return true if read only.
     *
     * MAY override
     *
     * @param array $args other arguments
     *
     * @return boolean is read only action?
     */

    function isReadOnly($args)
    {
        return true;
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show authenticating user's most recent repeats
 *
 * 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   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Show authenticating user's most recent repeats
 *
 * @category API
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiTimelineRetweetedByMeAction extends ApiAuthAction
{
    const DEFAULTCOUNT = 20;
    const MAXCOUNT     = 200;
    const MAXNOTICES   = 3200;

    var $repeats  = null;
    var $cnt      = self::DEFAULTCOUNT;
    var $page     = 1;
    var $since_id = null;
    var $max_id   = null;

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

        // TRANS: Server error displayed calling unimplemented API method for 'retweeted by me'.
        $this->serverError(_('Unimplemented.'), 503);

        return false;
    }

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


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show most recent notices that are repeats in user's inbox
 *
 * 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   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Show most recent notices that are repeats in user's inbox
 *
 * @category API
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiTimelineRetweetedToMeAction extends ApiAuthAction
{
    const DEFAULTCOUNT = 20;
    const MAXCOUNT     = 200;
    const MAXNOTICES   = 3200;

    var $repeats  = null;
    var $cnt      = self::DEFAULTCOUNT;
    var $page     = 1;
    var $since_id = null;
    var $max_id   = null;

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

        $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1);

        $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt));

        $since_id = $this->int('since_id');

        $max_id = $this->int('max_id');

        return true;
    }

    /**
     * Handle the request
     *
     * show a timeline of the user's repeated notices
     *
     * @return void
     */
    protected function handle()
    {
        parent::handle();

        $offset = ($this->page-1) * $this->cnt;
        $limit  = $this->cnt;

        // TRANS: Title for Atom feed "repeated to me". %s is the user nickname.
        $title      = sprintf(_("Repeated to %s"), $this->scoped->getNickname());
        $subtitle   = sprintf(
            // @todo FIXME: $profile is not defined.
            // TRANS: Subtitle for API action that shows most recent notices that are repeats in user's inbox.
            // TRANS: %1$s is the sitename, %2$s is a user nickname, %3$s is a user profile name.
            _('%1$s notices that were to repeated to %2$s / %3$s.'),
            $sitename, $this->scoped->getNickname(), $profile->getBestName()
        );
        $taguribase = TagURI::base();
        $id         = "tag:$taguribase:RepeatedToMe:" . $this->scoped->id;

        $link = common_local_url(
            'all',
             array('nickname' => $this->scoped->getNickname())
        );

        $strm = $this->scoped->repeatedToMe($offset, $limit, $this->since_id, $this->max_id);

        switch ($this->format) {
        case 'xml':
            $this->showXmlTimeline($strm);
            break;
        case 'json':
            $this->showJsonTimeline($strm);
            break;
        case 'atom':
            header('Content-Type: application/atom+xml; charset=utf-8');

            $atom = new AtomNoticeFeed($this->scoped->getUser());

            $atom->setId($id);
            $atom->setTitle($title);
            $atom->setSubtitle($subtitle);
            $atom->setUpdated('now');
            $atom->addLink($link);

            $id = $this->arg('id');

            $atom->setSelfLink($self);
            $atom->addEntryFromNotices($strm);

            $this->raw($atom->getString());

            break;
        case 'as':
            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
            $doc = new ActivityStreamJSONDocument($this->scoped->getUser());
            $doc->setTitle($title);
            $doc->addLink($link, 'alternate', 'text/html');
            $doc->addItemsFromNotices($strm);
            $this->raw($doc->asString());
            break;
        default:
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), $code = 404);
            break;
        }
    }

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


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Show authenticating user's most recent notices that have been repeated
 *
 * 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   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Show authenticating user's most recent notices that have been repeated
 *
 * @category API
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class ApiTimelineRetweetsOfMeAction extends ApiAuthAction
{
    const DEFAULTCOUNT = 20;
    const MAXCOUNT     = 200;
    const MAXNOTICES   = 3200;

    var $repeats  = null;
    var $cnt      = self::DEFAULTCOUNT;
    var $page     = 1;
    var $since_id = null;
    var $max_id   = null;

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

        $cnt = $this->int('count', self::DEFAULTCOUNT, self::MAXCOUNT, 1);

        $page = $this->int('page', 1, (self::MAXNOTICES/$this->cnt));

        $since_id = $this->int('since_id');

        $max_id = $this->int('max_id');

        return true;
    }

    /**
     * Handle the request
     *
     * show a timeline of the user's repeated notices
     *
     * @param array $args $_REQUEST data (unused)
     *
     * @return void
     */
    function handle()
    {
        parent::handle();

        $offset = ($this->page-1) * $this->cnt;
        $limit  = $this->cnt;

        // TRANS: Title of list of repeated notices of the logged in user.
        // TRANS: %s is the nickname of the logged in user.
        $title      = sprintf(_("Repeats of %s"), $this->auth_user->nickname);
        $sitename   = common_config('site', 'name');

        $profile = $this->auth_user->getProfile();

        $subtitle   = sprintf(
            // TRANS: Subtitle of API time with retweets of me.
            // TRANS: %1$s is the StatusNet sitename, %2$s is the user nickname, %3$s is the user profile name.
            _('%1$s notices that %2$s / %3$s has repeated.'),
            $sitename, $this->auth_user->nickname, $profile->getBestName()
        );

        $taguribase = TagURI::base();
        $id         = "tag:$taguribase:RepeatsOfMe:" . $this->auth_user->id;

        $link = common_local_url(
            'all',
             array('nickname' => $this->auth_user->nickname)
        );

        // This is a really bad query for some reason

        if (!common_config('performance', 'high')) {
            $strm = $this->auth_user->repeatsOfMe($offset, $limit, $this->since_id, $this->max_id);
        } else {
            $strm = new Notice();
            $strm->whereAdd('0 = 1');
            $strm->find();
        }

        switch ($this->format) {
        case 'xml':
            $this->showXmlTimeline($strm);
            break;
        case 'json':
            $this->showJsonTimeline($strm);
            break;
        case 'atom':
            header('Content-Type: application/atom+xml; charset=utf-8');
            $atom = new AtomNoticeFeed($this->auth_user);
            $atom->setId($id);
            $atom->setTitle($title);
            $atom->setSubtitle($subtitle);
            $atom->setUpdated('now');
            $atom->addLink($link);
            $atom->setSelfLink($this->getSelfUri());
            $atom->addEntryFromNotices($strm);
            $this->raw($atom->getString());
            break;
        case 'as':
            header('Content-Type: ' . ActivityStreamJSONDocument::CONTENT_TYPE);
            $doc = new ActivityStreamJSONDocument($this->auth_user);
            $doc->setTitle($title);
            $doc->addLink($link, 'alternate', 'text/html');
            $doc->addItemsFromNotices($strm);
            $this->raw($doc->asString());
            break;
        default:
            // TRANS: Client error displayed when coming across a non-supported API method.
            $this->clientError(_('API method not found.'), 404);
            break;
        }
    }

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


code:
<?php
/**
 * Repeat action.
 *
 * PHP version 5
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 *
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2008, 2009, StatusNet, Inc.
 *
 * 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/>.
 */

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

/**
 * Repeat action
 *
 * @category Action
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl.html AGPLv3
 * @link     http://status.net/
 */
class RepeatAction extends FormAction
{
    protected $notice = null;   // Notice that is being repeated.
    protected $repeat = null;   // The resulting repeat object/notice.

    function title()
    {
        return _m('TITLE', 'Repeat notice');
    }

    protected function doPreparation()
    {
        $id = $this->trimmed('notice');

        if (empty($id)) {
            // TRANS: Client error displayed when trying to repeat a notice while not providing a notice ID.
            $this->clientError(_('No notice specified.'));
        }

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

        if (!$this->notice instanceof Notice) {
            // TRANS: Client error displayed when trying to repeat a non-existing notice.
            $this->clientError(_('Notice not found.'));
        }

        $this->repeat = $this->notice->repeat($this->scoped, 'web');
        if (!$this->repeat instanceof Notice) {
            // TRANS: Error when unable to repeat a notice for unknown reason.
            $this->clientError(_('Could not repeat notice for unknown reason. Please contact the webmaster!'));
        }

        return true;
    }

    /**
     * Class handler.
     *
     * @param array $args query arguments
     *
     * @return void
     */
    protected function showContent()
    {
        $this->element('p', array('id' => 'repeat_response',
                                  'class' => 'repeated'),
                            // TRANS: Confirmation text after repeating a notice.
                            _('Repeated!'));
    }
}


code:
<?php
/**
 * StatusNet, the distributed open-source microblogging tool
 *
 * Form for repeating 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  Form
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2009 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link      http://status.net/
 */

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

/**
 * Form for repeating a notice
 *
 * @category Form
 * @package  StatusNet
 * @author   Evan Prodromou <evan@status.net>
 * @license  http://www.fsf.org/licensing/licenses/agpl-3.0.html GNU Affero General Public License version 3.0
 * @link     http://status.net/
 */
class RepeatForm extends Form
{
    /**
     * Notice to repeat
     */
    var $notice = null;

    /**
     * Constructor
     *
     * @param HTMLOutputter $out    output channel
     * @param Notice        $notice notice to repeat
     */
    function __construct($out=null, $notice=null)
    {
        parent::__construct($out);

        $this->notice = $notice;
    }

    /**
     * ID of the form
     *
     * @return int ID of the form
     */
    function id()
    {
        return 'repeat-' . $this->notice->id;
    }

    /**
     * Action of the form
     *
     * @return string URL of the action
     */
    function action()
    {
        return common_local_url('repeat');
    }

    /**
     * Legend of the Form
     *
     * @return void
     */
    function formLegend()
    {
        // TRANS: For legend for notice repeat form.
        $this->out->element('legend', null, _('Repeat this notice?'));
    }

    /**
     * Data elements
     *
     * @return void
     */
    function formData()
    {
        $this->out->hidden('notice-n'.$this->notice->id,
                           $this->notice->id,
                           'notice');
    }

    /**
     * Action elements
     *
     * @return void
     */
    function formActions()
    {
        $this->out->submit('repeat-submit-' . $this->notice->id,
                           // TRANS: Button text to repeat a notice on notice repeat form.
                           _m('BUTTON','Yes'), 'submit', null,
                           // TRANS: Button title to repeat a notice on notice repeat form.
                           _('Repeat this notice.'));
    }

    /**
     * Class of the form.
     *
     * @return string the form's class
     */
    function formClass()
    {
        return 'form_repeat';
    }
}


code:
<?php

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

class RepeatCommand extends Command
{
    var $other = null;
    function __construct($user, $other)
    {
        parent::__construct($user);
        $this->other = $other;
    }

    function handle($channel)
    {
        $notice = $this->getNotice($this->other);

        try {
            $repeat = $notice->repeat($this->scoped->id, $channel->source());
            $recipient = $notice->getProfile();

            // TRANS: Message given having repeated a notice from another user.
            // TRANS: %s is the name of the user for which the notice was repeated.
            $channel->output($this->user, sprintf(_('Notice from %s repeated.'), $recipient->nickname));
        } catch (Exception $e) {
            $channel->error($this->user, $e->getMessage());
        }
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Stream of notices repeated by me
 *
 * PHP version 5
 *
 * 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  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

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

/**
 * Stream of notices repeated by me
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class RepeatedByMeNoticeStream extends ScopingNoticeStream
{
    function __construct(Profile $target, Profile $scoped=null)
    {
        parent::__construct(new CachingNoticeStream(new RawRepeatedByMeNoticeStream($target),
                                                    'user:repeated_by_me:'.$target->getID()),
                            $scoped);
    }
}

/**
 * Raw stream of notices repeated by me
 *
 * @category  General
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class RawRepeatedByMeNoticeStream extends NoticeStream
{
    protected $target;

    function __construct(Profile $target)
    {
        $this->target = $target;
    }

    function getNoticeIds($offset, $limit, $since_id, $max_id)
    {
        $notice = new Notice();

        $notice->selectAdd(); // clears it
        $notice->selectAdd('id');

        $notice->profile_id = $this->target->getID();
        $notice->whereAdd('repeat_of IS NOT NULL');

        $notice->orderBy('created DESC, id DESC');

        if (!is_null($offset)) {
            $notice->limit($offset, $limit);
        }

        Notice::addWhereSinceId($notice, $since_id);
        Notice::addWhereMaxId($notice, $max_id);

        $ids = array();

        if ($notice->find()) {
            while ($notice->fetch()) {
                $ids[] = $notice->id;
            }
        }

        $notice->free();
        $notice = NULL;

        return $ids;
    }
}


code:
<?php
/**
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2011, StatusNet, Inc.
 *
 * Stream of notices that are repeats of mine
 *
 * PHP version 5
 *
 * 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  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

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

/**
 * Stream of notices that are repeats of mine
 *
 * @category  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */

class RepeatsOfMeNoticeStream extends ScopingNoticeStream
{
    function __construct(Profile $target, Profile $scoped=null)
    {
        parent::__construct(new CachingNoticeStream(new RawRepeatsOfMeNoticeStream($target),
                                                    'user:repeats_of_me:'.$target->getID()),
                            $scoped);
    }
}

/**
 * Raw stream of notices that are repeats of mine
 *
 * @category  Stream
 * @package   StatusNet
 * @author    Evan Prodromou <evan@status.net>
 * @copyright 2011 StatusNet, Inc.
 * @license   http://www.fsf.org/licensing/licenses/agpl-3.0.html AGPL 3.0
 * @link      http://status.net/
 */
class RawRepeatsOfMeNoticeStream extends NoticeStream
{
    protected $target;

    function __construct(Profile $target)
    {
        $this->target = $target;
    }

    function getNoticeIds($offset, $limit, $since_id, $max_id)
    {
        $qry =
          'SELECT DISTINCT original.id AS id ' .
          'FROM notice original JOIN notice rept ON original.id = rept.repeat_of ' .
          'WHERE original.profile_id = ' . $this->target->getID() . ' ';

        $since = Notice::whereSinceId($since_id, 'original.id', 'original.created');
        if ($since) {
            $qry .= "AND ($since) ";
        }

        $max = Notice::whereMaxId($max_id, 'original.id', 'original.created');
        if ($max) {
            $qry .= "AND ($max) ";
        }

        $qry .= 'ORDER BY original.created, original.id DESC ';

        if (!is_null($offset)) {
            $qry .= "LIMIT $limit OFFSET $offset";
        }

        $ids = array();

        $notice = new Notice();

        $notice->query($qry);

        while ($notice->fetch()) {
            $ids[] = $notice->id;
        }

        $notice->free();
        $notice = NULL;

        return $ids;
    }
}


code:
<?php

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

class ThreadedNoticeListInlineRepeatsItem extends ThreadedNoticeListRepeatsItem
{
    function showStart()
    {
        $this->out->elementStart('div', array('class' => 'notice-repeats'));
    }

    function showEnd()
    {
        $this->out->elementEnd('div');
    }
}


code:
<?php

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

/**
 * Placeholder for showing repeats...
 */
class ThreadedNoticeListRepeatsItem extends NoticeListActorsItem
{
    function getProfiles()
    {
        $repeats = Notice::listGet('repeat_of', array($this->notice->getID()));

        $profiles = array();
        foreach ($repeats[$this->notice->getID()] as $rep) {
            $profiles[] = $rep->profile_id;
        }

        return $profiles;
    }

    function magicList($items)
    {
        if (count($items) > 4) {
            return parent::magicList(array_slice($items, 0, 3));
        } else {
            return parent::magicList($items);
        }
    }

    function getListMessage($count, $you)
    {
        if ($count == 1 && $you) {
            // darn first person being different from third person!
            // TRANS: List message for notice repeated by logged in user.
            return _m('REPEATLIST', 'You repeated this.');
        } else if ($count > 4) {
            // TRANS: List message for when more than 4 people repeat something.
            // TRANS: %%s is a list of users liking a notice, %d is the number over 4 that like the notice.
            // TRANS: Plural is decided on the total number of users liking the notice (count of %%s + %d).
            return sprintf(_m('%%s and %d other repeated this.',
                              '%%s and %d others repeated this.',
                              $count - 3),
                           $count - 3);
        } else {
            // TRANS: List message for repeated notices.
            // TRANS: %%s is a list of users who have repeated a notice.
            // TRANS: Plural is based on the number of of users that have repeated a notice.
            return sprintf(_m('%%s repeated this.',
                              '%%s repeated this.',
                              $count),
                           $count);
        }
    }

    function showStart()
    {
        $this->out->elementStart('li', array('class' => 'notice-data notice-repeats'));
    }

    function showEnd()
    {
        $this->out->elementEnd('li');
    }
}


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-08 18:20+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=CHARSET\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=INTEGER; plural=EXPRESSION;\n"

#. TRANS: Client exception thrown when trying to share multiple activities at once.
#: SharePlugin.php:91
msgid "Can only handle share activities with exactly one object."
msgstr ""

#. TRANS: Client exception thrown when trying to share a non-activity object.
#: SharePlugin.php:97
msgid "Can only handle shared activities."
msgstr ""

#: SharePlugin.php:108
msgid "Shared activity does not have an id"
msgstr ""

#. TRANS: Help message for IM/SMS command "repeat #<notice_id>".
#: SharePlugin.php:329
msgctxt "COMMANDHELP"
msgid "repeat a notice with a given id"
msgstr ""

#. TRANS: Help message for IM/SMS command "repeat <nickname>".
#: SharePlugin.php:331
msgctxt "COMMANDHELP"
msgid "repeat the last notice from user"
msgstr ""

#. TRANS: Plugin description.
#: SharePlugin.php:370
msgid "Shares (repeats) using ActivityStreams."
msgstr ""

#. TRANS: List message for notice repeated by logged in user.
#: lib/threadednoticelistrepeatsitem.php:36
msgctxt "REPEATLIST"
msgid "You repeated this."
msgstr ""

#. TRANS: List message for when more than 4 people repeat something.
#. TRANS: %%s is a list of users liking a notice, %d is the number over 4 that like the notice.
#. TRANS: Plural is decided on the total number of users liking the notice (count of %%s + %d).
#: lib/threadednoticelistrepeatsitem.php:41
#, php-format
msgid "%%s and %d other repeated this."
msgid_plural "%%s and %d others repeated this."
msgstr[0] ""
msgstr[1] ""

#. TRANS: List message for repeated notices.
#. TRANS: %%s is a list of users who have repeated a notice.
#. TRANS: Plural is based on the number of of users that have repeated a notice.
#: lib/threadednoticelistrepeatsitem.php:49
#, php-format
msgid "%%s repeated this."
msgid_plural "%%s repeated this."
msgstr[0] ""
msgstr[1] ""

#: actions/repeat.php:48
msgctxt "TITLE"
msgid "Repeat notice"
msgstr ""

#. TRANS: Button text to repeat a notice on notice repeat form.
#: forms/repeat.php:115
msgctxt "BUTTON"
msgid "Yes"
msgstr ""


code:
The ShareNotice plugin allows sharing of notices to Twitter, Facebook and other
platforms.

Installation
============
add "addPlugin('ShareNotice');"
to the bottom of your config.php

Settings
========
targets: Array of platforms allowed to share to

Example
=======

addPlugin('ShareNotice', array(
    'targets' => array(
        array('Twitter'),
        array('Facebook'),
        array('StatusNet', array('baseurl' => 'http://example.org'))
    )
));

path: plugins/ShareNotice/README


code:
<?php
/*
 * StatusNet - the distributed open-source microblogging tool
 * Copyright (C) 2010, StatusNet, Inc.
 *
 * 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/>.
 */

/**
 * @package ShareNoticePlugin
 * @maintainer Brion Vibber <brion@status.net>
 */

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

class ShareNoticePlugin extends Plugin
{
    const PLUGIN_VERSION = '2.0.0';

    public $targets = array(
        array('Twitter'),
        array('Facebook'),
        array('StatusNet', array('baseurl' => 'http://identi.ca'))
    );

    public function onEndShowStylesheets(Action $action) {
        $action->cssLink($this->path('css/sharenotice.css'));
        return true;
    }

    function onStartShowNoticeItem($item)
    {
        $notice = $item->notice;
        $out = $item->out;

        $out->elementStart('ul', array('class' => 'notice-share'));
        foreach ($this->targets as $data) {
            $type = $data[0];
            $args = (count($data) > 1) ? $data[1] : array();
            $target = $this->getShareTarget($type, $notice, $args);
            $this->showShareTarget($out, $target);
        }
        $out->elementEnd('ul');
    }

    private function getShareTarget($type, $notice, $args)
    {
        $class = ucfirst($type) . 'ShareTarget';

        return new $class($notice, $args);
    }

    private function showShareTarget(HTMLOutputter $out, NoticeShareTarget $target)
    {
        $class = $target->getClass();
        $text = $target->getText();
        $url = $target->targetUrl();

        $out->elementStart('li', array('class' => 'notice-share-' . $class));
        $out->elementStart('a', array(
            'href' => $url,
            'title' => $text,
            'target' => '_blank'
        ));
        $out->element('span', array(), $text);
        $out->elementEnd('a');
        $out->elementEnd('li');
    }
}

abstract class NoticeShareTarget
{
    protected $notice;

    public function __construct($notice)
    {
        $this->notice = $notice;
    }

    public abstract function getClass();

    public abstract function getText();

    public abstract function targetUrl();
}

abstract class GenericNoticeShareTarget extends NoticeShareTarget
{
    protected function maxLength()
    {
        return 140; // typical
    }

    protected function statusText()
    {
        // TRANS: %s is notice content that is shared on Twitter, Facebook or another platform.
        $pattern = _m('"%s"');
        $url = $this->notice->getUrl();
        $suffix = ' ' . $url;
        $room = $this->maxLength() - mb_strlen($suffix) - (mb_strlen($pattern) - mb_strlen('%s'));

        $content = $this->notice->content;
        // TRANS: Truncation symbol.
        $truncation_symbol = _m('…');
        $truncation_symbol_length = mb_strlen($truncation_symbol);
        if (mb_strlen($content) > $room) {
            $content = mb_substr($content, 0, $room - $truncation_symbol_length) . $truncation_symbol;
        }

        return sprintf($pattern, $content) . $suffix;
    }
}

class TwitterShareTarget extends GenericNoticeShareTarget
{
    public function getClass()
    {
        return 'twitter';
    }

    public function getText()
    {
        // TRANS: Tooltip for image to share a notice on Twitter.
        return _m('Share on Twitter');
    }

    public function targetUrl()
    {
        $args = array(
            'status' => $this->statusText()
        );
        return 'http://twitter.com/home?' .
                http_build_query($args, null, '&');
    }
}

class StatusNetShareTarget extends GenericNoticeShareTarget
{
    protected $baseurl;

    public function __construct($notice, $args)
    {
        parent::__construct($notice);
        $this->baseurl = $args['baseurl'];
    }

    public function getClass()
    {
        return 'statusnet';
    }

    public function getText()
    {
        $host = parse_url($this->baseurl, PHP_URL_HOST);
        // TRANS: Tooltip for image to share a notice on another platform (other than Twitter or Facebook).
        // TRANS: %s is a host name.
        return sprintf(_m('Share on %s'), $host);
    }

    public function targetUrl()
    {
        $args = array(
            'status_textarea' => $this->statusText()
        );
        return $this->baseurl . '/notice/new?' .
                http_build_query($args, null, '&');
    }
}

class FacebookShareTarget extends NoticeShareTarget
{
    public function getClass()
    {
        return 'facebook';
    }

    public function getText()
    {
        // TRANS: Tooltip for image to share a notice on Facebook.
        return _m('Share on Facebook');
    }

    public function targetUrl()
    {
        $args = array(
            'u' => $this->notice->getUrl(),
            // TRANS: %s is notice content that is shared on Twitter, Facebook or another platform.
            't' => sprintf(_m('"%s"'), $this->notice->content),
        );
        return 'http://www.facebook.com/sharer.php?' .
            http_build_query($args, null, '&');
    }

    /**
     * Provide plugin version information.
     *
     * This data is used when showing the version page.
     *
     * @param array &$versions array of version data arrays; see EVENTS.txt
     *
     * @return boolean hook value
     */
    function onPluginVersion(array &$versions)
    {
        $url = 'https://git.gnu.io/gnu/gnu-social/tree/master/plugins/ShareNotice';

        $versions[] = array('name' => 'ShareNotice',
            'version' => self::PLUGIN_VERSION,
            'author' => 'Brion Vibber',
            'homepage' => $url,
            'rawdescription' =>
            // TRANS: Plugin description.
            _m('This plugin allows sharing of notices to Twitter, Facebook and other platforms.'));

        return true;
    }
}


code:
# SOME DESCRIPTIVE TITLE.
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
# This file is distributed under the same license as the PACKAGE package.
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
#
#, fuzzy
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2019-06-08 18:20+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
"Language: \n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"

#. TRANS: %s is notice content that is shared on Twitter, Facebook or another platform.
#: ShareNoticePlugin.php:108 ShareNoticePlugin.php:199
#, php-format
msgid "\"%s\""
msgstr ""

#. TRANS: Truncation symbol.
#: ShareNoticePlugin.php:115
msgid "…"
msgstr ""

#. TRANS: Tooltip for image to share a notice on Twitter.
#: ShareNoticePlugin.php:135
msgid "Share on Twitter"
msgstr ""

#. TRANS: Tooltip for image to share a notice on another platform (other than Twitter or Facebook).
#. TRANS: %s is a host name.
#: ShareNoticePlugin.php:168
#, php-format
msgid "Share on %s"
msgstr ""

#. TRANS: Tooltip for image to share a notice on Facebook.
#: ShareNoticePlugin.php:191
msgid "Share on Facebook"
msgstr ""

#. TRANS: Plugin description.
#: ShareNoticePlugin.php:224
msgid ""
"This plugin allows sharing of notices to Twitter, Facebook and other "
"platforms."
msgstr ""


code:
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2015 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 10:25+0000\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: Afrikaans (http://www.transifex.com/gnu-social/gnu-social/language/af/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: af\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"

#. TRANS: %s is notice content that is shared on Twitter, Facebook or another
#. platform.
#: ShareNoticePlugin.php:106 ShareNoticePlugin.php:197
#, php-format
msgid "\"%s\""
msgstr ""

#. TRANS: Truncation symbol.
#: ShareNoticePlugin.php:113
msgid "…"
msgstr ""

#. TRANS: Tooltip for image to share a notice on Twitter.
#: ShareNoticePlugin.php:133
msgid "Share on Twitter"
msgstr ""

#. TRANS: Tooltip for image to share a notice on another platform (other than
#. Twitter or Facebook).
#. TRANS: %s is a host name.
#: ShareNoticePlugin.php:166
#, php-format
msgid "Share on %s"
msgstr ""

#. TRANS: Tooltip for image to share a notice on Facebook.
#: ShareNoticePlugin.php:189
msgid "Share on Facebook"
msgstr ""

#. TRANS: Plugin description.
#: ShareNoticePlugin.php:222
msgid ""
"This plugin allows sharing of notices to Twitter, Facebook and other "
"platforms."
msgstr ""


code:
# Translation file for GNU social - the free software social networking platform
# Copyright (C) 2011 - 2019 Free Software Foundation, Inc http://www.fsf.org
# This file is under https://www.gnu.org/licenses/agpl v3 or later
#
# Translators:
msgid ""
msgstr ""
"Project-Id-Version: GNU social\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2015-02-02 17:47+0100\n"
"PO-Revision-Date: 2015-02-07 10:32+0000\n"
"Last-Translator: digitaldreamer <digitaldreamer@email.cz>\n"
"Language-Team: Arabic (http://www.transifex.com/gnu-social/gnu-social/language/ar/)\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Language: ar\n"
"Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n"

#. TRANS: %s is notice content that is shared on Twitter, Facebook or another
#. platform.
#: ShareNoticePlugin.php:106 ShareNoticePlugin.php:197
#, php-format
msgid "\"%s\""
msgstr "\"%s\""

#. TRANS: Truncation symbol.
#: ShareNoticePlugin.php:113
msgid "…"
msgstr "..."

#. TRANS: Tooltip for image to share a notice on Twitter.
#: ShareNoticePlugin.php:133
msgid "Share on Twitter"
msgstr "تشارك على تويتر"

#. TRANS: Tooltip for image to share a notice on another platform (other than
#. Twitter or Facebook).
#. TRANS: %s is a host name.
#: ShareNoticePlugin.php:166
#, php-format
msgid "Share on %s"
msgstr "تشارك على %s"

#. TRANS: Tooltip for image to share a notice on Facebook.
#: ShareNoticePlugin.php:189
msgid "Share on Facebook"
msgstr "تشارك على فيسبك"

#. TRANS: Plugin description.
#: ShareNoticePlugin.php:222
msgid ""
"This plugin allows sharing of notices to Twitter, Facebook and other "
"platforms."
msgstr "يتيح هذا الملحق تشارك الإشعارات على تويتر وفيسبك ومنصات أخرى."