forked from treehouse/mastodon
Mute button progress so far. WIP, doesn't entirely work correctly.
parent
89fc2d7f48
commit
442fdbfc53
|
@ -21,6 +21,14 @@ export const ACCOUNT_UNBLOCK_REQUEST = 'ACCOUNT_UNBLOCK_REQUEST';
|
||||||
export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
|
export const ACCOUNT_UNBLOCK_SUCCESS = 'ACCOUNT_UNBLOCK_SUCCESS';
|
||||||
export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL';
|
export const ACCOUNT_UNBLOCK_FAIL = 'ACCOUNT_UNBLOCK_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_MUTE_REQUEST = 'ACCOUNT_MUTE_REQUEST';
|
||||||
|
export const ACCOUNT_MUTE_SUCCESS = 'ACCOUNT_MUTE_SUCCESS';
|
||||||
|
export const ACCOUNT_MUTE_FAIL = 'ACCOUNT_MUTE_FAIL';
|
||||||
|
|
||||||
|
export const ACCOUNT_UNMUTE_REQUEST = 'ACCOUNT_UNMUTE_REQUEST';
|
||||||
|
export const ACCOUNT_UNMUTE_SUCCESS = 'ACCOUNT_UNMUTE_SUCCESS';
|
||||||
|
export const ACCOUNT_UNMUTE_FAIL = 'ACCOUNT_UNMUTE_FAIL';
|
||||||
|
|
||||||
export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
|
export const ACCOUNT_TIMELINE_FETCH_REQUEST = 'ACCOUNT_TIMELINE_FETCH_REQUEST';
|
||||||
export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
|
export const ACCOUNT_TIMELINE_FETCH_SUCCESS = 'ACCOUNT_TIMELINE_FETCH_SUCCESS';
|
||||||
export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL';
|
export const ACCOUNT_TIMELINE_FETCH_FAIL = 'ACCOUNT_TIMELINE_FETCH_FAIL';
|
||||||
|
@ -328,6 +336,76 @@ export function unblockAccountFail(error) {
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
export function muteAccount(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(muteAccountRequest(id));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/accounts/${id}/mute`).then(response => {
|
||||||
|
// Pass in entire statuses map so we can use it to filter stuff in different parts of the reducers
|
||||||
|
dispatch(muteAccountSuccess(response.data, getState().get('statuses')));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(muteAccountFail(id, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unmuteAccount(id) {
|
||||||
|
return (dispatch, getState) => {
|
||||||
|
dispatch(unmuteAccountRequest(id));
|
||||||
|
|
||||||
|
api(getState).post(`/api/v1/accounts/${id}/unmute`).then(response => {
|
||||||
|
dispatch(unmuteAccountSuccess(response.data));
|
||||||
|
}).catch(error => {
|
||||||
|
dispatch(unmuteAccountFail(id, error));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function muteAccountRequest(id) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MUTE_REQUEST,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function muteAccountSuccess(relationship, statuses) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MUTE_SUCCESS,
|
||||||
|
relationship,
|
||||||
|
statuses
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function muteAccountFail(error) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_MUTE_FAIL,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unmuteAccountRequest(id) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_UNMUTE_REQUEST,
|
||||||
|
id
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unmuteAccountSuccess(relationship) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_UNMUTE_SUCCESS,
|
||||||
|
relationship
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export function unmuteAccountFail(error) {
|
||||||
|
return {
|
||||||
|
type: ACCOUNT_UNMUTE_FAIL,
|
||||||
|
error
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
export function fetchFollowers(id) {
|
export function fetchFollowers(id) {
|
||||||
return (dispatch, getState) => {
|
return (dispatch, getState) => {
|
||||||
dispatch(fetchFollowersRequest(id));
|
dispatch(fetchFollowersRequest(id));
|
||||||
|
|
|
@ -5,7 +5,9 @@ import {
|
||||||
followAccount,
|
followAccount,
|
||||||
unfollowAccount,
|
unfollowAccount,
|
||||||
blockAccount,
|
blockAccount,
|
||||||
unblockAccount
|
unblockAccount,
|
||||||
|
muteAccount,
|
||||||
|
unmuteAccount,
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
|
|
||||||
const makeMapStateToProps = () => {
|
const makeMapStateToProps = () => {
|
||||||
|
@ -34,6 +36,14 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
} else {
|
} else {
|
||||||
dispatch(blockAccount(account.get('id')));
|
dispatch(blockAccount(account.get('id')));
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
onMute (account) {
|
||||||
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
|
dispatch(unmuteAccount(account.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(muteAccount(account.get('id')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,10 @@ import {
|
||||||
unreblog,
|
unreblog,
|
||||||
unfavourite
|
unfavourite
|
||||||
} from '../actions/interactions';
|
} from '../actions/interactions';
|
||||||
import { blockAccount } from '../actions/accounts';
|
import {
|
||||||
|
blockAccount,
|
||||||
|
muteAccount
|
||||||
|
} from '../actions/accounts';
|
||||||
import { deleteStatus } from '../actions/statuses';
|
import { deleteStatus } from '../actions/statuses';
|
||||||
import { initReport } from '../actions/reports';
|
import { initReport } from '../actions/reports';
|
||||||
import { openMedia } from '../actions/modal';
|
import { openMedia } from '../actions/modal';
|
||||||
|
@ -69,7 +72,11 @@ const mapDispatchToProps = (dispatch) => ({
|
||||||
|
|
||||||
onReport (status) {
|
onReport (status) {
|
||||||
dispatch(initReport(status.get('account'), status));
|
dispatch(initReport(status.get('account'), status));
|
||||||
}
|
},
|
||||||
|
|
||||||
|
onMute (account) {
|
||||||
|
dispatch(muteAccount(account.get('id')));
|
||||||
|
},
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,9 @@ const messages = defineMessages({
|
||||||
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
edit_profile: { id: 'account.edit_profile', defaultMessage: 'Edit profile' },
|
||||||
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
unblock: { id: 'account.unblock', defaultMessage: 'Unblock @{name}' },
|
||||||
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
unfollow: { id: 'account.unfollow', defaultMessage: 'Unfollow' },
|
||||||
|
unmute: { id: 'account.unmute', defaultMessage: 'Unmute' },
|
||||||
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
block: { id: 'account.block', defaultMessage: 'Block @{name}' },
|
||||||
|
mute: { id: 'account.mute', defaultMessage: 'Mute' },
|
||||||
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
follow: { id: 'account.follow', defaultMessage: 'Follow' },
|
||||||
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
report: { id: 'account.report', defaultMessage: 'Report @{name}' },
|
||||||
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
|
disclaimer: { id: 'account.disclaimer', defaultMessage: 'This user is from another instance. This number may be larger.' }
|
||||||
|
@ -35,6 +37,7 @@ const ActionBar = React.createClass({
|
||||||
onBlock: React.PropTypes.func.isRequired,
|
onBlock: React.PropTypes.func.isRequired,
|
||||||
onMention: React.PropTypes.func.isRequired,
|
onMention: React.PropTypes.func.isRequired,
|
||||||
onReport: React.PropTypes.func.isRequired,
|
onReport: React.PropTypes.func.isRequired,
|
||||||
|
onMute: React.PropTypes.func.isRequired,
|
||||||
intl: React.PropTypes.object.isRequired
|
intl: React.PropTypes.object.isRequired
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -67,6 +70,12 @@ const ActionBar = React.createClass({
|
||||||
extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
|
extraInfo = <abbr title={intl.formatMessage(messages.disclaimer)}>*</abbr>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.unmute), action: this.props.onMute });
|
||||||
|
} else {
|
||||||
|
menu.push({ text: intl.formatMessage(messages.mute), action: this.props.onMute });
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='account__action-bar'>
|
<div className='account__action-bar'>
|
||||||
<div style={outerDropdownStyle}>
|
<div style={outerDropdownStyle}>
|
||||||
|
|
|
@ -16,6 +16,7 @@ const Header = React.createClass({
|
||||||
onBlock: React.PropTypes.func.isRequired,
|
onBlock: React.PropTypes.func.isRequired,
|
||||||
onMention: React.PropTypes.func.isRequired,
|
onMention: React.PropTypes.func.isRequired,
|
||||||
onReport: React.PropTypes.func.isRequired
|
onReport: React.PropTypes.func.isRequired
|
||||||
|
onMute: React.PropTypes.func.isRequired,
|
||||||
},
|
},
|
||||||
|
|
||||||
mixins: [PureRenderMixin],
|
mixins: [PureRenderMixin],
|
||||||
|
@ -37,6 +38,10 @@ const Header = React.createClass({
|
||||||
this.context.router.push('/report');
|
this.context.router.push('/report');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
handleMute() {
|
||||||
|
this.props.onMute(this.props.account);
|
||||||
|
},
|
||||||
|
|
||||||
render () {
|
render () {
|
||||||
const { account, me } = this.props;
|
const { account, me } = this.props;
|
||||||
|
|
||||||
|
@ -58,6 +63,7 @@ const Header = React.createClass({
|
||||||
onBlock={this.handleBlock}
|
onBlock={this.handleBlock}
|
||||||
onMention={this.handleMention}
|
onMention={this.handleMention}
|
||||||
onReport={this.handleReport}
|
onReport={this.handleReport}
|
||||||
|
onMute={this.handleMute}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -5,7 +5,9 @@ import {
|
||||||
followAccount,
|
followAccount,
|
||||||
unfollowAccount,
|
unfollowAccount,
|
||||||
blockAccount,
|
blockAccount,
|
||||||
unblockAccount
|
unblockAccount,
|
||||||
|
muteAccount,
|
||||||
|
unmuteAccount
|
||||||
} from '../../../actions/accounts';
|
} from '../../../actions/accounts';
|
||||||
import { mentionCompose } from '../../../actions/compose';
|
import { mentionCompose } from '../../../actions/compose';
|
||||||
import { initReport } from '../../../actions/reports';
|
import { initReport } from '../../../actions/reports';
|
||||||
|
@ -44,6 +46,14 @@ const mapDispatchToProps = dispatch => ({
|
||||||
|
|
||||||
onReport (account) {
|
onReport (account) {
|
||||||
dispatch(initReport(account));
|
dispatch(initReport(account));
|
||||||
|
},
|
||||||
|
|
||||||
|
onMute (account) {
|
||||||
|
if (account.getIn(['relationship', 'muting'])) {
|
||||||
|
dispatch(unmuteAccount(account.get('id')));
|
||||||
|
} else {
|
||||||
|
dispatch(muteAccount(account.get('id')));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ import {
|
||||||
ACCOUNT_UNFOLLOW_SUCCESS,
|
ACCOUNT_UNFOLLOW_SUCCESS,
|
||||||
ACCOUNT_BLOCK_SUCCESS,
|
ACCOUNT_BLOCK_SUCCESS,
|
||||||
ACCOUNT_UNBLOCK_SUCCESS,
|
ACCOUNT_UNBLOCK_SUCCESS,
|
||||||
|
ACCOUNT_MUTE_SUCCESS,
|
||||||
|
ACCOUNT_UNMUTE_SUCCESS,
|
||||||
RELATIONSHIPS_FETCH_SUCCESS
|
RELATIONSHIPS_FETCH_SUCCESS
|
||||||
} from '../actions/accounts';
|
} from '../actions/accounts';
|
||||||
import Immutable from 'immutable';
|
import Immutable from 'immutable';
|
||||||
|
@ -25,6 +27,8 @@ export default function relationships(state = initialState, action) {
|
||||||
case ACCOUNT_UNFOLLOW_SUCCESS:
|
case ACCOUNT_UNFOLLOW_SUCCESS:
|
||||||
case ACCOUNT_BLOCK_SUCCESS:
|
case ACCOUNT_BLOCK_SUCCESS:
|
||||||
case ACCOUNT_UNBLOCK_SUCCESS:
|
case ACCOUNT_UNBLOCK_SUCCESS:
|
||||||
|
case ACCOUNT_MUTE_SUCCESS:
|
||||||
|
case ACCOUNT_UNMUTE_SUCCESS:
|
||||||
return normalizeRelationship(state, action.relationship);
|
return normalizeRelationship(state, action.relationship);
|
||||||
case RELATIONSHIPS_FETCH_SUCCESS:
|
case RELATIONSHIPS_FETCH_SUCCESS:
|
||||||
return normalizeRelationships(state, action.relationships);
|
return normalizeRelationships(state, action.relationships);
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::AccountsController < ApiController
|
class Api::V1::AccountsController < ApiController
|
||||||
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock]
|
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||||
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock]
|
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock, :mute, :unmute]
|
||||||
before_action :require_user!, except: [:show, :following, :followers, :statuses]
|
before_action :require_user!, except: [:show, :following, :followers, :statuses]
|
||||||
before_action :set_account, except: [:verify_credentials, :suggestions, :search]
|
before_action :set_account, except: [:verify_credentials, :suggestions, :search]
|
||||||
|
|
||||||
|
@ -86,10 +86,17 @@ class Api::V1::AccountsController < ApiController
|
||||||
@followed_by = { @account.id => false }
|
@followed_by = { @account.id => false }
|
||||||
@blocking = { @account.id => true }
|
@blocking = { @account.id => true }
|
||||||
@requested = { @account.id => false }
|
@requested = { @account.id => false }
|
||||||
|
@muting = { @account.id => current_user.account.muting?(@account.id) }
|
||||||
|
|
||||||
render action: :relationship
|
render action: :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mute
|
||||||
|
MuteService.new.call(current_user.account, @account)
|
||||||
|
set_relationship
|
||||||
|
render action: :relationship
|
||||||
|
end
|
||||||
|
|
||||||
def unfollow
|
def unfollow
|
||||||
UnfollowService.new.call(current_user.account, @account)
|
UnfollowService.new.call(current_user.account, @account)
|
||||||
set_relationship
|
set_relationship
|
||||||
|
@ -102,6 +109,12 @@ class Api::V1::AccountsController < ApiController
|
||||||
render action: :relationship
|
render action: :relationship
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unmute
|
||||||
|
UnmuteService.new.call(current_user.account, @account)
|
||||||
|
set_relationship
|
||||||
|
render action: :relationship
|
||||||
|
end
|
||||||
|
|
||||||
def relationships
|
def relationships
|
||||||
ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
|
ids = params[:id].is_a?(Enumerable) ? params[:id].map(&:to_i) : [params[:id].to_i]
|
||||||
|
|
||||||
|
@ -109,6 +122,7 @@ class Api::V1::AccountsController < ApiController
|
||||||
@following = Account.following_map(ids, current_user.account_id)
|
@following = Account.following_map(ids, current_user.account_id)
|
||||||
@followed_by = Account.followed_by_map(ids, current_user.account_id)
|
@followed_by = Account.followed_by_map(ids, current_user.account_id)
|
||||||
@blocking = Account.blocking_map(ids, current_user.account_id)
|
@blocking = Account.blocking_map(ids, current_user.account_id)
|
||||||
|
@muting = Account.muting_map(ids, current_user.account_id)
|
||||||
@requested = Account.requested_map(ids, current_user.account_id)
|
@requested = Account.requested_map(ids, current_user.account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -130,6 +144,7 @@ class Api::V1::AccountsController < ApiController
|
||||||
@following = Account.following_map([@account.id], current_user.account_id)
|
@following = Account.following_map([@account.id], current_user.account_id)
|
||||||
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
|
@followed_by = Account.followed_by_map([@account.id], current_user.account_id)
|
||||||
@blocking = Account.blocking_map([@account.id], current_user.account_id)
|
@blocking = Account.blocking_map([@account.id], current_user.account_id)
|
||||||
|
@muting = Account.muting_map([@account.id], current_user.account_id)
|
||||||
@requested = Account.requested_map([@account.id], current_user.account_id)
|
@requested = Account.requested_map([@account.id], current_user.account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Api::V1::MutesController < ApiController
|
||||||
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
|
respond_to :json
|
||||||
|
|
||||||
|
def index
|
||||||
|
results = Mute.where(account: current_account).paginate_by_max_id(limit_param(DEFAULT_ACCOUNTS_LIMIT), params[:max_id], params[:since_id])
|
||||||
|
accounts = Account.where(id: results.map(&:target_account_id)).map { |a| [a.id, a] }.to_h
|
||||||
|
@accounts = results.map { |f| accounts[f.target_account_id] }
|
||||||
|
|
||||||
|
set_account_counters_maps(@accounts)
|
||||||
|
|
||||||
|
next_path = api_v1_mutes_url(max_id: results.last.id) if results.size == limit_param(DEFAULT_ACCOUNTS_LIMIT)
|
||||||
|
prev_path = api_v1_mutes_url(since_id: results.first.id) unless results.empty?
|
||||||
|
|
||||||
|
set_pagination_headers(next_path, prev_path)
|
||||||
|
end
|
||||||
|
end
|
|
@ -22,18 +22,8 @@ class FeedManager
|
||||||
end
|
end
|
||||||
|
|
||||||
def push(timeline_type, account, status)
|
def push(timeline_type, account, status)
|
||||||
timeline_key = key(timeline_type, account.id)
|
redis.zadd(key(timeline_type, account.id), status.id, status.reblog? ? status.reblog_of_id : status.id)
|
||||||
|
trim(timeline_type, account.id)
|
||||||
if status.reblog?
|
|
||||||
# If the original status is within 40 statuses from top, do not re-insert it into the feed
|
|
||||||
rank = redis.zrevrank(timeline_key, status.reblog_of_id)
|
|
||||||
return if !rank.nil? && rank < 40
|
|
||||||
redis.zadd(timeline_key, status.id, status.reblog_of_id)
|
|
||||||
else
|
|
||||||
redis.zadd(timeline_key, status.id, status.id)
|
|
||||||
trim(timeline_type, account.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status))
|
broadcast(account.id, event: 'update', payload: inline_render(account, 'api/v1/statuses/show', status))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -95,31 +85,47 @@ class FeedManager
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_from_home?(status, receiver)
|
def filter_from_home?(status, receiver)
|
||||||
should_filter = false
|
should_filter = receiver.muting?(status.account_id) # Filter if I'm muting this person
|
||||||
|
|
||||||
if status.reply? && status.in_reply_to_id.nil?
|
if status.reply? && status.in_reply_to_id.nil? # Filter out replies to nobody
|
||||||
should_filter = true
|
should_filter = true
|
||||||
elsif status.reply? && !status.in_reply_to_account_id.nil? # Filter out if it's a reply
|
elsif status.reply? && !status.in_reply_to_account_id.nil? # If it's a reply
|
||||||
should_filter = !receiver.following?(status.in_reply_to_account) # and I'm not following the person it's a reply to
|
should_filter = !receiver.following?(status.in_reply_to_account) # filter if I'm not following the person it's a reply to
|
||||||
should_filter &&= !(receiver.id == status.in_reply_to_account_id) # and it's not a reply to me
|
should_filter &&= !(receiver.id == status.in_reply_to_account_id) # and it's not a reply to me
|
||||||
should_filter &&= !(status.account_id == status.in_reply_to_account_id) # and it's not a self-reply
|
should_filter &&= !(status.account_id == status.in_reply_to_account_id) # and it's not a self-reply
|
||||||
elsif status.reblog? # Filter out a reblog
|
elsif status.reblog? # If it's a reblog
|
||||||
should_filter = receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person
|
should_filter = receiver.blocking?(status.reblog.account) # filter if I'm blocking the reblogged person
|
||||||
|
should_filter ||= receiver.muting?(status.reblog.account) # or if I'm muting the reblogged person
|
||||||
end
|
end
|
||||||
|
|
||||||
should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # or if it mentions someone I blocked
|
should_filter ||= receiver.blocking?(status.mentions.map(&:account_id)) # Filter if it mentions someone I blocked
|
||||||
|
|
||||||
should_filter
|
should_filter
|
||||||
end
|
end
|
||||||
|
|
||||||
def filter_from_mentions?(status, receiver)
|
def filter_from_mentions?(status, receiver)
|
||||||
should_filter = receiver.id == status.account_id # Filter if I'm mentioning myself
|
should_filter = receiver.id == status.account_id # Filter out if I'm mentioning myself
|
||||||
should_filter ||= receiver.blocking?(status.account) # or it's from someone I blocked
|
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 ||= 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.account.silenced? && !receiver.following?(status.account)) # of if the account is silenced and I'm not following them
|
||||||
|
|
||||||
|
if status.reply? && !status.in_reply_to_account_id.nil?
|
||||||
|
should_filter ||= receiver.blocking?(status.in_reply_to_account) # or if it's a reply to a user I blocked
|
||||||
|
end
|
||||||
|
|
||||||
|
should_filter
|
||||||
|
end
|
||||||
|
|
||||||
|
def filter_from_public?(status, receiver)
|
||||||
|
should_filter = receiver.blocking?(status.account) # Filter out if I'm blocking that account
|
||||||
|
should_filter ||= receiver.muting?(status.account_id) # or if I'm muting this person
|
||||||
|
should_filter ||= receiver.blocking?(status.mentions.includes(:account).map(&:account)) # or if it mentions someone I blocked
|
||||||
|
|
||||||
if status.reply? && !status.in_reply_to_account_id.nil? # or it's a reply
|
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
|
should_filter ||= receiver.blocking?(status.in_reply_to_account) # to somebody I've blocked
|
||||||
|
should_filter ||= receiver.muting?(status.in_reply_to_account) # or to somebody I'm muting
|
||||||
|
elsif status.reblog? # or if it's a reblog
|
||||||
|
should_filter ||= receiver.blocking?(status.reblog.account) # if I'm blocking the reblogged person
|
||||||
|
should_filter ||= receiver.muting?(status.reblog.account) # or if I'm muting the reblogged person
|
||||||
end
|
end
|
||||||
|
|
||||||
should_filter
|
should_filter
|
||||||
|
|
|
@ -46,6 +46,10 @@ class Account < ApplicationRecord
|
||||||
has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
|
has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
|
||||||
has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
|
has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
|
||||||
|
|
||||||
|
# Mute relationships
|
||||||
|
has_many :mute_relationships, class_name: 'Mute', foreign_key: 'account_id', dependent: :destroy
|
||||||
|
has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
|
||||||
|
|
||||||
# Media
|
# Media
|
||||||
has_many :media_attachments, dependent: :destroy
|
has_many :media_attachments, dependent: :destroy
|
||||||
|
|
||||||
|
@ -73,6 +77,10 @@ class Account < ApplicationRecord
|
||||||
block_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
|
block_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mute!(other_account)
|
||||||
|
mute_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
|
||||||
|
end
|
||||||
|
|
||||||
def unfollow!(other_account)
|
def unfollow!(other_account)
|
||||||
follow = active_relationships.find_by(target_account: other_account)
|
follow = active_relationships.find_by(target_account: other_account)
|
||||||
follow&.destroy
|
follow&.destroy
|
||||||
|
@ -83,6 +91,11 @@ class Account < ApplicationRecord
|
||||||
block&.destroy
|
block&.destroy
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def unmute!(other_account)
|
||||||
|
mute = mute_relationships.find_by(target_account: other_account)
|
||||||
|
mute&.destroy
|
||||||
|
end
|
||||||
|
|
||||||
def following?(other_account)
|
def following?(other_account)
|
||||||
following.include?(other_account)
|
following.include?(other_account)
|
||||||
end
|
end
|
||||||
|
@ -91,6 +104,10 @@ class Account < ApplicationRecord
|
||||||
blocking.include?(other_account)
|
blocking.include?(other_account)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def muting?(other_account)
|
||||||
|
muting.include?(other_account)
|
||||||
|
end
|
||||||
|
|
||||||
def requested?(other_account)
|
def requested?(other_account)
|
||||||
follow_requests.where(target_account: other_account).exists?
|
follow_requests.where(target_account: other_account).exists?
|
||||||
end
|
end
|
||||||
|
@ -188,6 +205,10 @@ class Account < ApplicationRecord
|
||||||
follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
|
follow_mapping(Block.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def muting_map(target_account_ids, account_id)
|
||||||
|
follow_mapping(Mute.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
|
||||||
|
end
|
||||||
|
|
||||||
def requested_map(target_account_ids, account_id)
|
def requested_map(target_account_ids, account_id)
|
||||||
follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
|
follow_mapping(FollowRequest.where(target_account_id: target_account_ids, account_id: account_id), :target_account_id)
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Mute < ApplicationRecord
|
||||||
|
include Paginable
|
||||||
|
include Streamable
|
||||||
|
|
||||||
|
belongs_to :account
|
||||||
|
belongs_to :target_account, class_name: 'Account'
|
||||||
|
|
||||||
|
validates :account, :target_account, presence: true
|
||||||
|
validates :account_id, uniqueness: { scope: :target_account_id }
|
||||||
|
|
||||||
|
def verb
|
||||||
|
destroyed? ? :unmute : :mute
|
||||||
|
end
|
||||||
|
|
||||||
|
def target
|
||||||
|
target_account
|
||||||
|
end
|
||||||
|
|
||||||
|
def object_type
|
||||||
|
:person
|
||||||
|
end
|
||||||
|
|
||||||
|
def hidden?
|
||||||
|
true
|
||||||
|
end
|
||||||
|
|
||||||
|
def title
|
||||||
|
destroyed? ? "#{account.acct} is no longer muting #{target_account.acct}" : "#{account.acct} muted #{target_account.acct}"
|
||||||
|
end
|
||||||
|
end
|
|
@ -103,7 +103,10 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def as_home_timeline(account)
|
def as_home_timeline(account)
|
||||||
where(account: [account] + account.following)
|
muted = Mute.where(account: account).pluck(:target_account_id)
|
||||||
|
query = where(account: [account] + account.following)
|
||||||
|
query = query.where('statuses.account_id NOT IN (?)', muted) unless muted.empty?
|
||||||
|
query
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_public_timeline(account = nil, local_only = false)
|
def as_public_timeline(account = nil, local_only = false)
|
||||||
|
@ -169,8 +172,10 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
def filter_timeline(query, account)
|
def filter_timeline(query, account)
|
||||||
blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id)
|
blocked = Block.where(account: account).pluck(:target_account_id) + Block.where(target_account: account).pluck(:account_id)
|
||||||
query = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty?
|
muted = Mute.where(account: account).pluck(:target_account_id)
|
||||||
query = query.where('accounts.silenced = TRUE') if account.silenced?
|
query = query.where('statuses.account_id NOT IN (?)', blocked) unless blocked.empty? # Only give us statuses from people we haven't blocked
|
||||||
|
query = query.where('statuses.account_id NOT IN (?)', muted) unless muted.empty? # and out of those, only people we haven't muted
|
||||||
|
query = query.where('accounts.silenced = TRUE') if account.silenced? # and if we're hellbanned, only people who are also hellbanned
|
||||||
query
|
query
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -192,6 +197,6 @@ class Status < ApplicationRecord
|
||||||
private
|
private
|
||||||
|
|
||||||
def filter_from_context?(status, account)
|
def filter_from_context?(status, account)
|
||||||
account&.blocking?(status.account_id) || (status.account.silenced? && !account&.following?(status.account_id)) || !status.permitted?(account)
|
account&.blocking?(status.account_id) || account&.muting?(status.account_id) || (status.account.silenced? && !account&.following?(status.account_id)) || !status.permitted?(account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class MuteService < BaseService
|
||||||
|
def call(account, target_account)
|
||||||
|
return if account.id == target_account.id
|
||||||
|
clear_home_timeline(account, target_account)
|
||||||
|
account.mute!(target_account)
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def clear_home_timeline(account, target_account)
|
||||||
|
home_key = FeedManager.instance.key(:home, account.id)
|
||||||
|
|
||||||
|
target_account.statuses.select('id').find_each do |status|
|
||||||
|
redis.zrem(home_key, status.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def redis
|
||||||
|
Redis.current
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class UnmuteService < BaseService
|
||||||
|
def call(account, target_account)
|
||||||
|
return unless account.muting?(target_account)
|
||||||
|
|
||||||
|
account.unmute!(target_account)
|
||||||
|
end
|
||||||
|
end
|
|
@ -4,4 +4,5 @@ attribute :id
|
||||||
node(:following) { |account| @following[account.id] || false }
|
node(:following) { |account| @following[account.id] || false }
|
||||||
node(:followed_by) { |account| @followed_by[account.id] || false }
|
node(:followed_by) { |account| @followed_by[account.id] || false }
|
||||||
node(:blocking) { |account| @blocking[account.id] || false }
|
node(:blocking) { |account| @blocking[account.id] || false }
|
||||||
|
node(:muting) { |account| @muting[account.id] || false }
|
||||||
node(:requested) { |account| @requested[account.id] || false }
|
node(:requested) { |account| @requested[account.id] || false }
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
collection @accounts
|
||||||
|
extends 'api/v1/accounts/show'
|
|
@ -127,6 +127,7 @@ Rails.application.routes.draw do
|
||||||
resources :media, only: [:create]
|
resources :media, only: [:create]
|
||||||
resources :apps, only: [:create]
|
resources :apps, only: [:create]
|
||||||
resources :blocks, only: [:index]
|
resources :blocks, only: [:index]
|
||||||
|
resources :mutes, only: [:index]
|
||||||
resources :favourites, only: [:index]
|
resources :favourites, only: [:index]
|
||||||
resources :reports, only: [:index, :create]
|
resources :reports, only: [:index, :create]
|
||||||
|
|
||||||
|
@ -160,6 +161,8 @@ Rails.application.routes.draw do
|
||||||
post :unfollow
|
post :unfollow
|
||||||
post :block
|
post :block
|
||||||
post :unblock
|
post :unblock
|
||||||
|
post :mute
|
||||||
|
post :unmute
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,12 @@
|
||||||
|
class CreateMutes < ActiveRecord::Migration[5.0]
|
||||||
|
def change
|
||||||
|
create_table :mutes do |t|
|
||||||
|
t.integer :account_id, null: false
|
||||||
|
t.integer :target_account_id, null: false
|
||||||
|
t.timestamps null: false
|
||||||
|
end
|
||||||
|
|
||||||
|
add_index :mutes, [:account_id, :target_account_id], unique: true
|
||||||
|
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 20170217012631) do
|
ActiveRecord::Schema.define(version: 20170301222600) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -110,6 +110,14 @@ ActiveRecord::Schema.define(version: 20170217012631) do
|
||||||
t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true, using: :btree
|
t.index ["account_id", "status_id"], name: "index_mentions_on_account_id_and_status_id", unique: true, using: :btree
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "mutes", force: :cascade do |t|
|
||||||
|
t.integer "account_id", null: false
|
||||||
|
t.integer "target_account_id", null: false
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["account_id", "target_account_id"], name: "index_mutes_on_account_id_and_target_account_id", unique: true, using: :btree
|
||||||
|
end
|
||||||
|
|
||||||
create_table "notifications", force: :cascade do |t|
|
create_table "notifications", force: :cascade do |t|
|
||||||
t.integer "account_id"
|
t.integer "account_id"
|
||||||
t.integer "activity_id"
|
t.integer "activity_id"
|
||||||
|
|
|
@ -116,6 +116,44 @@ RSpec.describe Api::V1::AccountsController, type: :controller do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe 'POST #mute' do
|
||||||
|
let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.account.follow!(other_account)
|
||||||
|
post :mute, params: {id: other_account.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'does not remove the following relation between user and target user' do
|
||||||
|
expect(user.account.following?(other_account)).to be true
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates a muting relation' do
|
||||||
|
expect(user.account.muting?(other_account)).to be true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST #unmute' do
|
||||||
|
let(:other_account) { Fabricate(:user, email: 'bob@example.com', account: Fabricate(:account, username: 'bob')).account }
|
||||||
|
|
||||||
|
before do
|
||||||
|
user.account.mute!(other_account)
|
||||||
|
post :unmute, params: { id: other_account.id }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'removes the muting relation between user and target user' do
|
||||||
|
expect(user.account.muting?(other_account)).to be false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe 'GET #relationships' do
|
describe 'GET #relationships' do
|
||||||
let(:simon) { Fabricate(:user, email: 'simon@example.com', account: Fabricate(:account, username: 'simon')).account }
|
let(:simon) { Fabricate(:user, email: 'simon@example.com', account: Fabricate(:account, username: 'simon')).account }
|
||||||
let(:lewis) { Fabricate(:user, email: 'lewis@example.com', account: Fabricate(:account, username: 'lewis')).account }
|
let(:lewis) { Fabricate(:user, email: 'lewis@example.com', account: Fabricate(:account, username: 'lewis')).account }
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Api::V1::MutesController, type: :controller do
|
||||||
|
render_views
|
||||||
|
|
||||||
|
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||||
|
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
||||||
|
|
||||||
|
before do
|
||||||
|
allow(controller).to receive(:doorkeeper_token) { token }
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'GET #index' do
|
||||||
|
it 'returns http success' do
|
||||||
|
get :index
|
||||||
|
expect(response).to have_http_status(:success)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
Fabricator(:mute) do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe Mute, type: :model do
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe MuteService do
|
||||||
|
subject { MuteService.new }
|
||||||
|
end
|
|
@ -0,0 +1,5 @@
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe UnmuteService do
|
||||||
|
subject { UnmuteService.new }
|
||||||
|
end
|
Loading…
Reference in New Issue