summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2018-03-27 11:47:17 +0900
committersyuilo <syuilotan@yahoo.co.jp>2018-03-27 11:47:17 +0900
commit27183b2142e30a25975880012c00d203a9ebbf1a (patch)
treefa39db23af4aed2cab709c1626c9b095038c69e9 /src
parentRefactor (diff)
parentMerge pull request #1304 from rinsuki/features/implement-media-video (diff)
downloadmisskey-27183b2142e30a25975880012c00d203a9ebbf1a.tar.gz
misskey-27183b2142e30a25975880012c00d203a9ebbf1a.tar.bz2
misskey-27183b2142e30a25975880012c00d203a9ebbf1a.zip
Merge branch 'master' of https://github.com/syuilo/misskey
Diffstat (limited to 'src')
-rw-r--r--src/api/models/user.ts2
-rw-r--r--src/api/private/signup.ts2
-rw-r--r--src/crypto_key.cc111
-rw-r--r--src/crypto_key.d.ts1
-rw-r--r--src/web/app/common/views/components/media-list.vue3
-rw-r--r--src/web/app/desktop/views/components/index.ts2
-rw-r--r--src/web/app/desktop/views/components/media-video-dialog.vue70
-rw-r--r--src/web/app/desktop/views/components/media-video.vue67
-rw-r--r--src/web/app/mobile/views/components/index.ts2
-rw-r--r--src/web/app/mobile/views/components/media-video.vue36
10 files changed, 295 insertions, 1 deletions
diff --git a/src/api/models/user.ts b/src/api/models/user.ts
index 3f0b10d4c2..d3875a65b9 100644
--- a/src/api/models/user.ts
+++ b/src/api/models/user.ts
@@ -59,6 +59,7 @@ export type IUser = {
is_suspended: boolean;
keywords: string[];
account: {
+ keypair: string;
email: string;
links: string[];
password: string;
@@ -160,6 +161,7 @@ export const pack = (
delete _user.latest_post;
// Remove private properties
+ delete _user.account.keypair;
delete _user.account.password;
delete _user.account.token;
delete _user.account.two_factor_temp_secret;
diff --git a/src/api/private/signup.ts b/src/api/private/signup.ts
index 902642425c..690f3001cc 100644
--- a/src/api/private/signup.ts
+++ b/src/api/private/signup.ts
@@ -1,6 +1,7 @@
import * as uuid from 'uuid';
import * as express from 'express';
import * as bcrypt from 'bcryptjs';
+import { generate as generateKeypair } from '../../crypto_key';
import recaptcha = require('recaptcha-promise');
import User, { IUser, validateUsername, validatePassword, pack } from '../models/user';
import generateUserToken from '../common/generate-native-user-token';
@@ -119,6 +120,7 @@ export default async (req: express.Request, res: express.Response) => {
username: username,
username_lower: username.toLowerCase(),
account: {
+ keypair: generateKeypair(),
token: secret,
email: null,
links: null,
diff --git a/src/crypto_key.cc b/src/crypto_key.cc
new file mode 100644
index 0000000000..c8e4d8f7f0
--- /dev/null
+++ b/src/crypto_key.cc
@@ -0,0 +1,111 @@
+#include <nan.h>
+#include <openssl/bio.h>
+#include <openssl/buffer.h>
+#include <openssl/crypto.h>
+#include <openssl/pem.h>
+#include <openssl/rsa.h>
+#include <openssl/x509.h>
+
+NAN_METHOD(extractPublic)
+{
+ const auto sourceString = info[0]->ToString();
+ if (!sourceString->IsOneByte()) {
+ Nan::ThrowError("Malformed character found");
+ return;
+ }
+
+ size_t sourceLength = sourceString->Length();
+ const auto sourceBuf = new char[sourceLength];
+
+ Nan::DecodeWrite(sourceBuf, sourceLength, sourceString);
+
+ const auto source = BIO_new_mem_buf(sourceBuf, sourceLength);
+ if (source == nullptr) {
+ Nan::ThrowError("Memory allocation failed");
+ delete sourceBuf;
+ return;
+ }
+
+ const auto rsa = PEM_read_bio_RSAPrivateKey(source, nullptr, nullptr, nullptr);
+
+ BIO_free(source);
+ delete sourceBuf;
+
+ if (rsa == nullptr) {
+ Nan::ThrowError("Decode failed");
+ return;
+ }
+
+ const auto destination = BIO_new(BIO_s_mem());
+ if (destination == nullptr) {
+ Nan::ThrowError("Memory allocation failed");
+ return;
+ }
+
+ const auto result = PEM_write_bio_RSAPublicKey(destination, rsa);
+
+ RSA_free(rsa);
+
+ if (result != 1) {
+ Nan::ThrowError("Public key extraction failed");
+ BIO_free(destination);
+ return;
+ }
+
+ char *pem;
+ const auto pemLength = BIO_get_mem_data(destination, &pem);
+
+ info.GetReturnValue().Set(Nan::Encode(pem, pemLength));
+ BIO_free(destination);
+}
+
+NAN_METHOD(generate)
+{
+ const auto exponent = BN_new();
+ const auto mem = BIO_new(BIO_s_mem());
+ const auto rsa = RSA_new();
+ char *data;
+ long result;
+
+ if (exponent == nullptr || mem == nullptr || rsa == nullptr) {
+ Nan::ThrowError("Memory allocation failed");
+ goto done;
+ }
+
+ result = BN_set_word(exponent, 65537);
+ if (result != 1) {
+ Nan::ThrowError("Exponent setting failed");
+ goto done;
+ }
+
+ result = RSA_generate_key_ex(rsa, 2048, exponent, nullptr);
+ if (result != 1) {
+ Nan::ThrowError("Key generation failed");
+ goto done;
+ }
+
+ result = PEM_write_bio_RSAPrivateKey(mem, rsa, NULL, NULL, 0, NULL, NULL);
+ if (result != 1) {
+ Nan::ThrowError("Key export failed");
+ goto done;
+ }
+
+ result = BIO_get_mem_data(mem, &data);
+ info.GetReturnValue().Set(Nan::Encode(data, result));
+
+done:
+ RSA_free(rsa);
+ BIO_free(mem);
+ BN_free(exponent);
+}
+
+NAN_MODULE_INIT(InitAll)
+{
+ Nan::Set(target, Nan::New<v8::String>("extractPublic").ToLocalChecked(),
+ Nan::GetFunction(Nan::New<v8::FunctionTemplate>(extractPublic)).ToLocalChecked());
+
+ Nan::Set(target, Nan::New<v8::String>("generate").ToLocalChecked(),
+ Nan::GetFunction(Nan::New<v8::FunctionTemplate>(generate)).ToLocalChecked());
+}
+
+NODE_MODULE(crypto_key, InitAll);
diff --git a/src/crypto_key.d.ts b/src/crypto_key.d.ts
new file mode 100644
index 0000000000..28ac2f9683
--- /dev/null
+++ b/src/crypto_key.d.ts
@@ -0,0 +1 @@
+export function generate(): String;
diff --git a/src/web/app/common/views/components/media-list.vue b/src/web/app/common/views/components/media-list.vue
index d0da584a40..64172ad0b4 100644
--- a/src/web/app/common/views/components/media-list.vue
+++ b/src/web/app/common/views/components/media-list.vue
@@ -1,7 +1,8 @@
<template>
<div class="mk-media-list" :data-count="mediaList.length">
<template v-for="media in mediaList">
- <mk-media-image :image="media" :key="media.id"/>
+ <mk-media-video :video="media" :key="media.id" v-if="media.type.startsWith('video')" :inline-playable="mediaList.length === 1"/>
+ <mk-media-image :image="media" :key="media.id" v-else />
</template>
</div>
</template>
diff --git a/src/web/app/desktop/views/components/index.ts b/src/web/app/desktop/views/components/index.ts
index 9bca603a53..3798bf6d2d 100644
--- a/src/web/app/desktop/views/components/index.ts
+++ b/src/web/app/desktop/views/components/index.ts
@@ -13,6 +13,7 @@ import analogClock from './analog-clock.vue';
import ellipsisIcon from './ellipsis-icon.vue';
import mediaImage from './media-image.vue';
import mediaImageDialog from './media-image-dialog.vue';
+import mediaVideo from './media-video.vue';
import notifications from './notifications.vue';
import postForm from './post-form.vue';
import repostForm from './repost-form.vue';
@@ -42,6 +43,7 @@ Vue.component('mk-analog-clock', analogClock);
Vue.component('mk-ellipsis-icon', ellipsisIcon);
Vue.component('mk-media-image', mediaImage);
Vue.component('mk-media-image-dialog', mediaImageDialog);
+Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-notifications', notifications);
Vue.component('mk-post-form', postForm);
Vue.component('mk-repost-form', repostForm);
diff --git a/src/web/app/desktop/views/components/media-video-dialog.vue b/src/web/app/desktop/views/components/media-video-dialog.vue
new file mode 100644
index 0000000000..cbf862cd1c
--- /dev/null
+++ b/src/web/app/desktop/views/components/media-video-dialog.vue
@@ -0,0 +1,70 @@
+<template>
+<div class="mk-media-video-dialog">
+ <div class="bg" @click="close"></div>
+ <video :src="video.url" :title="video.name" controls autoplay ref="video"/>
+</div>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import * as anime from 'animejs';
+
+export default Vue.extend({
+ props: ['video', 'start'],
+ mounted() {
+ anime({
+ targets: this.$el,
+ opacity: 1,
+ duration: 100,
+ easing: 'linear'
+ });
+ const videoTag = this.$refs.video as HTMLVideoElement
+ if (this.start) videoTag.currentTime = this.start
+ },
+ methods: {
+ close() {
+ anime({
+ targets: this.$el,
+ opacity: 0,
+ duration: 100,
+ easing: 'linear',
+ complete: () => this.$destroy()
+ });
+ }
+ }
+});
+</script>
+
+<style lang="stylus" scoped>
+.mk-media-video-dialog
+ display block
+ position fixed
+ z-index 2048
+ top 0
+ left 0
+ width 100%
+ height 100%
+ opacity 0
+
+ > .bg
+ display block
+ position fixed
+ z-index 1
+ top 0
+ left 0
+ width 100%
+ height 100%
+ background rgba(0, 0, 0, 0.7)
+
+ > video
+ position fixed
+ z-index 2
+ top 0
+ right 0
+ bottom 0
+ left 0
+ max-width 80vw
+ max-height 80vh
+ margin auto
+
+</style>
diff --git a/src/web/app/desktop/views/components/media-video.vue b/src/web/app/desktop/views/components/media-video.vue
new file mode 100644
index 0000000000..4fd955a821
--- /dev/null
+++ b/src/web/app/desktop/views/components/media-video.vue
@@ -0,0 +1,67 @@
+<template>
+ <video class="mk-media-video"
+ :src="video.url"
+ :title="video.name"
+ controls
+ @dblclick.prevent="onClick"
+ ref="video"
+ v-if="inlinePlayable" />
+ <a class="mk-media-video-thumbnail"
+ :href="video.url"
+ :style="imageStyle"
+ @click.prevent="onClick"
+ :title="video.name"
+ v-else>
+ %fa:R play-circle%
+ </a>
+</template>
+
+<script lang="ts">
+import Vue from 'vue';
+import MkMediaVideoDialog from './media-video-dialog.vue';
+
+export default Vue.extend({
+ props: ['video', 'inlinePlayable'],
+ computed: {
+ imageStyle(): any {
+ return {
+ 'background-image': `url(${this.video.url}?thumbnail&size=512)`
+ };
+ }
+ },
+ methods: {
+ onClick() {
+ const videoTag = this.$refs.video as (HTMLVideoElement | null)
+ var start = 0
+ if (videoTag) {
+ start = videoTag.currentTime
+ videoTag.pause()
+ }
+ (this as any).os.new(MkMediaVideoDialog, {
+ video: this.video,
+ start,
+ })
+ }
+ }
+})
+</script>
+
+<style lang="stylus" scoped>
+.mk-media-video
+ display block
+ width 100%
+ height 100%
+ border-radius 4px
+.mk-media-video-thumbnail
+ display flex
+ justify-content center
+ align-items center
+ font-size 3.5em
+
+ cursor zoom-in
+ overflow hidden
+ background-position center
+ background-size cover
+ width 100%
+ height 100%
+</style>
diff --git a/src/web/app/mobile/views/components/index.ts b/src/web/app/mobile/views/components/index.ts
index 4743f50e0d..fb8f65f47d 100644
--- a/src/web/app/mobile/views/components/index.ts
+++ b/src/web/app/mobile/views/components/index.ts
@@ -5,6 +5,7 @@ import timeline from './timeline.vue';
import post from './post.vue';
import posts from './posts.vue';
import mediaImage from './media-image.vue';
+import mediaVideo from './media-video.vue';
import drive from './drive.vue';
import postPreview from './post-preview.vue';
import subPostContent from './sub-post-content.vue';
@@ -27,6 +28,7 @@ Vue.component('mk-timeline', timeline);
Vue.component('mk-post', post);
Vue.component('mk-posts', posts);
Vue.component('mk-media-image', mediaImage);
+Vue.component('mk-media-video', mediaVideo);
Vue.component('mk-drive', drive);
Vue.component('mk-post-preview', postPreview);
Vue.component('mk-sub-post-content', subPostContent);
diff --git a/src/web/app/mobile/views/components/media-video.vue b/src/web/app/mobile/views/components/media-video.vue
new file mode 100644
index 0000000000..68cd48587a
--- /dev/null
+++ b/src/web/app/mobile/views/components/media-video.vue
@@ -0,0 +1,36 @@
+<template>
+ <a class="mk-media-video"
+ :href="video.url"
+ target="_blank"
+ :style="imageStyle"
+ :title="video.name">
+ %fa:R play-circle%
+ </a>
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+export default Vue.extend({
+ props: ['video'],
+ computed: {
+ imageStyle(): any {
+ return {
+ 'background-image': `url(${this.video.url}?thumbnail&size=512)`
+ };
+ }
+ },})
+</script>
+
+<style lang="stylus" scoped>
+.mk-media-video
+ display flex
+ justify-content center
+ align-items center
+
+ font-size 3.5em
+ overflow hidden
+ background-position center
+ background-size cover
+ width 100%
+ height 100%
+</style>