diff --git a/app/assets/javascripts/components/components/column_collapsable.jsx b/app/assets/javascripts/components/components/column_collapsable.jsx index 90c561bce7a..67675905523 100644 --- a/app/assets/javascripts/components/components/column_collapsable.jsx +++ b/app/assets/javascripts/components/components/column_collapsable.jsx @@ -40,10 +40,11 @@ const ColumnCollapsable = React.createClass({ render () { const { icon, fullHeight, children } = this.props; const { collapsed } = this.state; - + const collapsedClassName = collapsed ? 'collapsable-collapsed' : 'collapsable'; + return (
-
+
{({ opacity, height }) => diff --git a/app/assets/javascripts/components/components/loading_indicator.jsx b/app/assets/javascripts/components/components/loading_indicator.jsx index c8a2639241f..913a4bf997f 100644 --- a/app/assets/javascripts/components/components/loading_indicator.jsx +++ b/app/assets/javascripts/components/components/loading_indicator.jsx @@ -4,12 +4,11 @@ const style = { textAlign: 'center', fontSize: '16px', fontWeight: '500', - color: '#616b86', paddingTop: '120px' }; const LoadingIndicator = () => ( -
+
); diff --git a/app/assets/javascripts/components/components/media_gallery.jsx b/app/assets/javascripts/components/components/media_gallery.jsx index a13448d0b4d..b0e397e80f9 100644 --- a/app/assets/javascripts/components/components/media_gallery.jsx +++ b/app/assets/javascripts/components/components/media_gallery.jsx @@ -16,8 +16,6 @@ const outerStyle = { }; const spoilerStyle = { - background: '#000', - color: '#fff', textAlign: 'center', height: '100%', cursor: 'pointer', @@ -84,14 +82,14 @@ const MediaGallery = React.createClass({ if (!this.state.visible) { if (sensitive) { children = ( -
+
); } else { children = ( -
+
diff --git a/app/assets/javascripts/components/components/video_player.jsx b/app/assets/javascripts/components/components/video_player.jsx index 3edc8f6727b..ccd67ddf09a 100644 --- a/app/assets/javascripts/components/components/video_player.jsx +++ b/app/assets/javascripts/components/components/video_player.jsx @@ -28,8 +28,6 @@ const muteStyle = { const spoilerStyle = { marginTop: '8px', - background: '#000', - color: '#fff', textAlign: 'center', height: '100%', cursor: 'pointer', @@ -122,7 +120,7 @@ const VideoPlayer = React.createClass({ if (!this.state.visible) { if (sensitive) { return ( -
+
{spoilerButton} @@ -130,7 +128,7 @@ const VideoPlayer = React.createClass({ ); } else { return ( -
+
{spoilerButton} diff --git a/app/assets/javascripts/components/features/account/components/header.jsx b/app/assets/javascripts/components/features/account/components/header.jsx index b2d943c1c6f..a4f0ca76854 100644 --- a/app/assets/javascripts/components/features/account/components/header.jsx +++ b/app/assets/javascripts/components/features/account/components/header.jsx @@ -35,7 +35,7 @@ const Header = React.createClass({ } if (me !== account.get('id') && account.getIn(['relationship', 'followed_by'])) { - info = + info = } if (me !== account.get('id')) { diff --git a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx index 0d41d192f2e..1766655c20f 100644 --- a/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx +++ b/app/assets/javascripts/components/features/follow_requests/components/account_authorize.jsx @@ -16,11 +16,8 @@ const outerStyle = { }; const panelStyle = { - background: '#2f3441', display: 'flex', flexDirection: 'row', - borderTop: '1px solid #363c4b', - borderBottom: '1px solid #363c4b', padding: '10px 0' }; @@ -40,10 +37,10 @@ const AccountAuthorize = ({ intl, account, onAuthorize, onReject }) => { -
+
-
+
diff --git a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx index 714be309bcf..3317210bf0d 100644 --- a/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx +++ b/app/assets/javascripts/components/features/home_timeline/components/column_settings.jsx @@ -10,7 +10,6 @@ const messages = defineMessages({ }); const outerStyle = { - background: '#373b4a', padding: '15px' }; @@ -18,7 +17,6 @@ const sectionStyle = { cursor: 'default', display: 'block', fontWeight: '500', - color: '#9baec8', marginBottom: '10px' }; @@ -42,8 +40,8 @@ const ColumnSettings = React.createClass({ return ( -
- +
+
} /> @@ -53,7 +51,7 @@ const ColumnSettings = React.createClass({ } />
- +
diff --git a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx index d20a4d170d9..0b7c737c6ba 100644 --- a/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx +++ b/app/assets/javascripts/components/features/notifications/components/clear_column_button.jsx @@ -4,8 +4,7 @@ const iconStyle = { position: 'absolute', right: '48px', top: '0', - cursor: 'pointer', - background: '#2f3441' + cursor: 'pointer' }; const ClearColumnButton = ({ onClick }) => ( diff --git a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx index b63c1881ac0..f1b8ef57fac 100644 --- a/app/assets/javascripts/components/features/notifications/components/column_settings.jsx +++ b/app/assets/javascripts/components/features/notifications/components/column_settings.jsx @@ -5,7 +5,6 @@ import ColumnCollapsable from '../../../components/column_collapsable'; import SettingToggle from './setting_toggle'; const outerStyle = { - background: '#373b4a', padding: '15px' }; @@ -13,7 +12,6 @@ const sectionStyle = { cursor: 'default', display: 'block', fontWeight: '500', - color: '#9baec8', marginBottom: '10px' }; @@ -40,8 +38,8 @@ const ColumnSettings = React.createClass({ return ( -
- +
+
@@ -49,7 +47,7 @@ const ColumnSettings = React.createClass({
- +
@@ -57,7 +55,7 @@ const ColumnSettings = React.createClass({
- +
@@ -65,7 +63,7 @@ const ColumnSettings = React.createClass({
- +
diff --git a/app/assets/javascripts/components/features/notifications/components/notification.jsx b/app/assets/javascripts/components/features/notifications/components/notification.jsx index 140ba9134e9..fa8466140f3 100644 --- a/app/assets/javascripts/components/features/notifications/components/notification.jsx +++ b/app/assets/javascripts/components/features/notifications/components/notification.jsx @@ -7,16 +7,6 @@ import Permalink from '../../../components/permalink'; import emojify from '../../../emoji'; import escapeTextContentForBrowser from 'react/lib/escapeTextContentForBrowser'; -const messageStyle = { - marginLeft: '68px', - padding: '8px 0', - paddingBottom: '0', - cursor: 'default', - color: '#d9e1e8', - fontSize: '15px', - position: 'relative' -}; - const linkStyle = { fontWeight: '500' }; @@ -32,9 +22,9 @@ const Notification = React.createClass({ renderFollow (account, link) { return (
-
+
- +
@@ -52,7 +42,7 @@ const Notification = React.createClass({ renderFavourite (notification, link) { return (
-
+
@@ -68,9 +58,9 @@ const Notification = React.createClass({ renderReblog (notification, link) { return (
-
+
- +
diff --git a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx index c2438f716ea..eae3c2be2bd 100644 --- a/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx +++ b/app/assets/javascripts/components/features/notifications/components/setting_toggle.jsx @@ -11,14 +11,13 @@ const labelSpanStyle = { display: 'inline-block', verticalAlign: 'middle', marginBottom: '14px', - marginLeft: '8px', - color: '#9baec8' + marginLeft: '8px' }; const SettingToggle = ({ settings, settingKey, label, onChange }) => ( ); diff --git a/app/assets/javascripts/components/features/status/components/card.jsx b/app/assets/javascripts/components/features/status/components/card.jsx index ccb06dfd582..1bb281c70b2 100644 --- a/app/assets/javascripts/components/features/status/components/card.jsx +++ b/app/assets/javascripts/components/features/status/components/card.jsx @@ -1,18 +1,6 @@ import PureRenderMixin from 'react-addons-pure-render-mixin'; import ImmutablePropTypes from 'react-immutable-proptypes'; -const outerStyle = { - display: 'flex', - cursor: 'pointer', - fontSize: '14px', - border: '1px solid #363c4b', - borderRadius: '4px', - color: '#616b86', - marginTop: '14px', - textDecoration: 'none', - overflow: 'hidden' -}; - const contentStyle = { flex: '1 1 auto', padding: '8px', @@ -20,25 +8,6 @@ const contentStyle = { overflow: 'hidden' }; -const titleStyle = { - display: 'block', - fontWeight: '500', - marginBottom: '5px', - color: '#d9e1e8', - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap' -}; - -const descriptionStyle = { - color: '#d9e1e8' -}; - -const imageOuterStyle = { - flex: '0 0 100px', - background: '#373b4a' -}; - const imageStyle = { display: 'block', width: '100%', @@ -77,20 +46,20 @@ const Card = React.createClass({ if (card.get('image')) { image = ( -
+
{card.get('title')}
); } return ( - + {image} -
- {card.get('title')} -

{card.get('description').substring(0, 50)}

- {getHostname(card.get('url'))} +
+ {card.get('title')} +

{card.get('description').substring(0, 50)}

+ {getHostname(card.get('url'))}
); diff --git a/app/assets/javascripts/components/features/status/components/detailed_status.jsx b/app/assets/javascripts/components/features/status/components/detailed_status.jsx index f2d6ae48a8d..8a7c0c5d5ea 100644 --- a/app/assets/javascripts/components/features/status/components/detailed_status.jsx +++ b/app/assets/javascripts/components/features/status/components/detailed_status.jsx @@ -52,7 +52,7 @@ const DetailedStatus = React.createClass({ } return ( -
+
@@ -62,7 +62,7 @@ const DetailedStatus = React.createClass({ {media} -
+
{applicationLink} · ·
diff --git a/app/assets/javascripts/components/features/ui/components/column_link.jsx b/app/assets/javascripts/components/features/ui/components/column_link.jsx index 901a29f5c14..2bd1e1017b2 100644 --- a/app/assets/javascripts/components/features/ui/components/column_link.jsx +++ b/app/assets/javascripts/components/features/ui/components/column_link.jsx @@ -4,7 +4,6 @@ const outerStyle = { display: 'block', padding: '15px', fontSize: '16px', - color: '#fff', textDecoration: 'none' }; diff --git a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx index 334e5c1991e..4c47fb8c54b 100644 --- a/app/assets/javascripts/components/features/ui/containers/modal_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/modal_container.jsx @@ -41,13 +41,12 @@ const imageStyle = { }; const loadingStyle = { - background: '#373b4a', width: '400px', paddingBottom: '120px' }; const preloader = () => ( -
+
); @@ -57,7 +56,6 @@ const leftNavStyle = { background: 'rgba(0, 0, 0, 0.5)', padding: '30px 15px', cursor: 'pointer', - color: '#fff', fontSize: '24px', top: '0', left: '-61px', @@ -72,7 +70,6 @@ const rightNavStyle = { background: 'rgba(0, 0, 0, 0.5)', padding: '30px 15px', cursor: 'pointer', - color: '#fff', fontSize: '24px', top: '0', right: '-61px', @@ -143,11 +140,11 @@ const Modal = React.createClass({ leftNav = rightNav = ''; if (hasLeft) { - leftNav =
; + leftNav =
; } if (hasRight) { - rightNav =
; + rightNav =
; } return ( diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index c4c876e30e4..c590f703897 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -256,6 +256,35 @@ button:focus { } } +.compact-header { + h1 { + font-size: 24px; + line-height: 28px; + color: $color3; + overflow: hidden; + font-weight: 500; + margin-bottom: 20px; + + a { + color: inherit; + text-decoration: none; + } + + small { + font-weight: 400; + color: $color2; + } + + img { + display: inline-block; + margin-bottom: -5px; + margin-right: 15px; + width: 36px; + height: 36px; + } + } +} + @import 'forms'; @import 'accounts'; @import 'stream_entries'; diff --git a/app/assets/stylesheets/components.scss b/app/assets/stylesheets/components.scss index 6bb683f17aa..f0948b0f39a 100644 --- a/app/assets/stylesheets/components.scss +++ b/app/assets/stylesheets/components.scss @@ -34,6 +34,7 @@ .column-icon { color: $color3; + background: lighten($color1, 4%); &:hover { color: lighten($color3, 7%); @@ -187,7 +188,7 @@ a.status__content__spoiler-link { display: inline-block; border-radius: 2px; - color: lighten($color1, 6%); + color: lighten($color1, 8%); font-weight: 500; font-size: 11px; padding: 0px 6px; @@ -200,7 +201,7 @@ a.status__content__spoiler-link { padding-left: 68px; position: relative; min-height: 48px; - border-bottom: 1px solid lighten($color1, 6%); + border-bottom: 1px solid lighten($color1, 8%); cursor: default; .status__relative-time { @@ -226,6 +227,8 @@ a.status__content__spoiler-link { } .detailed-status { + background: lighten($color1, 4%); + .status__content { font-size: 19px; line-height: 24px; @@ -237,12 +240,19 @@ a.status__content__spoiler-link { } } +.detailed-status__meta { + margin-top: 15px; + color: lighten($color1, 26%); + font-size: 14px; + line-height: 18px; +} + .detailed-status__action-bar { background: lighten($color1, 4%); display: flex; flex-direction: row; - border-top: 1px solid lighten($color1, 6%); - border-bottom: 1px solid lighten($color1, 6%); + border-top: 1px solid lighten($color1, 8%); + border-bottom: 1px solid lighten($color1, 8%); padding: 10px 0; } @@ -257,7 +267,7 @@ a.status__content__spoiler-link { .account { padding: 10px; - border-bottom: 1px solid lighten($color1, 6%); + border-bottom: 1px solid lighten($color1, 8%); .account__display-name { flex: 1 1 auto; @@ -298,6 +308,7 @@ a.status__content__spoiler-link { word-wrap: break-word; font-weight: 400; overflow: hidden; + color: $color3; p { margin-bottom: 20px; @@ -325,8 +336,8 @@ a.status__content__spoiler-link { } .account__action-bar { - border-top: 1px solid lighten($color1, 6%); - border-bottom: 1px solid lighten($color1, 6%); + border-top: 1px solid lighten($color1, 8%); + border-bottom: 1px solid lighten($color1, 8%); line-height: 36px; overflow: hidden; flex: 0 0 auto; @@ -337,7 +348,7 @@ a.status__content__spoiler-link { text-decoration: none; overflow: hidden; width: 80px; - border-left: 1px solid lighten($color1, 6%); + border-left: 1px solid lighten($color1, 8%); padding: 10px 5px; & > span { @@ -412,8 +423,9 @@ a.status__content__spoiler-link { opacity: 0.5; } - .status__content__spoiler-link { + a.status__content__spoiler-link { background: lighten($color1, 26%); + color: lighten($color1, 4%); &:hover { background: lighten($color1, 29%); @@ -422,6 +434,20 @@ a.status__content__spoiler-link { } } +.notification__message { + margin-left: 68px; + padding: 8px 0; + padding-bottom: 0; + cursor: default; + color: $color3; + font-size: 15px; + position: relative; + + .fa { + color: $color4; + } +} + .notification__display-name { color: inherit; text-decoration: none; @@ -646,7 +672,7 @@ a.status__content__spoiler-link { .tabs-bar { display: flex; - background: lighten($color1, 6%); + background: lighten($color1, 8%); flex: 0 0 auto; overflow-y: auto; } @@ -660,7 +686,7 @@ a.status__content__spoiler-link { text-align: center; font-size:12px; font-weight: 500; - border-bottom: 2px solid lighten($color1, 6%); + border-bottom: 2px solid lighten($color1, 8%); &.active { border-bottom: 2px solid $color4; @@ -850,7 +876,8 @@ a.status__content__spoiler-link { } .column-link { - background: lighten($color1, 6%); + background: lighten($color1, 8%); + color: $color5; &:hover { background: lighten($color1, 11%); @@ -883,6 +910,7 @@ a.status__content__spoiler-link { .autosuggest-textarea__textarea { height: 100px; + background: $color5; } .autosuggest-textarea__suggestions { @@ -968,11 +996,40 @@ button.active i.fa-retweet { } .status-card { + display: flex; + cursor: pointer; + font-size: 14px; + border: 1px solid lighten($color1, 8%); + border-radius: 4px; + color: lighten($color1, 26%); + margin-top: 14px; + text-decoration: none; + overflow: hidden; + &:hover { - background: lighten($color1, 6%); + background: lighten($color1, 8%); } } +.status-card__title { + display: block; + font-weight: 500; + margin-bottom: 5px; + color: $color3; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.status-card__description { + color: $color3; +} + +.status-card__image { + flex: 0 0 100px; + background: lighten($color1, 8%); +} + .load-more { display: block; color: lighten($color1, 26%); @@ -981,7 +1038,7 @@ button.active i.fa-retweet { text-decoration: none; &:hover { - background: lighten($color1, 6%); + background: lighten($color1, 8%); } } @@ -1020,3 +1077,53 @@ button.active i.fa-retweet { font-size: 14px; margin: 0; } + +.loading-indicator { + color: $color2; +} + +.collapsable-collapsed { + color: $color3; + background: lighten($color1, 4%); +} + +.collapsable { + color: $color5; + background: lighten($color1, 8%); +} + +.media-spoiler { + background: $color8; + color: $color5; +} + +.modal-container--preloader { + background: lighten($color1, 8%); +} + +.account--panel { + background: lighten($color1, 4%); + border-top: 1px solid lighten($color1, 8%); + border-bottom: 1px solid lighten($color1, 8%); +} + +.column-settings--outer { + background: lighten($color1, 8%); +} + +.column-settings--section { + color: $color3; +} + +.modal-container--nav { + color: $color5; +} + +.account--follows-info { + color: $color5; +} + +.setting-toggle { + color: $color3; +} + diff --git a/app/assets/stylesheets/stream_entries.scss b/app/assets/stylesheets/stream_entries.scss index 595275527ef..d427c1466f6 100644 --- a/app/assets/stylesheets/stream_entries.scss +++ b/app/assets/stylesheets/stream_entries.scss @@ -5,24 +5,24 @@ .entry { background: lighten($color2, 8%); - &, .detailed-status.light { + .detailed-status.light, .status.light { border-bottom: 1px solid $color2; } &:last-child { - &, .detailed-status.light { + &, .detailed-status.light, .status.light { border-bottom: 0; border-radius: 0 0 4px 4px; } } &:first-child { - &, .detailed-status.light { + &, .detailed-status.light, .status.light { border-radius: 4px 4px 0 0; } &:last-child { - &, .detailed-status.light { + &, .detailed-status.light, .status.light { border-radius: 4px; } } diff --git a/app/controllers/api/v1/follow_requests_controller.rb b/app/controllers/api/v1/follow_requests_controller.rb index a30e97e715d..740083735f5 100644 --- a/app/controllers/api/v1/follow_requests_controller.rb +++ b/app/controllers/api/v1/follow_requests_controller.rb @@ -18,12 +18,12 @@ class Api::V1::FollowRequestsController < ApiController end def authorize - FollowRequest.find_by!(account_id: params[:id], target_account: current_account).authorize! + AuthorizeFollowService.new.call(Account.find(params[:id]), current_account) render_empty end def reject - FollowRequest.find_by!(account_id: params[:id], target_account: current_account).reject! + RejectFollowService.new.call(Account.find(params[:id]), current_account) render_empty end end diff --git a/app/controllers/concerns/obfuscate_filename.rb b/app/controllers/concerns/obfuscate_filename.rb index 9f12cb7e97a..dde7ce8c686 100644 --- a/app/controllers/concerns/obfuscate_filename.rb +++ b/app/controllers/concerns/obfuscate_filename.rb @@ -1,4 +1,5 @@ # frozen_string_literal: true + module ObfuscateFilename extend ActiveSupport::Concern diff --git a/app/helpers/atom_builder_helper.rb b/app/helpers/atom_builder_helper.rb index fb8f0976c32..5d20f8c2d65 100644 --- a/app/helpers/atom_builder_helper.rb +++ b/app/helpers/atom_builder_helper.rb @@ -143,6 +143,10 @@ module AtomBuilderHelper xml.link(:rel => 'mentioned', :href => TagManager::COLLECTIONS[:public], 'ostatus:object-type' => TagManager::TYPES[:collection]) end + def privacy_scope(xml, level) + xml['mastodon'].scope(level) + end + def include_author(xml, account) object_type xml, :person uri xml, TagManager.instance.uri_for(account) @@ -152,6 +156,7 @@ module AtomBuilderHelper link_alternate xml, TagManager.instance.url_for(account) link_avatar xml, account portable_contact xml, account + privacy_scope xml, account.locked? ? :private : :public end def rich_content(xml, activity) @@ -216,6 +221,7 @@ module AtomBuilderHelper end category(xml, 'nsfw') if stream_entry.target.sensitive? + privacy_scope(xml, stream_entry.target.visibility) end end end @@ -237,6 +243,7 @@ module AtomBuilderHelper end category(xml, 'nsfw') if stream_entry.activity.sensitive? + privacy_scope(xml, stream_entry.activity.visibility) end private @@ -249,6 +256,7 @@ module AtomBuilderHelper 'xmlns:poco' => TagManager::POCO_XMLNS, 'xmlns:media' => TagManager::MEDIA_XMLNS, 'xmlns:ostatus' => TagManager::OS_XMLNS, + 'xmlns:mastodon' => TagManager::MTDN_XMLNS, }, &block) end diff --git a/app/lib/feed_manager.rb b/app/lib/feed_manager.rb index f0928a9451b..623a1af0352 100644 --- a/app/lib/feed_manager.rb +++ b/app/lib/feed_manager.rb @@ -107,7 +107,6 @@ class FeedManager should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked should_filter ||= (status.account.silenced? && !receiver.following?(status.account)) # of if the account is silenced and I'm not following them - should_filter ||= (status.private_visibility? && !receiver.following?(status.account)) # or if the mentioned account is not permitted to see the private status if status.reply? && !status.in_reply_to_account_id.nil? # or it's a reply should_filter ||= receiver.blocking?(status.in_reply_to_account) # to a user I blocked diff --git a/app/lib/tag_manager.rb b/app/lib/tag_manager.rb index 2508eea97d6..9fef70fda6f 100644 --- a/app/lib/tag_manager.rb +++ b/app/lib/tag_manager.rb @@ -7,15 +7,18 @@ class TagManager include RoutingHelper VERBS = { - post: 'http://activitystrea.ms/schema/1.0/post', - share: 'http://activitystrea.ms/schema/1.0/share', - favorite: 'http://activitystrea.ms/schema/1.0/favorite', - unfavorite: 'http://activitystrea.ms/schema/1.0/unfavorite', - delete: 'http://activitystrea.ms/schema/1.0/delete', - follow: 'http://activitystrea.ms/schema/1.0/follow', - unfollow: 'http://ostatus.org/schema/1.0/unfollow', - block: 'http://mastodon.social/schema/1.0/block', - unblock: 'http://mastodon.social/schema/1.0/unblock', + post: 'http://activitystrea.ms/schema/1.0/post', + share: 'http://activitystrea.ms/schema/1.0/share', + favorite: 'http://activitystrea.ms/schema/1.0/favorite', + unfavorite: 'http://activitystrea.ms/schema/1.0/unfavorite', + delete: 'http://activitystrea.ms/schema/1.0/delete', + follow: 'http://activitystrea.ms/schema/1.0/follow', + request_friend: 'http://activitystrea.ms/schema/1.0/request-friend', + authorize: 'http://activitystrea.ms/schema/1.0/authorize', + reject: 'http://activitystrea.ms/schema/1.0/reject', + unfollow: 'http://ostatus.org/schema/1.0/unfollow', + block: 'http://mastodon.social/schema/1.0/block', + unblock: 'http://mastodon.social/schema/1.0/unblock', }.freeze TYPES = { @@ -38,6 +41,7 @@ class TagManager POCO_XMLNS = 'http://portablecontacts.net/spec/1.0' DFRN_XMLNS = 'http://purl.org/macgirvin/dfrn/1.0' OS_XMLNS = 'http://ostatus.org/schema/1.0' + MTDN_XMLNS = 'http://mastodon.social/schema/1.0' def unique_tag(date, id, type) "tag:#{Rails.configuration.x.local_domain},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}" diff --git a/app/models/account.rb b/app/models/account.rb index c2a41c4c638..ed5c461974a 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -95,6 +95,10 @@ class Account < ApplicationRecord follow_requests.where(target_account: other_account).exists? end + def followers_domains + followers.reorder('').select('DISTINCT accounts.domain').map(&:domain) + end + def local? domain.nil? end diff --git a/app/models/favourite.rb b/app/models/favourite.rb index 3f3616dce0e..cd8e2098c2f 100644 --- a/app/models/favourite.rb +++ b/app/models/favourite.rb @@ -12,11 +12,11 @@ class Favourite < ApplicationRecord validates :status_id, uniqueness: { scope: :account_id } def verb - :favorite + destroyed? ? :unfavorite : :favorite end def title - "#{account.acct} favourited a status by #{status.account.acct}" + destroyed? ? "#{account.acct} no longer favourites a status by #{status.account.acct}" : "#{account.acct} favourited a status by #{status.account.acct}" end delegate :object_type, to: :target diff --git a/app/models/follow_request.rb b/app/models/follow_request.rb index 936ad0691b9..989c2c2a23b 100644 --- a/app/models/follow_request.rb +++ b/app/models/follow_request.rb @@ -2,6 +2,7 @@ class FollowRequest < ApplicationRecord include Paginable + include Streamable belongs_to :account belongs_to :target_account, class_name: 'Account' @@ -12,12 +13,47 @@ class FollowRequest < ApplicationRecord validates :account_id, uniqueness: { scope: :target_account_id } def authorize! + @verb = :authorize + account.follow!(target_account) MergeWorker.perform_async(target_account.id, account.id) + destroy! end def reject! + @verb = :reject destroy! end + + def verb + destroyed? ? (@verb || :delete) : :request_friend + end + + def target + target_account + end + + def object_type + :person + end + + def hidden? + true + end + + def title + if destroyed? + case @verb + when :authorize + "#{target_account.acct} authorized #{account.acct}'s request to follow" + when :reject + "#{target_account.acct} rejected #{account.acct}'s request to follow" + else + "#{account.acct} withdrew the request to follow #{target_account.acct}" + end + else + "#{account.acct} requested to follow #{target_account.acct}" + end + end end diff --git a/app/models/status.rb b/app/models/status.rb index d2be7230844..93594ec8f48 100644 --- a/app/models/status.rb +++ b/app/models/status.rb @@ -76,7 +76,11 @@ class Status < ApplicationRecord end def permitted?(other_account = nil) - private_visibility? ? (account.id == other_account&.id || other_account&.following?(account)) : other_account.nil? || !account.blocking?(other_account) + if private_visibility? + (account.id == other_account&.id || other_account&.following?(account) || mentions.include?(other_account)) + else + other_account.nil? || !account.blocking?(other_account) + end end def ancestors(account = nil) @@ -153,6 +157,10 @@ class Status < ApplicationRecord where('1 = 1') elsif !account.nil? && target_account.blocking?(account) where('1 = 0') + elsif !account.nil? + joins('LEFT OUTER JOIN mentions ON statuses.id = mentions.status_id') + .where('mentions.account_id = ?', account.id) + .where('statuses.visibility != ? OR mentions.id IS NOT NULL', Status.visibilities[:private]) else where.not(visibility: :private) end diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb index fcc691befdd..e0b85be1589 100644 --- a/app/models/stream_entry.rb +++ b/app/models/stream_entry.rb @@ -30,7 +30,7 @@ class StreamEntry < ApplicationRecord end def targeted? - [:follow, :unfollow, :block, :unblock, :share, :favorite].include? verb + [:follow, :request_friend, :authorize, :unfollow, :block, :unblock, :share, :favorite].include? verb end def target diff --git a/app/services/authorize_follow_service.rb b/app/services/authorize_follow_service.rb new file mode 100644 index 00000000000..1590d843385 --- /dev/null +++ b/app/services/authorize_follow_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class AuthorizeFollowService < BaseService + include StreamEntryRenderer + + def call(source_account, target_account) + follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account) + follow_request.authorize! + NotificationWorker.perform_async(stream_entry_to_xml(follow_request.stream_entry), target_account.id, source_account.id) unless source_account.local? + end +end diff --git a/app/services/block_service.rb b/app/services/block_service.rb index e04b6cc392f..095d2a8eb6e 100644 --- a/app/services/block_service.rb +++ b/app/services/block_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class BlockService < BaseService + include StreamEntryRenderer + def call(account, target_account) return if account.id == target_account.id @@ -10,6 +12,6 @@ class BlockService < BaseService block = account.block!(target_account) BlockWorker.perform_async(account.id, target_account.id) - NotificationWorker.perform_async(block.stream_entry.id, target_account.id) unless target_account.local? + NotificationWorker.perform_async(stream_entry_to_xml(block.stream_entry), account.id, target_account.id) unless target_account.local? end end diff --git a/app/services/concerns/stream_entry_renderer.rb b/app/services/concerns/stream_entry_renderer.rb new file mode 100644 index 00000000000..a4255daead0 --- /dev/null +++ b/app/services/concerns/stream_entry_renderer.rb @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +module StreamEntryRenderer + def stream_entry_to_xml(stream_entry) + renderer = StreamEntriesController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https) + renderer.render(:show, assigns: { stream_entry: stream_entry }, formats: [:atom]) + end +end diff --git a/app/services/favourite_service.rb b/app/services/favourite_service.rb index d5fbd29e9c2..ce1722b7719 100644 --- a/app/services/favourite_service.rb +++ b/app/services/favourite_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class FavouriteService < BaseService + include StreamEntryRenderer + # Favourite a status and notify remote user # @param [Account] account # @param [Status] status @@ -15,7 +17,7 @@ class FavouriteService < BaseService if status.local? NotifyService.new.call(favourite.status.account, favourite) else - NotificationWorker.perform_async(favourite.stream_entry.id, status.account_id) + NotificationWorker.perform_async(stream_entry_to_xml(favourite.stream_entry), account.id, status.account_id) end favourite diff --git a/app/services/fetch_remote_account_service.rb b/app/services/fetch_remote_account_service.rb index 3c3694a6505..baefa3a8614 100644 --- a/app/services/fetch_remote_account_service.rb +++ b/app/services/fetch_remote_account_service.rb @@ -22,7 +22,9 @@ class FetchRemoteAccountService < BaseService Rails.logger.debug "Going to webfinger #{username}@#{domain}" - return FollowRemoteAccountService.new.call("#{username}@#{domain}") + account = FollowRemoteAccountService.new.call("#{username}@#{domain}") + UpdateRemoteProfileService.new.call(xml, account) unless account.nil? + account rescue TypeError Rails.logger.debug "Unparseable URL given: #{url}" nil diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb index 9f34cb6ac5e..ac0392d16b2 100644 --- a/app/services/follow_service.rb +++ b/app/services/follow_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class FollowService < BaseService + include StreamEntryRenderer + # Follow a remote user, notify remote user about the follow # @param [Account] source_account From which to follow # @param [String] uri User URI to follow in the form of username@domain @@ -20,10 +22,14 @@ class FollowService < BaseService private def request_follow(source_account, target_account) - return unless target_account.local? - follow_request = FollowRequest.create!(account: source_account, target_account: target_account) - NotifyService.new.call(target_account, follow_request) + + if target_account.local? + NotifyService.new.call(target_account, follow_request) + else + NotificationWorker.perform_async(stream_entry_to_xml(follow_request.stream_entry), source_account.id, target_account.id) + AfterRemoteFollowRequestWorker.perform_async(follow_request.id) + end follow_request end @@ -34,8 +40,9 @@ class FollowService < BaseService if target_account.local? NotifyService.new.call(target_account, follow) else - subscribe_service.call(target_account) - NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) + subscribe_service.call(target_account) unless target_account.subscribed? + NotificationWorker.perform_async(stream_entry_to_xml(follow.stream_entry), source_account.id, target_account.id) + AfterRemoteFollowWorker.perform_async(follow.id) end MergeWorker.perform_async(target_account.id, source_account.id) diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb index c411e3e82dd..f0a62aa14e8 100644 --- a/app/services/process_feed_service.rb +++ b/app/services/process_feed_service.rb @@ -106,7 +106,8 @@ class ProcessFeedService < BaseService text: content(entry), spoiler_text: content_warning(entry), created_at: published(entry), - reply: thread?(entry) + reply: thread?(entry), + visibility: visibility_scope(entry) ) if thread?(entry) @@ -144,15 +145,9 @@ class ProcessFeedService < BaseService def mentions_from_xml(parent, xml) processed_account_ids = [] - public_visibility = false xml.xpath('./xmlns:link[@rel="mentioned"]', xmlns: TagManager::XMLNS).each do |link| - if link['ostatus:object-type'] == TagManager::TYPES[:collection] && link['href'] == TagManager::COLLECTIONS[:public] - public_visibility = true - next - elsif link['ostatus:object-type'] == TagManager::TYPES[:group] - next - end + next if [TagManager::TYPES[:group], TagManager::TYPES[:collection]].include? link['ostatus:object-type'] url = Addressable::URI.parse(link['href']) @@ -172,9 +167,6 @@ class ProcessFeedService < BaseService # So we can skip duplicate mentions processed_account_ids << mentioned_account.id end - - parent.visibility = public_visibility ? :public : :unlisted - parent.save! end def hashtags_from_xml(parent, xml) @@ -230,6 +222,10 @@ class ProcessFeedService < BaseService xml.at_xpath('./xmlns:summary', xmlns: TagManager::XMLNS)&.content || '' end + def visibility_scope(xml = @xml) + xml.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content&.to_sym || :public + end + def published(xml = @xml) xml.at_xpath('./xmlns:published', xmlns: TagManager::XMLNS).content end diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb index 5f91e312718..8420ca35193 100644 --- a/app/services/process_interaction_service.rb +++ b/app/services/process_interaction_service.rb @@ -29,6 +29,12 @@ class ProcessInteractionService < BaseService case verb(xml) when :follow follow!(account, target_account) unless target_account.locked? || target_account.blocking?(account) + when :request_friend + follow_request!(account, target_account) unless !target_account.locked? || target_account.blocking?(account) + when :authorize + authorize_follow_request!(account, target_account) + when :reject + reject_follow_request!(account, target_account) when :unfollow unfollow!(account, target_account) when :favorite @@ -72,6 +78,22 @@ class ProcessInteractionService < BaseService NotifyService.new.call(target_account, follow) end + def follow_request!(account, target_account) + follow_request = FollowRequest.create!(account: account, target_account: target_account) + NotifyService.new.call(target_account, follow_request) + end + + def authorize_follow_request!(account, target_account) + follow_request = FollowRequest.find_by(account: target_account, target_account: account) + follow_request&.authorize! + SubscribeService.new.call(account) unless account.subscribed? + end + + def reject_follow_request!(account, target_account) + follow_request = FollowRequest.find_by(account: target_account, target_account: account) + follow_request&.reject! + end + def unfollow!(account, target_account) account.unfollow!(target_account) end diff --git a/app/services/process_mentions_service.rb b/app/services/process_mentions_service.rb index 72568e702da..d3d3af8af2e 100644 --- a/app/services/process_mentions_service.rb +++ b/app/services/process_mentions_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ProcessMentionsService < BaseService + include StreamEntryRenderer + # Scan status for mentions and fetch remote mentioned users, create # local mention pointers, send Salmon notifications to mentioned # remote users @@ -28,12 +30,10 @@ class ProcessMentionsService < BaseService status.mentions.each do |mention| mentioned_account = mention.account - next if status.private_visibility? && (!mentioned_account.following?(status.account) || !mentioned_account.local?) - if mentioned_account.local? NotifyService.new.call(mentioned_account, mention) else - NotificationWorker.perform_async(status.stream_entry.id, mentioned_account.id) + NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, mentioned_account.id) end end end diff --git a/app/services/reblog_service.rb b/app/services/reblog_service.rb index 4ea0dbf6c42..7a52f041fc5 100644 --- a/app/services/reblog_service.rb +++ b/app/services/reblog_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class ReblogService < BaseService + include StreamEntryRenderer + # Reblog a status and notify its remote author # @param [Account] account Account to reblog from # @param [Status] reblogged_status Status to be reblogged @@ -18,15 +20,9 @@ class ReblogService < BaseService if reblogged_status.local? NotifyService.new.call(reblog.reblog.account, reblog) else - NotificationWorker.perform_async(reblog.stream_entry.id, reblog.reblog.account_id) + NotificationWorker.perform_async(stream_entry_to_xml(reblog.stream_entry), account.id, reblog.reblog.account_id) end reblog end - - private - - def send_interaction_service - @send_interaction_service ||= SendInteractionService.new - end end diff --git a/app/services/reject_follow_service.rb b/app/services/reject_follow_service.rb new file mode 100644 index 00000000000..0c568b981f2 --- /dev/null +++ b/app/services/reject_follow_service.rb @@ -0,0 +1,11 @@ +# frozen_string_literal: true + +class RejectFollowService < BaseService + include StreamEntryRenderer + + def call(source_account, target_account) + follow_request = FollowRequest.find_by!(account: source_account, target_account: target_account) + follow_request.reject! + NotificationWorker.perform_async(stream_entry_to_xml(follow_request.stream_entry), target_account.id, source_account.id) unless source_account.local? + end +end diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb index 48e8dd3b8f2..b1a646b1453 100644 --- a/app/services/remove_status_service.rb +++ b/app/services/remove_status_service.rb @@ -1,6 +1,8 @@ # frozen_string_literal: true class RemoveStatusService < BaseService + include StreamEntryRenderer + def call(status) remove_from_self(status) if status.account.local? remove_from_followers(status) @@ -43,7 +45,7 @@ class RemoveStatusService < BaseService def send_delete_salmon(account, status) return unless status.local? - NotificationWorker.perform_async(status.stream_entry.id, account.id) + NotificationWorker.perform_async(stream_entry_to_xml(status.stream_entry), status.account_id, account.id) end def remove_reblogs(status) diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb index 05a1e77e39d..99113eecaf2 100644 --- a/app/services/send_interaction_service.rb +++ b/app/services/send_interaction_service.rb @@ -2,27 +2,16 @@ class SendInteractionService < BaseService # Send an Atom representation of an interaction to a remote Salmon endpoint - # @param [StreamEntry] stream_entry + # @param [String] Entry XML + # @param [Account] source_account # @param [Account] target_account - def call(stream_entry, target_account) - envelope = salmon.pack(entry_xml(stream_entry), stream_entry.account.keypair) + def call(xml, source_account, target_account) + envelope = salmon.pack(xml, source_account.keypair) salmon.post(target_account.salmon_url, envelope) end private - def entry_xml(stream_entry) - Nokogiri::XML::Builder.new do |xml| - entry(xml, true) do - author(xml) do - include_author xml, stream_entry.account - end - - include_entry xml, stream_entry - end - end.to_xml - end - def salmon @salmon ||= OStatus2::Salmon.new end diff --git a/app/services/unblock_service.rb b/app/services/unblock_service.rb index f389364f910..84b1050c153 100644 --- a/app/services/unblock_service.rb +++ b/app/services/unblock_service.rb @@ -1,10 +1,12 @@ # frozen_string_literal: true class UnblockService < BaseService + include StreamEntryRenderer + def call(account, target_account) return unless account.blocking?(target_account) unblock = account.unblock!(target_account) - NotificationWorker.perform_async(unblock.stream_entry.id, target_account.id) unless target_account.local? + NotificationWorker.perform_async(stream_entry_to_xml(unblock.stream_entry), account.id, target_account.id) unless target_account.local? end end diff --git a/app/services/unfavourite_service.rb b/app/services/unfavourite_service.rb index de6e84e7d46..04293ee08b9 100644 --- a/app/services/unfavourite_service.rb +++ b/app/services/unfavourite_service.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true class UnfavouriteService < BaseService + include StreamEntryRenderer + def call(account, status) favourite = Favourite.find_by!(account: account, status: status) favourite.destroy! unless status.local? - NotificationWorker.perform_async(favourite.stream_entry.id, status.account_id) + NotificationWorker.perform_async(stream_entry_to_xml(favourite.stream_entry), account.id, status.account_id) end favourite diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb index f469793c1af..178da4da313 100644 --- a/app/services/unfollow_service.rb +++ b/app/services/unfollow_service.rb @@ -1,12 +1,14 @@ # frozen_string_literal: true class UnfollowService < BaseService + include StreamEntryRenderer + # Unfollow and notify the remote user # @param [Account] source_account Where to unfollow from # @param [Account] target_account Which to unfollow def call(source_account, target_account) follow = source_account.unfollow!(target_account) - NotificationWorker.perform_async(follow.stream_entry.id, target_account.id) unless target_account.local? + NotificationWorker.perform_async(stream_entry_to_xml(follow.stream_entry), source_account.id, target_account.id) unless target_account.local? UnmergeWorker.perform_async(target_account.id, source_account.id) end end diff --git a/app/services/update_remote_profile_service.rb b/app/services/update_remote_profile_service.rb index ad9c56540da..dc315db197b 100644 --- a/app/services/update_remote_profile_service.rb +++ b/app/services/update_remote_profile_service.rb @@ -10,6 +10,7 @@ class UpdateRemoteProfileService < BaseService unless author_xml.nil? account.display_name = author_xml.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:displayName', poco: TagManager::POCO_XMLNS).nil? account.note = author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).content unless author_xml.at_xpath('./poco:note', poco: TagManager::POCO_XMLNS).nil? + account.locked = author_xml.at_xpath('./mastodon:scope', mastodon: TagManager::MTDN_XMLNS)&.content == 'private' unless account.suspended? || DomainBlock.find_by(domain: account.domain)&.reject_media? account.avatar_remote_url = author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'] unless author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS).nil? || author_xml.at_xpath('./xmlns:link[@rel="avatar"]', xmlns: TagManager::XMLNS)['href'].blank? diff --git a/app/views/about/index.html.haml b/app/views/about/index.html.haml index 88bfe3d610c..022a3a9e48e 100644 --- a/app/views/about/index.html.haml +++ b/app/views/about/index.html.haml @@ -32,6 +32,7 @@ = link_to t('about.learn_more'), about_more_path = link_to t('about.terms'), terms_path = link_to t('about.source_code'), 'https://github.com/tootsuite/mastodon' + = link_to t('about.other_instances'), 'https://github.com/tootsuite/mastodon/blob/master/docs/Using-Mastodon/List-of-Mastodon-instances.md' = link_to t('about.get_started'), new_user_registration_path, class: 'button webapp-btn' = link_to t('auth.login'), new_user_session_path, class: 'button webapp-btn' diff --git a/app/views/admin/settings/index.html.haml b/app/views/admin/settings/index.html.haml index 5b482213bac..1429dbd9ee2 100644 --- a/app/views/admin/settings/index.html.haml +++ b/app/views/admin/settings/index.html.haml @@ -15,6 +15,10 @@ %td= best_in_place @settings['site_contact_username'], :value, url: admin_setting_path(@settings['site_contact_username']), place_holder: 'Enter a username' %tr %td= best_in_place @settings['site_contact_email'], :value, url: admin_setting_path(@settings['site_contact_email']), place_holder: 'Enter a public e-mail address' + %tr + %td + %strong Site title + %td= best_in_place @settings['site_title'], :value, url: admin_setting_path(@settings['site_title']) %tr %td %strong Site description @@ -33,4 +37,4 @@ Displayed on extended information page %br/ You can use HTML tags - %td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description']) \ No newline at end of file + %td= best_in_place @settings['site_extended_description'], :value, as: :textarea, url: admin_setting_path(@settings['site_extended_description']) diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml index e122e1c55fe..7eae6982bd0 100644 --- a/app/views/layouts/application.html.haml +++ b/app/views/layouts/application.html.haml @@ -13,7 +13,7 @@ %title = "#{yield(:page_title)} - " if content_for?(:page_title) - Mastodon + = Setting.site_title = stylesheet_link_tag 'application', media: 'all' = csrf_meta_tags diff --git a/app/views/tags/show.html.haml b/app/views/tags/show.html.haml index 412ec4fa5da..32a50e15862 100644 --- a/app/views/tags/show.html.haml +++ b/app/views/tags/show.html.haml @@ -1,10 +1,18 @@ +- content_for :page_title do + = "##{@tag.name}" + +.compact-header + %h1< + = link_to 'Mastodon', root_path + %small= "##{@tag.name}" + - if @statuses.empty? .accounts-grid = render partial: 'accounts/nothing_here' - else .activity-stream.h-feed - = render partial: 'stream_entries/status', collection: @statuses, as: :status, cached: true + = render partial: 'stream_entries/status', collection: @statuses, as: :status -.pagination - - if @statuses.size == 20 +- if @statuses.size == 20 + .pagination = link_to safe_join([t('pagination.next'), fa_icon('chevron-right')], ' '), tag_url(@tag, max_id: @statuses.last.id), class: 'next_page', rel: 'next' diff --git a/app/workers/after_remote_follow_request_worker.rb b/app/workers/after_remote_follow_request_worker.rb new file mode 100644 index 00000000000..ad94d276940 --- /dev/null +++ b/app/workers/after_remote_follow_request_worker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AfterRemoteFollowRequestWorker + include Sidekiq::Worker + + sidekiq_options retry: 5 + + def perform(follow_request_id) + follow_request = FollowRequest.find(follow_request_id) + updated_account = FetchRemoteAccountService.new.call(follow_request.target_account.remote_url) + + return if updated_account.locked? + + follow_request.destroy + FollowService.new.call(follow_request.account, updated_account.acct) + end +end diff --git a/app/workers/after_remote_follow_worker.rb b/app/workers/after_remote_follow_worker.rb new file mode 100644 index 00000000000..496aaf73eb9 --- /dev/null +++ b/app/workers/after_remote_follow_worker.rb @@ -0,0 +1,17 @@ +# frozen_string_literal: true + +class AfterRemoteFollowWorker + include Sidekiq::Worker + + sidekiq_options retry: 5 + + def perform(follow_id) + follow = Follow.find(follow_id) + updated_account = FetchRemoteAccountService.new.call(follow.target_account.remote_url) + + return unless updated_account.locked? + + follow.destroy + FollowService.new.call(follow.account, updated_account.acct) + end +end diff --git a/app/workers/notification_worker.rb b/app/workers/notification_worker.rb index e4c38d3844b..1a2faefd8e0 100644 --- a/app/workers/notification_worker.rb +++ b/app/workers/notification_worker.rb @@ -5,7 +5,7 @@ class NotificationWorker sidekiq_options retry: 5 - def perform(stream_entry_id, target_account_id) - SendInteractionService.new.call(StreamEntry.find(stream_entry_id), Account.find(target_account_id)) + def perform(xml, source_account_id, target_account_id) + SendInteractionService.new.call(xml, Account.find(source_account_id), Account.find(target_account_id)) end end diff --git a/app/workers/pubsubhubbub/distribution_worker.rb b/app/workers/pubsubhubbub/distribution_worker.rb index d5437bf6b51..4576dc4a249 100644 --- a/app/workers/pubsubhubbub/distribution_worker.rb +++ b/app/workers/pubsubhubbub/distribution_worker.rb @@ -8,13 +8,18 @@ class Pubsubhubbub::DistributionWorker def perform(stream_entry_id) stream_entry = StreamEntry.find(stream_entry_id) - return if stream_entry.hidden? + # Most hidden stream entries should not be PuSHed, + # but statuses need to be distributed to trusted + # followers even when they are hidden + return if stream_entry.hidden? && stream_entry.activity_type != 'Status' account = stream_entry.account renderer = AccountsController.renderer.new(method: 'get', http_host: Rails.configuration.x.local_domain, https: Rails.configuration.x.use_https) payload = renderer.render(:show, assigns: { account: account, entries: [stream_entry] }, formats: [:atom]) + domains = account.followers_domains - Subscription.where(account: account).active.select('id').find_each do |subscription| + Subscription.where(account: account).active.select('id, callback_url').find_each do |subscription| + next unless domains.include?(Addressable::URI.parse(subscription.callback_url).host) Pubsubhubbub::DeliveryWorker.perform_async(subscription.id, payload) end rescue ActiveRecord::RecordNotFound diff --git a/app/workers/push_notification_worker.rb b/app/workers/push_notification_worker.rb deleted file mode 100644 index a61d0e349ce..00000000000 --- a/app/workers/push_notification_worker.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -class PushNotificationWorker - include Sidekiq::Worker - - def perform(notification_id) - SendPushNotificationService.new.call(Notification.find(notification_id)) - rescue ActiveRecord::RecordNotFound - true - end -end diff --git a/config/locales/en.yml b/config/locales/en.yml index b1b1e7995dc..c6c7c236e7e 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -10,6 +10,7 @@ en: get_started: Get started learn_more: Learn more links: Links + other_instances: Other instances source_code: Source code status_count_after: statuses status_count_before: Who authored diff --git a/config/settings.yml b/config/settings.yml index a78bd067d53..71ce12e632e 100644 --- a/config/settings.yml +++ b/config/settings.yml @@ -1,5 +1,6 @@ # config/app.yml for rails-settings-cached defaults: &defaults + site_title: 'Mastodon' site_description: '' site_extended_description: '' site_contact_username: '' diff --git a/docs/Using-Mastodon/List-of-Mastodon-instances.md b/docs/Using-Mastodon/List-of-Mastodon-instances.md index 2f15df083d9..c4e22876902 100644 --- a/docs/Using-Mastodon/List-of-Mastodon-instances.md +++ b/docs/Using-Mastodon/List-of-Mastodon-instances.md @@ -1,12 +1,18 @@ -List of Mastodon instances +List of Known Mastodon instances ========================== -* [mastodon.social](https://mastodon.social) -* [social.tchncs.de](https://social.tchncs.de) -* [on.vu](https://on.vu) -* [animalliberation.social](https://animalliberation.social) -* [socially.constructed.space](https://socially.constructed.space) -* [epiktistes.com](https://epiktistes.com) -* [toot.zone](https://toot.zone) +| Name | Theme/Notes, if applicable | Open Registrations | +| -------------|-------------|---| +| [mastodon.social](https://mastodon.social) |Flagship, quick updates|Yes| +| [awoo.space](https://awoo.space) |Intentionally moderated, only federates with mastodon.social|Yes| +| [social.tchncs.de](https://social.tchncs.de)|N/A|Yes| +| [animalliberation.social](https://animalliberation.social) |Animal Rights|Yes| +| [socially.constructed.space](https://socially.constructed.space) |Single user|No| +| [epiktistes.com](https://epiktistes.com) |N/A|Yes| +| [toot.zone](https://toot.zone) |N/A|Yes| +| [on.vu](https://on.vu) | Appears defunct|No| +| [gay.crime.team](https://gay.crime.team) |N/A|Yes(?)| +| [gnusocial.me](https://gnusocial.me) |Yes, it's a mastodon instance now|Yes| -Let me know if you start running one so I can add it to the list! + +Let me know if you start running one so I can add it to the list! (Alternatively, add it yourself as a pull request). diff --git a/spec/controllers/api/v1/follows_controller_spec.rb b/spec/controllers/api/v1/follows_controller_spec.rb index 97d69ab7bff..cc4958ab598 100644 --- a/spec/controllers/api/v1/follows_controller_spec.rb +++ b/spec/controllers/api/v1/follows_controller_spec.rb @@ -14,6 +14,7 @@ RSpec.describe Api::V1::FollowsController, type: :controller do before do stub_request(:get, "https://quitter.no/.well-known/host-meta").to_return(request_fixture('.host-meta.txt')) stub_request(:get, "https://quitter.no/.well-known/webfinger?resource=acct:gargron@quitter.no").to_return(request_fixture('webfinger.txt')) + stub_request(:head, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(:status => 405, :body => "", :headers => {}) stub_request(:get, "https://quitter.no/api/statuses/user_timeline/7477.atom").to_return(request_fixture('feed.txt')) stub_request(:get, "https://quitter.no/avatar/7477-300-20160211190340.png").to_return(request_fixture('avatar.txt')) stub_request(:post, "https://quitter.no/main/push/hub").to_return(:status => 200, :body => "", :headers => {}) diff --git a/spec/helpers/atom_builder_helper_spec.rb b/spec/helpers/atom_builder_helper_spec.rb index 3d3bd56a19c..0aca58ee7eb 100644 --- a/spec/helpers/atom_builder_helper_spec.rb +++ b/spec/helpers/atom_builder_helper_spec.rb @@ -13,7 +13,7 @@ RSpec.describe AtomBuilderHelper, type: :helper do describe '#feed' do it 'creates a feed' do - expect(used_in_builder { |xml| helper.feed(xml) }).to match '' + expect(used_in_builder { |xml| helper.feed(xml) }).to match '' end end