Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
5e1f804dd1 | |||
15de89f2f9 | |||
df647a415c | |||
fc66231f8e | |||
71df3e1566 | |||
168c22fc98 | |||
792ec23d7a | |||
ff625253ce | |||
8c872c6b22 | |||
541f5bc0a6 | |||
77ff7b9df0 | |||
203cc5075e | |||
7abfcd06da | |||
724f81c7f3 | |||
4d2e98af7b | |||
08221fdda7 | |||
c0f72970b9 | |||
18bc4a49e8 | |||
f90b6dbed4 | |||
d2d991ff34 | |||
190a5e175b | |||
a05f5a91b8 | |||
4d02ff27be | |||
df92b41d25 | |||
075af96338 | |||
64bbc55336 | |||
06c621acc1 |
39
CHANGELOG.md
39
CHANGELOG.md
@ -5,8 +5,43 @@ If you encounter any problems with updating, please try the following:
|
||||
1. `npm run clean` or `npm run cleanall`
|
||||
2. Retry update (Don't forget `npm i`)
|
||||
|
||||
11.1.2 (2019/04/15)
|
||||
-------------------
|
||||
### Fixes
|
||||
* 画像描画の依存関係を変更
|
||||
* リモートユーザーのファイルを削除するときに古い方からではなく新しい方から削除されるのを修正
|
||||
* リアクションしてないのにリアクションしたことになる問題を修正
|
||||
* APIドキュメントの修正
|
||||
|
||||
11.1.1 (2019/04/15)
|
||||
-------------------
|
||||
### Fixes
|
||||
* Metaタグの application-name を Misskey で固定するように修正
|
||||
* トークメッセージが既読にならない問題を修正
|
||||
* デフォルトでHTLを表示するように
|
||||
|
||||
11.1.0 (2019/04/15)
|
||||
-------------------
|
||||
### Improvements
|
||||
* アイコン未設定時にランダムな画像を表示するように
|
||||
* 管理者やモデレーターはレートリミット無効に
|
||||
|
||||
### Fixes
|
||||
* メンションの「あなた」インジケーターが表示されない問題を修正
|
||||
* ブロックAPIでエラーが発生する問題を修正
|
||||
* プッシュ通知の購読に失敗する問題を修正
|
||||
|
||||
11.0.3 (2019/04/15)
|
||||
-------------------
|
||||
### Fixes
|
||||
* ハッシュタグ検索APIが動作しない問題を修正
|
||||
* モデレーターなのにアカウントメニューに「管理」が表示されない問題を修正
|
||||
* プッシュ通知の購読に失敗する問題を修正
|
||||
* ユーザー取得APIでユーザーを指定しない場合エラーになる問題を修正
|
||||
|
||||
11.0.2 (2019/04/15)
|
||||
-------------------
|
||||
### Fixes
|
||||
* アプリが作成できない問題を修正
|
||||
* 「ハイライト」が表示されない問題を修正
|
||||
* リモートの投稿に添付されている画像が小さい問題を修正
|
||||
@ -15,15 +50,19 @@ If you encounter any problems with updating, please try the following:
|
||||
|
||||
11.0.1 (2019/04/15)
|
||||
-------------------
|
||||
### Improvements
|
||||
* 不要な依存関係を削除
|
||||
|
||||
11.0.0 daybreak (2019/04/14)
|
||||
----------------------------
|
||||
### Improvements
|
||||
* **データベースがMongoDBからPostgreSQLに変更されました**
|
||||
* **Redisが必須に**
|
||||
* アカウントを完全に削除できるように
|
||||
* 投稿フォームで添付ファイルの閲覧注意を確認/設定できるように
|
||||
* ミュート/ブロック時にそのユーザーの投稿のウォッチをすべて解除するように
|
||||
|
||||
### Fixes
|
||||
* フォロー申請数が実際より1すくなくなる問題を修正
|
||||
* リストからアカウント削除したユーザーを削除できない問題を修正
|
||||
* リストTLでフォローしていないユーザーの非公開投稿が流れる問題を修正
|
||||
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "11.0.2",
|
||||
"version": "11.1.2",
|
||||
"codename": "daybreak",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -75,6 +75,7 @@
|
||||
"@types/portscanner": "2.1.0",
|
||||
"@types/pug": "2.0.4",
|
||||
"@types/qrcode": "1.3.2",
|
||||
"@types/random-seed": "0.3.3",
|
||||
"@types/ratelimiter": "2.1.28",
|
||||
"@types/redis": "2.8.12",
|
||||
"@types/rename": "1.0.1",
|
||||
@ -187,7 +188,9 @@
|
||||
"promise-sequential": "1.1.1",
|
||||
"pug": "2.0.3",
|
||||
"punycode": "2.1.1",
|
||||
"pureimage": "0.1.6",
|
||||
"qrcode": "1.3.3",
|
||||
"random-seed": "0.3.0",
|
||||
"randomcolor": "0.5.4",
|
||||
"ratelimiter": "3.3.0",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
|
@ -36,7 +36,9 @@ export default Vue.extend({
|
||||
return this.host === localHost ? `@${this.username}` : `@${this.username}@${toUnicode(this.host)}`;
|
||||
},
|
||||
isMe(): boolean {
|
||||
return this.$store.getters.isSignedIn && this.canonical.toLowerCase() === `@${this.$store.state.i.username}@${toUnicode(localHost)}`.toLowerCase();
|
||||
return this.$store.getters.isSignedIn && (
|
||||
`@${this.username}@${toUnicode(this.host)}` === `@${this.$store.state.i.username}@${toUnicode(localHost)}`.toLowerCase()
|
||||
);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
@ -88,8 +88,6 @@ export default Vue.extend({
|
||||
} else if (this.src == 'tag') {
|
||||
this.tagTl = this.$store.state.device.tl.arg;
|
||||
}
|
||||
} else if (this.$store.state.i.followingCount == 0) {
|
||||
this.src = 'hybrid';
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -130,8 +130,6 @@ export default Vue.extend({
|
||||
} else if (this.src == 'tag') {
|
||||
this.tagTl = this.$store.state.device.tl.arg;
|
||||
}
|
||||
} else if (this.$store.state.i.followingCount == 0) {
|
||||
this.src = 'hybrid';
|
||||
}
|
||||
},
|
||||
|
||||
|
90
src/misc/gen-avatar.ts
Normal file
90
src/misc/gen-avatar.ts
Normal file
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* Random avatar generator
|
||||
*/
|
||||
|
||||
const p = require('pureimage');
|
||||
import * as gen from 'random-seed';
|
||||
import { WriteStream } from 'fs';
|
||||
|
||||
const size = 256; // px
|
||||
const n = 5; // resolution
|
||||
const margin = (size / n) / 1.5;
|
||||
const colors = [
|
||||
'#e57373',
|
||||
'#F06292',
|
||||
'#BA68C8',
|
||||
'#9575CD',
|
||||
'#7986CB',
|
||||
'#64B5F6',
|
||||
'#4FC3F7',
|
||||
'#4DD0E1',
|
||||
'#4DB6AC',
|
||||
'#81C784',
|
||||
'#8BC34A',
|
||||
'#AFB42B',
|
||||
'#F57F17',
|
||||
'#FF5722',
|
||||
'#795548',
|
||||
'#455A64',
|
||||
];
|
||||
const bg = '#e9e9e9';
|
||||
|
||||
const actualSize = size - (margin * 2);
|
||||
const cellSize = actualSize / n;
|
||||
const sideN = Math.floor(n / 2);
|
||||
|
||||
/**
|
||||
* Generate buffer of random avatar by seed
|
||||
*/
|
||||
export function genAvatar(seed: string, stream: WriteStream): Promise<void> {
|
||||
const rand = gen.create(seed);
|
||||
const canvas = p.make(size, size);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
ctx.fillStyle = bg;
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(0, 0, size, size);
|
||||
|
||||
ctx.fillStyle = colors[rand(colors.length)];
|
||||
|
||||
// side bitmap (filled by false)
|
||||
const side: boolean[][] = new Array(sideN);
|
||||
for (let i = 0; i < side.length; i++) {
|
||||
side[i] = new Array(n).fill(false);
|
||||
}
|
||||
|
||||
// 1*n (filled by false)
|
||||
const center: boolean[] = new Array(n).fill(false);
|
||||
|
||||
// tslint:disable-next-line:prefer-for-of
|
||||
for (let x = 0; x < side.length; x++) {
|
||||
for (let y = 0; y < side[x].length; y++) {
|
||||
side[x][y] = rand(3) === 0;
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < center.length; i++) {
|
||||
center[i] = rand(3) === 0;
|
||||
}
|
||||
|
||||
// Draw
|
||||
for (let x = 0; x < n; x++) {
|
||||
for (let y = 0; y < n; y++) {
|
||||
const isXCenter = x === ((n - 1) / 2);
|
||||
if (isXCenter && !center[y]) continue;
|
||||
|
||||
const isLeftSide = x < ((n - 1) / 2);
|
||||
if (isLeftSide && !side[x][y]) continue;
|
||||
|
||||
const isRightSide = x > ((n - 1) / 2);
|
||||
if (isRightSide && !side[sideN - (x - sideN)][y]) continue;
|
||||
|
||||
const actualX = margin + (cellSize * x);
|
||||
const actualY = margin + (cellSize * y);
|
||||
ctx.beginPath();
|
||||
ctx.fillRect(actualX, actualY, cellSize, cellSize);
|
||||
}
|
||||
}
|
||||
|
||||
return p.encodePNGToStream(canvas, stream);
|
||||
}
|
@ -3,6 +3,7 @@ import { User, ILocalUser, IRemoteUser } from '../entities/user';
|
||||
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
|
||||
import rap from '@prezzemolo/rap';
|
||||
import { ensure } from '../../prelude/ensure';
|
||||
import config from '../../config';
|
||||
|
||||
@EntityRepository(User)
|
||||
export class UserRepository extends Repository<User> {
|
||||
@ -88,7 +89,7 @@ export class UserRepository extends Repository<User> {
|
||||
name: user.name,
|
||||
username: user.username,
|
||||
host: user.host,
|
||||
avatarUrl: user.avatarUrl,
|
||||
avatarUrl: user.avatarUrl ? user.avatarUrl : config.url + '/avatar/' + user.id,
|
||||
avatarColor: user.avatarColor,
|
||||
isAdmin: user.isAdmin || undefined,
|
||||
isBot: user.isBot || undefined,
|
||||
@ -122,6 +123,7 @@ export class UserRepository extends Repository<User> {
|
||||
bannerUrl: user.bannerUrl,
|
||||
bannerColor: user.bannerColor,
|
||||
isLocked: user.isLocked,
|
||||
isModerator: user.isModerator || undefined,
|
||||
description: profile!.description,
|
||||
location: profile!.location,
|
||||
birthday: profile!.birthday,
|
||||
|
@ -59,7 +59,7 @@ export default async (endpoint: string, user: User | null | undefined, app: App
|
||||
});
|
||||
}
|
||||
|
||||
if (ep.meta.requireCredential && ep.meta.limit) {
|
||||
if (ep.meta.requireCredential && ep.meta.limit && !user!.isAdmin && !user!.isModerator) {
|
||||
// Rate limit
|
||||
await limiter(ep, user!).catch(e => {
|
||||
throw new ApiError({
|
||||
|
@ -10,24 +10,68 @@ export const meta = {
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
desc: {
|
||||
'ja-JP': 'アプリを作成します。',
|
||||
'en-US': 'Create a application.'
|
||||
},
|
||||
|
||||
params: {
|
||||
name: {
|
||||
validator: $.str
|
||||
validator: $.str,
|
||||
desc: {
|
||||
'ja-JP': 'アプリの名前',
|
||||
'en-US': 'Name of application'
|
||||
}
|
||||
},
|
||||
|
||||
description: {
|
||||
validator: $.str
|
||||
validator: $.str,
|
||||
desc: {
|
||||
'ja-JP': 'アプリの説明',
|
||||
'en-US': 'Description of application'
|
||||
}
|
||||
},
|
||||
|
||||
permission: {
|
||||
validator: $.arr($.str).unique()
|
||||
validator: $.arr($.str).unique(),
|
||||
desc: {
|
||||
'ja-JP': 'このアプリに割り当てる権限(権限については"Permissions"を参照)',
|
||||
'en-US': 'Permissions assigned to this app (see "Permissions" for the permissions)'
|
||||
}
|
||||
},
|
||||
|
||||
// TODO: Check it is valid url
|
||||
callbackUrl: {
|
||||
validator: $.optional.nullable.str,
|
||||
default: null as any
|
||||
default: null as any,
|
||||
desc: {
|
||||
'ja-JP': 'アプリ認証時にコールバックするURL',
|
||||
'en-US': 'URL to call back at app authentication'
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: 'アプリケーションのID'
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
description: 'アプリケーションの名前'
|
||||
},
|
||||
callbackUrl: {
|
||||
type: 'string',
|
||||
nullable: true,
|
||||
description: 'コールバックするURL'
|
||||
},
|
||||
secret: {
|
||||
type: 'string',
|
||||
description: 'アプリケーションのシークレットキー'
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -11,6 +11,11 @@ export const meta = {
|
||||
|
||||
requireCredential: false,
|
||||
|
||||
desc: {
|
||||
'ja-JP': 'アプリを認証するためのトークンを作成します。',
|
||||
'en-US': 'Generate a token for authorize application.'
|
||||
},
|
||||
|
||||
params: {
|
||||
appSecret: {
|
||||
validator: $.str,
|
||||
|
@ -5,7 +5,7 @@ import create from '../../../../services/blocking/create';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { getUser } from '../../common/getters';
|
||||
import { Blockings, NoteWatchings } from '../../../../models';
|
||||
import { Blockings, NoteWatchings, Users } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@ -89,5 +89,5 @@ export default define(meta, async (ps, user) => {
|
||||
noteUserId: blockee.id
|
||||
});
|
||||
|
||||
return await Blockings.pack(blockee.id, user);
|
||||
return await Users.pack(blockee.id, user);
|
||||
});
|
||||
|
@ -5,7 +5,7 @@ import deleteBlocking from '../../../../services/blocking/delete';
|
||||
import define from '../../define';
|
||||
import { ApiError } from '../../error';
|
||||
import { getUser } from '../../common/getters';
|
||||
import { Blockings } from '../../../../models';
|
||||
import { Blockings, Users } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
stability: 'stable',
|
||||
@ -84,5 +84,5 @@ export default define(meta, async (ps, user) => {
|
||||
// Delete blocking
|
||||
await deleteBlocking(blocker, blockee);
|
||||
|
||||
return await Blockings.pack(blockee.id, user);
|
||||
return await Users.pack(blockee.id, user);
|
||||
});
|
||||
|
@ -48,6 +48,7 @@ export default define(meta, async (ps) => {
|
||||
const hashtags = await Hashtags.createQueryBuilder('tag')
|
||||
.where('tag.name like :q', { q: ps.query.toLowerCase() + '%' })
|
||||
.orderBy('tag.count', 'DESC')
|
||||
.groupBy('tag.id')
|
||||
.take(ps.limit!)
|
||||
.skip(ps.offset)
|
||||
.getMany();
|
||||
|
@ -44,6 +44,7 @@ export default define(meta, async (ps, user) => {
|
||||
|
||||
await SwSubscriptions.save({
|
||||
id: genId(),
|
||||
createdAt: new Date(),
|
||||
userId: user.id,
|
||||
endpoint: ps.endpoint,
|
||||
auth: ps.auth,
|
||||
|
@ -196,5 +196,5 @@ export default define(meta, async (ps, me) => {
|
||||
|
||||
const timeline = await query.take(ps.limit!).getMany();
|
||||
|
||||
return await Notes.packMany(timeline, user);
|
||||
return await Notes.packMany(timeline, me);
|
||||
});
|
||||
|
@ -65,6 +65,10 @@ export default define(meta, async (ps, me) => {
|
||||
let user;
|
||||
|
||||
if (ps.userIds) {
|
||||
if (ps.userIds.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const users = await Users.find({
|
||||
id: In(ps.userIds)
|
||||
});
|
||||
|
@ -23,7 +23,7 @@ export default class extends Channel {
|
||||
public onMessage(type: string, body: any) {
|
||||
switch (type) {
|
||||
case 'read':
|
||||
read(this.user!.id, this.otherpartyId, body.id);
|
||||
read(this.user!.id, this.otherpartyId, [body.id]);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
@ -21,12 +21,6 @@ app.use(async (ctx, next) => {
|
||||
// Init router
|
||||
const router = new Router();
|
||||
|
||||
router.get('/default-avatar.jpg', ctx => {
|
||||
const file = fs.createReadStream(`${__dirname}/assets/avatar.jpg`);
|
||||
ctx.set('Content-Type', 'image/jpeg');
|
||||
ctx.body = file;
|
||||
});
|
||||
|
||||
router.get('/app-default.jpg', ctx => {
|
||||
const file = fs.createReadStream(`${__dirname}/assets/dummy.png`);
|
||||
ctx.set('Content-Type', 'image/jpeg');
|
||||
|
@ -25,6 +25,8 @@ import Logger from '../services/logger';
|
||||
import { program } from '../argv';
|
||||
import { UserProfiles } from '../models';
|
||||
import { networkChart } from '../services/chart';
|
||||
import { genAvatar } from '../misc/gen-avatar';
|
||||
import { createTemp } from '../misc/create-temp';
|
||||
|
||||
export const serverLogger = new Logger('server', 'gray', false);
|
||||
|
||||
@ -72,6 +74,13 @@ router.use(activityPub.routes());
|
||||
router.use(nodeinfo.routes());
|
||||
router.use(wellKnown.routes());
|
||||
|
||||
router.get('/avatar/:x', async ctx => {
|
||||
const [temp] = await createTemp();
|
||||
await genAvatar(ctx.params.x, fs.createWriteStream(temp));
|
||||
ctx.set('Content-Type', 'image/png');
|
||||
ctx.body = fs.createReadStream(temp);
|
||||
});
|
||||
|
||||
router.get('/verify-email/:code', async ctx => {
|
||||
const profile = await UserProfiles.findOne({
|
||||
emailVerifyCode: ctx.params.code
|
||||
|
@ -8,7 +8,7 @@ html
|
||||
|
||||
head
|
||||
meta(charset='utf-8')
|
||||
meta(name='application-name' content= title || 'Misskey')
|
||||
meta(name='application-name' content='Misskey')
|
||||
meta(name='referrer' content='origin')
|
||||
meta(name='theme-color' content='#105779')
|
||||
meta(property='og:site_name' content= title || 'Misskey')
|
||||
|
@ -209,10 +209,10 @@ async function deleteOldFile(user: IRemoteUser) {
|
||||
}
|
||||
|
||||
if (user.bannerId) {
|
||||
q.andWhere('file.id != :bannerId', { bannerId: user.bannerId })
|
||||
q.andWhere('file.id != :bannerId', { bannerId: user.bannerId });
|
||||
}
|
||||
|
||||
q.orderBy('file.id', 'DESC');
|
||||
q.orderBy('file.id', 'ASC');
|
||||
|
||||
const oldFile = await q.getOne();
|
||||
|
||||
|
Reference in New Issue
Block a user