Merge branch 'kb_development' into kb_migration
This commit is contained in:
commit
c7fe057f92
40 changed files with 338 additions and 29 deletions
|
@ -62,7 +62,12 @@ class StatusesController < ApplicationController
|
||||||
|
|
||||||
def set_status
|
def set_status
|
||||||
@status = @account.statuses.find(params[:id])
|
@status = @account.statuses.find(params[:id])
|
||||||
authorize @status, :show?
|
|
||||||
|
if request.authorization.present? && request.authorization.match(/^Bearer /i)
|
||||||
|
raise Mastodon::NotPermittedError unless @status.capability_tokens.find_by(token: request.authorization.gsub(/^Bearer /i, ''))
|
||||||
|
else
|
||||||
|
authorize @status, :show?
|
||||||
|
end
|
||||||
rescue Mastodon::NotPermittedError
|
rescue Mastodon::NotPermittedError
|
||||||
not_found
|
not_found
|
||||||
end
|
end
|
||||||
|
|
|
@ -24,6 +24,7 @@ module ContextHelper
|
||||||
emoji_reactions: { 'fedibird' => 'http://fedibird.com/ns#', 'emojiReactions' => { '@id' => 'fedibird:emojiReactions', '@type' => '@id' } },
|
emoji_reactions: { 'fedibird' => 'http://fedibird.com/ns#', 'emojiReactions' => { '@id' => 'fedibird:emojiReactions', '@type' => '@id' } },
|
||||||
searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => 'fedibird:searchableBy', '@type' => '@id' } },
|
searchable_by: { 'fedibird' => 'http://fedibird.com/ns#', 'searchableBy' => { '@id' => 'fedibird:searchableBy', '@type' => '@id' } },
|
||||||
subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => 'kmyblue:subscribableBy', '@type' => '@id' } },
|
subscribable_by: { 'kmyblue' => 'http://kmy.blue/ns#', 'subscribableBy' => { '@id' => 'kmyblue:subscribableBy', '@type' => '@id' } },
|
||||||
|
limited_scope: { 'kmyblue' => 'http://kmy.blue/ns#', 'limitedScope' => { '@id' => 'kmyblue:limitedScope', '@type' => '@id' } },
|
||||||
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
other_setting: { 'fedibird' => 'http://fedibird.com/ns#', 'otherSetting' => 'fedibird:otherSetting' },
|
||||||
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
references: { 'fedibird' => 'http://fedibird.com/ns#', 'references' => { '@id' => 'fedibird:references', '@type' => '@id' } },
|
||||||
olm: {
|
olm: {
|
||||||
|
|
|
@ -77,6 +77,8 @@ module StatusesHelper
|
||||||
fa_icon 'key fw'
|
fa_icon 'key fw'
|
||||||
when 'private'
|
when 'private'
|
||||||
fa_icon 'lock fw'
|
fa_icon 'lock fw'
|
||||||
|
when 'limited'
|
||||||
|
fa_icon 'get-pocket fw'
|
||||||
when 'direct'
|
when 'direct'
|
||||||
fa_icon 'at fw'
|
fa_icon 'at fw'
|
||||||
end
|
end
|
||||||
|
|
|
@ -69,6 +69,8 @@ const messages = defineMessages({
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
edited: { id: 'status.edited', defaultMessage: 'Edited {date}' },
|
||||||
});
|
});
|
||||||
|
@ -398,10 +400,12 @@ class Status extends ImmutablePureComponent {
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
let visibilityIcon = visibilityIconInfo[status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
let visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||||
|
|
||||||
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
if (this.state.forceFilter === undefined ? matchedFilters : this.state.forceFilter) {
|
||||||
const minHandlers = this.props.muted ? {} : {
|
const minHandlers = this.props.muted ? {} : {
|
||||||
|
@ -562,7 +566,7 @@ class Status extends ImmutablePureComponent {
|
||||||
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
statusAvatar = <AvatarOverlay account={status.get('account')} friend={account} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
visibilityIcon = visibilityIconInfo[status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')] || visibilityIconInfo[status.get('visibility')];
|
||||||
|
|
||||||
let emojiReactionsBar = null;
|
let emojiReactionsBar = null;
|
||||||
if (!this.props.withoutEmojiReactions && status.get('emoji_reactions')) {
|
if (!this.props.withoutEmojiReactions && status.get('emoji_reactions')) {
|
||||||
|
|
|
@ -24,6 +24,8 @@ const messages = defineMessages({
|
||||||
login_long: { id: 'privacy.login.long', defaultMessage: 'Login user only' },
|
login_long: { id: 'privacy.login.long', defaultMessage: 'Login user only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
private_long: { id: 'privacy.private.long', defaultMessage: 'Visible for followers only' },
|
||||||
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual' },
|
||||||
|
mutual_long: { id: 'privacy.mutual.long', defaultMessage: 'Mutual follows only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
direct_long: { id: 'privacy.direct.long', defaultMessage: 'Visible for mentioned users only' },
|
||||||
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
change_privacy: { id: 'privacy.change', defaultMessage: 'Adjust status privacy' },
|
||||||
|
@ -232,6 +234,7 @@ class PrivacyDropdown extends PureComponent {
|
||||||
{ icon: 'key', value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_long) },
|
{ icon: 'key', value: 'login', text: formatMessage(messages.login_short), meta: formatMessage(messages.login_long) },
|
||||||
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
{ icon: 'unlock', value: 'unlisted', text: formatMessage(messages.unlisted_short), meta: formatMessage(messages.unlisted_long) },
|
||||||
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
{ icon: 'lock', value: 'private', text: formatMessage(messages.private_short), meta: formatMessage(messages.private_long) },
|
||||||
|
{ icon: 'exchange', value: 'mutual', text: formatMessage(messages.mutual_short), meta: formatMessage(messages.mutual_long) },
|
||||||
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
{ icon: 'at', value: 'direct', text: formatMessage(messages.direct_short), meta: formatMessage(messages.direct_long) },
|
||||||
];
|
];
|
||||||
this.selectableOptions = [...this.options];
|
this.selectableOptions = [...this.options];
|
||||||
|
|
|
@ -20,6 +20,8 @@ const messages = defineMessages({
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -51,10 +53,12 @@ class StatusCheckBox extends PureComponent {
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibilityIcon = visibilityIconInfo[status.get('visibility_ex')];
|
const visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')];
|
||||||
|
|
||||||
const labelComponent = (
|
const labelComponent = (
|
||||||
<div className='status-check-box__status poll__option__text'>
|
<div className='status-check-box__status poll__option__text'>
|
||||||
|
|
|
@ -30,6 +30,8 @@ const messages = defineMessages({
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
searchability_public_short: { id: 'searchability.public.short', defaultMessage: 'Public' },
|
||||||
searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
searchability_private_short: { id: 'searchability.unlisted.short', defaultMessage: 'Followers' },
|
||||||
|
@ -249,10 +251,12 @@ class DetailedStatus extends ImmutablePureComponent {
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibilityIcon = visibilityIconInfo[status.get('visibility_ex')];
|
const visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')];
|
||||||
const visibilityLink = <> · <Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></>;
|
const visibilityLink = <> · <Icon id={visibilityIcon.icon} title={visibilityIcon.text} /></>;
|
||||||
|
|
||||||
const searchabilityIconInfo = {
|
const searchabilityIconInfo = {
|
||||||
|
|
|
@ -27,6 +27,8 @@ const messages = defineMessages({
|
||||||
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
public_unlisted_short: { id: 'privacy.public_unlisted.short', defaultMessage: 'Public unlisted' },
|
||||||
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
login_short: { id: 'privacy.login.short', defaultMessage: 'Login only' },
|
||||||
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
private_short: { id: 'privacy.private.short', defaultMessage: 'Followers only' },
|
||||||
|
limited_short: { id: 'privacy.limited.short', defaultMessage: 'Limited menbers only' },
|
||||||
|
mutual_short: { id: 'privacy.mutual.short', defaultMessage: 'Mutual followers only' },
|
||||||
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
direct_short: { id: 'privacy.direct.short', defaultMessage: 'Mentioned people only' },
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -94,10 +96,12 @@ class BoostModal extends ImmutablePureComponent {
|
||||||
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
'public_unlisted': { icon: 'cloud', text: intl.formatMessage(messages.public_unlisted_short) },
|
||||||
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
'login': { icon: 'key', text: intl.formatMessage(messages.login_short) },
|
||||||
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
'private': { icon: 'lock', text: intl.formatMessage(messages.private_short) },
|
||||||
|
'limited': { icon: 'get-pocket', text: intl.formatMessage(messages.limited_short) },
|
||||||
|
'mutual': { icon: 'exchange', text: intl.formatMessage(messages.mutual_short) },
|
||||||
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
'direct': { icon: 'at', text: intl.formatMessage(messages.direct_short) },
|
||||||
};
|
};
|
||||||
|
|
||||||
const visibilityIcon = visibilityIconInfo[status.get('visibility_ex')];
|
const visibilityIcon = visibilityIconInfo[status.get('limited_scope') || status.get('visibility_ex')];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='modal-root__modal boost-modal'>
|
<div className='modal-root__modal boost-modal'>
|
||||||
|
|
|
@ -520,8 +520,11 @@
|
||||||
"privacy.change": "Change post privacy",
|
"privacy.change": "Change post privacy",
|
||||||
"privacy.direct.long": "Visible for mentioned users only",
|
"privacy.direct.long": "Visible for mentioned users only",
|
||||||
"privacy.direct.short": "Mentioned people only",
|
"privacy.direct.short": "Mentioned people only",
|
||||||
|
"privacy.limited.short": "Limited",
|
||||||
"privacy.login.long": "Visible for login users only",
|
"privacy.login.long": "Visible for login users only",
|
||||||
"privacy.login.short": "Login only",
|
"privacy.login.short": "Login only",
|
||||||
|
"privacy.mutual.long": "Mutual followers only",
|
||||||
|
"privacy.mutual.short": "Mutual",
|
||||||
"privacy.private.long": "Visible for followers only",
|
"privacy.private.long": "Visible for followers only",
|
||||||
"privacy.private.short": "Followers only",
|
"privacy.private.short": "Followers only",
|
||||||
"privacy.public.long": "Visible for all",
|
"privacy.public.long": "Visible for all",
|
||||||
|
|
|
@ -529,8 +529,11 @@
|
||||||
"privacy.change": "公開範囲を変更",
|
"privacy.change": "公開範囲を変更",
|
||||||
"privacy.direct.long": "指定された相手のみ閲覧可",
|
"privacy.direct.long": "指定された相手のみ閲覧可",
|
||||||
"privacy.direct.short": "指定された相手のみ",
|
"privacy.direct.short": "指定された相手のみ",
|
||||||
|
"privacy.limited.short": "限定投稿",
|
||||||
"privacy.login.long": "ログインユーザーのみ閲覧可、公開",
|
"privacy.login.long": "ログインユーザーのみ閲覧可、公開",
|
||||||
"privacy.login.short": "ログインユーザーのみ",
|
"privacy.login.short": "ログインユーザーのみ",
|
||||||
|
"privacy.mutual.long": "相互フォローさんのみ閲覧可、限定投稿",
|
||||||
|
"privacy.mutual.short": "相互のみ",
|
||||||
"privacy.private.long": "フォロワーのみ閲覧可",
|
"privacy.private.long": "フォロワーのみ閲覧可",
|
||||||
"privacy.private.short": "フォロワーのみ",
|
"privacy.private.short": "フォロワーのみ",
|
||||||
"privacy.public.long": "誰でも閲覧可、ホーム+ローカル+連合TL",
|
"privacy.public.long": "誰でも閲覧可、ホーム+ローカル+連合TL",
|
||||||
|
|
|
@ -119,7 +119,10 @@ class ActivityPub::Activity
|
||||||
|
|
||||||
dereferencer = ActivityPub::Dereferencer.new(@object, permitted_origin: @account.uri, signature_actor: signed_fetch_actor)
|
dereferencer = ActivityPub::Dereferencer.new(@object, permitted_origin: @account.uri, signature_actor: signed_fetch_actor)
|
||||||
|
|
||||||
@object = dereferencer.object unless dereferencer.object.nil?
|
return if dereferencer.object.nil?
|
||||||
|
|
||||||
|
@object = dereferencer.object
|
||||||
|
@json = @object
|
||||||
end
|
end
|
||||||
|
|
||||||
def signed_fetch_actor
|
def signed_fetch_actor
|
||||||
|
|
|
@ -133,6 +133,7 @@ class ActivityPub::Activity::Create < ActivityPub::Activity
|
||||||
reply: @status_parser.reply,
|
reply: @status_parser.reply,
|
||||||
sensitive: @account.sensitized? || @status_parser.sensitive || false,
|
sensitive: @account.sensitized? || @status_parser.sensitive || false,
|
||||||
visibility: @status_parser.visibility,
|
visibility: @status_parser.visibility,
|
||||||
|
limited_scope: @status_parser.limited_scope,
|
||||||
searchability: searchability,
|
searchability: searchability,
|
||||||
thread: replied_to_status,
|
thread: replied_to_status,
|
||||||
conversation: conversation_from_uri(@object['conversation']),
|
conversation: conversation_from_uri(@object['conversation']),
|
||||||
|
|
|
@ -86,6 +86,14 @@ class ActivityPub::Parser::StatusParser
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def limited_scope
|
||||||
|
if @object['limitedScope'] == 'Mutual'
|
||||||
|
:mutual
|
||||||
|
else
|
||||||
|
:none
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def language
|
def language
|
||||||
if content_language_map?
|
if content_language_map?
|
||||||
@object['contentMap'].keys.first
|
@object['contentMap'].keys.first
|
||||||
|
|
|
@ -100,7 +100,7 @@ class ActivityPub::TagManager
|
||||||
[account_followers_url(status.account)]
|
[account_followers_url(status.account)]
|
||||||
when 'login'
|
when 'login'
|
||||||
[account_followers_url(status.account), 'as:LoginOnly', 'LoginUser']
|
[account_followers_url(status.account), 'as:LoginOnly', 'LoginUser']
|
||||||
when 'direct', 'limited'
|
when 'direct'
|
||||||
if status.account.silenced?
|
if status.account.silenced?
|
||||||
# Only notify followers if the account is locally silenced
|
# Only notify followers if the account is locally silenced
|
||||||
account_ids = status.active_mentions.pluck(:account_id)
|
account_ids = status.active_mentions.pluck(:account_id)
|
||||||
|
@ -118,6 +118,11 @@ class ActivityPub::TagManager
|
||||||
result << followers_uri_for(mention.account) if mention.account.group?
|
result << followers_uri_for(mention.account) if mention.account.group?
|
||||||
end.compact
|
end.compact
|
||||||
end
|
end
|
||||||
|
when 'limited'
|
||||||
|
status.mentions.each_with_object([]) do |mention, result|
|
||||||
|
result << uri_for(mention.account)
|
||||||
|
result << followers_uri_for(mention.account) if mention.account.group?
|
||||||
|
end.compact
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -216,6 +221,10 @@ class ActivityPub::TagManager
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def limited_scope(status)
|
||||||
|
status.mutual_limited? ? 'Mutual' : ''
|
||||||
|
end
|
||||||
|
|
||||||
def subscribable_by(account)
|
def subscribable_by(account)
|
||||||
account.dissubscribable ? [] : [COLLECTIONS[:public]]
|
account.dissubscribable ? [] : [COLLECTIONS[:public]]
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,8 @@ class StatusReachFinder
|
||||||
|
|
||||||
if @status.reblog?
|
if @status.reblog?
|
||||||
[]
|
[]
|
||||||
|
elsif @status.limited_visibility?
|
||||||
|
Account.where(id: mentioned_account_ids).where.not(domain: banned_domains).inboxes
|
||||||
else
|
else
|
||||||
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
Account.where(id: reached_account_ids).where.not(domain: banned_domains).inboxes
|
||||||
end
|
end
|
||||||
|
@ -37,6 +39,8 @@ class StatusReachFinder
|
||||||
def reached_account_inboxes_for_misskey
|
def reached_account_inboxes_for_misskey
|
||||||
if @status.reblog?
|
if @status.reblog?
|
||||||
[]
|
[]
|
||||||
|
elsif @status.limited_visibility?
|
||||||
|
Account.where(id: mentioned_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
||||||
else
|
else
|
||||||
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
Account.where(id: reached_account_ids).where(domain: banned_domains_for_misskey).inboxes
|
||||||
end
|
end
|
||||||
|
|
|
@ -303,6 +303,10 @@ module AccountInteractions
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def mutuals
|
||||||
|
followers.merge(Account.where(id: following))
|
||||||
|
end
|
||||||
|
|
||||||
def relations_map(account_ids, domains = nil, **options)
|
def relations_map(account_ids, domains = nil, **options)
|
||||||
relations = {
|
relations = {
|
||||||
blocked_by: Account.blocked_by_map(account_ids, id),
|
blocked_by: Account.blocked_by_map(account_ids, id),
|
||||||
|
|
|
@ -55,6 +55,10 @@ module HasUserSettings
|
||||||
settings['send_without_domain_blocks']
|
settings['send_without_domain_blocks']
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def setting_unsafe_limited_distribution
|
||||||
|
settings['unsafe_limited_distribution']
|
||||||
|
end
|
||||||
|
|
||||||
def setting_stop_emoji_reaction_streaming
|
def setting_stop_emoji_reaction_streaming
|
||||||
settings['stop_emoji_reaction_streaming']
|
settings['stop_emoji_reaction_streaming']
|
||||||
end
|
end
|
||||||
|
|
|
@ -29,6 +29,7 @@
|
||||||
# ordered_media_attachment_ids :bigint(8) is an Array
|
# ordered_media_attachment_ids :bigint(8) is an Array
|
||||||
# searchability :integer
|
# searchability :integer
|
||||||
# markdown :boolean default(FALSE)
|
# markdown :boolean default(FALSE)
|
||||||
|
# limited_scope :integer
|
||||||
#
|
#
|
||||||
|
|
||||||
require 'ostruct'
|
require 'ostruct'
|
||||||
|
@ -54,6 +55,7 @@ class Status < ApplicationRecord
|
||||||
|
|
||||||
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, _suffix: :visibility
|
enum visibility: { public: 0, unlisted: 1, private: 2, direct: 3, limited: 4, public_unlisted: 10, login: 11 }, _suffix: :visibility
|
||||||
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 }, _suffix: :searchability
|
enum searchability: { public: 0, private: 1, direct: 2, limited: 3, unsupported: 4, public_unlisted: 10 }, _suffix: :searchability
|
||||||
|
enum limited_scope: { none: 0, mutual: 1 }, _suffix: :limited
|
||||||
|
|
||||||
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
|
||||||
|
|
||||||
|
@ -79,6 +81,7 @@ class Status < ApplicationRecord
|
||||||
has_many :references, through: :reference_objects, class_name: 'Status', source: :target_status
|
has_many :references, through: :reference_objects, class_name: 'Status', source: :target_status
|
||||||
has_many :referenced_by_status_objects, foreign_key: 'target_status_id', class_name: 'StatusReference', inverse_of: :target_status, dependent: :destroy
|
has_many :referenced_by_status_objects, foreign_key: 'target_status_id', class_name: 'StatusReference', inverse_of: :target_status, dependent: :destroy
|
||||||
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
|
has_many :referenced_by_statuses, through: :referenced_by_status_objects, class_name: 'Status', source: :status
|
||||||
|
has_many :capability_tokens, class_name: 'StatusCapabilityToken', inverse_of: :status, dependent: :destroy
|
||||||
|
|
||||||
has_and_belongs_to_many :tags
|
has_and_belongs_to_many :tags
|
||||||
has_and_belongs_to_many :preview_cards
|
has_and_belongs_to_many :preview_cards
|
||||||
|
|
25
app/models/status_capability_token.rb
Normal file
25
app/models/status_capability_token.rb
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
# == Schema Information
|
||||||
|
#
|
||||||
|
# Table name: status_capability_tokens
|
||||||
|
#
|
||||||
|
# id :bigint(8) not null, primary key
|
||||||
|
# status_id :bigint(8) not null
|
||||||
|
# token :string
|
||||||
|
# created_at :datetime not null
|
||||||
|
# updated_at :datetime not null
|
||||||
|
#
|
||||||
|
class StatusCapabilityToken < ApplicationRecord
|
||||||
|
belongs_to :status
|
||||||
|
|
||||||
|
validates :token, presence: true
|
||||||
|
|
||||||
|
before_validation :generate_token, on: :create
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def generate_token
|
||||||
|
self.token = Doorkeeper::OAuth::Helpers::UniqueToken.generate
|
||||||
|
end
|
||||||
|
end
|
|
@ -31,6 +31,7 @@ class UserSettings
|
||||||
setting :reaction_deck, default: nil
|
setting :reaction_deck, default: nil
|
||||||
setting :stop_emoji_reaction_streaming, default: false
|
setting :stop_emoji_reaction_streaming, default: false
|
||||||
setting :emoji_reaction_streaming_notify_impl2, default: false
|
setting :emoji_reaction_streaming_notify_impl2, default: false
|
||||||
|
setting :unsafe_limited_distribution, default: false
|
||||||
|
|
||||||
namespace :web do
|
namespace :web do
|
||||||
setting :advanced_layout, default: false
|
setting :advanced_layout, default: false
|
||||||
|
|
|
@ -4,7 +4,7 @@ class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model
|
||||||
attributes :id, :type, :actor, :published, :to, :cc, :virtual_object
|
attributes :id, :type, :actor, :published, :to, :cc, :virtual_object
|
||||||
|
|
||||||
class << self
|
class << self
|
||||||
def from_status(status, allow_inlining: true, for_misskey: false)
|
def from_status(status, use_bearcap: true, allow_inlining: true, for_misskey: false)
|
||||||
new.tap do |presenter|
|
new.tap do |presenter|
|
||||||
presenter.id = ActivityPub::TagManager.instance.activity_uri_for(status)
|
presenter.id = ActivityPub::TagManager.instance.activity_uri_for(status)
|
||||||
presenter.type = status.reblog? ? 'Announce' : 'Create'
|
presenter.type = status.reblog? ? 'Announce' : 'Create'
|
||||||
|
@ -20,6 +20,8 @@ class ActivityPub::ActivityPresenter < ActiveModelSerializers::Model
|
||||||
else
|
else
|
||||||
ActivityPub::TagManager.instance.uri_for(status.proper)
|
ActivityPub::TagManager.instance.uri_for(status.proper)
|
||||||
end
|
end
|
||||||
|
elsif status.limited_visibility? && use_bearcap && !status.account.user&.setting_unsafe_limited_distribution
|
||||||
|
"bear:?#{{ u: ActivityPub::TagManager.instance.uri_for(status.proper), t: status.capability_tokens.first.token }.to_query}"
|
||||||
else
|
else
|
||||||
status.proper
|
status.proper
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references
|
context_extensions :atom_uri, :conversation, :sensitive, :voters_count, :searchable_by, :references, :limited_scope
|
||||||
|
|
||||||
attributes :id, :type, :summary,
|
attributes :id, :type, :summary,
|
||||||
:in_reply_to, :published, :url,
|
:in_reply_to, :published, :url,
|
||||||
:attributed_to, :to, :cc, :sensitive,
|
:attributed_to, :to, :cc, :sensitive,
|
||||||
:atom_uri, :in_reply_to_atom_uri,
|
:atom_uri, :in_reply_to_atom_uri,
|
||||||
:conversation, :searchable_by
|
:conversation, :searchable_by, :limited_scope
|
||||||
|
|
||||||
attribute :references, if: :not_private_post?
|
attribute :references, if: :not_private_post?
|
||||||
|
|
||||||
|
@ -148,12 +148,16 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
|
||||||
ActivityPub::TagManager.instance.searchable_by(object)
|
ActivityPub::TagManager.instance.searchable_by(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def limited_scope
|
||||||
|
ActivityPub::TagManager.instance.limited_scope(object)
|
||||||
|
end
|
||||||
|
|
||||||
def local?
|
def local?
|
||||||
object.account.local?
|
object.account.local?
|
||||||
end
|
end
|
||||||
|
|
||||||
def not_private_post?
|
def not_private_post?
|
||||||
!object.private_visibility?
|
!object.private_visibility? && !object.direct_visibility? && !object.limited_visibility?
|
||||||
end
|
end
|
||||||
|
|
||||||
def poll_options
|
def poll_options
|
||||||
|
|
|
@ -118,6 +118,7 @@ class REST::InstanceSerializer < ActiveModel::Serializer
|
||||||
:kmyblue_reaction_deck,
|
:kmyblue_reaction_deck,
|
||||||
:kmyblue_visibility_login,
|
:kmyblue_visibility_login,
|
||||||
:status_reference,
|
:status_reference,
|
||||||
|
:visibility_mutual,
|
||||||
]
|
]
|
||||||
|
|
||||||
capabilities << :profile_search unless Chewy.enabled?
|
capabilities << :profile_search unless Chewy.enabled?
|
||||||
|
|
|
@ -4,7 +4,7 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
include FormattingHelper
|
include FormattingHelper
|
||||||
|
|
||||||
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
attributes :id, :created_at, :in_reply_to_id, :in_reply_to_account_id,
|
||||||
:sensitive, :spoiler_text, :visibility, :visibility_ex, :language,
|
:sensitive, :spoiler_text, :visibility, :visibility_ex, :limited_scope, :language,
|
||||||
:uri, :url, :replies_count, :reblogs_count, :searchability, :markdown,
|
:uri, :url, :replies_count, :reblogs_count, :searchability, :markdown,
|
||||||
:status_reference_ids, :status_references_count, :status_referred_by_count,
|
:status_reference_ids, :status_references_count, :status_referred_by_count,
|
||||||
:favourites_count, :emoji_reactions, :emoji_reactions_count, :reactions, :edited_at
|
:favourites_count, :emoji_reactions, :emoji_reactions_count, :reactions, :edited_at
|
||||||
|
@ -66,11 +66,11 @@ class REST::StatusSerializer < ActiveModel::Serializer
|
||||||
end
|
end
|
||||||
|
|
||||||
def visibility_ex
|
def visibility_ex
|
||||||
if object.limited_visibility?
|
object.visibility
|
||||||
'private'
|
end
|
||||||
else
|
|
||||||
object.visibility
|
def limited_scope
|
||||||
end
|
!object.none_limited? && object.limited_visibility? ? object.limited_scope : nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def searchability
|
def searchability
|
||||||
|
|
|
@ -127,6 +127,8 @@ class REST::V1::InstanceSerializer < ActiveModel::Serializer
|
||||||
:kmyblue_reaction_deck,
|
:kmyblue_reaction_deck,
|
||||||
:kmyblue_visibility_login,
|
:kmyblue_visibility_login,
|
||||||
:status_reference,
|
:status_reference,
|
||||||
|
:visibility_mutual,
|
||||||
|
:kmyblue_limited_scope,
|
||||||
]
|
]
|
||||||
|
|
||||||
capabilities << :profile_search unless Chewy.enabled?
|
capabilities << :profile_search unless Chewy.enabled?
|
||||||
|
|
|
@ -209,7 +209,7 @@ class ActivityPub::ProcessAccountService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def fetch_instance_info
|
def fetch_instance_info
|
||||||
FetchInstanceInfoWorker.perform_async(@account.domain) unless InstanceInfo.exists?(domain: @account.domain)
|
ActivityPub::FetchInstanceInfoWorker.perform_async(@account.domain) unless InstanceInfo.exists?(domain: @account.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
def actor_type
|
def actor_type
|
||||||
|
|
|
@ -32,7 +32,7 @@ class BackupService < BaseService
|
||||||
add_comma = true
|
add_comma = true
|
||||||
|
|
||||||
file.write(statuses.map do |status|
|
file.write(statuses.map do |status|
|
||||||
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status), ActivityPub::ActivitySerializer)
|
item = serialize_payload(ActivityPub::ActivityPresenter.from_status(status, use_bearcap: false), ActivityPub::ActivitySerializer)
|
||||||
item.delete('@context')
|
item.delete('@context')
|
||||||
|
|
||||||
unless item[:type] == 'Announce' || item[:object][:attachment].blank?
|
unless item[:type] == 'Announce' || item[:object][:attachment].blank?
|
||||||
|
|
|
@ -74,6 +74,8 @@ class PostStatusService < BaseService
|
||||||
end) || @options[:spoiler_text].present?
|
end) || @options[:spoiler_text].present?
|
||||||
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
@text = @options.delete(:spoiler_text) if @text.blank? && @options[:spoiler_text].present?
|
||||||
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
@visibility = @options[:visibility] || @account.user&.setting_default_privacy
|
||||||
|
@visibility = :direct if @in_reply_to&.limited_visibility?
|
||||||
|
@visibility = :limited if @options[:visibility] == 'mutual'
|
||||||
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
|
@visibility = :unlisted if (@visibility&.to_sym == :public || @visibility&.to_sym == :public_unlisted || @visibility&.to_sym == :login) && @account.silenced?
|
||||||
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
@visibility = :public_unlisted if @visibility&.to_sym == :public && !@options[:force_visibility] && !@options[:application]&.superapp && @account.user&.setting_public_post_to_unlisted
|
||||||
@searchability = searchability
|
@searchability = searchability
|
||||||
|
@ -113,7 +115,7 @@ class PostStatusService < BaseService
|
||||||
|
|
||||||
def process_status!
|
def process_status!
|
||||||
@status = @account.statuses.new(status_attributes)
|
@status = @account.statuses.new(status_attributes)
|
||||||
process_mentions_service.call(@status, save_records: false)
|
process_mentions_service.call(@status, limited_type: @status.limited_visibility? ? 'mutual' : '', save_records: false)
|
||||||
safeguard_mentions!(@status)
|
safeguard_mentions!(@status)
|
||||||
|
|
||||||
UpdateStatusExpirationService.new.call(@status)
|
UpdateStatusExpirationService.new.call(@status)
|
||||||
|
@ -122,6 +124,7 @@ class PostStatusService < BaseService
|
||||||
# the media attachments when the status is created
|
# the media attachments when the status is created
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
@status.save!
|
@status.save!
|
||||||
|
@status.capability_tokens.create! if @status.limited_visibility?
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -245,6 +248,7 @@ class PostStatusService < BaseService
|
||||||
spoiler_text: @options[:spoiler_text] || '',
|
spoiler_text: @options[:spoiler_text] || '',
|
||||||
markdown: @markdown,
|
markdown: @markdown,
|
||||||
visibility: @visibility,
|
visibility: @visibility,
|
||||||
|
limited_scope: @visibility == :limited ? :mutual : :none,
|
||||||
searchability: @searchability,
|
searchability: @searchability,
|
||||||
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
|
language: valid_locale_cascade(@options[:language], @account.user&.preferred_posting_language, I18n.default_locale),
|
||||||
application: @options[:application],
|
application: @options[:application],
|
||||||
|
|
|
@ -7,8 +7,9 @@ class ProcessMentionsService < BaseService
|
||||||
# and create local mention pointers
|
# and create local mention pointers
|
||||||
# @param [Status] status
|
# @param [Status] status
|
||||||
# @param [Boolean] save_records Whether to save records in database
|
# @param [Boolean] save_records Whether to save records in database
|
||||||
def call(status, save_records: true)
|
def call(status, limited_type: '', save_records: true)
|
||||||
@status = status
|
@status = status
|
||||||
|
@limited_type = limited_type
|
||||||
@save_records = save_records
|
@save_records = save_records
|
||||||
|
|
||||||
return unless @status.local?
|
return unless @status.local?
|
||||||
|
@ -62,6 +63,8 @@ class ProcessMentionsService < BaseService
|
||||||
"@#{mentioned_account.acct}"
|
"@#{mentioned_account.acct}"
|
||||||
end
|
end
|
||||||
|
|
||||||
|
process_mutual! if @limited_type == 'mutual'
|
||||||
|
|
||||||
@status.save! if @save_records
|
@status.save! if @save_records
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -92,4 +95,12 @@ class ProcessMentionsService < BaseService
|
||||||
def mention_undeliverable?(mentioned_account)
|
def mention_undeliverable?(mentioned_account)
|
||||||
mentioned_account.nil? || (!mentioned_account.local? && !mentioned_account.activitypub?)
|
mentioned_account.nil? || (!mentioned_account.local? && !mentioned_account.activitypub?)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def process_mutual!
|
||||||
|
mentioned_account_ids = @current_mentions.map(&:account_id)
|
||||||
|
|
||||||
|
@status.account.mutuals.find_each do |target_account|
|
||||||
|
@current_mentions << @status.mentions.new(silent: true, account: target_account) unless mentioned_account_ids.include?(target_account.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class FetchInstanceInfoWorker
|
class ActivityPub::FetchInstanceInfoWorker
|
||||||
include Sidekiq::Worker
|
include Sidekiq::Worker
|
||||||
include JsonLdHelper
|
include JsonLdHelper
|
||||||
include Redisable
|
include Redisable
|
||||||
|
@ -64,9 +64,9 @@ class FetchInstanceInfoWorker
|
||||||
|
|
||||||
body_to_json(response.body_with_limit)
|
body_to_json(response.body_with_limit)
|
||||||
elsif response.code == 410
|
elsif response.code == 410
|
||||||
raise FetchInstanceInfoWorker::GoneError, "#{domain} is gone from the server"
|
raise ActivityPub::FetchInstanceInfoWorker::GoneError, "#{@instance.domain} is gone from the server"
|
||||||
else
|
else
|
||||||
raise FetchInstanceInfoWorker::RequestError, "Request for #{domain} returned HTTP #{response.code}"
|
raise ActivityPub::FetchInstanceInfoWorker::RequestError, "Request for #{@instance.domain} returned HTTP #{response.code}"
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
|
@ -7,7 +7,7 @@ class Scheduler::UpdateInstanceInfoScheduler
|
||||||
|
|
||||||
def perform
|
def perform
|
||||||
Instance.select(:domain).reorder(nil).find_in_batches do |instances|
|
Instance.select(:domain).reorder(nil).find_in_batches do |instances|
|
||||||
FetchInstanceInfoWorker.push_bulk(instances) do |instance|
|
ActivityPub::FetchInstanceInfoWorker.push_bulk(instances) do |instance|
|
||||||
[instance.domain]
|
[instance.domain]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -247,6 +247,7 @@ en:
|
||||||
setting_theme: Site theme
|
setting_theme: Site theme
|
||||||
setting_trends: Show today's trends
|
setting_trends: Show today's trends
|
||||||
setting_unfollow_modal: Show confirmation dialog before unfollowing someone
|
setting_unfollow_modal: Show confirmation dialog before unfollowing someone
|
||||||
|
setting_unsafe_limited_distribution: Send limit posts with unsafe way to other servers
|
||||||
setting_use_blurhash: Show colorful gradients for hidden media
|
setting_use_blurhash: Show colorful gradients for hidden media
|
||||||
setting_use_pending_items: Slow mode
|
setting_use_pending_items: Slow mode
|
||||||
severity: Severity
|
severity: Severity
|
||||||
|
|
|
@ -73,6 +73,7 @@ ja:
|
||||||
setting_reject_unlisted_subscription: Misskeyやそのフォーク(Calckeyなど)は、フォローしていないアカウントの「未収載」投稿を **購読・検索** することができます。これはkmyblueの挙動と異なります。そのようなサーバーのうち管理人が指定したものに、指定した公開範囲の投稿を「フォロワーのみ」として配送します。ただし構造上、完璧な対応は困難でたまに未収載として配信されること、ご理解ください
|
setting_reject_unlisted_subscription: Misskeyやそのフォーク(Calckeyなど)は、フォローしていないアカウントの「未収載」投稿を **購読・検索** することができます。これはkmyblueの挙動と異なります。そのようなサーバーのうち管理人が指定したものに、指定した公開範囲の投稿を「フォロワーのみ」として配送します。ただし構造上、完璧な対応は困難でたまに未収載として配信されること、ご理解ください
|
||||||
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
|
setting_show_application: 投稿するのに使用したアプリが投稿の詳細ビューに表示されるようになります
|
||||||
setting_stop_emoji_reaction_streaming: 通信容量の節約に役立ちます
|
setting_stop_emoji_reaction_streaming: 通信容量の節約に役立ちます
|
||||||
|
setting_unsafe_limited_distribution: Mastodon 3.5、4.0、4.1のサーバーにも限定投稿(相互のみ)が届くようになりますが、安全でない方法で送信します
|
||||||
setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
|
setting_use_blurhash: ぼかしはメディアの色を元に生成されますが、細部は見えにくくなっています
|
||||||
setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします
|
setting_use_pending_items: 新着があってもタイムラインを自動的にスクロールしないようにします
|
||||||
username: アルファベット大文字と小文字、数字、アンダーバー「_」が使えます
|
username: アルファベット大文字と小文字、数字、アンダーバー「_」が使えます
|
||||||
|
@ -255,6 +256,7 @@ ja:
|
||||||
setting_theme: サイトテーマ
|
setting_theme: サイトテーマ
|
||||||
setting_trends: 本日のトレンドタグを表示する
|
setting_trends: 本日のトレンドタグを表示する
|
||||||
setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する
|
setting_unfollow_modal: フォローを解除する前に確認ダイアログを表示する
|
||||||
|
setting_unsafe_limited_distribution: 安全でない方法で限定投稿を他サーバーに配信する (非推奨)
|
||||||
setting_use_blurhash: 非表示のメディアを色付きのぼかしで表示する
|
setting_use_blurhash: 非表示のメディアを色付きのぼかしで表示する
|
||||||
setting_use_pending_items: 手動更新モード
|
setting_use_pending_items: 手動更新モード
|
||||||
severity: 重大性
|
severity: 重大性
|
||||||
|
|
12
db/migrate/20230812083752_create_status_capability_token.rb
Normal file
12
db/migrate/20230812083752_create_status_capability_token.rb
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class CreateStatusCapabilityToken < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
create_table :status_capability_tokens do |t|
|
||||||
|
t.belongs_to :status, null: false, foreign_key: { on_delete: :cascade }
|
||||||
|
t.string :token
|
||||||
|
t.datetime :created_at, null: false
|
||||||
|
t.datetime :updated_at, null: false
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class AddLimitedScopeToStatuses < ActiveRecord::Migration[7.0]
|
||||||
|
def change
|
||||||
|
add_column :statuses, :limited_scope, :integer
|
||||||
|
end
|
||||||
|
end
|
12
db/schema.rb
12
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[7.0].define(version: 2023_08_04_222017) do
|
ActiveRecord::Schema[7.0].define(version: 2023_08_12_130612) 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"
|
||||||
|
|
||||||
|
@ -1029,6 +1029,14 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_04_222017) do
|
||||||
t.index ["var"], name: "index_site_uploads_on_var", unique: true
|
t.index ["var"], name: "index_site_uploads_on_var", unique: true
|
||||||
end
|
end
|
||||||
|
|
||||||
|
create_table "status_capability_tokens", force: :cascade do |t|
|
||||||
|
t.bigint "status_id", null: false
|
||||||
|
t.string "token"
|
||||||
|
t.datetime "created_at", null: false
|
||||||
|
t.datetime "updated_at", null: false
|
||||||
|
t.index ["status_id"], name: "index_status_capability_tokens_on_status_id"
|
||||||
|
end
|
||||||
|
|
||||||
create_table "status_edits", force: :cascade do |t|
|
create_table "status_edits", force: :cascade do |t|
|
||||||
t.bigint "status_id", null: false
|
t.bigint "status_id", null: false
|
||||||
t.bigint "account_id"
|
t.bigint "account_id"
|
||||||
|
@ -1114,6 +1122,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_04_222017) do
|
||||||
t.bigint "ordered_media_attachment_ids", array: true
|
t.bigint "ordered_media_attachment_ids", array: true
|
||||||
t.integer "searchability"
|
t.integer "searchability"
|
||||||
t.boolean "markdown", default: false
|
t.boolean "markdown", default: false
|
||||||
|
t.integer "limited_scope"
|
||||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
|
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
|
||||||
t.index ["account_id"], name: "index_statuses_on_account_id"
|
t.index ["account_id"], name: "index_statuses_on_account_id"
|
||||||
t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)"
|
t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)"
|
||||||
|
@ -1395,6 +1404,7 @@ ActiveRecord::Schema[7.0].define(version: 2023_08_04_222017) do
|
||||||
add_foreign_key "scheduled_statuses", "accounts", on_delete: :cascade
|
add_foreign_key "scheduled_statuses", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", name: "fk_957e5bda89", on_delete: :cascade
|
add_foreign_key "session_activations", "oauth_access_tokens", column: "access_token_id", name: "fk_957e5bda89", on_delete: :cascade
|
||||||
add_foreign_key "session_activations", "users", name: "fk_e5fda67334", on_delete: :cascade
|
add_foreign_key "session_activations", "users", name: "fk_e5fda67334", on_delete: :cascade
|
||||||
|
add_foreign_key "status_capability_tokens", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "status_edits", "accounts", on_delete: :nullify
|
add_foreign_key "status_edits", "accounts", on_delete: :nullify
|
||||||
add_foreign_key "status_edits", "statuses", on_delete: :cascade
|
add_foreign_key "status_edits", "statuses", on_delete: :cascade
|
||||||
add_foreign_key "status_pins", "accounts", name: "fk_d4cb435b62", on_delete: :cascade
|
add_foreign_key "status_pins", "accounts", name: "fk_d4cb435b62", on_delete: :cascade
|
||||||
|
|
|
@ -290,6 +290,7 @@ RSpec.describe ActivityPub::Activity::Create do
|
||||||
|
|
||||||
expect(status).to_not be_nil
|
expect(status).to_not be_nil
|
||||||
expect(status.visibility).to eq 'limited'
|
expect(status.visibility).to eq 'limited'
|
||||||
|
expect(status.limited_scope).to eq 'none'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'creates silent mention' do
|
it 'creates silent mention' do
|
||||||
|
@ -298,6 +299,50 @@ RSpec.describe ActivityPub::Activity::Create do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context 'when limited_scope' do
|
||||||
|
let(:recipient) { Fabricate(:account) }
|
||||||
|
|
||||||
|
let(:object_json) do
|
||||||
|
{
|
||||||
|
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||||
|
type: 'Note',
|
||||||
|
content: 'Lorem ipsum',
|
||||||
|
to: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||||
|
limitedScope: 'Mutual',
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates status' do
|
||||||
|
status = sender.statuses.first
|
||||||
|
|
||||||
|
expect(status).to_not be_nil
|
||||||
|
expect(status.visibility).to eq 'limited'
|
||||||
|
expect(status.limited_scope).to eq 'mutual'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when invalid limited_scope' do
|
||||||
|
let(:recipient) { Fabricate(:account) }
|
||||||
|
|
||||||
|
let(:object_json) do
|
||||||
|
{
|
||||||
|
id: [ActivityPub::TagManager.instance.uri_for(sender), '#bar'].join,
|
||||||
|
type: 'Note',
|
||||||
|
content: 'Lorem ipsum',
|
||||||
|
to: ActivityPub::TagManager.instance.uri_for(recipient),
|
||||||
|
limitedScope: 'IdosdsazsF',
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'creates status' do
|
||||||
|
status = sender.statuses.first
|
||||||
|
|
||||||
|
expect(status).to_not be_nil
|
||||||
|
expect(status.visibility).to eq 'limited'
|
||||||
|
expect(status.limited_scope).to eq 'none'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context 'when direct' do
|
context 'when direct' do
|
||||||
let(:recipient) { Fabricate(:account) }
|
let(:recipient) { Fabricate(:account) }
|
||||||
|
|
||||||
|
|
|
@ -168,7 +168,17 @@ RSpec.describe PostStatusService, type: :service do
|
||||||
status = subject.call(account, text: 'test status update')
|
status = subject.call(account, text: 'test status update')
|
||||||
|
|
||||||
expect(ProcessMentionsService).to have_received(:new)
|
expect(ProcessMentionsService).to have_received(:new)
|
||||||
expect(mention_service).to have_received(:call).with(status, save_records: false)
|
expect(mention_service).to have_received(:call).with(status, limited_type: '', save_records: false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'mutual visibility' do
|
||||||
|
account = Fabricate(:account)
|
||||||
|
text = 'This is an English text.'
|
||||||
|
|
||||||
|
status = subject.call(account, text: text, visibility: 'mutual')
|
||||||
|
|
||||||
|
expect(status.visibility).to eq 'limited'
|
||||||
|
expect(status.limited_scope).to eq 'mutual'
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'safeguards mentions' do
|
it 'safeguards mentions' do
|
||||||
|
|
97
spec/workers/activitypub/fetch_instance_info_worker_spec.rb
Normal file
97
spec/workers/activitypub/fetch_instance_info_worker_spec.rb
Normal file
|
@ -0,0 +1,97 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
describe ActivityPub::FetchInstanceInfoWorker do
|
||||||
|
subject { described_class.new }
|
||||||
|
|
||||||
|
let(:wellknown_nodeinfo) do
|
||||||
|
{
|
||||||
|
links: [
|
||||||
|
{
|
||||||
|
rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
|
||||||
|
href: 'https://example.com/nodeinfo/2.0',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:nodeinfo) do
|
||||||
|
{
|
||||||
|
version: '2.0',
|
||||||
|
software: {
|
||||||
|
name: 'mastodon',
|
||||||
|
version: '4.2.0-beta1',
|
||||||
|
},
|
||||||
|
protocols: ['activitypub'],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
let(:wellknown_nodeinfo_json) { Oj.dump(wellknown_nodeinfo) }
|
||||||
|
let(:nodeinfo_json) { Oj.dump(nodeinfo) }
|
||||||
|
|
||||||
|
context 'when success' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(status: 200, body: wellknown_nodeinfo_json)
|
||||||
|
stub_request(:get, 'https://example.com/nodeinfo/2.0').to_return(status: 200, body: nodeinfo_json)
|
||||||
|
Fabricate(:account, domain: 'example.com')
|
||||||
|
Instance.refresh
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'performs a mastodon instance' do
|
||||||
|
subject.perform('example.com')
|
||||||
|
|
||||||
|
info = InstanceInfo.find_by(domain: 'example.com')
|
||||||
|
expect(info).to_not be_nil
|
||||||
|
expect(info.software).to eq 'mastodon'
|
||||||
|
expect(info.version).to eq '4.2.0-beta1'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when update' do
|
||||||
|
let(:new_nodeinfo) do
|
||||||
|
{
|
||||||
|
version: '2.0',
|
||||||
|
software: {
|
||||||
|
name: 'mastodon',
|
||||||
|
version: '4.2.0-beta3',
|
||||||
|
},
|
||||||
|
protocols: ['activitypub'],
|
||||||
|
}
|
||||||
|
end
|
||||||
|
let(:new_nodeinfo_json) { Oj.dump(new_nodeinfo) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(status: 200, body: wellknown_nodeinfo_json)
|
||||||
|
Fabricate(:account, domain: 'example.com')
|
||||||
|
Instance.refresh
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'performs a mastodon instance' do
|
||||||
|
stub_request(:get, 'https://example.com/nodeinfo/2.0').to_return(status: 200, body: nodeinfo_json)
|
||||||
|
subject.perform('example.com')
|
||||||
|
stub_request(:get, 'https://example.com/nodeinfo/2.0').to_return(status: 200, body: new_nodeinfo_json)
|
||||||
|
subject.perform('example.com')
|
||||||
|
|
||||||
|
info = InstanceInfo.find_by(domain: 'example.com')
|
||||||
|
expect(info).to_not be_nil
|
||||||
|
expect(info.software).to eq 'mastodon'
|
||||||
|
expect(info.version).to eq '4.2.0-beta3'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context 'when failed' do
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(status: 404)
|
||||||
|
Fabricate(:account, domain: 'example.com')
|
||||||
|
Instance.refresh
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'performs a mastodon instance' do
|
||||||
|
expect { subject.perform('example.com') }.to raise_error(ActivityPub::FetchInstanceInfoWorker::RequestError, 'Request for example.com returned HTTP 404')
|
||||||
|
|
||||||
|
info = InstanceInfo.find_by(domain: 'example.com')
|
||||||
|
expect(info).to be_nil
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,6 +5,12 @@ require 'rails_helper'
|
||||||
describe Scheduler::UpdateInstanceInfoScheduler do
|
describe Scheduler::UpdateInstanceInfoScheduler do
|
||||||
let(:worker) { described_class.new }
|
let(:worker) { described_class.new }
|
||||||
|
|
||||||
|
before do
|
||||||
|
stub_request(:get, 'https://example.com/.well-known/nodeinfo').to_return(status: 200, body: '{}')
|
||||||
|
Fabricate(:account, domain: 'example.com')
|
||||||
|
Instance.refresh
|
||||||
|
end
|
||||||
|
|
||||||
describe 'perform' do
|
describe 'perform' do
|
||||||
it 'runs without error' do
|
it 'runs without error' do
|
||||||
expect { worker.perform }.to_not raise_error
|
expect { worker.perform }.to_not raise_error
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue