* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wop

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* add notes

* wip

* wip

* wip

* wip

* sound

* wip

* add kick_gaba2

* wip
This commit is contained in:
syuilo
2020-08-18 22:44:21 +09:00
committed by GitHub
parent 122076e8ea
commit 9855405b89
70 changed files with 2191 additions and 184 deletions

View File

@@ -0,0 +1,68 @@
import $ from 'cafy';
import define from '../../define';
import { ApiError } from '../../error';
import { Channels, DriveFiles } from '../../../../models';
import { Channel } from '../../../../models/entities/channel';
import { genId } from '../../../../misc/gen-id';
import { ID } from '../../../../misc/cafy-id';
export const meta = {
tags: ['channels'],
requireCredential: true as const,
kind: 'write:channels',
params: {
name: {
validator: $.str.range(1, 128)
},
description: {
validator: $.nullable.optional.str.range(1, 2048)
},
bannerId: {
validator: $.nullable.optional.type(ID),
}
},
res: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Channel',
},
errors: {
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'cd1e9f3e-5a12-4ab4-96f6-5d0a2cc32050'
},
}
};
export default define(meta, async (ps, user) => {
let banner = null;
if (ps.bannerId != null) {
banner = await DriveFiles.findOne({
id: ps.bannerId,
userId: user.id
});
if (banner == null) {
throw new ApiError(meta.errors.noSuchFile);
}
}
const channel = await Channels.save({
id: genId(),
createdAt: new Date(),
userId: user.id,
name: ps.name,
description: ps.description || null,
bannerId: banner ? banner.id : null,
} as Channel);
return await Channels.pack(channel, user);
});

View File

@@ -0,0 +1,28 @@
import define from '../../define';
import { Channels } from '../../../../models';
export const meta = {
tags: ['channels'],
requireCredential: false as const,
res: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Channel',
}
},
};
export default define(meta, async (ps, me) => {
const query = Channels.createQueryBuilder('channel')
.where('channel.lastNotedAt IS NOT NULL')
.orderBy('channel.lastNotedAt', 'DESC');
const channels = await query.take(10).getMany();
return await Promise.all(channels.map(x => Channels.pack(x, me)));
});

View File

@@ -0,0 +1,45 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Channels, ChannelFollowings } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
export const meta = {
tags: ['channels'],
requireCredential: true as const,
kind: 'write:channels',
params: {
channelId: {
validator: $.type(ID),
},
},
errors: {
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: 'c0031718-d573-4e85-928e-10039f1fbb68'
},
}
};
export default define(meta, async (ps, user) => {
const channel = await Channels.findOne({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
await ChannelFollowings.save({
id: genId(),
createdAt: new Date(),
followerId: user.id,
followeeId: channel.id,
});
});

View File

@@ -0,0 +1,28 @@
import define from '../../define';
import { Channels, ChannelFollowings } from '../../../../models';
export const meta = {
tags: ['channels', 'account'],
requireCredential: true as const,
kind: 'read:channels',
res: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Channel',
}
},
};
export default define(meta, async (ps, me) => {
const followings = await ChannelFollowings.find({
followerId: me.id,
});
return await Promise.all(followings.map(x => Channels.pack(x.followeeId, me)));
});

View File

@@ -0,0 +1,28 @@
import define from '../../define';
import { Channels } from '../../../../models';
export const meta = {
tags: ['channels', 'account'],
requireCredential: true as const,
kind: 'read:channels',
res: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Channel',
}
},
};
export default define(meta, async (ps, me) => {
const channels = await Channels.find({
userId: me.id,
});
return await Promise.all(channels.map(x => Channels.pack(x, me)));
});

View File

@@ -0,0 +1,43 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Channels } from '../../../../models';
export const meta = {
tags: ['channels'],
requireCredential: false as const,
params: {
channelId: {
validator: $.type(ID),
},
},
res: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Channel',
},
errors: {
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: '6f6c314b-7486-4897-8966-c04a66a02923'
},
}
};
export default define(meta, async (ps, me) => {
const channel = await Channels.findOne({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
return await Channels.pack(channel, me);
});

View File

@@ -0,0 +1,99 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Notes, Channels } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { activeUsersChart } from '../../../../services/chart';
export const meta = {
tags: ['notes', 'channels'],
requireCredential: false as const,
params: {
channelId: {
validator: $.type(ID),
desc: {
'ja-JP': 'チャンネルのID'
}
},
limit: {
validator: $.optional.num.range(1, 100),
default: 10,
desc: {
'ja-JP': '最大数'
}
},
sinceId: {
validator: $.optional.type(ID),
desc: {
'ja-JP': '指定すると、その投稿を基点としてより新しい投稿を取得します'
}
},
untilId: {
validator: $.optional.type(ID),
desc: {
'ja-JP': '指定すると、その投稿を基点としてより古い投稿を取得します'
}
},
sinceDate: {
validator: $.optional.num,
desc: {
'ja-JP': '指定した時間を基点としてより新しい投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
}
},
untilDate: {
validator: $.optional.num,
desc: {
'ja-JP': '指定した時間を基点としてより古い投稿を取得します。数値は、1970年1月1日 00:00:00 UTC から指定した日時までの経過時間をミリ秒単位で表します。'
}
},
},
res: {
type: 'array' as const,
optional: false as const, nullable: false as const,
items: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Note',
}
},
errors: {
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: '4d0eeeba-a02c-4c3c-9966-ef60d38d2e7f'
}
}
};
export default define(meta, async (ps, user) => {
const channel = await Channels.findOne({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
//#region Construct query
const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId)
.andWhere('note.channelId = :channelId', { channelId: channel.id })
.leftJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.channel', 'channel');
//#endregion
const timeline = await query.take(ps.limit!).getMany();
activeUsersChart.update(user);
return await Notes.packMany(timeline, user);
});

View File

@@ -0,0 +1,42 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Channels, ChannelFollowings } from '../../../../models';
export const meta = {
tags: ['channels'],
requireCredential: true as const,
kind: 'write:channels',
params: {
channelId: {
validator: $.type(ID),
},
},
errors: {
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: '19959ee9-0153-4c51-bbd9-a98c49dc59d6'
},
}
};
export default define(meta, async (ps, user) => {
const channel = await Channels.findOne({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
await ChannelFollowings.delete({
followerId: user.id,
followeeId: channel.id,
});
});

View File

@@ -0,0 +1,93 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Channels, DriveFiles } from '../../../../models';
export const meta = {
tags: ['channels'],
requireCredential: true as const,
kind: 'write:channels',
params: {
channelId: {
validator: $.type(ID),
},
name: {
validator: $.optional.str.range(1, 128)
},
description: {
validator: $.nullable.optional.str.range(1, 2048)
},
bannerId: {
validator: $.nullable.optional.type(ID),
}
},
res: {
type: 'object' as const,
optional: false as const, nullable: false as const,
ref: 'Channel',
},
errors: {
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: 'f9c5467f-d492-4c3c-9a8d-a70dacc86512'
},
accessDenied: {
message: 'You do not have edit privilege of the channel.',
code: 'ACCESS_DENIED',
id: '1fb7cb09-d46a-4fdf-b8df-057788cce513'
},
noSuchFile: {
message: 'No such file.',
code: 'NO_SUCH_FILE',
id: 'e86c14a4-0da2-4032-8df3-e737a04c7f3b'
},
}
};
export default define(meta, async (ps, me) => {
const channel = await Channels.findOne({
id: ps.channelId,
});
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
if (channel.userId !== me.id) {
throw new ApiError(meta.errors.accessDenied);
}
let banner = undefined;
if (ps.bannerId != null) {
banner = await DriveFiles.findOne({
id: ps.bannerId,
userId: me.id
});
if (banner == null) {
throw new ApiError(meta.errors.noSuchFile);
}
} else if (ps.bannerId === null) {
banner = null;
}
await Channels.update(channel.id, {
...(ps.name !== undefined ? { name: ps.name } : {}),
...(ps.description !== undefined ? { description: ps.description } : {}),
...(banner ? { bannerId: banner.id } : {}),
});
return await Channels.pack(channel.id, me);
});

View File

@@ -24,7 +24,6 @@ export default define(meta, async (ps, user, token) => {
return await Users.pack(user, user, {
detail: true,
includeHasUnreadNotes: true,
includeSecrets: isSecure
});
});

View File

@@ -7,11 +7,12 @@ import { fetchMeta } from '../../../../misc/fetch-meta';
import { ApiError } from '../../error';
import { ID } from '../../../../misc/cafy-id';
import { User } from '../../../../models/entities/user';
import { Users, DriveFiles, Notes } from '../../../../models';
import { Users, DriveFiles, Notes, Channels } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file';
import { Note } from '../../../../models/entities/note';
import { DB_MAX_NOTE_TEXT_LENGTH } from '../../../../misc/hard-limits';
import { noteVisibilities } from '../../../../types';
import { Channel } from '../../../../models/entities/channel';
let maxNoteTextLength = 500;
@@ -128,19 +129,26 @@ export const meta = {
},
replyId: {
validator: $.optional.type(ID),
validator: $.optional.nullable.type(ID),
desc: {
'ja-JP': '返信対象'
}
},
renoteId: {
validator: $.optional.type(ID),
validator: $.optional.nullable.type(ID),
desc: {
'ja-JP': 'Renote対象'
}
},
channelId: {
validator: $.optional.nullable.type(ID),
desc: {
'ja-JP': 'チャンネル'
}
},
poll: {
validator: $.optional.obj({
choices: $.arr($.str)
@@ -206,7 +214,13 @@ export const meta = {
message: 'Poll is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
id: '04da457d-b083-4055-9082-955525eda5a5'
}
},
noSuchChannel: {
message: 'No such channel.',
code: 'NO_SUCH_CHANNEL',
id: 'b1653923-5453-4edc-b786-7c4f39bb0bbb'
},
}
};
@@ -269,6 +283,15 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.contentRequired);
}
let channel: Channel | undefined;
if (ps.channelId != null) {
channel = await Channels.findOne(ps.channelId);
if (channel == null) {
throw new ApiError(meta.errors.noSuchChannel);
}
}
// 投稿を作成
const note = await create(user, {
createdAt: new Date(),
@@ -286,6 +309,7 @@ export default define(meta, async (ps, user) => {
localOnly: ps.localOnly,
visibility: ps.visibility,
visibleUsers,
channel,
apMentions: ps.noExtractMentions ? [] : undefined,
apHashtags: ps.noExtractHashtags ? [] : undefined,
apEmojis: ps.noExtractEmojis ? [] : undefined,

View File

@@ -80,6 +80,7 @@ export default define(meta, async (ps, user) => {
const query = makePaginationQuery(Notes.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
.andWhere('note.visibility = \'public\'')
.andWhere('note.channelId IS NULL')
.leftJoinAndSelect('note.user', 'user');
generateRepliesQuery(query, user);

View File

@@ -13,6 +13,7 @@ import { generateRepliesQuery } from '../../common/generate-replies-query';
import { injectPromo } from '../../common/inject-promo';
import { injectFeatured } from '../../common/inject-featured';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-query';
export const meta = {
desc: {
@@ -131,6 +132,7 @@ export default define(meta, async (ps, user) => {
.leftJoinAndSelect('note.user', 'user')
.setParameters(followingQuery.getParameters());
generateChannelQuery(query, user);
generateRepliesQuery(query, user);
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);

View File

@@ -13,6 +13,7 @@ import { generateRepliesQuery } from '../../common/generate-replies-query';
import { injectPromo } from '../../common/inject-promo';
import { injectFeatured } from '../../common/inject-featured';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-query';
export const meta = {
desc: {
@@ -99,6 +100,7 @@ export default define(meta, async (ps, user) => {
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.leftJoinAndSelect('note.user', 'user');
generateChannelQuery(query, user);
generateRepliesQuery(query, user);
generateVisibilityQuery(query, user);
if (user) generateMutedUserQuery(query, user);

View File

@@ -11,6 +11,7 @@ import { generateRepliesQuery } from '../../common/generate-replies-query';
import { injectPromo } from '../../common/inject-promo';
import { injectFeatured } from '../../common/inject-featured';
import { generateMutedNoteQuery } from '../../common/generate-muted-note-query';
import { generateChannelQuery } from '../../common/generate-channel-query';
export const meta = {
desc: {
@@ -124,6 +125,7 @@ export default define(meta, async (ps, user) => {
.leftJoinAndSelect('note.user', 'user')
.setParameters(followingQuery.getParameters());
generateChannelQuery(query, user);
generateRepliesQuery(query, user);
generateVisibilityQuery(query, user);
generateMutedUserQuery(query, user);