Merge branch 'kb_lts' into kb_development

This commit is contained in:
KMY 2023-09-21 15:58:48 +09:00
commit 5177e8d07f
36 changed files with 337 additions and 98 deletions

View file

@ -33,58 +33,10 @@ Sudachiインストール終了後、追加で`/etc/elasticsearch/sudachi/config
} }
``` ```
## 新規インストールの場合 ## インストール手順
1. 本家Mastodonとセットアップ手順はほとんど一緒です。kmyblueが独自に必須ソフトウェアを追加したわけではありません。ただしkmyblueはMastodonの開発中コードを取り込んでいるので、Rubyなどのバージョンアップ作業が必要になる場合があります。Mastodon公式のセットアップ手順を盲信せず、画面の指示に従ってインストールを進めてください。CloudFlareを組み合わせてセットアップしたとき、サーバーに接続すると400が出るなどのトラブルが出ることがありますが、大抵はMastodon本家由来のトラブルだと思われるので基本サポートはしません [Wiki](https://github.com/kmycode/mastodon/wiki/Installation)を参照してください
2. ただひとつ差異があります。Gitリポジトリはこのkmyblueに向けてください。`kb_development`ブランチの最新コミットではなく、`kb`で始まる最新のタグを取り込むことを強くおすすめします
## 本家Mastodonからのマイグレーションの場合 ## アップデート手順
kmyblueから本家Mastodonに戻りたい場合もあると思いますので、**必ずデータベースのバックアップをとってください**。 [Wiki](https://github.com/kmycode/mastodon/wiki/Updation)を参照してください
1. kmyblueのリリースートに、kmyblueバージョンに対応した本家Mastodonのバージョンが記載されています。それを参照して、まず本家Mastodonをそのバージョンまでバージョンアップしてください
2. Gitのリモートにkmyblueを追加して、そのままチェックアウトしてください
3. データベースのマイグレーションなどを行ってください
```
sudo systemctl stop mastodon-*
bundle install
yarn install
RAILS_ENV=production bin/rails db:migrate
RAILS_ENV=production bin/rails assets:clobber
RAILS_ENV=production bin/rails assets:precompile
# ElasticSearchを使用する場合
RAILS_ENV=production bin/tootctl search deploy
RAILS_ENV=production bin/tootctl cache clear
sudo systemctl start mastodon-web mastodon-streaming mastodon-sidekiq
```
## kmyblueのバージョンをアップデートする
リリースノートを参照して、自分に必要な作業を特定してください。面倒な場合は毎回全部実行してしまっても問題ありません。(プリコンパイルが失敗する可能性があるのでご注意ください)
```
# Rubyパッケージアップデート
bundle intall
# JSパッケージアップデート
yarn install
# DBマイグレーション
RAILS_ENV=production bin/rails db:migrate
# プリコンパイル
# うまくいかない場合エラーは出ないのにWeb表示が崩れるはclobberしてからprecompile
# それでもうまくいかない場合はsudo systemctl stop mastodon-webしてから試す
# それでもうまくいかない場合はサーバーOSを再起動してから試す
RAILS_ENV=production bin/rails assets:clobber # プリコンパイルがうまくいかない場合
RAILS_ENV=production bin/rails assets:precompile
# サーバー再起動
sudo systemctl restart mastodon-web
sudo systemctl restart mastodon-streaming
sudo systemctl restart mastodon-sidekiq
```

View file

@ -33,7 +33,7 @@ class Api::V1::Statuses::EmojiReactionedByAccountsController < Api::BaseControll
def paginated_emoji_reactions def paginated_emoji_reactions
EmojiReaction.paginate_by_max_id( EmojiReaction.paginate_by_max_id(
limit_param(1000), # limit_param(DEFAULT_ACCOUNTS_LIMIT), limit_param(DEFAULT_ACCOUNTS_LIMIT),
params[:max_id], params[:max_id],
params[:since_id] params[:since_id]
) )

View file

@ -785,11 +785,12 @@ export function insertExpirationCompose(position, data) {
}; };
} }
export function insertReferenceCompose(position, url) { export function insertReferenceCompose(position, url, attributeType) {
return { return {
type: COMPOSE_REFERENCE_INSERT, type: COMPOSE_REFERENCE_INSERT,
position, position,
url, url,
attributeType,
}; };
} }

View file

@ -51,6 +51,10 @@ export const EMOJI_REACTIONS_FETCH_REQUEST = 'EMOJI_REACTIONS_FETCH_REQUEST';
export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS'; export const EMOJI_REACTIONS_FETCH_SUCCESS = 'EMOJI_REACTIONS_FETCH_SUCCESS';
export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL'; export const EMOJI_REACTIONS_FETCH_FAIL = 'EMOJI_REACTIONS_FETCH_FAIL';
export const EMOJI_REACTIONS_EXPAND_REQUEST = 'EMOJI_REACTIONS_EXPAND_REQUEST';
export const EMOJI_REACTIONS_EXPAND_SUCCESS = 'EMOJI_REACTIONS_EXPAND_SUCCESS';
export const EMOJI_REACTIONS_EXPAND_FAIL = 'EMOJI_REACTIONS_EXPAND_FAIL';
export const PIN_REQUEST = 'PIN_REQUEST'; export const PIN_REQUEST = 'PIN_REQUEST';
export const PIN_SUCCESS = 'PIN_SUCCESS'; export const PIN_SUCCESS = 'PIN_SUCCESS';
export const PIN_FAIL = 'PIN_FAIL'; export const PIN_FAIL = 'PIN_FAIL';
@ -547,8 +551,9 @@ export function fetchEmojiReactions(id) {
dispatch(fetchEmojiReactionsRequest(id)); dispatch(fetchEmojiReactionsRequest(id));
api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => { api(getState).get(`/api/v1/statuses/${id}/emoji_reactioned_by`).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map((er) => er.account))); dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
dispatch(fetchEmojiReactionsSuccess(id, response.data)); dispatch(fetchEmojiReactionsSuccess(id, response.data, next ? next.uri : null));
}).catch(error => { }).catch(error => {
dispatch(fetchEmojiReactionsFail(id, error)); dispatch(fetchEmojiReactionsFail(id, error));
}); });
@ -562,11 +567,12 @@ export function fetchEmojiReactionsRequest(id) {
}; };
} }
export function fetchEmojiReactionsSuccess(id, accounts) { export function fetchEmojiReactionsSuccess(id, accounts, next) {
return { return {
type: EMOJI_REACTIONS_FETCH_SUCCESS, type: EMOJI_REACTIONS_FETCH_SUCCESS,
id, id,
accounts, accounts,
next,
}; };
} }
@ -577,6 +583,48 @@ export function fetchEmojiReactionsFail(id, error) {
}; };
} }
export function expandEmojiReactions(id) {
return (dispatch, getState) => {
const url = getState().getIn(['user_lists', 'emoji_reactioned_by', id, 'next']);
if (url === null) {
return;
}
dispatch(expandEmojiReactionsRequest(id));
api(getState).get(url).then(response => {
const next = getLinks(response).refs.find(link => link.rel === 'next');
dispatch(importFetchedAccounts(response.data.map((er) => er.account)));
dispatch(expandEmojiReactionsSuccess(id, response.data, next ? next.uri : null));
}).catch(error => dispatch(expandEmojiReactionsFail(id, error)));
};
}
export function expandEmojiReactionsRequest(id) {
return {
type: EMOJI_REACTIONS_EXPAND_REQUEST,
id,
};
}
export function expandEmojiReactionsSuccess(id, accounts, next) {
return {
type: EMOJI_REACTIONS_EXPAND_SUCCESS,
id,
accounts,
next,
};
}
export function expandEmojiReactionsFail(id, error) {
return {
type: EMOJI_REACTIONS_EXPAND_FAIL,
id,
error,
};
}
export function fetchStatusReferences(id) { export function fetchStatusReferences(id) {
return (dispatch, getState) => { return (dispatch, getState) => {
dispatch(fetchStatusReferencesRequest(id)); dispatch(fetchStatusReferencesRequest(id));

View file

@ -52,6 +52,7 @@ const messages = defineMessages({
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
reference: { id: 'status.reference', defaultMessage: 'Add reference' }, reference: { id: 'status.reference', defaultMessage: 'Add reference' },
quote: { id: 'status.quote', defaultMessage: 'Add ref (quote in other servers)' },
hide: { id: 'status.hide', defaultMessage: 'Hide post' }, hide: { id: 'status.hide', defaultMessage: 'Hide post' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
@ -97,6 +98,8 @@ class StatusActionBar extends ImmutablePureComponent {
onBookmarkCategoryAdder: PropTypes.func, onBookmarkCategoryAdder: PropTypes.func,
onFilter: PropTypes.func, onFilter: PropTypes.func,
onAddFilter: PropTypes.func, onAddFilter: PropTypes.func,
onReference: PropTypes.func,
onQuote: PropTypes.func,
onInteractionModal: PropTypes.func, onInteractionModal: PropTypes.func,
withDismiss: PropTypes.bool, withDismiss: PropTypes.bool,
withCounters: PropTypes.bool, withCounters: PropTypes.bool,
@ -271,6 +274,10 @@ class StatusActionBar extends ImmutablePureComponent {
this.props.onReference(this.props.status); this.props.onReference(this.props.status);
}; };
handleQuote = () => {
this.props.onQuote(this.props.status);
};
handleHideClick = () => { handleHideClick = () => {
this.props.onFilter(); this.props.onFilter();
}; };
@ -316,6 +323,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (publicStatus) { if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference }); menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
} }
menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal }); menu.push({ text: intl.formatMessage(status.get('bookmarked') ? messages.removeBookmark : messages.bookmark), action: this.handleBookmarkClickOriginal });

View file

@ -203,7 +203,11 @@ const mapDispatchToProps = (dispatch, { intl, contextType }) => ({
}, },
onReference (status) { onReference (status) {
dispatch(insertReferenceCompose(0, status.get('url'))); dispatch(insertReferenceCompose(0, status.get('url'), 'BT'));
},
onQuote (status) {
dispatch(insertReferenceCompose(0, status.get('url'), 'QT'));
}, },
onTranslate (status) { onTranslate (status) {

View file

@ -8,7 +8,9 @@ import ImmutablePropTypes from 'react-immutable-proptypes';
import ImmutablePureComponent from 'react-immutable-pure-component'; import ImmutablePureComponent from 'react-immutable-pure-component';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { fetchEmojiReactions } from 'mastodon/actions/interactions'; import { debounce } from 'lodash';
import { fetchEmojiReactions, expandEmojiReactions } from 'mastodon/actions/interactions';
import ColumnHeader from 'mastodon/components/column_header'; import ColumnHeader from 'mastodon/components/column_header';
import { Icon } from 'mastodon/components/icon'; import { Icon } from 'mastodon/components/icon';
import ScrollableList from 'mastodon/components/scrollable_list'; import ScrollableList from 'mastodon/components/scrollable_list';
@ -25,7 +27,9 @@ const messages = defineMessages({
const mapStateToProps = (state, props) => { const mapStateToProps = (state, props) => {
return { return {
accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId]), accountIds: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'items']),
hasMore: !!state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'next']),
isLoading: state.getIn(['user_lists', 'emoji_reactioned_by', props.params.statusId, 'isLoading'], true),
}; };
}; };
@ -35,6 +39,8 @@ class EmojiReactions extends ImmutablePureComponent {
params: PropTypes.object.isRequired, params: PropTypes.object.isRequired,
dispatch: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired,
accountIds: ImmutablePropTypes.list, accountIds: ImmutablePropTypes.list,
hasMore: PropTypes.bool,
isLoading: PropTypes.bool,
multiColumn: PropTypes.bool, multiColumn: PropTypes.bool,
intl: PropTypes.object.isRequired, intl: PropTypes.object.isRequired,
}; };
@ -45,18 +51,16 @@ class EmojiReactions extends ImmutablePureComponent {
} }
} }
componentWillReceiveProps (nextProps) {
if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
this.props.dispatch(fetchEmojiReactions(nextProps.params.statusId));
}
}
handleRefresh = () => { handleRefresh = () => {
this.props.dispatch(fetchEmojiReactions(this.props.params.statusId)); this.props.dispatch(fetchEmojiReactions(this.props.params.statusId));
}; };
handleLoadMore = debounce(() => {
this.props.dispatch(expandEmojiReactions(this.props.params.statusId));
}, 300, { leading: true });
render () { render () {
const { intl, accountIds, multiColumn } = this.props; const { intl, accountIds, hasMore, isLoading, multiColumn } = this.props;
if (!accountIds) { if (!accountIds) {
return ( return (
@ -68,7 +72,7 @@ class EmojiReactions extends ImmutablePureComponent {
let groups = {}; let groups = {};
for (const emoji_reaction of accountIds) { for (const emoji_reaction of accountIds) {
const key = emoji_reaction.account.id; const key = emoji_reaction.account_id;
const value = emoji_reaction; const value = emoji_reaction;
if (!groups[key]) groups[key] = [value]; if (!groups[key]) groups[key] = [value];
else groups[key].push(value); else groups[key].push(value);
@ -82,12 +86,15 @@ class EmojiReactions extends ImmutablePureComponent {
showBackButton showBackButton
multiColumn={multiColumn} multiColumn={multiColumn}
extraButton={( extraButton={(
<button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleRefresh}><Icon id='refresh' /></button> <button type='button' className='column-header__button' title={intl.formatMessage(messages.refresh)} aria-label={intl.formatMessage(messages.refresh)} onClick={this.handleLoadMore}><Icon id='refresh' /></button>
)} )}
/> />
<ScrollableList <ScrollableList
scrollKey='emoji_reactions' scrollKey='emoji_reactions'
onLoadMore={this.handleLoadMore}
hasMore={hasMore}
isLoading={isLoading}
emptyMessage={emptyMessage} emptyMessage={emptyMessage}
bindToDocument={!multiColumn} bindToDocument={!multiColumn}
> >

View file

@ -46,6 +46,7 @@ const messages = defineMessages({
admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' }, admin_domain: { id: 'status.admin_domain', defaultMessage: 'Open moderation interface for {domain}' },
copy: { id: 'status.copy', defaultMessage: 'Copy link to post' }, copy: { id: 'status.copy', defaultMessage: 'Copy link to post' },
reference: { id: 'status.reference', defaultMessage: 'Add reference' }, reference: { id: 'status.reference', defaultMessage: 'Add reference' },
quote: { id: 'status.quote', defaultMessage: 'Add ref (quote in other servers)' },
blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' }, blockDomain: { id: 'account.block_domain', defaultMessage: 'Block domain {domain}' },
unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' }, unblockDomain: { id: 'account.unblock_domain', defaultMessage: 'Unblock domain {domain}' },
unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' }, unmute: { id: 'account.unmute', defaultMessage: 'Unmute @{name}' },
@ -74,6 +75,7 @@ class ActionBar extends PureComponent {
onFavourite: PropTypes.func.isRequired, onFavourite: PropTypes.func.isRequired,
onEmojiReact: PropTypes.func.isRequired, onEmojiReact: PropTypes.func.isRequired,
onReference: PropTypes.func.isRequired, onReference: PropTypes.func.isRequired,
onQuote: PropTypes.func.isRequired,
onBookmark: PropTypes.func.isRequired, onBookmark: PropTypes.func.isRequired,
onBookmarkCategoryAdder: PropTypes.func.isRequired, onBookmarkCategoryAdder: PropTypes.func.isRequired,
onDelete: PropTypes.func.isRequired, onDelete: PropTypes.func.isRequired,
@ -208,6 +210,10 @@ class ActionBar extends PureComponent {
this.props.onReference(this.props.status); this.props.onReference(this.props.status);
}; };
handleQuote = () => {
this.props.onQuote(this.props.status);
};
handleEmojiPick = (data) => { handleEmojiPick = (data) => {
this.props.onEmojiReact(this.props.status, data); this.props.onEmojiReact(this.props.status, data);
}; };
@ -248,6 +254,7 @@ class ActionBar extends PureComponent {
if (publicStatus) { if (publicStatus) {
menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference }); menu.push({ text: intl.formatMessage(messages.reference), action: this.handleReference });
menu.push({ text: intl.formatMessage(messages.quote), action: this.handleQuote });
} }
menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick }); menu.push({ text: intl.formatMessage(messages.bookmark_category), action: this.handleBookmarkCategoryAdderClick });

View file

@ -363,7 +363,11 @@ class Status extends ImmutablePureComponent {
}; };
handleReference = (status) => { handleReference = (status) => {
this.props.dispatch(insertReferenceCompose(0, status.get('url'))); this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'BT'));
};
handleQuote = (status) => {
this.props.dispatch(insertReferenceCompose(0, status.get('url'), 'QT'));
}; };
handleBookmarkClick = (status) => { handleBookmarkClick = (status) => {
@ -750,6 +754,7 @@ class Status extends ImmutablePureComponent {
onReblog={this.handleReblogClick} onReblog={this.handleReblogClick}
onReblogForceModal={this.handleReblogForceModalClick} onReblogForceModal={this.handleReblogForceModalClick}
onReference={this.handleReference} onReference={this.handleReference}
onQuote={this.handleQuote}
onBookmark={this.handleBookmarkClick} onBookmark={this.handleBookmarkClick}
onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick} onBookmarkCategoryAdder={this.handleBookmarkCategoryAdderClick}
onDelete={this.handleDeleteClick} onDelete={this.handleDeleteClick}

View file

@ -683,6 +683,7 @@
"status.open": "Expand this post", "status.open": "Expand this post",
"status.pin": "Pin on profile", "status.pin": "Pin on profile",
"status.pinned": "Pinned post", "status.pinned": "Pinned post",
"status.quote": "Ref (quote in other servers)",
"status.read_more": "Read more", "status.read_more": "Read more",
"status.reblog": "Boost", "status.reblog": "Boost",
"status.reblog_private": "Boost with original visibility", "status.reblog_private": "Boost with original visibility",

View file

@ -769,6 +769,7 @@
"status.open": "詳細を表示", "status.open": "詳細を表示",
"status.pin": "プロフィールに固定表示", "status.pin": "プロフィールに固定表示",
"status.pinned": "固定された投稿", "status.pinned": "固定された投稿",
"status.quote": "参照 (他サーバーで引用扱い)",
"status.read_more": "もっと見る", "status.read_more": "もっと見る",
"status.reblog": "ブースト", "status.reblog": "ブースト",
"status.reblog_private": "ブースト", "status.reblog_private": "ブースト",

View file

@ -252,10 +252,11 @@ const insertExpiration = (state, position, data) => {
}); });
}; };
const insertReference = (state, url) => { const insertReference = (state, url, attributeType) => {
const oldText = state.get('text'); const oldText = state.get('text');
const attribute = attributeType || 'BT';
if (oldText.indexOf(`BT ${url}`) >= 0) { if (oldText.indexOf(`${attribute} ${url}`) >= 0) {
return state; return state;
} }
@ -271,12 +272,12 @@ const insertReference = (state, url) => {
if (oldText.length > 0) { if (oldText.length > 0) {
const lastLine = oldText.slice(oldText.lastIndexOf('\n') + 1, oldText.length - 1); const lastLine = oldText.slice(oldText.lastIndexOf('\n') + 1, oldText.length - 1);
if (lastLine.startsWith('BT ')) { if (lastLine.startsWith(`${attribute} `)) {
newLine = '\n'; newLine = '\n';
} }
} }
const referenceText = `${newLine}BT ${url}`; const referenceText = `${newLine}${attribute} ${url}`;
const text = `${oldText}${referenceText}`; const text = `${oldText}${referenceText}`;
return state.merge({ return state.merge({
@ -526,7 +527,7 @@ export default function compose(state = initialState, action) {
case COMPOSE_EXPIRATION_INSERT: case COMPOSE_EXPIRATION_INSERT:
return insertExpiration(state, action.position, action.data); return insertExpiration(state, action.position, action.data);
case COMPOSE_REFERENCE_INSERT: case COMPOSE_REFERENCE_INSERT:
return insertReference(state, action.url); return insertReference(state, action.url, action.attributeType);
case COMPOSE_UPLOAD_CHANGE_SUCCESS: case COMPOSE_UPLOAD_CHANGE_SUCCESS:
return state return state
.set('is_changing_upload', false) .set('is_changing_upload', false)

View file

@ -57,7 +57,12 @@ import {
FAVOURITES_EXPAND_REQUEST, FAVOURITES_EXPAND_REQUEST,
FAVOURITES_EXPAND_SUCCESS, FAVOURITES_EXPAND_SUCCESS,
FAVOURITES_EXPAND_FAIL, FAVOURITES_EXPAND_FAIL,
EMOJI_REACTIONS_FETCH_REQUEST,
EMOJI_REACTIONS_FETCH_SUCCESS, EMOJI_REACTIONS_FETCH_SUCCESS,
EMOJI_REACTIONS_FETCH_FAIL,
EMOJI_REACTIONS_EXPAND_REQUEST,
EMOJI_REACTIONS_EXPAND_SUCCESS,
EMOJI_REACTIONS_EXPAND_FAIL,
STATUS_REFERENCES_FETCH_SUCCESS, STATUS_REFERENCES_FETCH_SUCCESS,
} from '../actions/interactions'; } from '../actions/interactions';
import { import {
@ -101,12 +106,33 @@ const normalizeList = (state, path, accounts, next) => {
})); }));
}; };
const normalizeEmojiReactionList = (state, path, rows, next) => {
return state.setIn(path, ImmutableMap({
next,
items: ImmutableList(rows.map(normalizeEmojiReactionRow)),
isLoading: false,
}));
};
const appendToList = (state, path, accounts, next) => { const appendToList = (state, path, accounts, next) => {
return state.updateIn(path, map => { return state.updateIn(path, map => {
return map.set('next', next).set('isLoading', false).update('items', list => list.concat(accounts.map(item => item.id))); return map.set('next', next).set('isLoading', false).update('items', list => list.concat(accounts.map(item => item.id)));
}); });
}; };
const appendToEmojiReactionList = (state, path, rows, next) => {
return state.updateIn(path, map => {
return map.set('next', next).set('isLoading', false).update('items', list => list.concat(rows.map(normalizeEmojiReactionRow)));
});
};
const normalizeEmojiReactionRow = (row) => {
const accountId = row.account ? row.account.id : 0;
delete row.account;
row.account_id = accountId;
return row;
};
const normalizeFollowRequest = (state, notification) => { const normalizeFollowRequest = (state, notification) => {
return state.updateIn(['follow_requests', 'items'], list => { return state.updateIn(['follow_requests', 'items'], list => {
return list.filterNot(item => item === notification.account.id).unshift(notification.account.id); return list.filterNot(item => item === notification.account.id).unshift(notification.account.id);
@ -167,8 +193,16 @@ export default function userLists(state = initialState, action) {
case FAVOURITES_FETCH_FAIL: case FAVOURITES_FETCH_FAIL:
case FAVOURITES_EXPAND_FAIL: case FAVOURITES_EXPAND_FAIL:
return state.setIn(['favourited_by', action.id, 'isLoading'], false); return state.setIn(['favourited_by', action.id, 'isLoading'], false);
case EMOJI_REACTIONS_FETCH_REQUEST:
case EMOJI_REACTIONS_EXPAND_REQUEST:
return state.setIn(['emoji_reactioned_by', action.id, 'isLoading'], true);
case EMOJI_REACTIONS_FETCH_FAIL:
case EMOJI_REACTIONS_EXPAND_FAIL:
return state.setIn(['emoji_reactioned_by', action.id, 'isLoading'], false);
case EMOJI_REACTIONS_FETCH_SUCCESS: case EMOJI_REACTIONS_FETCH_SUCCESS:
return state.setIn(['emoji_reactioned_by', action.id], ImmutableList(action.accounts)); return normalizeEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next);
case EMOJI_REACTIONS_EXPAND_SUCCESS:
return appendToEmojiReactionList(state, ['emoji_reactioned_by', action.id], action.accounts, action.next);
case STATUS_REFERENCES_FETCH_SUCCESS: case STATUS_REFERENCES_FETCH_SUCCESS:
return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id))); return state.setIn(['referred_by', action.id], ImmutableList(action.statuses.map(item => item.id)));
case NOTIFICATIONS_UPDATE: case NOTIFICATIONS_UPDATE:

View file

@ -46,6 +46,7 @@ class Form::AdminSettings
receive_other_servers_emoji_reaction receive_other_servers_emoji_reaction
streaming_other_servers_emoji_reaction streaming_other_servers_emoji_reaction
enable_emoji_reaction enable_emoji_reaction
check_lts_version_only
).freeze ).freeze
INTEGER_KEYS = %i( INTEGER_KEYS = %i(
@ -72,6 +73,7 @@ class Form::AdminSettings
receive_other_servers_emoji_reaction receive_other_servers_emoji_reaction
streaming_other_servers_emoji_reaction streaming_other_servers_emoji_reaction
enable_emoji_reaction enable_emoji_reaction
check_lts_version_only
).freeze ).freeze
UPLOAD_KEYS = %i( UPLOAD_KEYS = %i(

View file

@ -36,5 +36,13 @@ class SoftwareUpdate < ApplicationRecord
def urgent_pending? def urgent_pending?
pending_to_a.any?(&:urgent?) pending_to_a.any?(&:urgent?)
end end
def major_pending?
pending_to_a.any?(&:major_type?)
end
def patch_pending?
pending_to_a.any?(&:patch_type?)
end
end end
end end

View file

@ -226,6 +226,10 @@ class Status < ApplicationRecord
!reblog_of_id.nil? !reblog_of_id.nil?
end end
def quote
reference_objects.where(attribute_type: 'QT').first&.target_status
end
def within_realtime_window? def within_realtime_window?
created_at >= REAL_TIME_WINDOW.ago created_at >= REAL_TIME_WINDOW.ago
end end

View file

@ -17,7 +17,6 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
attribute :quote_uri, if: :quote? attribute :quote_uri, if: :quote?
attribute :misskey_quote, key: :_misskey_quote, if: :quote? attribute :misskey_quote, key: :_misskey_quote, if: :quote?
attribute :misskey_content, key: :_misskey_content, if: :quote?
has_many :virtual_attachments, key: :attachment has_many :virtual_attachments, key: :attachment
has_many :virtual_tags, key: :tag has_many :virtual_tags, key: :tag
@ -172,21 +171,21 @@ class ActivityPub::NoteSerializer < ActivityPub::Serializer
end end
def quote? def quote?
object.references.count == 1 && object.account.user&.settings&.[]('single_ref_to_quote') @quote ||= (object.reference_objects.count == 1 && object.account.user&.settings&.[]('single_ref_to_quote')) || object.reference_objects.where(attribute_type: 'QT').count == 1
end
def quote_post
@quote_post ||= object.quote || object.references.first
end end
def quote_uri def quote_uri
ActivityPub::TagManager.instance.uri_for(object.references.first) ActivityPub::TagManager.instance.uri_for(quote_post)
end end
def misskey_quote def misskey_quote
quote_uri quote_uri
end end
def misskey_content
object.text
end
def poll_options def poll_options
object.preloadable_poll.loaded_options object.preloadable_poll.loaded_options
end end

View file

@ -47,8 +47,8 @@ class DeliveryAntennaService
end end
antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago)) antennas = antennas.where(account_id: Account.without_suspended.joins(:user).select('accounts.id').where('users.current_sign_in_at > ?', User::ACTIVE_DURATION.ago))
antennas = antennas.where(account: @status.account.followers) if [:public, :public_unlisted, :login, :limited].exclude?(@status.visibility.to_sym) && !@status.public_searchability? antennas = antennas.where(account: @status.account.followers) if followers_only?
antennas = antennas.where(account: @status.mentioned_accounts) if @status.visibility.to_sym == :limited antennas = antennas.where(account: @status.mentioned_accounts) if mentioned_users_only?
antennas = antennas.where(with_media_only: false) unless @status.with_media? antennas = antennas.where(with_media_only: false) unless @status.with_media?
antennas = antennas.where(ignore_reblog: false) if @status.reblog? antennas = antennas.where(ignore_reblog: false) if @status.reblog?
antennas = antennas.where(stl: false, ltl: false) antennas = antennas.where(stl: false, ltl: false)
@ -116,6 +116,26 @@ class DeliveryAntennaService
collection.deliver! collection.deliver!
end end
def followers_only?
case @status.visibility.to_sym
when :public, :public_unlisted, :login, :limited
false
when :unlisted
if @status.local?
!@status.public_searchability?
else
info = InstanceInfo.find_by(domain: @status.account.domain)
info&.software == 'firefish' || !@status.public_searchability?
end
else
true
end
end
def mentioned_users_only?
@status.visibility.to_sym == :limited
end
class AntennaCollection class AntennaCollection
def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter def initialize(status, update, stl_home = false) # rubocop:disable Style/OptionalBooleanParameter
@status = status @status = status

View file

@ -58,11 +58,11 @@ class ProcessReferencesService < BaseService
private private
def references def references
@references = @reference_parameters + scan_text! @references ||= @reference_parameters + scan_text!
end end
def old_references def old_references
@old_references = @status.references.pluck(:id) @old_references ||= @status.references.pluck(:id)
end end
def added_references def added_references
@ -112,7 +112,7 @@ class ProcessReferencesService < BaseService
def create_notifications! def create_notifications!
return if @added_objects.blank? return if @added_objects.blank?
local_reference_objects = @added_objects.filter { |ref| ref.target_status.account.local? } local_reference_objects = @added_objects.filter { |ref| ref.target_status.account.local? && StatusPolicy.new(ref.target_status.account, ref.status).show? }
return if local_reference_objects.empty? return if local_reference_objects.empty?
LocalNotificationWorker.push_bulk(local_reference_objects) do |ref| LocalNotificationWorker.push_bulk(local_reference_objects) do |ref|

View file

@ -27,11 +27,18 @@ class SoftwareUpdateCheckService < BaseService
end end
def api_url def api_url
ENV.fetch('UPDATE_CHECK_URL', 'https://api.joinmastodon.org/update-check') ENV.fetch('UPDATE_CHECK_URL', 'https://kmy.blue/update-check')
end end
def version def version
@version ||= Mastodon::Version.to_s.split('+')[0] if ENV.fetch('UPDATE_CHECK_SOURCE', 'kmyblue') == 'kmyblue'
@version = "#{Mastodon::Version.kmyblue_major}.#{Mastodon::Version.kmyblue_minor}"
@version += '-lts' if Setting.check_lts_version_only
else
@version = Mastodon::Version.to_s.split('+')[0]
end
@version
end end
def process_update_notices!(update_notices) def process_update_notices!(update_notices)

View file

@ -55,6 +55,9 @@
.fields-group .fields-group
= f.input :authorized_fetch, as: :boolean, wrapper: :with_label, label: t('admin.settings.security.authorized_fetch'), warning_hint: authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil, hint: t('admin.settings.security.authorized_fetch_hint'), disabled: authorized_fetch_overridden?, recommended: authorized_fetch_overridden? ? :overridden : nil = f.input :authorized_fetch, as: :boolean, wrapper: :with_label, label: t('admin.settings.security.authorized_fetch'), warning_hint: authorized_fetch_overridden? ? t('admin.settings.security.authorized_fetch_overridden_hint') : nil, hint: t('admin.settings.security.authorized_fetch_hint'), disabled: authorized_fetch_overridden?, recommended: authorized_fetch_overridden? ? :overridden : nil
.fields-group
= f.input :check_lts_version_only, as: :boolean, wrapper: :with_label, kmyblue: true, hint: false
%h4= t('admin.settings.discovery.follow_recommendations') %h4= t('admin.settings.discovery.follow_recommendations')
.fields-group .fields-group

View file

@ -31,7 +31,7 @@
.fields-row .fields-row
.fields-group.fields-row__column.fields-row__column-12 .fields-group.fields-row__column.fields-row__column-12
= ff.input :default_searchability, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability') = ff.input :default_searchability, collection: Status.selectable_searchabilities, wrapper: :with_label, kmyblue: true, include_blank: false, label_method: lambda { |searchability| safe_join([I18n.t("statuses.searchabilities.#{searchability}"), I18n.t("statuses.searchabilities.#{searchability}_long")], ' - ') }, required: false, hint: false, label: I18n.t('simple_form.labels.defaults.setting_default_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_default_searchability')
.fields-group .fields-group
= ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability') = ff.input :disallow_unlisted_public_searchability, wrapper: :with_label, kmyblue: true, label: I18n.t('simple_form.labels.defaults.setting_disallow_unlisted_public_searchability'), hint: I18n.t('simple_form.hints.defaults.setting_disallow_unlisted_public_searchability')

View file

@ -997,6 +997,9 @@ en:
other: Used by %{count} people over the last week other: Used by %{count} people over the last week
title: Trends title: Trends
trending: Trending trending: Trending
update-pendings:
major: Major update pending
patch: Patch update pending
warning_presets: warning_presets:
add_new: Add new add_new: Add new
delete: Delete delete: Delete

View file

@ -991,6 +991,9 @@ ja:
other: 週間%{count}人に使用されました other: 週間%{count}人に使用されました
title: トレンド title: トレンド
trending: トレンド trending: トレンド
update_pendings:
major: メジャーアップデートあり
patch: パッチアップデートあり
warning_presets: warning_presets:
add_new: 追加 add_new: 追加
delete: 削除 delete: 削除

View file

@ -62,6 +62,7 @@ en:
setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts) setting_aggregate_reblogs: Do not show new boosts for posts that have been recently boosted (only affects newly-received boosts)
setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon setting_always_send_emails: Normally e-mail notifications won't be sent when you are actively using Mastodon
setting_bookmark_category_needed: When removing from all category, unbookmarked automatically setting_bookmark_category_needed: When removing from all category, unbookmarked automatically
setting_default_searchability: kmyblue・Fedibirdでは検索許可設定に基づき検索されます。Misskeyでは当設定に関係なく、全ての公開・ローカル公開・未収載投稿が検索されます。Mastodon・Firefishでは検索許可の代わりにプロフィール設定の「公開投稿を他のサーバーで自由に検索できるようにする」設定が適用されます
setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click setting_default_sensitive: Sensitive media is hidden by default and can be revealed with a click
setting_display_media_default: Hide media marked as sensitive setting_display_media_default: Hide media marked as sensitive
setting_display_media_hide_all: Always hide media setting_display_media_hide_all: Always hide media
@ -315,6 +316,7 @@ en:
activity_api_enabled: Publish aggregate statistics about user activity in the API activity_api_enabled: Publish aggregate statistics about user activity in the API
backups_retention_period: User archive retention period backups_retention_period: User archive retention period
bootstrap_timeline_accounts: Always recommend these accounts to new users bootstrap_timeline_accounts: Always recommend these accounts to new users
check_lts_version_only: Check kmyblue LTS version only when update check
closed_registrations_message: Custom message when sign-ups are not available closed_registrations_message: Custom message when sign-ups are not available
content_cache_retention_period: Content cache retention period content_cache_retention_period: Content cache retention period
custom_css: Custom CSS custom_css: Custom CSS

View file

@ -65,6 +65,7 @@ ja:
setting_always_send_emails: 通常、Mastodon からメール通知は行われません。 setting_always_send_emails: 通常、Mastodon からメール通知は行われません。
setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります setting_bookmark_category_needed: すべてのカテゴリから削除したとき、ブックマークが自動で外れるようになります
setting_boost_modal: ブーストの公開範囲が指定できるようになります setting_boost_modal: ブーストの公開範囲が指定できるようになります
setting_default_searchability: kmyblue・Fedibirdでは検索許可設定に基づき検索されます。Misskeyでは当設定に関係なく、全ての公開・ローカル公開・未収載投稿が検索されます。Mastodon・Firefishでは検索許可の代わりにプロフィール設定の「公開投稿を他のサーバーで自由に検索できるようにする」設定が適用されます
setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります setting_default_sensitive: 閲覧注意状態のメディアはデフォルトでは内容が伏せられ、クリックして初めて閲覧できるようになります
setting_disallow_unlisted_public_searchability: この設定を有効にすると、未収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります setting_disallow_unlisted_public_searchability: この設定を有効にすると、未収載投稿と検索範囲「誰でも」は両立できず不特定多数からの検索が不可になります。Fedibirdと同じ挙動になります
setting_display_media_default: 閲覧注意としてマークされたメディアは隠す setting_display_media_default: 閲覧注意としてマークされたメディアは隠す
@ -330,6 +331,7 @@ ja:
activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する activity_api_enabled: APIでユーザーアクティビティに関する集計統計を公開する
backups_retention_period: ユーザーアーカイブの保持期間 backups_retention_period: ユーザーアーカイブの保持期間
bootstrap_timeline_accounts: おすすめユーザーに常に表示するアカウント bootstrap_timeline_accounts: おすすめユーザーに常に表示するアカウント
check_lts_version_only: 更新チェックの時、LTSバージョンのみ確認する
closed_registrations_message: アカウント作成を停止している時のカスタムメッセージ closed_registrations_message: アカウント作成を停止している時のカスタムメッセージ
content_cache_retention_period: コンテンツキャッシュの保持期間 content_cache_retention_period: コンテンツキャッシュの保持期間
custom_css: カスタムCSS custom_css: カスタムCSS

View file

@ -4,7 +4,11 @@ SimpleNavigation::Configuration.run do |navigation|
navigation.items do |n| navigation.items do |n|
n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path n.item :web, safe_join([fa_icon('chevron-left fw'), t('settings.back')]), root_path
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.critical_update_pending')]), admin_software_updates_path, if: -> { ENV['UPDATE_CHECK_URL'] != '' && current_user.can?(:view_devops) && SoftwareUpdate.urgent_pending? }, html: { class: 'warning' } if ENV['UPDATE_CHECK_URL'] != '' && current_user.can?(:view_devops)
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.critical_update_pending')]), admin_software_updates_path, if: -> { SoftwareUpdate.urgent_pending? }, html: { class: 'warning' }
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.update_pendings.major')]), admin_software_updates_path, if: -> { !SoftwareUpdate.urgent_pending? && SoftwareUpdate.major_pending? }, html: { class: 'warning' }
n.item :software_updates, safe_join([fa_icon('exclamation-circle fw'), t('admin.update_pendings.patch')]), admin_software_updates_path, if: -> { !SoftwareUpdate.urgent_pending? && SoftwareUpdate.patch_pending? }, html: { class: 'warning' }
end
n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification|/settings/privacy} n.item :profile, safe_join([fa_icon('user fw'), t('settings.profile')]), settings_profile_path, if: -> { current_user.functional? }, highlights_on: %r{/settings/profile|/settings/featured_tags|/settings/verification|/settings/privacy}

View file

@ -41,6 +41,7 @@ defaults: &defaults
receive_other_servers_emoji_reaction: false receive_other_servers_emoji_reaction: false
streaming_other_servers_emoji_reaction: false streaming_other_servers_emoji_reaction: false
enable_emoji_reaction: true enable_emoji_reaction: true
check_lts_version_only: true
development: development:
<<: *defaults <<: *defaults

View file

@ -18,7 +18,7 @@ class AddUniqueIndexOnPreviewCardsStatuses < ActiveRecord::Migration[6.1]
def supports_concurrent_reindex? def supports_concurrent_reindex?
@supports_concurrent_reindex ||= begin @supports_concurrent_reindex ||= begin
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
version >= 12_000 version >= 120_000
end end
end end

View file

@ -202,7 +202,7 @@ module Mastodon
def supports_add_column_with_default? def supports_add_column_with_default?
version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
version >= 11_000 version >= 110_000
end end
# Adds a foreign key with only minimal locking on the tables involved. # Adds a foreign key with only minimal locking on the tables involved.

View file

@ -62,7 +62,11 @@ module Mastodon
end end
def gem_version def gem_version
@gem_version ||= Gem::Version.new(to_s.split('+')[0]) @gem_version ||= if ENV.fetch('UPDATE_CHECK_SOURCE', 'kmyblue') == 'kmyblue'
Gem::Version.new("#{kmyblue_major}.#{kmyblue_minor}")
else
Gem::Version.new(to_s.split('+')[0])
end
end end
def repository def repository

View file

@ -3,6 +3,9 @@
User-agent: GPTBot User-agent: GPTBot
Disallow: / Disallow: /
User-agent: CCBot
Disallow: /
User-agent: * User-agent: *
Disallow: /media_proxy/ Disallow: /media_proxy/
Disallow: /interact/ Disallow: /interact/

View file

@ -73,7 +73,6 @@ describe ActivityPub::NoteSerializer do
expect(subject['quoteUri']).to_not be_nil expect(subject['quoteUri']).to_not be_nil
expect(subject['quoteUri']).to eq referred.uri expect(subject['quoteUri']).to eq referred.uri
expect(subject['_misskey_quote']).to eq referred.uri expect(subject['_misskey_quote']).to eq referred.uri
expect(subject['_misskey_content']).to eq referred.text
expect(subject['references']['first']['items']).to include referred.uri expect(subject['references']['first']['items']).to include referred.uri
end end
end end

View file

@ -12,6 +12,7 @@ RSpec.describe DeliveryAntennaService, type: :service do
let(:domain) { nil } let(:domain) { nil }
let(:spoiler_text) { '' } let(:spoiler_text) { '' }
let(:tags) { Tag.find_or_create_by_names(['hoge']) } let(:tags) { Tag.find_or_create_by_names(['hoge']) }
let(:software) { nil }
let(:status) do let(:status) do
url = domain.present? ? 'https://example.com/status' : nil url = domain.present? ? 'https://example.com/status' : nil
status = Fabricate(:status, account: alice, spoiler_text: spoiler_text, visibility: visibility, searchability: searchability, text: 'Hello my body #hoge', url: url) status = Fabricate(:status, account: alice, spoiler_text: spoiler_text, visibility: visibility, searchability: searchability, text: 'Hello my body #hoge', url: url)
@ -30,6 +31,8 @@ RSpec.describe DeliveryAntennaService, type: :service do
let(:mode) { :home } let(:mode) { :home }
before do before do
Fabricate(:instance_info, domain: domain, software: software) if domain.present? && software.present?
bob.follow!(alice) bob.follow!(alice)
alice.block!(ohagi) alice.block!(ohagi)
@ -359,4 +362,42 @@ RSpec.describe DeliveryAntennaService, type: :service do
expect(antenna_feed_of(antenna)).to include status.id expect(antenna_feed_of(antenna)).to include status.id
end end
end end
context 'with federated unlisted post' do
let(:visibility) { :unlisted }
let(:searchability) { :public }
let(:domain) { 'fast.example.com' }
let!(:antenna) { antenna_with_keyword(bob, 'body') }
let!(:empty_antenna) { antenna_with_keyword(tom, 'body') }
context 'when unknown domain' do
let(:software) { nil }
it 'detecting antenna' do
expect(antenna_feed_of(antenna)).to include status.id
expect(antenna_feed_of(empty_antenna)).to include status.id
end
end
context 'when misskey domain' do
let(:software) { 'misskey' }
it 'detecting antenna' do
expect(antenna_feed_of(antenna)).to include status.id
expect(antenna_feed_of(empty_antenna)).to include status.id
end
end
context 'when firefish domain' do
let(:software) { 'firefish' }
it 'detecting antenna' do
expect(antenna_feed_of(antenna)).to include status.id
end
it 'not detecting antenna' do
expect(antenna_feed_of(empty_antenna)).to_not include status.id
end
end
end
end end

View file

@ -6,10 +6,16 @@ RSpec.describe ProcessReferencesService, type: :service do
let(:text) { 'Hello' } let(:text) { 'Hello' }
let(:account) { Fabricate(:user).account } let(:account) { Fabricate(:user).account }
let(:visibility) { :public } let(:visibility) { :public }
let(:target_status_visibility) { :public }
let(:status) { Fabricate(:status, account: account, text: text, visibility: visibility) } let(:status) { Fabricate(:status, account: account, text: text, visibility: visibility) }
let(:target_status) { Fabricate(:status, account: Fabricate(:user).account) } let(:target_status) { Fabricate(:status, account: Fabricate(:user).account, visibility: target_status_visibility) }
let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) } let(:target_status_uri) { ActivityPub::TagManager.instance.uri_for(target_status) }
def notify?(target_status_id = nil)
target_status_id ||= target_status.id
StatusReference.exists?(id: Notification.where(type: 'status_reference').select(:activity_id), target_status_id: target_status_id)
end
describe 'posting new status' do describe 'posting new status' do
subject do subject do
described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote) described_class.new.call(status, reference_parameters, urls: urls, fetch_remote: fetch_remote)
@ -27,6 +33,7 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'RT' expect(subject.pluck(1)).to include 'RT'
expect(notify?).to be true
end end
end end
@ -39,6 +46,59 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 2 expect(subject.size).to eq 2
expect(subject).to include [target_status.id, 'RT'] expect(subject).to include [target_status.id, 'RT']
expect(subject).to include [target_status2.id, 'BT'] expect(subject).to include [target_status2.id, 'BT']
expect(notify?).to be true
expect(notify?(target_status2.id)).to be true
end
end
context 'when private post' do
let(:text) { "Hello RT #{target_status_uri}" }
let(:visibility) { :private }
it 'post status' do
expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'RT'
expect(notify?).to be false
end
end
context 'when cannot show private post' do
let(:text) { "Hello RT #{target_status_uri}" }
let(:target_status_visibility) { :private }
it 'post status' do
expect(subject.size).to eq 0
expect(notify?).to be false
end
end
context 'with quote' do
let(:text) { "Hello QT #{target_status_uri}" }
it 'post status' do
expect(subject.size).to eq 1
expect(subject.pluck(0)).to include target_status.id
expect(subject.pluck(1)).to include 'QT'
expect(status.quote).to_not be_nil
expect(status.quote.id).to eq target_status.id
expect(notify?).to be true
end
end
context 'with quote and reference' do
let(:target_status2) { Fabricate(:status) }
let(:target_status2_uri) { ActivityPub::TagManager.instance.uri_for(target_status2) }
let(:text) { "Hello QT #{target_status_uri}\nBT #{target_status2_uri}" }
it 'post status' do
expect(subject.size).to eq 2
expect(subject).to include [target_status.id, 'QT']
expect(subject).to include [target_status2.id, 'BT']
expect(status.quote).to_not be_nil
expect(status.quote.id).to eq target_status.id
expect(notify?).to be true
expect(notify?(target_status2.id)).to be true
end end
end end
@ -47,6 +107,7 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status' do it 'post status' do
expect(subject.size).to eq 0 expect(subject.size).to eq 0
expect(notify?).to be false
end end
end end
@ -143,6 +204,7 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status' do it 'post status' do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject).to include target_status.id expect(subject).to include target_status.id
expect(notify?).to be true
end end
end end
@ -154,6 +216,7 @@ RSpec.describe ProcessReferencesService, type: :service do
expect(subject.size).to eq 2 expect(subject.size).to eq 2
expect(subject).to include target_status.id expect(subject).to include target_status.id
expect(subject).to include target_status2.id expect(subject).to include target_status2.id
expect(notify?(target_status2.id)).to be true
end end
end end
@ -173,6 +236,7 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status' do it 'post status' do
expect(subject.size).to eq 0 expect(subject.size).to eq 0
expect(notify?).to be false
end end
end end
@ -183,6 +247,7 @@ RSpec.describe ProcessReferencesService, type: :service do
it 'post status' do it 'post status' do
expect(subject.size).to eq 1 expect(subject.size).to eq 1
expect(subject).to include target_status2.id expect(subject).to include target_status2.id
expect(notify?(target_status2.id)).to be true
end end
end end
end end

View file

@ -6,7 +6,7 @@ RSpec.describe SoftwareUpdateCheckService, type: :service do
subject { described_class.new } subject { described_class.new }
shared_examples 'when the feature is enabled' do shared_examples 'when the feature is enabled' do
let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.to_s.split('+')[0]}" } let(:full_update_check_url) { "#{update_check_url}?version=#{Mastodon::Version.kmyblue_major}.#{Mastodon::Version.kmyblue_minor}-lts" }
let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) } let(:devops_role) { Fabricate(:user_role, name: 'DevOps', permissions: UserRole::FLAGS[:view_devops]) }
let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) } let(:owner_user) { Fabricate(:user, role: UserRole.find_by(name: 'Owner')) }
@ -139,7 +139,7 @@ RSpec.describe SoftwareUpdateCheckService, type: :service do
end end
context 'when using the default update checking API' do context 'when using the default update checking API' do
let(:update_check_url) { 'https://api.joinmastodon.org/update-check' } let(:update_check_url) { 'https://kmy.blue/update-check' }
it_behaves_like 'when the feature is enabled' it_behaves_like 'when the feature is enabled'
end end