Compare commits

...

27 Commits
2.1.1 ... 2.4.0

Author SHA1 Message Date
3f2a7a561e アイコンのレンダリングを改善 2018-05-06 18:04:37 +09:00
332af15e3c Fix 2018-05-06 17:37:50 +09:00
fe184ce84a Fix 2018-05-06 17:36:47 +09:00
661c7f45ba Merge pull request #1570 from m4sk1n/master
i18n: Improve Polish translation
2018-05-06 16:52:50 +09:00
01c0545409 i18n: Improve Polish translation
Signed-off-by: Marcin Mikołajczak <me@m4sk.in>
2018-05-06 09:48:53 +02:00
c6492d3d58 誰もフォローしていないときはローカルタイムラインを表示するように 2018-05-06 14:35:28 +09:00
638a2ab684 2.3.1 2018-05-06 02:08:40 +09:00
650f79d0fd Fix bugs 2018-05-06 02:08:27 +09:00
a64817cea1 2.3.0 2018-05-06 01:47:25 +09:00
c32c3c1370 Fix bug 2018-05-06 01:46:35 +09:00
29ad7ab0cf Provide prev and next note link 2018-05-06 01:43:53 +09:00
11716fa9d3 Provide og:type 2018-05-06 01:37:32 +09:00
210f11ffd8 Update theme color 🎨 2018-05-06 01:35:17 +09:00
814751d76a Merge branch 'master' of https://github.com/syuilo/misskey 2018-05-06 01:34:51 +09:00
440cf139bb メタ情報をレンダリングするように 2018-05-06 01:34:48 +09:00
276d8ffc66 Update README.md 2018-05-05 02:46:23 +09:00
d9cdc1f079 2.2.0 2018-05-04 18:32:13 +09:00
414c600356 ファイルのURLを保存するように 2018-05-04 18:32:03 +09:00
e37c19fdcd 2.1.4 2018-05-04 18:02:17 +09:00
d69b919961 oops 2018-05-04 18:02:09 +09:00
1311db8060 2.1.3 2018-05-04 18:00:02 +09:00
ed9e7520f1 Fix bug 2018-05-04 17:59:51 +09:00
8fe6da0cad 2.1.2 2018-05-04 17:41:12 +09:00
b8eac630ed 🍕 2018-05-04 17:40:50 +09:00
a5b9d7eb3b oops 2018-05-04 17:38:34 +09:00
06c453c3bc ✌️ 2018-05-04 17:27:14 +09:00
97b7567770 ✌️ 2018-05-04 17:20:40 +09:00
25 changed files with 394 additions and 54 deletions

View File

@ -52,9 +52,9 @@ If you want to translate Misskey, please see [Translation guide](./docs/translat
[List of all contributors](https://github.com/syuilo/misskey/graphs/contributors)
### :earth_americas: Translators
| ![][mirro-san-icon] | ![][Conan-kun-icon] |
|:-:|:-:|
| [Mirro][mirro-san-link]<br>English, French | [Asriel][Conan-kun-link]<br>English, French |
| ![][mirro-san-icon] | ![][Conan-kun-icon] | ![][m4sk1n-icon] |
|:-:|:-:|:-:|
| [Mirro][mirro-san-link]<br>English, French | [Asriel][Conan-kun-link]<br>English, French | [Marcin Mikołajczak][m4sk1n-link]<br>Polish |
:four_leaf_clover: Copyright
----------------------------------------------------------------
@ -100,4 +100,5 @@ Misskey is an open-source software licensed under [GNU AGPLv3](LICENSE).
[mirro-san-icon]: https://avatars1.githubusercontent.com/u/17948612?s=70&v=4
[Conan-kun-link]: https://github.com/Conan-kun
[Conan-kun-icon]: https://avatars3.githubusercontent.com/u/30003708?s=70&v=4
[m4sk1n-link]: https://github.com/m4sk1n
[m4sk1n-icon]: https://avatars3.githubusercontent.com/u/21127288?s=70&v=4

View File

@ -0,0 +1,101 @@
const chalk = require('chalk');
const log = require('single-line-log').stdout;
const sequential = require('promise-sequential');
const { default: DriveFile, DriveFileChunk } = require('../built/models/drive-file');
const { default: DriveFileThumbnail, DriveFileThumbnailChunk } = require('../built/models/drive-file-thumbnail');
const { default: User } = require('../built/models/user');
const q = {
'metadata._user.host': {
$ne: null
}
};
async function main() {
const promiseGens = [];
const count = await DriveFile.count(q);
let prev;
for (let i = 0; i < count; i++) {
promiseGens.push(() => {
const promise = new Promise(async (res, rej) => {
const file = await DriveFile.findOne(prev ? Object.assign({
_id: { $lt: prev._id }
}, q) : q, {
sort: {
_id: -1
}
});
prev = file;
function skip() {
res([i, file, false]);
}
if (file == null) return skip();
log(chalk`{gray ${i}} scanning {bold ${file._id}} ${file.filename} ...`);
const attachingUsersCount = await User.count({
$or: [{
avatarId: file._id
}, {
bannerId: file._id
}]
}, { limit: 1 });
if (attachingUsersCount !== 0) return skip();
Promise.all([
// チャンクをすべて削除
DriveFileChunk.remove({
files_id: file._id
}),
DriveFile.update({ _id: file._id }, {
$set: {
'metadata.deletedAt': new Date(),
'metadata.isExpired': true
}
})
]).then(async () => {
res([i, file, true]);
//#region サムネイルもあれば削除
const thumbnail = await DriveFileThumbnail.findOne({
'metadata.originalId': file._id
});
if (thumbnail) {
DriveFileThumbnailChunk.remove({
files_id: thumbnail._id
});
DriveFileThumbnail.remove({ _id: thumbnail._id });
}
//#endregion
});
});
promise.then(([i, file, deleted]) => {
if (deleted) {
log(chalk`{gray ${i}} {red deleted: {bold ${file._id}} ${file.filename}}`);
} else {
log(chalk`{gray ${i}} {green skipped: {bold ${file._id}} ${file.filename}}`);
}
log.clear();
console.log();
});
return promise;
});
}
return await sequential(promiseGens);
}
main().then(() => {
console.log('ALL DONE');
}).catch(console.error);

View File

@ -6,10 +6,6 @@ const { default: Note } = require('../built/models/note');
const { default: MessagingMessage } = require('../built/models/messaging-message');
const { default: User } = require('../built/models/user');
const args = process.argv.slice(2);
const skip = parseInt(args[0] || '0', 10);
async function main() {
const promiseGens = [];
@ -17,13 +13,9 @@ async function main() {
let prev;
for (let i = skip; i < count; i++) {
for (let i = 0; i < count; i++) {
promiseGens.push(() => {
const promise = new Promise(async (res, rej) => {
function skip() {
res([i, file, false]);
}
const file = await DriveFile.findOne(prev ? {
_id: { $lt: prev._id }
} : {}, {
@ -34,6 +26,10 @@ async function main() {
prev = file;
function skip() {
res([i, file, false]);
}
if (file == null) return skip();
log(chalk`{gray ${i}} scanning {bold ${file._id}} ${file.filename} ...`);

View File

@ -59,9 +59,15 @@ gulp.task('build:ts', () => {
.pipe(gulp.dest('./built/'));
});
gulp.task('build:copy', () =>
gulp.task('build:copy:views', () =>
gulp.src('./src/server/web/views/**/*').pipe(gulp.dest('./built/server/web/views'))
);
gulp.task('build:copy', ['build:copy:views'], () =>
gulp.src([
'./build/Release/crypto_key.node',
'./src/const.json',
'./src/server/web/views/**/*',
'./src/**/assets/**/*',
'!./src/client/app/**/assets/**/*'
]).pipe(gulp.dest('./built/'))

View File

@ -95,7 +95,7 @@ common/views/components/nav.vue:
donors: "Sponsorzy"
repository: "Repozytorium"
develop: "Autorzy"
feedback: "Opinie"
feedback: "Podziel się opinią"
common/views/components/note-menu.vue:
favorite: "Dodaj do ulubionych"
@ -232,7 +232,7 @@ desktop/views/components/drive.nav-folder.vue:
desktop/views/components/drive.vue:
search: "Szukaj"
load-more: "Załaduj więcej"
empty-drive: "Twój dysk jest posty"
empty-drive: "Twój dysk jest pusty"
empty-drive-description: "Możesz wysłać plik klikając prawym przyciskiem myszy i wybierając \"Wyślij plik\" lub przeciągnąć plik i upuścić w tym oknie."
empty-folder: "Ten katalog jest posty"
unable-to-process: "Nie udało się dokończyć działania."
@ -240,6 +240,7 @@ desktop/views/components/drive.vue:
unhandled-error: "Nieznany błąd"
url-upload: "Wyślij z adresu"
url-of-file: "Adres URL pliku, który chcesz wysłać"
url-upload-requested: "Zaplanowano wysyłanie"
may-take-time: "Może trochę potrwać, zanim wysyłanie zostanie ukończone."
create-folder: "Utwórz katalog"
folder-name: "Nazwa katalogu"
@ -254,6 +255,7 @@ desktop/views/components/messaging-window.vue:
desktop/views/components/notes.note.vue:
reposted-by: "Udostępniono przez {}"
reply: "Odpowiedz"
renote: "Przeredaguj"
add-reaction: "Dodaj reakcję"
detail: "Pokaż szczegóły"
@ -267,11 +269,13 @@ desktop/views/components/post-form.vue:
quote-placeholder: "Zacytuj ten wpis…"
note: "Wyślij"
reply: "Odpowiedz"
posted: "Posted!"
renote: "Przeredaguj"
posted: "Opublikowano!"
replied: "Odpowiedziano!"
reposted: "Udostępniono!"
note-failed: "Nie udało się wysłać"
reply-failed: "Nie udało się odpowiedzieć"
renote-failed: "Nie udało się przeredagować"
posting: "Wysyłanie"
attach-media-from-local: "Załącz zawartość multimedialną z komputera"
attach-media-from-drive: "Załącz zawartość multimedialną z dysku"
@ -289,14 +293,19 @@ desktop/views/components/post-form-window.vue:
desktop/views/components/renote-form.vue:
quote: "Cytuj…"
cancel: "Anuluj"
renote: "Przeredaguj"
reposting: "Udostępnianie…"
success: "Udostępniono!"
failure: "Nie udało się przeredagować"
desktop/views/components/renote-form-window.vue:
title: "Czy na pewno chcesz przeredagować ten wpis?"
desktop/views/components/settings.vue:
profile: "Profil"
notification: "Powiadomienie"
notification: "Powiadomienia"
apps: "Aplikacje"
mute: "Wycisz"
mute: "Wyciszanie"
drive: "Dysk"
security: "Bezpieczeństwo"
password: "Hasło"
@ -392,7 +401,7 @@ desktop/views/pages/user/user.followers-you-know.vue:
no-users: "Brak użytkowników"
desktop/views/pages/user/user.friends.vue:
title: "Najczęściej odpisujący"
title: "Najbardziej aktywni"
loading: "Ładowanie"
no-users: "Brak użytkowników"

69
migration/2.4.0.js Normal file
View File

@ -0,0 +1,69 @@
// for Node.js interpret
const chalk = require('chalk');
const sequential = require('promise-sequential');
const { default: User } = require('../built/models/user');
const { default: DriveFile } = require('../built/models/drive-file');
async function main() {
const promiseGens = [];
const count = await User.count({});
let prev;
for (let i = 0; i < count; i++) {
promiseGens.push(() => {
const promise = new Promise(async (res, rej) => {
const user = await User.findOne(prev ? {
_id: { $gt: prev._id }
} : {}, {
sort: {
_id: 1
}
});
prev = user;
const set = {};
if (user.avatarId != null) {
const file = await DriveFile.findOne({ _id: user.avatarId });
if (file && file.metadata.properties.avgColor) {
set.avatarColor = file.metadata.properties.avgColor;
}
}
if (user.bannerId != null) {
const file = await DriveFile.findOne({ _id: user.bannerId });
if (file && file.metadata.properties.avgColor) {
set.bannerColor = file.metadata.properties.avgColor;
}
}
User.update({
_id: user._id
}, {
$set: set
}).then(() => {
res([i, user]);
}).catch(rej);
});
promise.then(([i, user]) => {
console.log(chalk`{gray ${i}} {green done: {bold ${user._id}} @${user.username}}`);
});
return promise;
});
}
return await sequential(promiseGens);
}
main().then(() => {
console.log('ALL DONE');
}).catch(console.error);

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "2.1.1",
"clientVersion": "1.0.5188",
"version": "2.4.0",
"clientVersion": "1.0.5215",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@ -58,6 +58,7 @@
"@types/koa-multer": "1.0.0",
"@types/koa-router": "7.0.28",
"@types/koa-send": "4.1.1",
"@types/koa-views": "^2.0.3",
"@types/koa__cors": "2.2.2",
"@types/kue": "0.11.8",
"@types/license-checker": "15.0.0",
@ -146,6 +147,7 @@
"koa-router": "7.4.0",
"koa-send": "4.1.3",
"koa-slow": "2.1.0",
"koa-views": "^6.1.4",
"kue": "0.11.6",
"license-checker": "18.0.0",
"loader-utils": "1.1.0",

View File

@ -1,3 +1,5 @@
block vars
doctype html
!= '\n<!-- Thank you for using Misskey! @syuilo -->\n'
@ -9,9 +11,17 @@ html
meta(name='application-name' content='Misskey')
meta(name='theme-color' content=themeColor)
meta(name='referrer' content='origin')
meta(property='og:site_name' content='Misskey')
link(rel='manifest' href='/manifest.json')
title Misskey
title
block title
| Misskey
block desc
meta(name='description' content='A SNS')
block meta
style
include ./../../../built/client/assets/init.css

View File

@ -1,8 +1,6 @@
<template>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="{ borderRadius: clientSettings.circleIcons ? '100%' : null }">
<img v-if="disablePreview" :src="`${user.avatarUrl}?thumbnail&size=128`" alt=""/>
<img v-else :src="`${user.avatarUrl}?thumbnail&size=128`" alt="" v-user-preview="user.id"/>
</router-link>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-if="disablePreview"></router-link>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else v-user-preview="user.id"></router-link>
</template>
<script lang="ts">
@ -10,6 +8,7 @@ import Vue from 'vue';
export default Vue.extend({
props: {
user: {
type: Object,
required: true
},
target: {
@ -20,20 +19,23 @@ export default Vue.extend({
required: false,
default: false
}
},
computed: {
style(): any {
return {
backgroundColor: this.user.avatarColor ? `rgb(${ this.user.avatarColor.join(',') })` : null,
backgroundImage: `url(${ this.user.avatarUrl }?thumbnail)`,
borderRadius: (this as any).clientSettings.circleIcons ? '100%' : null
};
}
}
});
</script>
<style lang="stylus" scoped>
.mk-avatar
display block
> img
display inline-block
width 100%
height 100%
margin 0
border-radius inherit
vertical-align bottom
background-size cover
background-position center center
transition border-radius 1s ease
</style>

View File

@ -31,6 +31,12 @@ export default Vue.extend({
};
},
created() {
if ((this as any).os.i.followingCount == 0) {
this.src = 'local';
}
},
mounted() {
(this.$refs.tl as any).$once('loaded', () => {
this.$emit('loaded');

View File

@ -75,6 +75,12 @@ export default Vue.extend({
}
},
created() {
if ((this as any).os.i.followingCount == 0) {
this.src = 'local';
}
},
mounted() {
document.title = 'Misskey';

View File

@ -9,7 +9,7 @@
<div class="body">
<div class="top">
<a class="avatar">
<img :src="`${user.avatarUrl}?thumbnail&size=200`" alt="avatar"/>
<img :src="user.avatarUrl" alt="avatar"/>
</a>
<mk-follow-button v-if="os.isSignedIn && os.i.id != user.id" :user="user"/>
</div>

View File

@ -1,5 +1,5 @@
{
"copyright": "Copyright (c) 2014-2018 syuilo",
"themeColor": "#5cbb2d",
"themeColor": "#f66e4f",
"themeColorForeground": "#fff"
}

View File

@ -28,7 +28,8 @@ export type IMetadata = {
_user: any;
folderId: mongo.ObjectID;
comment: string;
uri: string;
uri?: string;
url?: string;
deletedAt?: Date;
isExpired?: boolean;
};

View File

@ -286,7 +286,7 @@ export const pack = async (
_id: -1
}
});
return prev ? prev._id : null;
return prev ? prev._id.toHexString() : null;
})();
// Get next note info
@ -304,7 +304,7 @@ export const pack = async (
_id: 1
}
});
return next ? next._id : null;
return next ? next._id.toHexString() : null;
})();
if (_note.replyId) {

View File

@ -24,7 +24,7 @@ export async function createImage(actor: IRemoteUser, value): Promise<IDriveFile
log(`Creating the Image: ${image.url}`);
return await uploadFromUrl(image.url, actor);
return await uploadFromUrl(image.url, actor, null, image.url);
}
/**

View File

@ -1,6 +1,6 @@
/**
* 投稿を表す文字列を取得します。
* @param {*} note 投稿
* @param {*} note (packされた)投稿
*/
const summarize = (note: any): string => {
if (note.isHidden) {

View File

@ -4,6 +4,7 @@
import $ from 'cafy'; import ID from '../../../../cafy-id';
import User, { isValidName, isValidDescription, isValidLocation, isValidBirthday, pack } from '../../../../models/user';
import event from '../../../../publishers/stream';
import DriveFile from '../../../../models/drive-file';
/**
* Update myself
@ -51,12 +52,34 @@ module.exports = async (params, user, app) => new Promise(async (res, rej) => {
if (autoWatchErr) return rej('invalid autoWatch param');
if (autoWatch != null) user.settings.autoWatch = autoWatch;
if (avatarId) {
const avatar = await DriveFile.findOne({
_id: avatarId
});
if (avatar != null && avatar.metadata.properties.avgColor) {
user.avatarColor = avatar.metadata.properties.avgColor;
}
}
if (bannerId) {
const banner = await DriveFile.findOne({
_id: bannerId
});
if (banner != null && banner.metadata.properties.avgColor) {
user.bannerColor = banner.metadata.properties.avgColor;
}
}
await User.update(user._id, {
$set: {
name: user.name,
description: user.description,
avatarId: user.avatarId,
avatarColor: user.avatarColor,
bannerId: user.bannerId,
bannerColor: user.bannerColor,
profile: user.profile,
isBot: user.isBot,
settings: user.settings

View File

@ -6,6 +6,8 @@ import * as mongodb from 'mongodb';
import DriveFile, { getDriveFileBucket } from '../../models/drive-file';
import DriveFileThumbnail, { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
const assets = `${__dirname}/../../server/file/assets/`;
const commonReadableHandlerGenerator = (ctx: Koa.Context) => (e: Error): void => {
console.error(e);
ctx.status = 500;
@ -25,16 +27,16 @@ export default async function(ctx: Koa.Context) {
if (file == null) {
ctx.status = 404;
await send(ctx, `${__dirname}/assets/dummy.png`);
await send(ctx, '/dummy.png', { root: assets });
return;
}
if (file.metadata.deletedAt) {
ctx.status = 410;
if (file.metadata.isExpired) {
await send(ctx, `${__dirname}/assets/cache-expired.png`);
await send(ctx, '/cache-expired.png', { root: assets });
} else {
await send(ctx, `${__dirname}/assets/tombstone.png`);
await send(ctx, '/tombstone.png', { root: assets });
}
return;
}

View File

@ -7,14 +7,32 @@ import * as Koa from 'koa';
import * as Router from 'koa-router';
import * as send from 'koa-send';
import * as favicon from 'koa-favicon';
import * as views from 'koa-views';
import docs from './docs';
import User from '../../models/user';
import parseAcct from '../../acct/parse';
import { fa } from '../../build/fa';
import config from '../../config';
import Note, { pack as packNote } from '../../models/note';
import getNoteSummary from '../../renderers/get-note-summary';
const consts = require('../../const.json');
const client = `${__dirname}/../../client/`;
// Init app
const app = new Koa();
// Init renderer
app.use(views(__dirname + '/views', {
extension: 'pug',
options: {
config,
themeColor: consts.themeColor,
facss: fa.dom.css()
}
}));
// Serve favicon
app.use(favicon(`${client}/assets/favicon.ico`));
@ -42,17 +60,21 @@ router.get('/assets/*', async ctx => {
// Apple touch icon
router.get('/apple-touch-icon.png', async ctx => {
await send(ctx, `${client}/assets/apple-touch-icon.png`);
await send(ctx, '/assets/apple-touch-icon.png', {
root: client
});
});
// ServiceWroker
router.get(/^\/sw\.(.+?)\.js$/, async ctx => {
await send(ctx, `${client}/assets/sw.${ctx.params[0]}.js`);
});
//router.get(/^\/sw\.(.+?)\.js$/, async ctx => {
// await send(ctx, `${client}/assets/sw.${ctx.params[0]}.js`);
//});
// Manifest
router.get('/manifest.json', async ctx => {
await send(ctx, `${client}/assets/manifest.json`);
await send(ctx, '/assets/manifest.json', {
root: client
});
});
//#endregion
@ -63,6 +85,38 @@ router.use('/docs', docs.routes());
// URL preview endpoint
router.get('/url', require('./url-preview'));
//#region for crawlers
// User
router.get('/@:user', async ctx => {
const { username, host } = parseAcct(ctx.params.user);
const user = await User.findOne({
usernameLower: username.toLowerCase(),
host
});
if (user != null) {
await ctx.render('user', { user });
} else {
ctx.status = 404;
}
});
// Note
router.get('/notes/:note', async ctx => {
const note = await Note.findOne({ _id: ctx.params.note });
if (note != null) {
const _note = await packNote(note);
await ctx.render('note', {
note: _note,
summary: getNoteSummary(_note)
});
} else {
ctx.status = 404;
}
});
//#endregion
// Render base html for all requests
router.get('*', async ctx => {
await send(ctx, `app/base.html`, {

View File

@ -14,6 +14,8 @@ module.exports = async (ctx: Koa.Context) => {
function wrap(url: string): string {
return url != null
? `https://images.weserv.nl/?url=${url.replace(/^https?:\/\//, '')}`
? url.startsWith('https://')
? url
: `https://images.weserv.nl/?url=${url.replace(/^http:\/\//, '')}`
: null;
}

View File

@ -0,0 +1,25 @@
extends ../../../../src/client/app/base
block vars
- const user = note.user;
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = `${config.url}/notes/${note.id}`;
block title
= `${title} | Misskey`
block desc
meta(name='description' content= summary)
block meta
meta(name='twitter:card' content='summary')
meta(property='og:type' content='article')
meta(property='og:title' content= title)
meta(property='og:description' content= summary)
meta(property='og:url' content= url)
meta(property='og:image' content= user.avatarUrl)
if note.prev
link(rel='prev' href=`${config.url}/notes/${note.prev}`)
if note.next
link(rel='next' href=`${config.url}/notes/${note.next}`)

View File

@ -0,0 +1,20 @@
extends ../../../../src/client/app/base
block vars
- const title = user.name ? `${user.name} (@${user.username})` : `@${user.username}`;
- const url = config.url + '/@' + (user.host ? `${user.username}@${user.host}` : user.username);
- const img = user.avatarId ? `${config.drive_url}/${user.avatarId}` : null;
block title
= `${title} | Misskey`
block desc
meta(name='description' content= user.description)
block meta
meta(name='twitter:card' content='summary')
meta(property='og:type' content='blog')
meta(property='og:title' content= title)
meta(property='og:description' content= user.description)
meta(property='og:url' content= url)
meta(property='og:image' content= img)

View File

@ -62,6 +62,7 @@ const addFile = async (
comment: string = null,
folderId: mongodb.ObjectID = null,
force: boolean = false,
url: string = null,
uri: string = null
): Promise<IDriveFile> => {
log(`registering ${name} (user: ${getAcct(user)}, path: ${path})`);
@ -296,6 +297,10 @@ const addFile = async (
properties: properties
} as IMetadata;
if (url !== null) {
metadata.url = url;
}
if (uri !== null) {
metadata.uri = uri;
}

View File

@ -43,7 +43,7 @@ export default async (url, user, folderId = null, uri = null): Promise<IDriveFil
let error;
try {
driveFile = await create(user, path, name, null, folderId, false, uri);
driveFile = await create(user, path, name, null, folderId, false, url, uri);
log(`created: ${driveFile._id}`);
} catch (e) {
error = e;