diff --git a/.autogen/autogen.sh b/.autogen/autogen.sh
new file mode 100755
index 0000000000..1ea71ff00c
--- /dev/null
+++ b/.autogen/autogen.sh
@@ -0,0 +1,86 @@
+#!/usr/bin/env bash
+# BEARER_TOKEN=
+# CAMPAIGN_ID=
+# GITHUB_TOKEN=
+# HEAD='acid-chicken:patch-autogen'
+# REPO='syuilo/misskey'
+test "$(curl -LSs -w '\n' -- "https://api.github.com/repos/$REPO/pulls?access_token=$GITHUB_TOKEN" | jq -r '.[].head.label' | grep $HEAD)" && exit 1
+cd "$(dirname $0)/.." && \
+touch null.cache && \
+rm *.cache && \
+git checkout master && \
+git pull origin master && \
+git pull upstream master && \
+git stash && \
+git rebase -f upstream/master && \
+git branch patch-autogen && \
+git checkout patch-autogen && \
+git reset --hard HEAD || \
+exit 1
+touch patreon.md.cache && \
+rm patreon.md.cache && \
+echo '<!-- PATREON_START -->' > patreon.md.cache && \
+URL="https://www.patreon.com/api/oauth2/v2/campaigns/$CAMPAIGN_ID/members?include=currently_entitled_tiers,user&fields%5Btier%5D=title&fields%5Buser%5D=full_name,thumb_url,url,hide_pledges"
+while :
+ do
+  touch patreon.raw.cache && \
+  rm patreon.raw.cache && \
+  curl -LSs -w '\n' -H "Authorization: Bearer $BEARER_TOKEN" -- $URL > patreon.raw.cache && \
+  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 && \
+  echo '<table><tr>' >> patreon.md.cache && \
+  cat patreon.cache | \
+  awk -F'\t' '{print $2,$1}' | \
+  sed -e 's/ /\\" alt=\\"/' | \
+  xargs -I% echo '<td><img src="%"></td>' >> patreon.md.cache && \
+  echo '</tr><tr>' >> patreon.md.cache && \
+  cat patreon.cache | \
+  awk -F'\t' '{print $3,$1}' | \
+  sed -e 's/ /\\">/' | \
+  xargs -I% echo '<td><a href="%</a></td>' >> patreon.md.cache && \
+  echo '</tr></table>' >> patreon.md.cache || \
+  exit 1
+  NEW_URL="$(cat patreon.raw.cache | jq -r '.links.next')"
+  test "$NEW_URL" = 'null' && \
+  break || \
+  URL="$NEW_URL"
+done
+IGNORE= && \
+echo -e "\n**Last updated:** $(date -uR | sed 's/\+0000/UTC/')\n<!-- PATREON_END -->" >> patreon.md.cache && \
+touch README.md && \
+touch .autogen/README.md && \
+rm .autogen/README.md && \
+mv README.md .autogen/README.md && \
+cat .autogen/README.md | while IFS= read LINE;
+ do
+  if [[ -z "$IGNORE" ]]
+   then
+    if [[ "$LINE" = '<!-- PATREON_START -->' ]]
+     then
+      IGNORE='PATREON_INSIDE'
+     else
+      echo "$LINE" >> README.md
+    fi
+   else
+    if [[ "$LINE" = '<!-- PATREON_END -->' ]]
+     then
+      IGNORE=
+      cat patreon.md.cache >> README.md
+    fi
+  fi
+done
+cat patreon.md.cache
+touch null.cache && \
+rm *.cache && \
+diff .autogen/README.md README.md > diff.cache
+cat diff.cache && \
+test 4 -lt $(cat diff.cache | wc -l) && \
+git add README.md && \
+git commit -m 'Update README.md [AUTOGEN]' && \
+git push -f origin patch-autogen && \
+curl -LSs -w '\n' -X POST -d '{"title":"[AUTOMATED] Update README.md","body":"*This pull request was created by a tool.*","head":"'$HEAD'","base":"master"}' -- "https://api.github.com/repos/$REPO/pulls?access_token=$GITHUB_TOKEN"
+git stash
+git checkout master
+git branch -D patch-autogen
diff --git a/package.json b/package.json
index 03bb4796ed..3b40ed1767 100644
--- a/package.json
+++ b/package.json
@@ -191,7 +191,7 @@
 		"style-loader": "0.22.1",
 		"stylus": "0.54.5",
 		"stylus-loader": "3.0.2",
-		"summaly": "2.1.3",
+		"summaly": "2.1.4",
 		"systeminformation": "3.42.9",
 		"syuilo-password-strength": "0.0.1",
 		"textarea-caret": "3.1.0",
diff --git a/src/client/app/common/views/components/url-preview.vue b/src/client/app/common/views/components/url-preview.vue
index 95dafa8f4c..be69012737 100644
--- a/src/client/app/common/views/components/url-preview.vue
+++ b/src/client/app/common/views/components/url-preview.vue
@@ -1,5 +1,7 @@
 <template>
-<iframe v-if="player" :src="player" heigth="250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
+<div v-if="player.url" class="player" :style="`padding: ${(player.height || 0) / (player.width || 1) * 100}% 0 0`">
+	<iframe :src="player.url" :width="player.width || '100%'" :heigth="player.height || 250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
+</div>
 <div v-else-if="tweetUrl && detail" class="twitter">
 	<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
 		<a :href="url"></a>
@@ -46,7 +48,11 @@ export default Vue.extend({
 			thumbnail: null,
 			icon: null,
 			sitename: null,
-			player: null,
+			player: {
+				url: null,
+				width: null,
+				height: null
+			},
 			tweetUrl: null,
 			misskeyUrl
 		};
@@ -170,9 +176,17 @@ export default Vue.extend({
 </script>
 
 <style lang="stylus" scoped>
-iframe
+.twitter
+	position relative
 	width 100%
 
+	> iframe
+		height 100%
+		left 0
+		position absolute
+		top 0
+		width 100%
+
 root(isDark)
 	> a
 		display block
diff --git a/src/client/app/mobile/views/components/follow-button.vue b/src/client/app/mobile/views/components/follow-button.vue
index b6a52fe1ed..360ee91d4b 100644
--- a/src/client/app/mobile/views/components/follow-button.vue
+++ b/src/client/app/mobile/views/components/follow-button.vue
@@ -99,7 +99,7 @@ export default Vue.extend({
 	cursor pointer
 	padding 0 16px
 	margin 0
-	min-width 150px
+	min-width 100px
 	line-height 36px
 	font-size 14px
 	font-weight bold
diff --git a/src/client/app/mobile/views/components/index.ts b/src/client/app/mobile/views/components/index.ts
index 38c130ecbf..3e830f4e96 100644
--- a/src/client/app/mobile/views/components/index.ts
+++ b/src/client/app/mobile/views/components/index.ts
@@ -12,6 +12,7 @@ import noteCard from './note-card.vue';
 import userCard from './user-card.vue';
 import noteDetail from './note-detail.vue';
 import followButton from './follow-button.vue';
+import muteButton from './mute-button.vue';
 import friendsMaker from './friends-maker.vue';
 import notification from './notification.vue';
 import notifications from './notifications.vue';
@@ -36,6 +37,7 @@ Vue.component('mk-note-card', noteCard);
 Vue.component('mk-user-card', userCard);
 Vue.component('mk-note-detail', noteDetail);
 Vue.component('mk-follow-button', followButton);
+Vue.component('mk-mute-button', muteButton);
 Vue.component('mk-friends-maker', friendsMaker);
 Vue.component('mk-notification', notification);
 Vue.component('mk-notifications', notifications);
diff --git a/src/client/app/mobile/views/components/mute-button.vue b/src/client/app/mobile/views/components/mute-button.vue
new file mode 100644
index 0000000000..3cb568615d
--- /dev/null
+++ b/src/client/app/mobile/views/components/mute-button.vue
@@ -0,0 +1,79 @@
+<template>
+<button
+  class="mk-mute-button"
+  :class="{ active: user.isMuted }"
+  @click="onClick">
+  <span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
+  <span v-else>%fa:eye% %i18n:@unmute%</span>
+</button>
+</template>
+
+<script lang="ts">
+import Vue from 'vue'
+export default Vue.extend({
+  props: {
+    user: {
+      type: Object,
+      required: true
+    }
+  },
+  methods: {
+    onClick() {
+      if (!this.user.isMuted) {
+        this.mute();
+      } else {
+        this.unmute();
+      }
+    },
+    mute() {
+      (this as any).api('mute/create', { userId: this.user.id})
+        .then(() => { this.user.isMuted = true })
+        .catch(() => { alert('error')})
+    },
+    unmute() {
+      (this as any).api('mute/delete', { userId: this.user.id })
+        .then(() => { this.user.isMuted = false })
+        .catch(() => { alert('error') })
+    }
+  },
+})
+</script>
+
+
+<style lang="stylus" scoped>
+@import '~const.styl'
+
+.mk-mute-button
+  display block
+  user-select none 
+  cursor pointer
+  padding 0 16px
+  margin 0
+  min-width 100px
+  line-height 36px
+  font-size 14px
+  font-weight bold
+  color $theme-color
+  background transparent
+  outline none
+  border solid 1px $theme-color
+  border-radius 36px
+
+  &:hover
+    background rgba($theme-color, 0.1)
+
+  &:active
+    background rgba($theme-color, 0.2)
+
+  &.active
+    color $theme-color-foreground
+    background $theme-color
+
+    &:hover
+      background lighten($theme-color, 10%)
+      border-color lighten($theme-color, 10%)
+    &:active
+      background darken($theme-color, 10%)
+      border-color darken($theme-color, 10%)
+
+</style>
diff --git a/src/client/app/mobile/views/pages/user.vue b/src/client/app/mobile/views/pages/user.vue
index d573538fdd..e72867352f 100644
--- a/src/client/app/mobile/views/pages/user.vue
+++ b/src/client/app/mobile/views/pages/user.vue
@@ -11,6 +11,7 @@
 					<a class="avatar">
 						<img :src="user.avatarUrl" alt="avatar"/>
 					</a>
+					<mk-mute-button v-if="$store.state.i.id != user.id" :user="user"/>
 					<mk-follow-button v-if="$store.getters.isSignedIn && $store.state.i.id != user.id" :user="user"/>
 				</div>
 				<div class="title">
@@ -184,6 +185,9 @@ root(isDark)
 							border 4px solid $bg
 							border-radius 12px
 
+				> .mk-mute-button
+					float right
+
 				> .mk-follow-button
 					float right