Compare commits
26 Commits
Author | SHA1 | Date | |
---|---|---|---|
22015044a5 | |||
61f86dcb2b | |||
8f3bce6b11 | |||
ee736e73a9 | |||
99f867897e | |||
c66c5b6e75 | |||
f25ecc19b9 | |||
48e09970f3 | |||
f05cb79604 | |||
46d3293edd | |||
9703d613cf | |||
704e217dbb | |||
a103032d94 | |||
c7207a4bd7 | |||
35c65fe589 | |||
6d5bd0c484 | |||
cfbb6e8092 | |||
feef4a933e | |||
468bc67569 | |||
0d517fa52f | |||
d9054367c1 | |||
1213373027 | |||
100a525507 | |||
92ba64c35c | |||
a8ee51ffd6 | |||
5538afc61d |
@ -30,7 +30,7 @@ while :
|
||||
touch patreon.cache && \
|
||||
rm patreon.cache && \
|
||||
cat patreon.raw.cache | \
|
||||
jq -r '(.data|map(select(.relationships.currently_entitled_tiers.data[]))|map(.relationships.user.data.id))as$data|.included|map(select(.attributes.hide_pledges==false))|map(select(.id as$id|$data|contains([$id])))|map(.attributes|[.full_name,.thumb_url,.url]|@tsv)|.[]|@text' >> patreon.cache && \
|
||||
jq -r '(.data|map(select(.relationships.currently_entitled_tiers.data[]))|map(.relationships.user.data.id))as$data|.included|map(select(.id as$id|$data|contains([$id])))|map(.attributes|[.full_name,.thumb_url,.url]|@tsv)|.[]|@text' >> patreon.cache && \
|
||||
echo '<table><tr>' >> patreon.md.cache && \
|
||||
cat patreon.cache | \
|
||||
awk -F'\t' '{print $2,$1}' | \
|
||||
|
14
package.json
14
package.json
@ -1,8 +1,8 @@
|
||||
{
|
||||
"name": "misskey",
|
||||
"author": "syuilo <i@syuilo.com>",
|
||||
"version": "10.20.0",
|
||||
"clientVersion": "1.0.10607",
|
||||
"version": "10.22.0",
|
||||
"clientVersion": "1.0.10633",
|
||||
"codename": "nighthike",
|
||||
"main": "./built/index.js",
|
||||
"private": true,
|
||||
@ -20,10 +20,10 @@
|
||||
"format": "gulp format"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.4",
|
||||
"@fortawesome/free-brands-svg-icons": "5.3.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.3.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.3.1",
|
||||
"@fortawesome/fontawesome-svg-core": "1.2.6",
|
||||
"@fortawesome/free-brands-svg-icons": "5.4.1",
|
||||
"@fortawesome/free-regular-svg-icons": "5.4.1",
|
||||
"@fortawesome/free-solid-svg-icons": "5.4.1",
|
||||
"@koa/cors": "2.2.2",
|
||||
"@prezzemolo/rap": "0.1.2",
|
||||
"@prezzemolo/zip": "0.0.3",
|
||||
@ -179,7 +179,7 @@
|
||||
"qrcode": "1.3.0",
|
||||
"ratelimiter": "3.2.0",
|
||||
"recaptcha-promise": "0.1.3",
|
||||
"reconnecting-websocket": "4.1.8",
|
||||
"reconnecting-websocket": "4.1.9",
|
||||
"redis": "2.8.0",
|
||||
"request": "2.88.0",
|
||||
"request-promise-native": "1.0.5",
|
||||
|
@ -44,7 +44,6 @@ export default define({
|
||||
},
|
||||
fetch() {
|
||||
fetch(`https://api.rss2json.com/v1/api.json?rss_url=${this.props.url}`, {
|
||||
cache: 'no-cache'
|
||||
}).then(res => {
|
||||
res.json().then(feed => {
|
||||
this.items = feed.items;
|
||||
|
@ -307,7 +307,7 @@ export default Vue.extend({
|
||||
display block
|
||||
width 100%
|
||||
padding 16px
|
||||
color #555
|
||||
color var(--text)
|
||||
border-top solid 1px rgba(#000, 0.05)
|
||||
|
||||
&:hover
|
||||
@ -326,6 +326,6 @@ export default Vue.extend({
|
||||
margin 0
|
||||
padding 16px
|
||||
text-align center
|
||||
color #aaa
|
||||
color var(--text)
|
||||
|
||||
</style>
|
||||
|
@ -2,8 +2,8 @@
|
||||
<div class="mk-ui" v-hotkey.global="keymap">
|
||||
<div class="bg" v-if="$store.getters.isSignedIn && $store.state.i.wallpaperUrl" :style="style"></div>
|
||||
<x-header class="header" v-if="navbar == 'top'" v-show="!zenMode" ref="header"/>
|
||||
<x-sidebar class="sidebar" v-if="navbar != 'top'" ref="sidebar"/>
|
||||
<div class="content" :class="[{ sidebar: navbar != 'top' }, navbar]">
|
||||
<x-sidebar class="sidebar" v-if="navbar != 'top'" v-show="!zenMode" ref="sidebar"/>
|
||||
<div class="content" :class="[{ sidebar: navbar != 'top', zen: zenMode }, navbar]">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<mk-stream-indicator v-if="$store.getters.isSignedIn"/>
|
||||
@ -73,7 +73,9 @@ export default Vue.extend({
|
||||
toggleZenMode() {
|
||||
this.zenMode = !this.zenMode;
|
||||
this.$nextTick(() => {
|
||||
this.$store.commit('setUiHeaderHeight', this.$refs.header.$el.offsetHeight);
|
||||
if (this.$refs.header) {
|
||||
this.$store.commit('setUiHeaderHeight', this.$refs.header.$el.offsetHeight);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -102,4 +104,7 @@ export default Vue.extend({
|
||||
> .content.sidebar.right
|
||||
padding-right 68px
|
||||
|
||||
> .content.zen
|
||||
padding 0 !important
|
||||
|
||||
</style>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
||||
<x-notes ref="timeline" :more="existMore ? more : null" :media-view="mediaView"/>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -15,7 +15,7 @@
|
||||
<ui-switch v-model="column.isMediaView" @change="onChangeSettings">%i18n:@is-media-view%</ui-switch>
|
||||
</div>
|
||||
<x-list-tl v-if="column.type == 'list'" :list="column.list" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
<x-hashtag-tl v-if="column.type == 'hashtag'" :tag-tl="$store.state.settings.tagTimelines.find(x => x.id == column.tagTlId)" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
<x-hashtag-tl v-else-if="column.type == 'hashtag'" :tag-tl="$store.state.settings.tagTimelines.find(x => x.id == column.tagTlId)" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
<x-tl v-else :src="column.type" :media-only="column.isMediaOnly" :media-view="column.isMediaView"/>
|
||||
</x-column>
|
||||
</template>
|
||||
|
@ -23,6 +23,7 @@ export type Source = {
|
||||
url: string;
|
||||
port: number;
|
||||
https?: { [x: string]: string };
|
||||
disableHsts?: boolean;
|
||||
mongodb: {
|
||||
host: string;
|
||||
port: number;
|
||||
|
@ -26,17 +26,19 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<IDriv
|
||||
|
||||
let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive);
|
||||
|
||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
||||
// URLを更新する
|
||||
if (file.metadata.url !== image.url) {
|
||||
file = await DriveFile.findOneAndUpdate({ _id: file._id }, {
|
||||
$set: {
|
||||
'metadata.url': image.url,
|
||||
'metadata.uri': image.url
|
||||
}
|
||||
}, {
|
||||
returnNewDocument: true
|
||||
});
|
||||
if (file.metadata.isRemote) {
|
||||
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、
|
||||
// URLを更新する
|
||||
if (file.metadata.url !== image.url) {
|
||||
file = await DriveFile.findOneAndUpdate({ _id: file._id }, {
|
||||
$set: {
|
||||
'metadata.url': image.url,
|
||||
'metadata.uri': image.url
|
||||
}
|
||||
}, {
|
||||
returnNewDocument: true
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return file;
|
||||
|
@ -41,7 +41,7 @@ app.use(compress({
|
||||
|
||||
// HSTS
|
||||
// 6months (15552000sec)
|
||||
if (config.url.startsWith('https')) {
|
||||
if (config.url.startsWith('https') && !config.disableHsts) {
|
||||
app.use(async (ctx, next) => {
|
||||
ctx.set('strict-transport-security', 'max-age=15552000; preload');
|
||||
await next();
|
||||
|
@ -37,10 +37,10 @@ async function save(path: string, name: string, type: string, hash: string, size
|
||||
if (config.drive && config.drive.storage == 'minio') {
|
||||
const minio = new Minio.Client(config.drive.config);
|
||||
|
||||
const keyDir = `${config.drive.prefix}/${uuid.v4()}`;
|
||||
const key = `${keyDir}/${name}`;
|
||||
const thumbnailKeyDir = `${config.drive.prefix}/${uuid.v4()}`;
|
||||
const thumbnailKey = `${thumbnailKeyDir}/${name}.thumbnail.jpg`;
|
||||
const [ext] = (name.match(/\.([a-zA-Z0-9_-]+)$/) || ['']);
|
||||
|
||||
const key = `${config.drive.prefix}/${uuid.v4()}${ext}`;
|
||||
const thumbnailKey = `${config.drive.prefix}/${uuid.v4()}.jpg`;
|
||||
|
||||
const baseUrl = config.drive.baseUrl
|
||||
|| `${ config.drive.config.useSSL ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? `:${config.drive.config.port}` : '' }/${ config.drive.bucket }`;
|
||||
@ -64,8 +64,8 @@ async function save(path: string, name: string, type: string, hash: string, size
|
||||
key: key,
|
||||
thumbnailKey: thumbnailKey
|
||||
},
|
||||
url: `${ baseUrl }/${ keyDir }/${ encodeURIComponent(name) }`,
|
||||
thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailKeyDir }/${ encodeURIComponent(name) }.thumbnail.jpg` : null
|
||||
url: `${ baseUrl }/${ key }`,
|
||||
thumbnailUrl: thumbnail ? `${ baseUrl }/${ thumbnailKey }` : null
|
||||
});
|
||||
|
||||
const file = await DriveFile.insert({
|
||||
|
30
src/tools/clean-remote-files.ts
Normal file
30
src/tools/clean-remote-files.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import * as promiseLimit from 'promise-limit';
|
||||
import DriveFile, { IDriveFile } from '../models/drive-file';
|
||||
import del from '../services/drive/delete-file';
|
||||
|
||||
const limit = promiseLimit(16);
|
||||
|
||||
DriveFile.find({
|
||||
'metadata._user.host': {
|
||||
$ne: null
|
||||
},
|
||||
'metadata.deletedAt': { $exists: false }
|
||||
}, {
|
||||
fields: {
|
||||
_id: true
|
||||
}
|
||||
}).then(async files => {
|
||||
console.log(`there is ${files.length} files`);
|
||||
|
||||
await Promise.all(files.map(file => limit(() => job(file))));
|
||||
|
||||
console.log('ALL DONE');
|
||||
});
|
||||
|
||||
async function job(file: IDriveFile): Promise<any> {
|
||||
file = await DriveFile.findOne({ _id: file._id });
|
||||
|
||||
await del(file, true);
|
||||
|
||||
console.log('done', file._id);
|
||||
}
|
103
test/api.ts
103
test/api.ts
@ -85,16 +85,27 @@ const uploadFile = async (user: any): Promise<any> => {
|
||||
|
||||
describe('API', () => {
|
||||
// Reset database each test
|
||||
beforeEach(() => Promise.all([
|
||||
db.get('users').drop(),
|
||||
db.get('posts').drop(),
|
||||
db.get('driveFiles.files').drop(),
|
||||
db.get('driveFiles.chunks').drop(),
|
||||
db.get('driveFolders').drop(),
|
||||
db.get('apps').drop(),
|
||||
db.get('accessTokens').drop(),
|
||||
db.get('authSessions').drop()
|
||||
]));
|
||||
beforeEach(() => new Promise((res) => {
|
||||
// APIがなにかレスポンスを返した後に、後処理を行う場合があり、
|
||||
// レスポンスを受け取ってすぐデータベースをリセットすると
|
||||
// その後処理と競合し(テスト自体は合格するものの)エラーがコンソールに出力され
|
||||
// 見た目的に気持ち悪くなるので、後処理が終るのを待つために500msくらい待ってから
|
||||
// データベースをリセットするようにする
|
||||
setTimeout(async () => {
|
||||
await Promise.all([
|
||||
db.get('users').drop(),
|
||||
db.get('posts').drop(),
|
||||
db.get('driveFiles.files').drop(),
|
||||
db.get('driveFiles.chunks').drop(),
|
||||
db.get('driveFolders').drop(),
|
||||
db.get('apps').drop(),
|
||||
db.get('accessTokens').drop(),
|
||||
db.get('authSessions').drop()
|
||||
]);
|
||||
|
||||
res();
|
||||
}, 500);
|
||||
}));
|
||||
|
||||
describe('signup', () => {
|
||||
it('不正なユーザー名でアカウントが作成できない', async(async () => {
|
||||
@ -1114,4 +1125,76 @@ describe('API', () => {
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
});
|
||||
|
||||
describe('messaging/messages/create', () => {
|
||||
it('メッセージを送信できる', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
const bob = await signup({ username: 'bob' });
|
||||
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: bob.id,
|
||||
text: 'test'
|
||||
}, alice);
|
||||
|
||||
expect(res).have.status(200);
|
||||
expect(res.body).be.a('object');
|
||||
expect(res.body).have.property('text').eql('test');
|
||||
}));
|
||||
|
||||
it('自分自身にはメッセージを送信できない', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: alice.id,
|
||||
text: 'Yo'
|
||||
}, alice);
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('存在しないユーザーにはメッセージを送信できない', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: '000000000000000000000000',
|
||||
text: 'test'
|
||||
}, alice);
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('不正なユーザーIDで怒られる', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: 'foo',
|
||||
text: 'test'
|
||||
}, alice);
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('テキストが無くて怒られる', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
const bob = await signup({ username: 'bob' });
|
||||
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: bob.id
|
||||
}, alice);
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
|
||||
it('文字数オーバーで怒られる', async(async () => {
|
||||
const alice = await signup({ username: 'alice' });
|
||||
const bob = await signup({ username: 'bob' });
|
||||
|
||||
const res = await request('/messaging/messages/create', {
|
||||
userId: bob.id,
|
||||
text: '!'.repeat(1001)
|
||||
}, alice);
|
||||
|
||||
expect(res).have.status(400);
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
Reference in New Issue
Block a user