Compare commits

...

41 Commits

Author SHA1 Message Date
d040dc19bc Merge branch 'develop' 2019-04-15 12:23:20 +09:00
a38ae4a402 11.0.2 2019-04-15 12:23:03 +09:00
772063aade Refactor 2019-04-15 12:20:48 +09:00
31c26354c5 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-15 12:11:58 +09:00
64b331c5b2 Update read.ts 2019-04-15 12:11:32 +09:00
94f8a145ec Better permisson Fix #2341 (#4611)
* Better permisson Fix #2341

* add kinds.ts

* test

* fix

* v11

* fix
2019-04-15 12:10:40 +09:00
b357afa30a Clean up 2019-04-15 12:04:23 +09:00
fee0437493 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-15 12:03:04 +09:00
663b8864c1 Fix bug 2019-04-15 12:03:00 +09:00
c6eafdde30 Fix bug 2019-04-15 11:59:25 +09:00
f32ff95256 New Crowdin translations (#4685)
* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Czech)
2019-04-15 11:33:08 +09:00
0452b75c3e Fix #4688 (#4689) 2019-04-15 11:32:53 +09:00
97efd23ec8 Update README.md [AUTOGEN] (#4691) 2019-04-15 11:31:32 +09:00
ea3e311528 簡易的なパーミッションの後方互換 (#4687) 2019-04-15 03:48:54 +09:00
0565454419 callbackUrlをappのレスポンスに追加 (#4686) 2019-04-15 03:39:31 +09:00
35c79c2f29 Fix bug 2019-04-15 03:20:14 +09:00
e6089aec48 Update README.md 2019-04-15 02:45:16 +09:00
dd3f7834c3 Merge branch 'develop' 2019-04-15 01:06:57 +09:00
f8cf18d880 11.0.1 2019-04-15 01:06:36 +09:00
2ac7e242b6 Fix test 2019-04-15 01:03:57 +09:00
d8b604a94f Update CHANGELOG.md 2019-04-14 23:18:01 +09:00
a1f7b00981 Clean up 2019-04-14 22:54:33 +09:00
b7ee42198c New translations ja-JP.yml (German) (#4683) 2019-04-14 22:40:35 +09:00
d66e4b7ff9 Merge branch 'develop' 2019-04-14 20:38:55 +09:00
7f9789cf65 11.0.0 2019-04-14 20:36:13 +09:00
b41c18e033 New translations ja-JP.yml (English) (#4681) 2019-04-14 20:31:15 +09:00
3c73a7ec6d inc/dec score 2019-04-14 20:28:57 +09:00
182c09d952 Update note.ts 2019-04-14 20:26:47 +09:00
9fb86aed04 Clean up 2019-04-14 18:18:20 +09:00
b8f7b13b05 11.0.0-beta.16 2019-04-14 17:44:16 +09:00
1ee1d5b6d1 Update and clean dependencies 🚀 2019-04-14 17:40:30 +09:00
154c38c314 Fix URL 2019-04-14 17:23:11 +09:00
83b7010d6a 10.100.0 2019-04-09 21:13:52 +09:00
71654cbe47 Fix #4636 2019-04-09 21:11:05 +09:00
e8f96e848a Merge branch 'v10' of https://github.com/syuilo/misskey into v10 2019-04-09 21:10:36 +09:00
251abf21d4 Update .gitignore 2019-04-09 21:10:18 +09:00
d103427932 Fix non media thumbnails (#4380) 2019-04-09 21:07:46 +09:00
592cdfa910 ユーザーリストでフォローボタンを表示するように (#4654) 2019-04-08 20:18:42 +09:00
f2ad1a0406 Fix: 投稿ウィジットでローカルのみの公開範囲で投稿できない (#4653) 2019-04-08 20:16:00 +09:00
82af9320c0 Fix: TLを遡った時に抜けがある時がある (v10) (#4629)
* Update the cursor when the timeline is updated

* fix releaseQueue
2019-04-08 15:18:44 +09:00
fceebf7388 Fix #4562 (#4563) 2019-04-08 15:17:39 +09:00
52 changed files with 352 additions and 777 deletions

View File

@ -5,8 +5,20 @@ 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.0.0 (daybreak)
-----------------
11.0.2 (2019/04/15)
-------------------
* アプリが作成できない問題を修正
* 「ハイライト」が表示されない問題を修正
* リモートの投稿に添付されている画像が小さい問題を修正
* モバイル版でリストの名前が表示されない問題を修正
* APIドキュメントにパーミッション一覧を追加
11.0.1 (2019/04/15)
-------------------
* 不要な依存関係を削除
11.0.0 daybreak (2019/04/14)
----------------------------
* **データベースがMongoDBからPostgreSQLに変更されました**
* **Redisが必須に**
* アカウントを完全に削除できるように
@ -25,7 +37,7 @@ If you encounter any problems with updating, please try the following:
* 例えば`game.settings.map``game.map`になる
### 既知の問題
* アプリが作成できない
* ユーザー認証無しでのアプリが作成できない
* 依存ライブラリの問題と思わるため、対応が難しい
### Migration

View File

@ -59,7 +59,7 @@ Organize and store your files! Want to post a picture you have already uploaded?
---
...and more! Experience Misskey with your own eyes at [misskey.xyz](https://misskey.xyz) or join one of the [other instances](https://joinmisskey.github.io/) that are available.
...and more! Experience Misskey with your own eyes at [misskey.io](https://misskey.io/) or join one of the [other instances](https://joinmisskey.github.io/) that are available.
Screen shots
----------------------------------------------------------------
@ -103,7 +103,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weep" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
@ -111,7 +110,6 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
</tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
<td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td>
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
@ -165,7 +163,7 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
</tr><tr>
</tr></table>
**Last updated:** Sun, 14 Apr 2019 08:13:12 UTC
**Last updated:** Mon, 15 Apr 2019 01:59:07 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@ -1,9 +0,0 @@
{
'targets': [
{
'target_name': 'crypto_key',
'sources': ['src/crypto_key.cc'],
'include_dirs': ['<!(node -e "require(\'nan\')")']
}
]
}

View File

@ -49,7 +49,6 @@ gulp.task('build:copy:views', () =>
gulp.task('build:copy', gulp.parallel('build:copy:views', () =>
gulp.src([
'./build/Release/crypto_key.node',
'./src/const.json',
'./src/server/web/views/**/*',
'./src/**/assets/**/*',

View File

@ -69,6 +69,9 @@ common:
following: "Sledovaní"
followers: "Sledující"
favorites: "Oblíbené"
permissions:
'read:drive': "Prohlížet Disk"
'write:drive': "Pracovat s Diskem"
empty-timeline-info:
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
explore: "Najít uživatele"

View File

@ -7,6 +7,7 @@ common:
about: "Danke, dass Du Misskey gefunden hast. Misskey ist eine <b>dezentralisierte Microblogging-Plattform</b>, welche auf der ganzen Welt verteilt ist. Da es innerhalb es Fediversums existiert (ein Universum, in dem verschiedene Soziale Netzwerke organisiert sind), ist es unmittelbar mit anderen sozialen Netzwerken verbunden. Warum nimmst du dir nicht einmal eine Auszeit von dem Trubel der Stadt und tauchst in das neue Internet hinein?"
intro:
title: "Was ist Misskey?"
about: "Misskey ist eine Quelloffene, <b>dezentralisierte microblogging Software</b>. Es bietet eine erweiterbare Benutzeroberfläche, verschiedenste Möglichkeiten auf Beiträge zu reagieren, kostenlosen Datenspeicher, und andere fortschrittliche Funktionen. Zusätzlich ist Misskey dazu in der Lage, sich mittels des Fediverse mit beliebig vielen anderen ActivityPub-kompatiblen Diensten zu verbinden. Wenn du zum Beispiel einen Betrag mit Misskey abschickst, wird dieser auch für Nutzer von Mastodon oder Pleroma sichtbar sein. So ähnlich wie eine Radioübertragung zwischen Planeten."
features: "Funktionen"
rich-contents: "Notizen"
reaction: "Reaktionen"
@ -19,6 +20,13 @@ common:
load-more: "Mehr laden"
enter-password: "Bitte Passwort eingeben"
2fa: "Zwei-Faktor-Authentifizierung"
customize-home: "Layout Anpassen"
featured-notes: "Hervorgehobene Beiträge"
dark-mode: "Dunkler Modus"
signin: "Einloggen"
signup: "Registrieren"
signout: "Ausloggen"
reload-to-apply-the-setting: "Die Seite muss zum Übernehmen dieser Einstellung aktualisiert werden. Soll die Seite jetzt neu geladen werden?"
got-it: "Verstanden!"
customization-tips:
title: "Anpassung-Tipps"
@ -26,6 +34,7 @@ common:
notification:
file-uploaded: "Datei hochgeladen!"
message-from: "Nachricht von {}:"
reversi-invited: "Zu einem Spiel eingeladen"
reversi-invited-by: "Eingeladen von {}:"
notified-by: "Benachrichtigt von {}:"
reply-from: "Antwort von {}:"
@ -46,7 +55,20 @@ common:
drive: "Drive"
messaging: "Unterhaltungen"
home: "Home"
favorites: "Diese Notiz favorisieren"
deck: "Stapel"
timeline: "Zeitleiste"
explore: "Entdecken"
following: "Folgt"
followers: "Folgende"
favorites: "Diesen Beitrag favorisieren"
permissions:
'read:account': "Accountinformationen anzeigen."
'write:account': "Accountinformationen bearbeiten."
'read:drive': "Dateien anzeigen"
'write:drive': "Dateien bearbeiten"
empty-timeline-info:
follow-users-to-make-your-timeline: "Beiträge von Benutzern, denen du folgst, werden in der Zeitleiste angezeigt."
explore: "Benutzer finden"
weekday-short:
sunday: "So"
monday: "Mo"
@ -77,11 +99,11 @@ common:
note-visibility:
public: "Öffentlich"
home: "Startseite"
home-desc: "Nur auf die Startseite posten"
home-desc: "Auf die Startseite posten"
followers: "Abonnenten"
followers-desc: "Nur für diejenigen sichtbar, die dir folgen"
specified: "Direkt"
specified-desc: "Nur für bestimmte Benutzer posten"
specified-desc: "Nur für bestimmte Benutzer sichtbar"
local-public: "Öffentlich (nur lokal)"
local-home: "Home (nur lokal)"
local-followers: "Follower (nur lokal)"
@ -91,11 +113,34 @@ common:
c: "Was geht dir durch den Kopf?"
d: "Willst du etwas sagen?"
e: "Schreib hier etwas!"
f: "Warte darauf, das du schreibst."
f: "Warte darauf, das du schreibst..."
settings: "Einstellungen"
_settings:
profile: "Dein Profil"
notification: "Benachrichtigungen"
apps: "Anwendungen"
tags: "Hashtags"
mute-and-block: "Stummschalten/Blocken"
blocking: "Blocken"
security: "Sicherheit"
signin: "Login-Verlauf"
password: "Passwort"
other: "Mehr"
appearance: "Designs"
behavior: "Verhalten"
fetch-on-scroll: "Unendliches laden beim scrollen"
fetch-on-scroll-desc: "Wenn beim scrollen das Ende erreicht wird, lädt die Anwendung automatisch neue Inhalte nach."
note-visibility: "Sichtbarkeit von Beiträgen"
default-note-visibility: "Die Standardsichtbarkeit"
remember-note-visibility: "Erinnerung an Sichtbarkeit von Beiträgen"
web-search-engine: "Web-Suchmaschine"
web-search-engine-desc: "Beispiel: https://www.google.de/search?q={{query}}"
keep-cw: "Inhaltswarnung beibehalten"
keep-cw-desc: "Wenn auf einen Beitrag geantwortet wird, wird die Inhaltswarnung des Originalbeitrags übernommen."
i-like-sushi: "Ich bevorzuge Sushi anstelle von Pudding"
show-reversi-board-labels: "Zeige Reihen- und Spaltenbeschreibungen in Reversi an"
use-avatar-reversi-stones: "Avatar als Stein in Reversi anzeigen"
disable-animated-mfm: "Animierten Text in Beiträgen deaktivieren"
search: "Suche"
delete: "Löschen"
loading: "Laden"
@ -103,7 +148,7 @@ common:
update-available: "Eine neue Version von Misskey ist verfügbar ({newer}, aktuell ist {current}). Lade die Seite neu um die aktuelle Version zu laden"
my-token-regenerated: "Dein Token wurde generiert. Du wirst jetzt abgemeldet."
verified-user: "Verifizierter Benutzer"
do-not-use-in-production: "Dies ist eine Entwicklungsversion. Nicht in einer Produktionsumgebung verwenden."
do-not-use-in-production: "Dies ist eine Entwicklungsversion. Nicht in einer Produktivumgebung verwenden."
error:
retry: "Erneut versuchen"
reversi:
@ -117,7 +162,7 @@ common:
analog-clock: "Analoge Uhr"
profile: "Profil"
calendar: "Kalender"
timemachine: "Kalender (Zeitmaschiene)"
timemachine: "Kalender (Zeitmaschine)"
activity: "Aktivitäten"
rss: "RSS Leser"
memo: "Notizen"
@ -128,7 +173,7 @@ common:
notifications: "Benachrichtigungen"
users: "Empfohlene Benutzer"
polls: "Umfrage"
post-form: "Beitragsform"
post-form: "\"Neuer Beitrag\"-Formular"
server: "Server-Info"
nav: "Navigation"
tips: "Tipps"
@ -178,7 +223,7 @@ common/views/components/games/reversi/reversi.room.vue:
ready: "Bereit"
common/views/components/connect-failed.vue:
title: "Verbindung zum Server ist fehlgeschlagen"
description: "Es gibt entweder ein Problem mit deiner Internetverbindung, der Server ist nicht erreichbar oder wird gerade gewartet. Bitte versuche es später noch einmal."
description: "Entweder gibt es ein Problem mit deiner Internetverbindung oder der Server ist zur Zeit nicht erreichbar oder wird gewartet. Bitte versuche es später noch einmal."
thanks: "Vielen Dank für das nutzen von Misskey."
troubleshoot: "Problembehandlung"
common/views/components/connect-failed.troubleshooter.vue:
@ -195,8 +240,8 @@ common/views/components/connect-failed.troubleshooter.vue:
no-internet: "Keine Internetverbindung"
no-internet-desc: "Bitte vergewissere dich, dass du mit dem Internet verbunden bist."
no-server: "Verbindung mit dem Server nicht möglich"
no-server-desc: "Die Internetverbindung scheint in Ordnung zu sein, aber eine Verbindung mit dem Misskey Server konnte nicht hergestellt werden. Möglicherweise ist dieser zur Zeit offline oder in der Wartung, bitte versuche es später noch einmal."
success: "Erfolgreich mit dem Misskey Server verbunden"
no-server-desc: "Die Internetverbindung scheint in Ordnung zu sein, aber eine Verbindung mit dem Misskey-Server konnte nicht hergestellt werden. Möglicherweise ist dieser zur Zeit offline oder wird gewartet. Bitte versuche es später noch einmal."
success: "Erfolgreich mit dem Misskey-Server verbunden"
success-desc: "Die Verbindung scheint zu funktionieren. Bitte lade die Seite neu."
flush: "Cache leeren"
set-version: "Version angeben"
@ -223,6 +268,7 @@ common/views/components/theme.vue:
invalid-theme: "Thema ist ungültig"
already-installed: "Thema ist bereits installiert"
author: "Autor"
desc: "Beschreibung"
export: "Exportieren"
import: "Importieren"
common/views/components/cw-button.vue:
@ -250,15 +296,15 @@ common/views/components/nav.vue:
status: "Status"
wiki: "Wiki"
donors: "Spender"
repository: "Projektarchiv"
repository: "Quellcode"
develop: "Entwickler"
feedback: "Feedback"
common/views/components/note-menu.vue:
favorite: "Diese Notiz favorisieren"
favorite: "Diesen Beitrag favorisieren"
unfavorite: "Aus Favoriten entfernen"
pin: "An die Profilseite pinnen"
delete: "Löschen"
delete-confirm: "Diesen Post löschen?"
delete-confirm: "Diesen Beitrag löschen?"
remote: "Auf Quelle anzeigen"
common/views/components/poll.vue:
vote-to: "Stimme für '{}'"
@ -295,8 +341,8 @@ common/views/components/signup.vue:
password: "Passwort"
password-placeholder: "Wir empfehlen mindestens 8 Zeichen"
weak-password: "Schwaches Passwort"
normal-password: "Faires Passwort"
strong-password: "Schwaches Passwort"
normal-password: "Normales Passwort"
strong-password: "Starkes Passwort"
retype: "Wiederholen"
retype-placeholder: "Bitte das Passwort erneut eingeben"
password-matched: "OK"
@ -318,16 +364,17 @@ common/views/components/uploader.vue:
common/views/components/visibility-chooser.vue:
public: "Öffentlich"
home: "Home"
home-desc: "Nur auf die Startseite posten"
home-desc: "Auf die Startseite posten"
followers: "Folgende"
followers-desc: "Nur für diejenigen sichtbar, die dir folgen"
specified: "Direkt"
specified-desc: "Poste nur für bestimmte Benutzer"
specified-desc: "Nur für bestimmte Benutzer sichtbar"
local-public: "Öffentlich (nur lokal)"
local-home: "Home (nur lokal)"
local-followers: "Follower (nur lokal)"
common/views/components/profile-editor.vue:
title: "Dein Profil"
name: "Name"
avatar: "Avatar"
banner: "Banner"
save: "Speichern"
@ -399,8 +446,8 @@ desktop/views/components/drive.file.vue:
copied: "Kopieren erfolgreich"
copied-url-to-clipboard: "URL wurde in die Zwischenablage kopiert"
desktop/views/components/drive.folder.vue:
unable-to-process: "Der Vorgang konnte nicht beendet werden"
circular-reference-detected: "Das Zielverzeichnis ist ein Unterverzeichnis des Verzeichnisses welches du verschieben möchtest"
unable-to-process: "Der Vorgang konnte nicht abgeschlossen werden"
circular-reference-detected: "Das Zielverzeichnis ist innerhalb des Verzeichnisses, dass du verschieben möchtest"
unhandled-error: "Unbekannter Fehler"
contextmenu:
move-to-this-folder: "Verschiebe in diesen Ordner"
@ -415,7 +462,7 @@ desktop/views/components/drive.vue:
empty-drive-description: "Du kannst rechts klicken und \"Datei hochladen\" auswählen oder eine Datei per Drag and Drop auf das Fenster ziehen."
empty-folder: "Dieser Ordner ist leer"
unable-to-process: "Der Vorgang konnte nicht beendet werden"
circular-reference-detected: "Das Zielverzeichnis ist ein Unterverzeichnis des Verzeichnisses welches du verschieben möchtest"
circular-reference-detected: "Das Zielverzeichnis ist innerhalb des Verzeichnisses, dass du verschieben möchtest"
unhandled-error: "Unbekannter Fehler"
url-upload: "Von einer URL hochladen"
url-of-file: "URL der Datei, welche du hochladen möchtest"
@ -439,15 +486,15 @@ desktop/views/input-dialog.vue:
cancel: "Abbrechen"
ok: "OK"
desktop/views/components/note-detail.vue:
private: "Dieser Post ist privat"
private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt"
location: "Ort"
renote: "Anmerkung"
add-reaction: "Reaktion hinzufügen"
desktop/views/components/note.vue:
reply: "Antworten"
renote: "Anmerkung"
private: "Dieser Post ist privat"
renote: "Anmerken"
private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt"
desktop/views/components/notes.vue:
error: "Laden fehlgeschlagen."
@ -459,9 +506,9 @@ desktop/views/components/post-form.vue:
hide-contents: "Inhalt verstecken"
reply-placeholder: "Antworte auf diese Anmerkung..."
quote-placeholder: "Zitiere diese Anmerkung..."
submit: "Beitragsform"
submit: "Abschicken"
reply: "Antworten"
renote: "Anmerkung"
renote: "Anmerken"
posted: "Gepostet!"
replied: "Geantwortet!"
reposted: "Weitergesagt!"
@ -477,7 +524,7 @@ desktop/views/components/post-form.vue:
error: "Fehler"
enter-username: "Bitte gib einen Benutzernamen ein..."
desktop/views/components/post-form-window.vue:
note: "Neue Notiz"
note: "Neuer Beitrag"
reply: "Antworten"
attaches: "{} Medien hinzugefügt"
uploading-media: "Lade {} Medien hoch"
@ -491,7 +538,7 @@ desktop/views/components/renote-form.vue:
success: "Weitergesagt!"
failure: "Weitersagen fehlgeschlagen"
desktop/views/components/renote-form-window.vue:
title: "Bist du dir sicher, dass du das reposten willst?"
title: "Bist du dir sicher, dass du das weitersagen willst?"
desktop/views/components/settings.2fa.vue:
url: "https://www.google.de/intl/de/landing/2step/"
register: "Ein Gerät registrieren"
@ -503,6 +550,7 @@ desktop/views/components/settings.2fa.vue:
common/views/components/api-settings.vue:
enter-password: "Bitte Passwort eingeben"
console:
parameter: "Parameter"
send: "Senden"
common/views/components/drive-settings.vue:
in-use: "benutzt"
@ -510,7 +558,7 @@ common/views/components/drive-settings.vue:
common/views/components/mute-and-block.vue:
save: "Speichern"
desktop/views/components/sub-note-content.vue:
private: "Dieser Post ist privat"
private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt"
poll: "Umfrage"
desktop/views/components/settings.tags.vue:
@ -600,22 +648,22 @@ mobile/views/components/drive.file-detail.vue:
download: "Download"
rename: "Umbenennen"
mobile/views/components/note.vue:
private: "Dieser Post ist privat"
private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt"
location: "Ort"
mobile/views/components/note-detail.vue:
reply: "Antworten"
private: "Dieser Post ist privat"
private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt"
location: "Ort"
mobile/views/components/notifications.vue:
empty: "Keine Benachrichtigungen"
mobile/views/components/post-form.vue:
reply: "Antworten"
renote: "Anmerkung"
renote: "Anmerken"
reply-placeholder: "Antworte auf diese Anmerkung..."
mobile/views/components/sub-note-content.vue:
private: "Dieser Post ist privat"
private: "Dieser Beitrag ist privat"
deleted: "Dieser Beitrag wurde entfernt"
poll: "Umfrage"
mobile/views/components/ui.nav.vue:
@ -632,7 +680,7 @@ mobile/views/pages/home.vue:
global: "Global"
mobile/views/pages/widgets.vue:
add-widget: "Hinzufügen"
customization-tips: "Anpassung-Tipps"
customization-tips: "Anpassungs-Tipps"
mobile/views/pages/widgets/activity.vue:
activity: "Aktivität"
mobile/views/pages/note.vue:
@ -653,4 +701,50 @@ deck:
list: "Listen"
rename: "Umbenennen"
deck/deck.user-column.vue:
following: "Folgen"
followers: "Folgende"
images: "Bilder"
activity: "Aktivität"
timeline: "Zeitleiste"
pinned-notes: "Angeheftete Beiträge"
docs:
edit-this-page-on-github: "Hast Du einen Fehler gefunden oder Lust, diese Dokumentation zu verbessern?"
edit-this-page-on-github-link: "Seite auf GitHub bearbeiten!"
api:
entities:
properties: "Eigenschaften"
endpoints:
params: "Parameter"
no-params: "Keine Parameter."
res: "Antwort"
require-credential: "Dieser Endpunkt erfordert eine Authentifizierung."
require-permission: "Dieser Endpunkt erfordert die {permission} Berechtigung."
has-limit: "Es gibt eine Ratenbegrenzung."
duration-limit: "Es sind maximal {max} Anfragen pro {duration} Millisekunden möglich."
min-interval-limit: "Es ist nur eine Anfrage alle {interval} Millisekunden möglich."
show-src: "Quellcode anzeigen."
show-src-link: "Quellcode auf GitHub anzeigen"
generated: "Dieses Dokument wird automatisch anhand der API-Definition generiert."
props:
name: "Name"
type: "Typ"
description: "Beschreibung"
dev/views/index.vue:
manage-apps: "Anwendungen verwalten"
dev/views/apps.vue:
manage-apps: "Anwendungen verwalten"
create-app: "Anwendung erstellen"
app-missing: "Keine Anwendungen"
dev/views/new-app.vue:
create-app: "Erstelle Anwendung"
app-name: "Name der Anwendung"
app-name-desc: "Der Name der Anwendung"
app-name-ex: "z.B. Misskey für iOS"
app-overview: "Beschreibung der Anwendung"
app-desc: "Eine kurze Beschreibung oder Einführung der Anwendung."
app-desc-ex: "z.B. Ein iOS-Client für Misskey."
callback-url: "Callback-URL (optional)"
callback-url-desc: "Die URL, auf die nach erfolgreicher Authentifizierung umgeleitet werden soll."
authority: "Berechtigungen"
authority-desc: "Nur die hier eingetragenen Berechtigungen, werden per API zur Verfügung stehen."
authority-warning: "Dies kann auch nach dem erstellen der Anwendung geändert werden, allerdings werden dann alle bisher generierten Token ungültig."

View File

@ -966,6 +966,7 @@ common/views/components/password-settings.vue:
changed: "Password changed"
failed: "Failed to change password"
common/views/components/post-form-attaches.vue:
attach-cancel: "Remove Attachment"
mark-as-sensitive: "Mark as 'sensitive'"
unmark-as-sensitive: "Unmark as 'sensitive'"
desktop/views/components/sub-note-content.vue:

View File

@ -74,10 +74,26 @@ common:
favorites: "お気に入り"
permissions:
'read:account': "アカウントの情報を見る"
'write:account': "アカウントの情報を変更する"
'read:drive': "ドライブを見る"
'write:drive': "ドライブを操作する"
"read:account": "アカウントの情報を見る"
"write:account": "アカウントの情報を変更する"
"read:blocks": "ブロックを見る"
"write:blocks": "ブロックを操作する"
"read:drive": "ドライブを見る"
"write:drive": "ドライブを操作する"
"read:favorites": "お気に入りを見る"
"write:favorites": "お気に入りを操作する"
"read:following": "フォローの情報を見る"
"write:following": "フォロー・フォロー解除する"
"read:messaging": "トークを見る"
"write:messaging": "トークを操作する"
"read:mutes": "ミュートを見る"
"write:mutes": "ミュートを操作する"
"write:notes": "投稿を作成・削除する"
"read:notifications": "通知を見る"
"write:notifications": "通知を操作する"
"read:reactions": "リアクションを見る"
"write:reactions": "リアクションを操作する"
"write:votes": "投票する"
empty-timeline-info:
follow-users-to-make-your-timeline: "ユーザーをフォローすると投稿がタイムラインに表示されます。"
@ -1804,14 +1820,17 @@ dev/views/apps.vue:
app-missing: "アプリなし"
dev/views/new-app.vue:
new-app: "新しいアプリケーション"
new-app-info: "アプリケーションはAPIからでも作成できます。 (app/create)"
create-app: "アプリケーションの作成"
app-name: "アプリケーション名"
app-name-placeholder: "ex) Misskey for iOS"
app-name-desc: "あなたのアプリの名称。"
app-name-ex: "ex) Misskey for iOS"
app-overview: "アプリの概要"
app-desc: "あなたのアプリの簡単な説明や紹介。"
app-desc-ex: "ex) Misskey iOSクライアント。"
app-overview-placeholder: " ex) Misskey iOSクライアント。"
app-overview-desc: "あなたのアプリの簡単な説明や紹介。"
callback-url: "コールバックURL (オプション)"
callback-url-placeholder: "ex) https://your.app.example.com/callback.php"
callback-url-desc: "ユーザーが認証フォームで認証した際にリダイレクトするURLを設定できます。"
authority: "権限"
authority-desc: "ここで要求した機能だけがAPIからアクセスできます。"

View File

@ -966,6 +966,7 @@ common/views/components/password-settings.vue:
changed: "비밀번호를 변경하였습니다"
failed: "비밀번호 변경을 실패하였습니다."
common/views/components/post-form-attaches.vue:
attach-cancel: "첨부 취소"
mark-as-sensitive: "열람주의로 설정"
unmark-as-sensitive: "열람주의 해제"
desktop/views/components/sub-note-content.vue:

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "11.0.0-beta.15",
"version": "11.0.2",
"codename": "daybreak",
"repository": {
"type": "git",
@ -12,8 +12,6 @@
"scripts": {
"start": "node ./index.js",
"init": "node ./built/init.js",
"migrate": "node ./built/migrate.js",
"debug": "DEBUG=misskey:* node ./index.js",
"build": "webpack && gulp build",
"webpack": "webpack",
"watch": "webpack --watch",
@ -34,14 +32,13 @@
"@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.5.8",
"@types/chai-http": "3.0.5",
"@types/bull": "3.5.11",
"@types/dateformat": "3.0.0",
"@types/deep-equal": "1.0.1",
"@types/double-ended-queue": "2.1.0",
"@types/elasticsearch": "5.0.30",
"@types/file-type": "10.6.0",
"@types/gulp": "4.0.5",
"@types/elasticsearch": "5.0.32",
"@types/file-type": "10.9.1",
"@types/gulp": "4.0.6",
"@types/gulp-mocha": "0.0.32",
"@types/gulp-rename": "0.0.33",
"@types/gulp-replace": "0.0.31",
@ -49,19 +46,19 @@
"@types/gulp-util": "3.0.34",
"@types/is-root": "1.0.0",
"@types/is-url": "1.2.28",
"@types/js-yaml": "3.12.0",
"@types/js-yaml": "3.12.1",
"@types/jsdom": "12.2.3",
"@types/katex": "0.10.1",
"@types/koa": "2.0.48",
"@types/koa-bodyparser": "5.0.2",
"@types/koa-compress": "2.0.8",
"@types/koa-compress": "2.0.9",
"@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19",
"@types/koa-logger": "3.1.1",
"@types/koa-mount": "3.0.1",
"@types/koa-multer": "1.0.0",
"@types/koa-router": "7.0.40",
"@types/koa-send": "4.1.1",
"@types/koa-send": "4.1.2",
"@types/koa-views": "2.0.3",
"@types/koa__cors": "2.2.3",
"@types/lolex": "3.1.1",
@ -69,37 +66,36 @@
"@types/mocha": "5.2.6",
"@types/mongodb": "3.1.22",
"@types/monk": "6.0.0",
"@types/node": "11.10.4",
"@types/nodemailer": "4.6.6",
"@types/node": "11.13.4",
"@types/nodemailer": "4.6.7",
"@types/nprogress": "0.0.29",
"@types/oauth": "0.9.1",
"@types/parse5": "5.0.0",
"@types/parsimmon": "1.10.0",
"@types/portscanner": "2.1.0",
"@types/pug": "2.0.4",
"@types/qrcode": "1.3.0",
"@types/qrcode": "1.3.2",
"@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.10",
"@types/redis": "2.8.12",
"@types/rename": "1.0.1",
"@types/request": "2.48.1",
"@types/request-promise-native": "1.0.15",
"@types/request-stats": "3.0.0",
"@types/rimraf": "2.0.2",
"@types/seedrandom": "2.4.27",
"@types/sharp": "0.21.2",
"@types/sharp": "0.22.1",
"@types/showdown": "1.9.2",
"@types/speakeasy": "2.0.4",
"@types/systeminformation": "3.23.1",
"@types/tinycolor2": "1.4.1",
"@types/tmp": "0.0.33",
"@types/tmp": "0.1.0",
"@types/uuid": "3.4.4",
"@types/web-push": "3.3.0",
"@types/webpack": "4.4.24",
"@types/webpack": "4.4.27",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "3.0.1",
"apexcharts": "3.6.6",
"apexcharts": "3.6.7",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@ -108,7 +104,6 @@
"bull": "3.7.0",
"cafy": "15.1.1",
"chai": "4.2.0",
"chai-http": "4.2.1",
"chalk": "2.4.2",
"cli-highlight": "2.1.0",
"commander": "2.20.0",
@ -122,11 +117,11 @@
"double-ended-queue": "2.1.0-0",
"elasticsearch": "15.4.1",
"emojilib": "2.4.0",
"eslint": "5.15.1",
"eslint": "5.16.0",
"eslint-plugin-vue": "5.2.2",
"eventemitter3": "3.1.0",
"feed": "2.0.4",
"file-type": "10.10.0",
"file-type": "10.11.0",
"fuckadblock": "3.2.1",
"gulp": "4.0.0",
"gulp-cssnano": "2.1.3",
@ -137,19 +132,19 @@
"gulp-sourcemaps": "2.6.5",
"gulp-stylus": "2.7.0",
"gulp-tslint": "8.1.4",
"gulp-typescript": "5.0.0",
"gulp-typescript": "5.0.1",
"gulp-uglify": "3.0.2",
"gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.13.1",
"html-minifier": "3.5.21",
"html-minifier": "4.0.0",
"http-signature": "1.2.0",
"insert-text-at-cursor": "0.1.2",
"insert-text-at-cursor": "0.2.0",
"is-root": "2.0.0",
"is-svg": "4.0.0",
"js-yaml": "3.13.0",
"is-svg": "4.1.0",
"js-yaml": "3.13.1",
"jsdom": "14.0.0",
"json5": "2.1.0",
"json5-loader": "1.0.1",
"json5-loader": "2.0.0",
"katex": "0.10.1",
"koa": "2.7.0",
"koa-bodyparser": "4.2.1",
@ -168,16 +163,15 @@
"lolex": "3.1.0",
"lookup-dns-cache": "2.1.0",
"minio": "7.0.5",
"mocha": "6.0.2",
"mocha": "6.1.3",
"moji": "0.5.1",
"moment": "2.24.0",
"mongodb": "3.2.3",
"monk": "6.0.6",
"ms": "2.1.1",
"nan": "2.12.1",
"nested-property": "0.0.7",
"node-fetch": "2.3.0",
"nodemailer": "5.1.1",
"nodemailer": "6.1.0",
"nprogress": "0.2.0",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
@ -208,7 +202,6 @@
"rimraf": "2.6.3",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"seedrandom": "2.4.4",
"sharp": "0.22.0",
"showdown": "1.9.0",
"showdown-highlightjs-extension": "0.1.2",
@ -218,15 +211,15 @@
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.2.0",
"systeminformation": "4.0.16",
"systeminformation": "4.1.4",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.2.3",
"textarea-caret": "3.1.0",
"tinycolor2": "1.4.1",
"tmp": "0.0.33",
"tmp": "0.1.0",
"ts-loader": "5.3.3",
"ts-node": "7.0.1",
"tslint": "5.13.1",
"tslint": "5.15.0",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.16-rc.1",
"typescript": "3.3.3333",
@ -248,7 +241,7 @@
"vue-loader": "15.7.0",
"vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1",
"vue-router": "3.0.3",
"vue-router": "3.0.4",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.15",
@ -258,8 +251,8 @@
"vuex": "3.1.0",
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.3",
"webpack": "4.28.4",
"webpack-cli": "3.2.3",
"webpack": "4.30.0",
"webpack-cli": "3.3.0",
"websocket": "1.0.28",
"ws": "6.2.1",
"xev": "2.0.1"

View File

@ -166,6 +166,7 @@ export default Vue.extend({
append(note) {
this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
},
releaseQueue() {

View File

@ -85,7 +85,7 @@ export default Vue.extend({
this.makePromise = cursor => this.$root.api('users/notes', {
userId: this.user.id,
limit: fetchLimit + 1,
untilId: cursor ? cursor : undefined,
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365,
withFiles: this.withFiles,
includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
@ -118,7 +118,6 @@ export default Vue.extend({
fileType: image,
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
limit: 9,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
for (const note of notes) {
for (const file of note.files) {

View File

@ -166,6 +166,7 @@ export default Vue.extend({
append(note) {
this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
},
releaseQueue() {

View File

@ -46,7 +46,6 @@ export default Vue.extend({
fileType: image,
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
limit: 9,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
for (const note of notes) {
for (const file of note.files) {

View File

@ -36,7 +36,7 @@ export default Vue.extend({
includeReplies: this.mode == 'with-replies',
includeMyRenotes: this.mode != 'my-posts',
withFiles: this.mode == 'with-media',
untilId: cursor ? cursor : undefined
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();

View File

@ -1,6 +1,6 @@
<template>
<mk-ui>
<b-card :header="$t('header')">
<b-card :header="$t('manage-apps')">
<b-button to="/app/new" variant="primary">{{ $t('create-app') }}</b-button>
<hr>
<div class="apps">

View File

@ -1,35 +1,22 @@
<template>
<mk-ui>
<b-card :header="$t('header')">
<b-card :header="$t('new-app')">
<b-alert show variant="info"><fa icon="info-circle"/> {{ $t('new-app-info') }}</b-alert>
<b-form @submit.prevent="onSubmit" autocomplete="off">
<b-form-group :label="$t('app-name')" :description="$t('description')">
<b-form-input v-model="name" type="text" :placeholder="$t('placeholder')" autocomplete="off" required/>
<b-form-group :label="$t('app-name')" :description="$t('app-name-desc')">
<b-form-input v-model="name" type="text" :placeholder="$t('app-name-placeholder')" autocomplete="off" required/>
</b-form-group>
<b-form-group :label="$t('app-overview')" :description="$t('description')">
<b-textarea v-model="description" :placeholder="$t('placeholder')" autocomplete="off" required></b-textarea>
<b-form-group :label="$t('app-overview')" :description="$t('app-overview-desc')">
<b-textarea v-model="description" :placeholder="$t('app-overview-placeholder')" autocomplete="off" required></b-textarea>
</b-form-group>
<b-form-group :label="$t('callback-url')" :description="$t('description')">
<b-input v-model="cb" type="url" placeholder="ex) https://your.app.example.com/callback.php" autocomplete="off"/>
<b-form-group :label="$t('callback-url')" :description="$t('callback-url-desc')">
<b-input v-model="cb" type="url" :placeholder="$t('callback-url-placeholder')" autocomplete="off"/>
</b-form-group>
<b-card :header="$t('header')">
<b-form-group :description="$t('description')">
<b-card :header="$t('authority')">
<b-form-group :description="$t('authority-desc')">
<b-alert show variant="warning"><fa icon="exclamation-triangle"/> {{ $t('authority-warning') }}</b-alert>
<b-form-checkbox-group v-model="permission" stacked>
<b-form-checkbox value="read:account">{{ $t('read:account') }}</b-form-checkbox>
<b-form-checkbox value="write:account">{{ $t('write:account') }}</b-form-checkbox>
<b-form-checkbox value="write:notes">{{ $t('write:notes') }}</b-form-checkbox>
<b-form-checkbox value="read:reactions">{{ $t('read:reactions') }}</b-form-checkbox>
<b-form-checkbox value="write:reactions">{{ $t('write:reactions') }}</b-form-checkbox>
<b-form-checkbox value="read:following">{{ $t('read:following') }}</b-form-checkbox>
<b-form-checkbox value="write:following">{{ $t('write:following') }}</b-form-checkbox>
<b-form-checkbox value="read:mutes">{{ $t('read:mutes') }}</b-form-checkbox>
<b-form-checkbox value="write:mutes">{{ $t('write:mutes') }}</b-form-checkbox>
<b-form-checkbox value="read:blocks">{{ $t('read:blocks') }}</b-form-checkbox>
<b-form-checkbox value="write:blocks">{{ $t('write:blocks') }}</b-form-checkbox>
<b-form-checkbox value="read:drive">{{ $t('read:drive') }}</b-form-checkbox>
<b-form-checkbox value="write:drive">{{ $t('write:drive') }}</b-form-checkbox>
<b-form-checkbox value="read:notifications">{{ $t('read:notifications') }}</b-form-checkbox>
<b-form-checkbox value="write:notifications">{{ $t('write:notifications') }}</b-form-checkbox>
<b-form-checkbox v-for="v in permissionsList" :value="v" :key="v">{{ $t(`@.permissions.${v}`) }} ({{ v }})</b-form-checkbox>
</b-form-checkbox-group>
</b-form-group>
</b-card>
@ -43,6 +30,8 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../i18n';
import { kinds } from '../../../../server/api/kinds';
export default Vue.extend({
i18n: i18n('dev/views/new-app.vue'),
data() {
@ -51,7 +40,8 @@ export default Vue.extend({
description: '',
cb: '',
nidState: null,
permission: []
permission: [],
permissionsList: kinds
};
},
methods: {

View File

@ -160,6 +160,7 @@ export default Vue.extend({
append(note) {
this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
},
releaseQueue() {

View File

@ -21,7 +21,7 @@ export default Vue.extend({
userId: this.user.id,
limit: fetchLimit + 1,
withFiles: this.withMedia,
untilId: cursor ? cursor : undefined
untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
if (notes.length == fetchLimit + 1) {
notes.pop();

View File

@ -39,7 +39,7 @@
<span :data-active="src == 'messages'" @click="src = 'messages'"><fa :icon="['far', 'envelope']"/> {{ $t('messages') }}<i class="badge" v-if="$store.state.i.hasUnreadSpecifiedNotes"><fa icon="circle"/></i></span>
<template v-if="lists">
<div class="hr" v-if="lists.length > 0"></div>
<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id"><fa icon="list"/> {{ l.title }}</span>
<span v-for="l in lists" :data-active="src == 'list' && list == l" @click="src = 'list'; list = l" :key="l.id"><fa icon="list"/> {{ l.name }}</span>
</template>
<div class="hr" v-if="$store.state.settings.tagTimelines && $store.state.settings.tagTimelines.length > 0"></div>
<span v-for="tl in $store.state.settings.tagTimelines" :data-active="src == 'tag' && tagTl == tl" @click="src = 'tag'; tagTl = tl" :key="tl.id"><fa icon="hashtag"/> {{ tl.title }}</span>

View File

@ -23,7 +23,6 @@ export default Vue.extend({
mounted() {
this.$root.api('users/notes', {
userId: this.user.id,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
this.notes = notes;
this.fetching = false;

View File

@ -37,7 +37,6 @@ export default Vue.extend({
fileType: image,
excludeNsfw: !this.$store.state.device.alwaysShowNsfw,
limit: 9,
untilDate: new Date().getTime() + 1000 * 86400 * 365
}).then(notes => {
for (const note of notes) {
for (const file of note.files) {

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -1,111 +0,0 @@
#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(Nan::GetCurrentContext()).ToLocalChecked();
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);

2
src/crypto_key.d.ts vendored
View File

@ -1,2 +0,0 @@
export function extractPublic(keypair: string): string;
export function generate(): string;

View File

@ -1,502 +0,0 @@
process.env.NODE_ENV = 'production';
import monk from 'monk';
import * as mongo from 'mongodb';
import * as fs from 'fs';
import * as uuid from 'uuid';
import chalk from 'chalk';
import config from './config';
import { initDb } from './db/postgre';
import { User } from './models/entities/user';
import { getRepository } from 'typeorm';
import generateUserToken from './server/api/common/generate-native-user-token';
import { DriveFile } from './models/entities/drive-file';
import { DriveFolder } from './models/entities/drive-folder';
import { InternalStorage } from './services/drive/internal-storage';
import { createTemp } from './misc/create-temp';
import { Note } from './models/entities/note';
import { Following } from './models/entities/following';
import { Poll } from './models/entities/poll';
import { PollVote } from './models/entities/poll-vote';
import { NoteFavorite } from './models/entities/note-favorite';
import { NoteReaction } from './models/entities/note-reaction';
import { UserPublickey } from './models/entities/user-publickey';
import { UserKeypair } from './models/entities/user-keypair';
import { extractPublic } from './crypto_key';
import { Emoji } from './models/entities/emoji';
import { toPuny as _toPuny } from './misc/convert-host';
import { UserProfile } from './models/entities/user-profile';
function toPuny(x: string | null): string | null {
if (x == null) return null;
return _toPuny(x);
}
const u = (config as any).mongodb.user ? encodeURIComponent((config as any).mongodb.user) : null;
const p = (config as any).mongodb.pass ? encodeURIComponent((config as any).mongodb.pass) : null;
const uri = `mongodb://${u && p ? `${u}:${p}@` : ''}${(config as any).mongodb.host}:${(config as any).mongodb.port}/${(config as any).mongodb.db}`;
const db = monk(uri);
let mdb: mongo.Db;
const test = false;
const limit = 500;
const nativeDbConn = async (): Promise<mongo.Db> => {
if (mdb) return mdb;
const db = await ((): Promise<mongo.Db> => new Promise((resolve, reject) => {
mongo.MongoClient.connect(uri, { useNewUrlParser: true }, (e: Error, client: any) => {
if (e) return reject(e);
resolve(client.db((config as any).mongodb.db));
});
}))();
mdb = db;
return db;
};
const _User = db.get<any>('users');
const _DriveFile = db.get<any>('driveFiles.files');
const _DriveFolder = db.get<any>('driveFolders');
const _Note = db.get<any>('notes');
const _Following = db.get<any>('following');
const _PollVote = db.get<any>('pollVotes');
const _Favorite = db.get<any>('favorites');
const _NoteReaction = db.get<any>('noteReactions');
const _Emoji = db.get<any>('emoji');
const getDriveFileBucket = async (): Promise<mongo.GridFSBucket> => {
const db = await nativeDbConn();
const bucket = new mongo.GridFSBucket(db, {
bucketName: 'driveFiles'
});
return bucket;
};
async function main() {
await initDb();
const Users = getRepository(User);
const UserProfiles = getRepository(UserProfile);
const DriveFiles = getRepository(DriveFile);
const DriveFolders = getRepository(DriveFolder);
const Notes = getRepository(Note);
const Followings = getRepository(Following);
const Polls = getRepository(Poll);
const PollVotes = getRepository(PollVote);
const NoteFavorites = getRepository(NoteFavorite);
const NoteReactions = getRepository(NoteReaction);
const UserPublickeys = getRepository(UserPublickey);
const UserKeypairs = getRepository(UserKeypair);
const Emojis = getRepository(Emoji);
async function migrateUser(user: any) {
await Users.save({
id: user._id.toHexString(),
createdAt: user.createdAt || new Date(),
username: user.username,
usernameLower: user.username.toLowerCase(),
host: toPuny(user.host),
token: generateUserToken(),
isAdmin: user.isAdmin || false,
name: user.name,
followersCount: user.followersCount || 0,
followingCount: user.followingCount || 0,
notesCount: user.notesCount || 0,
isBot: user.isBot || false,
isCat: user.isCat || false,
isVerified: user.isVerified || false,
inbox: user.inbox,
sharedInbox: user.sharedInbox,
uri: user.uri,
});
await UserProfiles.save({
userId: user._id.toHexString(),
description: user.description,
userHost: toPuny(user.host),
autoAcceptFollowed: true,
autoWatch: false,
password: user.password,
location: user.profile ? user.profile.location : null,
birthday: user.profile ? user.profile.birthday : null,
});
if (user.publicKey) {
await UserPublickeys.save({
userId: user._id.toHexString(),
keyId: user.publicKey.id,
keyPem: user.publicKey.publicKeyPem
});
}
if (user.keypair) {
await UserKeypairs.save({
userId: user._id.toHexString(),
publicKey: extractPublic(user.keypair),
privateKey: user.keypair,
});
}
}
async function migrateFollowing(following: any) {
await Followings.save({
id: following._id.toHexString(),
createdAt: new Date(),
followerId: following.followerId.toHexString(),
followeeId: following.followeeId.toHexString(),
// 非正規化
followerHost: following._follower ? toPuny(following._follower.host) : null,
followerInbox: following._follower ? following._follower.inbox : null,
followerSharedInbox: following._follower ? following._follower.sharedInbox : null,
followeeHost: following._followee ? toPuny(following._followee.host) : null,
followeeInbox: following._followee ? following._followee.inbox : null,
followeeSharedInbox: following._followee ? following._followee.sharedInbo : null
});
}
async function migrateDriveFolder(folder: any) {
await DriveFolders.save({
id: folder._id.toHexString(),
createdAt: folder.createdAt || new Date(),
name: folder.name,
parentId: folder.parentId ? folder.parentId.toHexString() : null,
});
}
async function migrateDriveFile(file: any) {
const user = await _User.findOne({
_id: file.metadata.userId
});
if (user == null) return;
if (file.metadata.storage && file.metadata.storage.key) { // when object storage
await DriveFiles.save({
id: file._id.toHexString(),
userId: user._id.toHexString(),
userHost: toPuny(user.host),
createdAt: file.uploadDate || new Date(),
md5: file.md5,
name: file.filename,
type: file.contentType,
properties: file.metadata.properties || {},
size: file.length,
url: file.metadata.url,
uri: file.metadata.uri,
accessKey: file.metadata.storage.key,
folderId: file.metadata.folderId ? file.metadata.folderId.toHexString() : null,
storedInternal: false,
isLink: false
});
} else if (!file.metadata.isLink) {
const [temp, clean] = await createTemp();
await new Promise(async (res, rej) => {
const bucket = await getDriveFileBucket();
const readable = bucket.openDownloadStream(file._id);
const dest = fs.createWriteStream(temp);
readable.pipe(dest);
readable.on('end', () => {
dest.end();
res();
});
});
const key = uuid.v4();
const url = InternalStorage.saveFromPath(key, temp);
await DriveFiles.save({
id: file._id.toHexString(),
userId: user._id.toHexString(),
userHost: toPuny(user.host),
createdAt: file.uploadDate || new Date(),
md5: file.md5,
name: file.filename,
type: file.contentType,
properties: file.metadata.properties,
size: file.length,
url: url,
uri: file.metadata.uri,
accessKey: key,
folderId: file.metadata.folderId ? file.metadata.folderId.toHexString() : null,
storedInternal: true,
isLink: false
});
clean();
} else {
await DriveFiles.save({
id: file._id.toHexString(),
userId: user._id.toHexString(),
userHost: toPuny(user.host),
createdAt: file.uploadDate || new Date(),
md5: file.md5,
name: file.filename,
type: file.contentType,
properties: file.metadata.properties,
size: file.length,
url: file.metadata.url,
uri: file.metadata.uri,
accessKey: null,
folderId: file.metadata.folderId ? file.metadata.folderId.toHexString() : null,
storedInternal: false,
isLink: true
});
}
}
async function migrateNote(note: any) {
await Notes.save({
id: note._id.toHexString(),
createdAt: note.createdAt || new Date(),
text: note.text,
cw: note.cw || null,
tags: note.tags || [],
userId: note.userId.toHexString(),
viaMobile: note.viaMobile || false,
geo: note.geo,
appId: null,
visibility: note.visibility || 'public',
visibleUserIds: note.visibleUserIds ? note.visibleUserIds.map((id: any) => id.toHexString()) : [],
replyId: note.replyId ? note.replyId.toHexString() : null,
renoteId: note.renoteId ? note.renoteId.toHexString() : null,
userHost: null,
fileIds: note.fileIds ? note.fileIds.map((id: any) => id.toHexString()) : [],
localOnly: note.localOnly || false,
hasPoll: note.poll != null
});
if (note.poll) {
await Polls.save({
noteId: note._id.toHexString(),
choices: note.poll.choices.map((x: any) => x.text),
expiresAt: note.poll.expiresAt,
multiple: note.poll.multiple,
votes: note.poll.choices.map((x: any) => x.votes),
noteVisibility: note.visibility,
userId: note.userId.toHexString(),
userHost: null
});
}
}
async function migratePollVote(vote: any) {
await PollVotes.save({
id: vote._id.toHexString(),
createdAt: vote.createdAt,
noteId: vote.noteId.toHexString(),
userId: vote.userId.toHexString(),
choice: vote.choice
});
}
async function migrateNoteFavorite(favorite: any) {
await NoteFavorites.save({
id: favorite._id.toHexString(),
createdAt: favorite.createdAt,
noteId: favorite.noteId.toHexString(),
userId: favorite.userId.toHexString(),
});
}
async function migrateNoteReaction(reaction: any) {
await NoteReactions.save({
id: reaction._id.toHexString(),
createdAt: reaction.createdAt,
noteId: reaction.noteId.toHexString(),
userId: reaction.userId.toHexString(),
reaction: reaction.reaction
});
}
async function reMigrateUser(user: any) {
const u = await _User.findOne({
_id: new mongo.ObjectId(user.id)
});
const avatar = u.avatarId ? await DriveFiles.findOne(u.avatarId.toHexString()) : null;
const banner = u.bannerId ? await DriveFiles.findOne(u.bannerId.toHexString()) : null;
await Users.update(user.id, {
avatarId: avatar ? avatar.id : null,
bannerId: banner ? banner.id : null,
avatarUrl: avatar ? avatar.url : null,
bannerUrl: banner ? banner.url : null
});
}
async function migrateEmoji(emoji: any) {
await Emojis.save({
id: emoji._id.toHexString(),
updatedAt: emoji.createdAt,
aliases: emoji.aliases,
url: emoji.url,
uri: emoji.uri,
host: toPuny(emoji.host),
name: emoji.name
});
}
let allUsersCount = await _User.count({
deletedAt: { $exists: false }
});
if (test && allUsersCount > limit) allUsersCount = limit;
for (let i = 0; i < allUsersCount; i++) {
const user = await _User.findOne({
deletedAt: { $exists: false }
}, {
skip: i
});
try {
await migrateUser(user);
console.log(`USER (${i + 1}/${allUsersCount}) ${user._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`USER (${i + 1}/${allUsersCount}) ${user._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allFollowingsCount = await _Following.count();
if (test && allFollowingsCount > limit) allFollowingsCount = limit;
for (let i = 0; i < allFollowingsCount; i++) {
const following = await _Following.findOne({}, {
skip: i
});
try {
await migrateFollowing(following);
console.log(`FOLLOWING (${i + 1}/${allFollowingsCount}) ${following._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`FOLLOWING (${i + 1}/${allFollowingsCount}) ${following._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allDriveFoldersCount = await _DriveFolder.count();
if (test && allDriveFoldersCount > limit) allDriveFoldersCount = limit;
for (let i = 0; i < allDriveFoldersCount; i++) {
const folder = await _DriveFolder.findOne({}, {
skip: i
});
try {
await migrateDriveFolder(folder);
console.log(`FOLDER (${i + 1}/${allDriveFoldersCount}) ${folder._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`FOLDER (${i + 1}/${allDriveFoldersCount}) ${folder._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allDriveFilesCount = await _DriveFile.count({
'metadata._user.host': null,
'metadata.deletedAt': { $exists: false }
});
if (test && allDriveFilesCount > limit) allDriveFilesCount = limit;
for (let i = 0; i < allDriveFilesCount; i++) {
const file = await _DriveFile.findOne({
'metadata._user.host': null,
'metadata.deletedAt': { $exists: false }
}, {
skip: i
});
try {
await migrateDriveFile(file);
console.log(`FILE (${i + 1}/${allDriveFilesCount}) ${file._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`FILE (${i + 1}/${allDriveFilesCount}) ${file._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allNotesCount = await _Note.count({
'_user.host': null,
'metadata.deletedAt': { $exists: false }
});
if (test && allNotesCount > limit) allNotesCount = limit;
for (let i = 0; i < allNotesCount; i++) {
const note = await _Note.findOne({
'_user.host': null,
'metadata.deletedAt': { $exists: false }
}, {
skip: i
});
try {
await migrateNote(note);
console.log(`NOTE (${i + 1}/${allNotesCount}) ${note._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`NOTE (${i + 1}/${allNotesCount}) ${note._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allPollVotesCount = await _PollVote.count();
if (test && allPollVotesCount > limit) allPollVotesCount = limit;
for (let i = 0; i < allPollVotesCount; i++) {
const vote = await _PollVote.findOne({}, {
skip: i
});
try {
await migratePollVote(vote);
console.log(`VOTE (${i + 1}/${allPollVotesCount}) ${vote._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`VOTE (${i + 1}/${allPollVotesCount}) ${vote._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allNoteFavoritesCount = await _Favorite.count();
if (test && allNoteFavoritesCount > limit) allNoteFavoritesCount = limit;
for (let i = 0; i < allNoteFavoritesCount; i++) {
const favorite = await _Favorite.findOne({}, {
skip: i
});
try {
await migrateNoteFavorite(favorite);
console.log(`FAVORITE (${i + 1}/${allNoteFavoritesCount}) ${favorite._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`FAVORITE (${i + 1}/${allNoteFavoritesCount}) ${favorite._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allNoteReactionsCount = await _NoteReaction.count();
if (test && allNoteReactionsCount > limit) allNoteReactionsCount = limit;
for (let i = 0; i < allNoteReactionsCount; i++) {
const reaction = await _NoteReaction.findOne({}, {
skip: i
});
try {
await migrateNoteReaction(reaction);
console.log(`REACTION (${i + 1}/${allNoteReactionsCount}) ${reaction._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`REACTION (${i + 1}/${allNoteReactionsCount}) ${reaction._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
let allActualUsersCount = await Users.count();
if (test && allActualUsersCount > limit) allActualUsersCount = limit;
for (let i = 0; i < allActualUsersCount; i++) {
const [user] = await Users.find({
take: 1,
skip: i
});
try {
await reMigrateUser(user);
console.log(`RE:USER (${i + 1}/${allActualUsersCount}) ${user.id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`RE:USER (${i + 1}/${allActualUsersCount}) ${user.id} ${chalk.red('ERR')}`);
console.error(e);
}
}
const allEmojisCount = await _Emoji.count();
for (let i = 0; i < allEmojisCount; i++) {
const emoji = await _Emoji.findOne({}, {
skip: i
});
try {
await migrateEmoji(emoji);
console.log(`EMOJI (${i + 1}/${allEmojisCount}) ${emoji._id} ${chalk.green('DONE')}`);
} catch (e) {
console.log(`EMOJI (${i + 1}/${allEmojisCount}) ${emoji._id} ${chalk.red('ERR')}`);
console.error(e);
}
}
console.log('DONE :)');
}
main();

View File

@ -93,7 +93,7 @@ export class Meta {
@Column('varchar', {
length: 512,
nullable: true,
default: 'https://ai.misskey.xyz/aiart/yubitun.png'
default: 'https://xn--931a.moe/aiart/yubitun.png'
})
public errorImageUrl: string | null;

View File

@ -125,7 +125,7 @@ export class Note {
public uri: string | null;
@Column('integer', {
default: 0
default: 0, select: false
})
public score: number;

View File

@ -25,6 +25,7 @@ export class AppRepository extends Repository<App> {
return {
id: app.id,
name: app.name,
callbackUrl: app.callbackUrl,
...(opts.includeSecret ? { secret: app.secret } : {}),
...(me ? {
isAuthorized: await AccessTokens.count({

View File

@ -14,7 +14,8 @@ export class AuthSessionRepository extends Repository<AuthSession> {
return await rap({
id: session.id,
app: Apps.pack(session.appId, me)
app: Apps.pack(session.appId, me),
token: session.token
});
}
}

View File

@ -19,7 +19,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
}
public getPublicUrl(file: DriveFile, thumbnail = false): string | null {
return thumbnail ? (file.thumbnailUrl || file.webpublicUrl || null) : (file.webpublicUrl || file.thumbnailUrl || file.url);
return thumbnail ? (file.thumbnailUrl || file.webpublicUrl || null) : (file.webpublicUrl || file.url);
}
public async clacDriveUsageOf(user: User['id'] | User): Promise<number> {

View File

@ -115,3 +115,8 @@ export function cumulativeSum(xs: number[]): number[] {
for (let i = 1; i < ys.length; i++) ys[i] += ys[i - 1];
return ys;
}
// Object.fromEntries()
export function fromEntries(xs: [string, any][]): { [x: string]: any; } {
return xs.reduce((obj, [k, v]) => Object.assign(obj, { [k]: v }), {} as { [x: string]: any; });
}

View File

@ -3,6 +3,7 @@ import $ from 'cafy';
import define from '../../define';
import { Apps } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
import { unique } from '../../../../prelude/array';
export const meta = {
tags: ['app'],
@ -34,6 +35,9 @@ export default define(meta, async (ps, user) => {
// Generate secret
const secret = rndstr('a-zA-Z0-9', 32);
// for backward compatibility
const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1')));
// Create account
const app = await Apps.save({
id: genId(),
@ -41,7 +45,7 @@ export default define(meta, async (ps, user) => {
userId: user ? user.id : null,
name: ps.name,
description: ps.description,
permission: ps.permission,
permission,
callbackUrl: ps.callbackUrl,
secret: secret
});

View File

@ -14,7 +14,7 @@ export const meta = {
requireCredential: true,
kind: 'favorites-read',
kind: 'read:favorites',
params: {
limit: {

View File

@ -14,7 +14,7 @@ export const meta = {
requireCredential: true,
kind: 'messaging-read',
kind: 'read:messaging',
params: {
limit: {

View File

@ -17,7 +17,7 @@ export const meta = {
requireCredential: true,
kind: 'messaging-read',
kind: 'read:messaging',
params: {
userId: {

View File

@ -20,7 +20,7 @@ export const meta = {
requireCredential: true,
kind: 'messaging-write',
kind: 'write:messaging',
params: {
userId: {

View File

@ -18,7 +18,7 @@ export const meta = {
requireCredential: true,
kind: 'messaging-write',
kind: 'write:messaging',
limit: {
duration: ms('1hour'),

View File

@ -15,7 +15,7 @@ export const meta = {
requireCredential: true,
kind: 'messaging-write',
kind: 'write:messaging',
params: {
messageId: {

View File

@ -18,7 +18,7 @@ export const meta = {
requireCredential: true,
kind: 'favorite-write',
kind: 'write:favorites',
params: {
noteId: {

View File

@ -17,7 +17,7 @@ export const meta = {
requireCredential: true,
kind: 'favorite-write',
kind: 'write:favorites',
params: {
noteId: {

View File

@ -35,6 +35,7 @@ export default define(meta, async (ps, user) => {
const day = 1000 * 60 * 60 * 24 * 3; // 3日前まで
const query = Notes.createQueryBuilder('note')
.addSelect('note.score')
.where('note.userHost IS NULL')
.andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) })
.andWhere(`note.visibility = 'public'`)

View File

@ -26,7 +26,7 @@ export const meta = {
requireCredential: true,
kind: 'vote-write',
kind: 'write:votes',
params: {
noteId: {

22
src/server/api/kinds.ts Normal file
View File

@ -0,0 +1,22 @@
export const kinds = [
'read:account',
'write:account',
'read:blocks',
'write:blocks',
'read:drive',
'write:drive',
'read:favorites',
'write:favorites',
'read:following',
'write:following',
'read:messaging',
'write:messaging',
'read:mutes',
'write:mutes',
'write:notes',
'read:notifications',
'write:notifications',
'read:reactions',
'write:reactions',
'write:votes'
];

View File

@ -1,6 +1,49 @@
import config from '../../../config';
import endpoints from '../endpoints';
import * as locale from '../../../../locales/';
import { fromEntries } from '../../../prelude/array';
import { kinds as kindsList } from '../kinds';
export interface IKindInfo {
endpoints: string[];
descs: { [x: string]: string; };
}
export function kinds() {
const kinds = fromEntries(
kindsList
.map(k => [k, {
endpoints: [],
descs: fromEntries(
Object.keys(locale)
.map(l => [l, locale[l].common.permissions[k] as string] as [string, string])
) as { [x: string]: string; }
}] as [ string, IKindInfo ])
) as { [x: string]: IKindInfo; };
const errors = [] as string[][];
for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
if (endpoint.meta.kind) {
const kind = endpoint.meta.kind;
if (kind in kinds) kinds[kind].endpoints.push(endpoint.name);
else errors.push([kind, endpoint.name]);
}
}
if (errors.length > 0) throw Error('\n ' + errors.map((e) => `Unknown kind (permission) "${e[0]}" found at ${e[1]}.`).join('\n '));
return kinds;
}
export function getDescription(lang = 'ja-JP'): string {
const permissionTable = (Object.entries(kinds()) as [string, IKindInfo][])
.map(e => `|${e[0]}|${e[1].descs[lang]}|${e[1].endpoints.map(f => `[${f}](#operation/${f})`).join(', ')}|`)
.join('\n');
const descriptions = {
'ja-JP': `**Misskey is a decentralized microblogging platform.**
export const description = `
## Usage
**APIはすべてPOSTでリクエスト/レスポンスともにJSON形式です。**
一部のAPIはリクエストに認証情報(APIキー)が必要です。リクエストの際に\`i\`というパラメータでAPIキーを添付してください。
@ -44,4 +87,12 @@ APIキーの生成方法を擬似コードで表すと次のようになりま
\`\`\` js
const i = sha256(userToken + secretKey);
\`\`\`
`;
## Permissions
|Permisson (kind)|Description|Endpoints|
|:--|:--|:--|
${permissionTable}
`
} as { [x: string]: string };
return lang in descriptions ? descriptions[lang] : descriptions['ja-JP'];
}

View File

@ -3,7 +3,7 @@ import { Context } from 'cafy';
import config from '../../../config';
import { errors as basicErrors } from './errors';
import { schemas } from './schemas';
import { description } from './description';
import { getDescription } from './description';
import { convertOpenApiSchema } from '../../../misc/schema';
export function genOpenapiSpec(lang = 'ja-JP') {
@ -13,7 +13,7 @@ export function genOpenapiSpec(lang = 'ja-JP') {
info: {
version: 'v1',
title: 'Misskey API',
description: '**Misskey is a decentralized microblogging platform.**\n\n' + description,
description: getDescription(lang),
'x-logo': { url: '/assets/api-doc.png' }
},
@ -110,7 +110,10 @@ export function genOpenapiSpec(lang = 'ja-JP') {
let desc = (endpoint.meta.desc ? endpoint.meta.desc[lang] : 'No description provided.') + '\n\n';
desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
if (endpoint.meta.kind) desc += ` / **Permission**: *${endpoint.meta.kind}*`;
if (endpoint.meta.kind) {
const kind = endpoint.meta.kind;
desc += ` / **Permission**: *${kind}*`;
}
const info = {
operationId: endpoint.name,

View File

@ -49,7 +49,8 @@ export default async (user: User, note: Note, reaction?: string) => {
})
.where('id = :id', { id: note.id })
.execute();
// v11 inc score
Notes.increment({ id: note.id }, 'score', 1);
perUserReactionsChart.update(user, note);

View File

@ -30,7 +30,8 @@ export default async (user: User, note: Note) => {
})
.where('id = :id', { id: note.id })
.execute();
// v11 dec score
Notes.decrement({ id: note.id }, 'score', 1);
publishNoteStream(note.id, 'unreacted', {
reaction: exist.reaction,

View File

@ -11,15 +11,15 @@ export default (
noteId: Note['id']
) => new Promise<any>(async (resolve, reject) => {
// Remove document
const res = await NoteUnreads.delete({
/*const res = */await NoteUnreads.delete({
userId: userId,
noteId: noteId
});
// v11 TODO: https://github.com/typeorm/typeorm/issues/2415
if (res.affected == 0) {
return;
}
//if (res.affected == 0) {
// return;
//}
const count1 = await NoteUnreads.count({
userId: userId,

View File

@ -11,7 +11,7 @@
* TS_NODE_FILES=true and TS_NODE_TRANSPILE_ONLY=true
* for more details, please see: https://github.com/TypeStrong/ts-node/issues/754
*/
/*
process.env.NODE_ENV = 'test';
import * as assert from 'assert';
@ -442,7 +442,6 @@ describe('API', () => {
});
describe('drive', () => {
/*
it('ドライブ情報を取得できる', async(async () => {
const bob = await signup({ username: 'bob' });
await uploadFile({
@ -461,7 +460,7 @@ describe('API', () => {
assert.strictEqual(res.status, 200);
assert.strictEqual(typeof res.body === 'object' && !Array.isArray(res.body), true);
expect(res.body).have.property('usage').eql(1792);
}));*/
}));
});
describe('drive/files/create', () => {
@ -793,7 +792,7 @@ describe('API', () => {
parentId: folderA.id
}, arisugawa);
expect(res).have.status(400);
assert.strictEqual(res.status, 400);
}));
it('存在しない親フォルダを設定できない', async(async () => {
@ -967,3 +966,4 @@ describe('API', () => {
}));
});
});
*/

View File

@ -1141,7 +1141,7 @@ describe('MFM', () => {
it('exlude emotes', () => {
const tokens = parse('*.*');
assert.deepStrictEqual(tokens, [
text("*.*"),
text('*.*'),
]);
});

View File

@ -161,7 +161,7 @@ describe('Note', () => {
it('文字数ぎりぎりで怒られない', async(async () => {
const post = {
text: '!'.repeat(1000)
text: '!'.repeat(500)
};
const res = await request('/notes/create', post, alice);
assert.strictEqual(res.status, 200);
@ -169,7 +169,7 @@ describe('Note', () => {
it('文字数オーバーで怒られる', async(async () => {
const post = {
text: '!'.repeat(1001)
text: '!'.repeat(501)
};
const res = await request('/notes/create', post, alice);
assert.strictEqual(res.status, 400);