Merge branch 'develop' into beta

This commit is contained in:
ThatOneCalculator 2023-05-01 21:06:11 -07:00
commit bd4b969489
No known key found for this signature in database
GPG Key ID: 8703CACD01000000
91 changed files with 2853 additions and 529 deletions

View File

@ -1,7 +1,8 @@
pipeline: pipeline:
testCommit: testCommit:
image: node:latest image: node:alpine
commands: commands:
- apk add --no-cache cargo python3 make g++
- cp .config/ci.yml .config/default.yml - cp .config/ci.yml .config/default.yml
- corepack enable - corepack enable
- corepack prepare pnpm@latest --activate - corepack prepare pnpm@latest --activate

File diff suppressed because it is too large Load Diff

View File

@ -68,8 +68,8 @@ import: "Import"
export: "Export" export: "Export"
files: "Files" files: "Files"
download: "Download" download: "Download"
driveFileDeleteConfirm: "Are you sure you want to delete the file \"{name}\"? Posts\ driveFileDeleteConfirm: "Are you sure you want to delete the file \"{name}\"? It\
\ with this file attached will also be deleted." \ will be removed from all posts that contain it as an attachment."
unfollowConfirm: "Are you sure that you want to unfollow {name}?" unfollowConfirm: "Are you sure that you want to unfollow {name}?"
exportRequested: "You've requested an export. This may take a while. It will be added\ exportRequested: "You've requested an export. This may take a while. It will be added\
\ to your Drive once completed." \ to your Drive once completed."
@ -197,6 +197,7 @@ perHour: "Per Hour"
perDay: "Per Day" perDay: "Per Day"
stopActivityDelivery: "Stop sending activities" stopActivityDelivery: "Stop sending activities"
blockThisInstance: "Block this instance" blockThisInstance: "Block this instance"
silenceThisInstance: "Silence this instance"
operations: "Operations" operations: "Operations"
software: "Software" software: "Software"
version: "Version" version: "Version"
@ -218,10 +219,13 @@ clearCachedFilesConfirm: "Are you sure that you want to delete all cached remote
blockedInstances: "Blocked Instances" blockedInstances: "Blocked Instances"
blockedInstancesDescription: "List the hostnames of the instances that you want to\ blockedInstancesDescription: "List the hostnames of the instances that you want to\
\ block. Listed instances will no longer be able to communicate with this instance." \ block. Listed instances will no longer be able to communicate with this instance."
silencedInstances: "Silenced Instances"
silencedInstancesDescription: "List the hostnames of the instances that you want to\
\ silence. Accounts in the listed instances are treated as \"Silenced\", can only make follow requests, and cannot mention local accounts if not followed. This will not affect the blocked instances."
hiddenTags: "Hidden Hashtags" hiddenTags: "Hidden Hashtags"
hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\ hiddenTagsDescription: "List the hashtags (without the #) of the hashtags you wish\
\ to hide from trending and explore. Hidden hashtags are still discoverable via\ \ to hide from trending and explore. Hidden hashtags are still discoverable via\
\ other means." \ other means. Blocked instances are not affected even if listed here."
muteAndBlock: "Mutes and Blocks" muteAndBlock: "Mutes and Blocks"
mutedUsers: "Muted users" mutedUsers: "Muted users"
blockedUsers: "Blocked users" blockedUsers: "Blocked users"
@ -240,6 +244,7 @@ noCustomEmojis: "There are no emoji"
noJobs: "There are no jobs" noJobs: "There are no jobs"
federating: "Federating" federating: "Federating"
blocked: "Blocked" blocked: "Blocked"
silenced: "Silenced"
suspended: "Suspended" suspended: "Suspended"
all: "All" all: "All"
subscribing: "Subscribing" subscribing: "Subscribing"
@ -829,7 +834,7 @@ active: "Active"
offline: "Offline" offline: "Offline"
notRecommended: "Not recommended" notRecommended: "Not recommended"
botProtection: "Bot Protection" botProtection: "Bot Protection"
instanceBlocking: "Blocked Instances" instanceBlocking: "Federation Block/Silence"
selectAccount: "Select account" selectAccount: "Select account"
switchAccount: "Switch account" switchAccount: "Switch account"
enabled: "Enabled" enabled: "Enabled"
@ -1042,7 +1047,7 @@ moveFromLabel: "Account you're moving from:"
moveFromDescription: "This will set an alias of your old account so that you can move\ moveFromDescription: "This will set an alias of your old account so that you can move\
\ from that account to this current one. Do this BEFORE moving from your older account.\ \ from that account to this current one. Do this BEFORE moving from your older account.\
\ Please enter the tag of the account formatted like @person@instance.com" \ Please enter the tag of the account formatted like @person@instance.com"
migrationConfirm: "Are you absolutely sure you want to migrate your acccount to {account}?\ migrationConfirm: "Are you absolutely sure you want to migrate your account to {account}?\
\ Once you do this, you won't be able to reverse it, and you won't be able to use\ \ Once you do this, you won't be able to reverse it, and you won't be able to use\
\ your account normally again.\nAlso, please ensure that you've set this current\ \ your account normally again.\nAlso, please ensure that you've set this current\
\ account as the account you're moving from." \ account as the account you're moving from."
@ -1197,7 +1202,7 @@ _mfm:
inlineMath: "Math (Inline)" inlineMath: "Math (Inline)"
inlineMathDescription: "Display math formulas (KaTeX) in-line" inlineMathDescription: "Display math formulas (KaTeX) in-line"
blockMath: "Math (Block)" blockMath: "Math (Block)"
blockMathDescription: "Display multi-line math formulas (KaTeX) in a block" blockMathDescription: "Display math formulas (KaTeX) in a block"
quote: "Quote" quote: "Quote"
quoteDescription: "Displays content as a quote." quoteDescription: "Displays content as a quote."
emoji: "Custom Emoji" emoji: "Custom Emoji"

View File

@ -1,9 +1,10 @@
_lang_: "Suomi"
username: Käyttäjänimi username: Käyttäjänimi
fetchingAsApObject: Hae Fedeversestä fetchingAsApObject: Hae Fedeversestä
gotIt: Selvä! gotIt: Selvä!
cancel: Peruuta cancel: Peruuta
enterUsername: Anna käyttäjänimi enterUsername: Anna käyttäjänimi
renotedBy: Buustannut {käyttäjä} renotedBy: Buustannut {user}
noNotes: Ei lähetyksiä noNotes: Ei lähetyksiä
noNotifications: Ei ilmoituksia noNotifications: Ei ilmoituksia
instance: Instanssi instance: Instanssi
@ -41,3 +42,182 @@ favorite: Lisää kirjanmerkkeihin
copyContent: Kopioi sisältö copyContent: Kopioi sisältö
deleteAndEdit: Poista ja muokkaa deleteAndEdit: Poista ja muokkaa
copyLink: Kopioi linkki copyLink: Kopioi linkki
makeFollowManuallyApprove: Seuraajapyyntö vaatii hyväksymistä
follow: Seuraa
pinned: Kiinnitä profiiliin
followRequestPending: Seuraajapyyntö odottaa
you: Sinä
unrenote: Peruuta buustaus
reaction: Reaktiot
reactionSettingDescription2: Vedä uudelleenjärjestelläksesi, napsauta poistaaksesi,
paina "+" lisätäksesi.
attachCancel: Poista liite
enterFileName: Anna tiedostonimi
mute: Hiljennä
unmute: Poista hiljennys
headlineMisskey: Avoimen lähdekoodin, hajautettu sosiaalisen median alusta, joka on
ikuisesti ilmainen! 🚀
monthAndDay: '{day}/{month}'
deleteAndEditConfirm: Oletko varma, että haluat poistaa tämän lähetyksen ja muokata
sitä? Menetät kaikki reaktiot, buustaukset ja vastaukset lähetyksestäsi.
addToList: Lisää listaan
sendMessage: Lähetä viesti
reply: Vastaa
loadMore: Lataa enemmän
showMore: Näytä enemmän
receiveFollowRequest: Seuraajapyyntö vastaanotettu
followRequestAccepted: Seuraajapyyntö hyväksytty
mentions: Maininnat
importAndExport: Tuo/Vie Tietosisältö
import: Tuo
export: Vie
files: Tiedostot
download: Lataa
unfollowConfirm: Oletko varma, ettet halua seurata enää käyttäjää {name}?
noLists: Sinulla ei ole listoja
note: Lähetys
notes: Lähetykset
following: Seuraa
createList: Luo lista
manageLists: Hallitse listoja
error: Virhe
somethingHappened: On tapahtunut virhe
retry: Yritä uudelleen
pageLoadError: Virhe ladattaessa sivua.
serverIsDead: Tämä palvelin ei vastaa. Yritä hetken kuluttua uudelleen.
youShouldUpgradeClient: Nähdäksesi tämän sivun, virkistä päivittääksesi asiakasohjelmasi.
privacy: Tietosuoja
defaultNoteVisibility: Oletusnäkyvyys
followRequest: Seuraajapyyntö
followRequests: Seuraajapyynnöt
unfollow: Poista seuraaminen
enterEmoji: Syötä emoji
renote: Buustaa
renoted: Buustattu.
cantRenote: Tätä lähetystä ei voi buustata.
cantReRenote: Buustausta ei voi buustata.
quote: Lainaus
pinnedNote: Lukittu lähetys
clickToShow: Napsauta nähdäksesi
sensitive: Herkkää sisältöä (NSFW)
add: Lisää
enableEmojiReactions: Ota käyttöön emoji-reaktiot
showEmojisInReactionNotifications: Näytä emojit reaktioilmoituksissa
reactionSetting: Reaktiot näytettäväksi reaktiovalitsimessa
rememberNoteVisibility: Muista lähetyksen näkyvyysasetukset
markAsSensitive: Merkitse herkäksi sisällöksi (NSFW)
unmarkAsSensitive: Poista merkintä herkkää sisältöä (NSFW)
renoteMute: Hiljennä buustit
renoteUnmute: Poista buustien hiljennys
block: Estä
unblock: Poista esto
unsuspend: Poista keskeytys
suspend: Keskeytys
blockConfirm: Oletko varma, että haluat estää tämän tilin?
unblockConfirm: Oletko varma, että haluat poistaa tämän tilin eston?
selectAntenna: Valitse antenni
selectWidget: Valitse vimpain
editWidgets: Muokkaa vimpaimia
editWidgetsExit: Valmis
emoji: Emoji
emojis: Emojit
emojiName: Emojin nimi
emojiUrl: Emojin URL-linkki
cacheRemoteFiles: Taltioi etätiedostot välimuistiin
flagAsBot: Merkitse tili botiksi
flagAsBotDescription: Ota tämä vaihtoehto käyttöön, jos tätä tiliä ohjaa ohjelma.
Jos se on käytössä, se toimii lippuna muille kehittäjille, jotta estetään loputtomat
vuorovaikutusketjut muiden bottien kanssa ja säädetään Calckeyn sisäiset järjestelmät
käsittelemään tätä tiliä botina.
flagAsCat: Oletko kissa? 🐱
flagAsCatDescription: Saat kissan korvat ja puhut kuin kissa!
flagSpeakAsCat: Puhu kuin kissa
flagShowTimelineReplies: Näytä vastaukset aikajanalla
addAccount: Lisää tili
loginFailed: Kirjautuminen epäonnistui
showOnRemote: Katsele etäinstanssilla
general: Yleistä
accountMoved: 'Käyttäjä on muuttanut uuteen tiliin:'
wallpaper: Taustakuva
setWallpaper: Aseta taustakuva
searchWith: 'Etsi: {q}'
youHaveNoLists: Sinulla ei ole listoja
followConfirm: Oletko varma, että haluat seurata käyttäjää {name}?
host: Isäntä
selectUser: Valitse käyttäjä
annotation: Kommentit
registeredAt: Rekisteröity
latestRequestReceivedAt: Viimeisin pyyntö vastaanotettu
latestRequestSentAt: Viimeisin pyyntö lähetetty
storageUsage: Tallennustilan käyttö
charts: Kaaviot
stopActivityDelivery: Lopeta toimintojen lähettäminen
blockThisInstance: Estä tämä instanssi
operations: Toiminnot
metadata: Metatieto
monitor: Seuranta
jobQueue: Työjono
cpuAndMemory: Prosessori ja muisti
network: Verkko
disk: Levy
clearCachedFiles: Tyhjennä välimuisti
clearCachedFilesConfirm: Oletko varma, että haluat tyhjentää kaikki välimuistiin tallennetut
etätiedostot?
blockedInstances: Estetyt instanssit
hiddenTags: Piilotetut asiatunnisteet
mention: Maininta
copyUsername: Kopioi käyttäjänimi
searchUser: Etsi käyttäjää
showLess: Sulje
youGotNewFollower: seurasi sinua
directNotes: Yksityisviestit
driveFileDeleteConfirm: Oletko varma, että haluat poistaa tiedoston " {name}"? Lähetykset,
jotka sisältyvät tiedostoon, poistuvat myös.
importRequested: Olet pyytänyt viemistä. Tämä voi viedä hetken.
exportRequested: Olet pyytänyt tuomista. Tämä voi viedä hetken. Se lisätään asemaan
kun tuonti valmistuu.
lists: Listat
followers: Seuraajat
followsYou: Seuraa sinua
pageLoadErrorDescription: Tämä yleensä johtuu verkkovirheistä tai selaimen välimuistista.
Kokeile tyhjentämällä välimuisti ja yritä sitten hetken kuluttua uudelleen.
enterListName: Anna listalle nimi
withNFiles: '{n} tiedosto(t)'
instanceInfo: Instanssin tiedot
clearQueue: Tyhjennä jono
suspendConfirm: Oletko varma, että haluat keskeyttää tämän tilin?
unsuspendConfirm: Oletko varma, että haluat poistaa tämän tilin keskeytyksen?
selectList: Valitse lista
customEmojis: Kustomoitu Emoji
addEmoji: Lisää
settingGuide: Suositellut asetukset
cacheRemoteFilesDescription: Kun tämä asetus ei ole käytössä, etätiedostot on ladattu
suoraan etäinstanssilta. Asetuksen poistaminen käytöstä vähentää tallennustilan
käyttöä, mutta lisää verkkoliikennettä kun pienoiskuvat eivät muodostu.
flagSpeakAsCatDescription: Lähetyksesi nyanifioidaan, kun olet kissatilassa
flagShowTimelineRepliesDescription: Näyttää käyttäjien vastaukset muiden käyttäjien
lähetyksiin aikajanalla, jos se on päällä.
autoAcceptFollowed: Automaattisesti hyväksy seuraamispyynnöt käyttäjiltä, joita seuraat
perHour: Tunnissa
removeWallpaper: Poista taustakuva
recipient: Vastaanottaja(t)
federation: Federaatio
software: Ohjelmisto
proxyAccount: Proxy-tili
proxyAccountDescription: Välitystili (Proxy-tili) on tili, joka toimii käyttäjien
etäseuraajana tietyin edellytyksin. Kun käyttäjä esimerkiksi lisää etäkäyttäjän
luetteloon, etäkäyttäjän toimintaa ei toimiteta instanssiin, jos yksikään paikallinen
käyttäjä ei seuraa kyseistä käyttäjää, joten välitystili seuraa sen sijaan.
latestStatus: Viimeisin tila
selectInstance: Valitse instanssi
instances: Instanssit
perDay: Päivässä
version: Versio
statistics: Tilastot
clearQueueConfirmTitle: Oletko varma, että haluat tyhjentää jonon?
introMisskey: Tervetuloa! Calckey on avoimen lähdekoodin, hajautettu sosiaalisen median
alusta, joka on ikuisesti ilmainen! 🚀
clearQueueConfirmText: Mitkään välittämättömät lähetykset, jotka ovat jonossa, eivät
federoidu. Yleensä tätä toimintoa ei tarvita.
blockedInstancesDescription: Lista instanssien isäntänimistä, jotka haluat estää.
Listatut instanssit eivät kykene kommunikoimaan enää tämän instanssin kanssa.

View File

@ -183,6 +183,7 @@ perHour: "1時間ごと"
perDay: "1日ごと" perDay: "1日ごと"
stopActivityDelivery: "アクティビティの配送を停止" stopActivityDelivery: "アクティビティの配送を停止"
blockThisInstance: "このインスタンスをブロック" blockThisInstance: "このインスタンスをブロック"
silenceThisInstance: "このインスタンスをサイレンス"
operations: "操作" operations: "操作"
software: "ソフトウェア" software: "ソフトウェア"
version: "バージョン" version: "バージョン"
@ -202,6 +203,8 @@ clearCachedFiles: "キャッシュをクリア"
clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?" clearCachedFilesConfirm: "キャッシュされたリモートファイルをすべて削除しますか?"
blockedInstances: "ブロックしたインスタンス" blockedInstances: "ブロックしたインスタンス"
blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。" blockedInstancesDescription: "ブロックしたいインスタンスのホストを改行で区切って設定します。ブロックされたインスタンスは、このインスタンスとやり取りできなくなります。"
silencedInstances: "サイレンスしたインスタンス"
silencedInstancesDescription: "サイレンスしたいインスタンスのホストを改行で区切って設定します。サイレンスされたインスタンスに所属するアカウントはすべて「サイレンス」として扱われ、フォローがすべてリクエストになり、フォロワーでないローカルアカウントにはメンションできなくなります。ブロックしたインスタンスには影響しません。"
muteAndBlock: "ミュートとブロック" muteAndBlock: "ミュートとブロック"
mutedUsers: "ミュートしたユーザー" mutedUsers: "ミュートしたユーザー"
blockedUsers: "ブロックしたユーザー" blockedUsers: "ブロックしたユーザー"
@ -220,6 +223,7 @@ noCustomEmojis: "絵文字はありません"
noJobs: "ジョブはありません" noJobs: "ジョブはありません"
federating: "連合中" federating: "連合中"
blocked: "ブロック中" blocked: "ブロック中"
silenced: "サイレンス中"
suspended: "配信停止" suspended: "配信停止"
all: "全て" all: "全て"
subscribing: "購読中" subscribing: "購読中"
@ -768,7 +772,7 @@ active: "アクティブ"
offline: "オフライン" offline: "オフライン"
notRecommended: "非推奨" notRecommended: "非推奨"
botProtection: "Botプロテクション" botProtection: "Botプロテクション"
instanceBlocking: "インスタンスブロック" instanceBlocking: "連合ブロック・サイレンス"
selectAccount: "アカウントを選択" selectAccount: "アカウントを選択"
switchAccount: "アカウントを切り替え" switchAccount: "アカウントを切り替え"
enabled: "有効" enabled: "有効"
@ -1079,7 +1083,7 @@ _mfm:
inlineMath: "数式(インライン)" inlineMath: "数式(インライン)"
inlineMathDescription: "数式(KaTeX)をインラインで表示します。" inlineMathDescription: "数式(KaTeX)をインラインで表示します。"
blockMath: "数式(ブロック)" blockMath: "数式(ブロック)"
blockMathDescription: "複数行の数式(KaTeX)をブロックで表示します。" blockMathDescription: "数式(KaTeX)をブロックで表示します。"
quote: "引用" quote: "引用"
quoteDescription: "内容が引用であることを示せます。" quoteDescription: "内容が引用であることを示せます。"
emoji: "カスタム絵文字" emoji: "カスタム絵文字"
@ -1120,6 +1124,7 @@ _mfm:
rotateDescription: "指定した角度で回転させます。" rotateDescription: "指定した角度で回転させます。"
plain: "プレーン" plain: "プレーン"
plainDescription: "内側の構文を全て無効にします。" plainDescription: "内側の構文を全て無効にします。"
position: 位置
_instanceTicker: _instanceTicker:
none: "表示しない" none: "表示しない"
remote: "リモートユーザーに表示" remote: "リモートユーザーに表示"
@ -1128,7 +1133,7 @@ _serverDisconnectedBehavior:
reload: "自動でリロード" reload: "自動でリロード"
dialog: "ダイアログで警告" dialog: "ダイアログで警告"
quiet: "控えめに警告" quiet: "控えめに警告"
nothing: "何も起こらない" nothing: "何もない"
_channel: _channel:
create: "チャンネルを作成" create: "チャンネルを作成"
edit: "チャンネルを編集" edit: "チャンネルを編集"

View File

@ -1009,9 +1009,9 @@ _mfm:
blockCode: "代码(块)" blockCode: "代码(块)"
blockCodeDescription: "语法高亮显示整块程序代码。" blockCodeDescription: "语法高亮显示整块程序代码。"
inlineMath: "数学公式(内嵌)" inlineMath: "数学公式(内嵌)"
inlineMathDescription: "显示内嵌的KaTex公式。" inlineMathDescription: "显示内嵌的KaTeX公式。"
blockMath: "数学公式(块)" blockMath: "数学公式(块)"
blockMathDescription: "显示整块的多行KaTex数学公式。" blockMathDescription: "显示整块的KaTeX数学公式。"
quote: "引用" quote: "引用"
quoteDescription: "可以用来表示引用的内容。" quoteDescription: "可以用来表示引用的内容。"
emoji: "自定义表情符号" emoji: "自定义表情符号"

View File

@ -1012,9 +1012,9 @@ _mfm:
blockCode: "程式碼(區塊)" blockCode: "程式碼(區塊)"
blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。" blockCodeDescription: "在區塊中用高亮度顯示,例如複數行的程式碼語法。"
inlineMath: "數學公式(內嵌)" inlineMath: "數學公式(內嵌)"
inlineMathDescription: "顯示內嵌的KaTex數學公式。" inlineMathDescription: "顯示內嵌的KaTeX數學公式。"
blockMath: "數學公式(方塊)" blockMath: "數學公式(方塊)"
blockMathDescription: "以區塊顯示複數行的KaTex數學式。" blockMathDescription: "以區塊顯示KaTeX數學式。"
quote: "引用" quote: "引用"
quoteDescription: "可以用來表示引用的内容。" quoteDescription: "可以用來表示引用的内容。"
emoji: "自訂表情符號" emoji: "自訂表情符號"

View File

@ -1,6 +1,6 @@
{ {
"name": "calckey", "name": "calckey",
"version": "13.2.0-beta9h", "version": "14.0.0-rc",
"codename": "aqua", "codename": "aqua",
"repository": { "repository": {
"type": "git", "type": "git",

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.2 KiB

After

Width:  |  Height:  |  Size: 473 KiB

View File

@ -0,0 +1,23 @@
export class LibreTranslate1682777547198 {
name = "LibreTranslate1682777547198";
async up(queryRunner) {
await queryRunner.query(`
ALTER TABLE "meta"
ADD "libreTranslateApiUrl" character varying(512)
`);
await queryRunner.query(`
ALTER TABLE "meta"
ADD "libreTranslateApiKey" character varying(128)
`);
}
async down(queryRunner) {
await queryRunner.query(`
ALTER TABLE "meta" DROP COLUMN "libreTranslateApiKey"
`);
await queryRunner.query(`
ALTER TABLE "meta" DROP COLUMN "libreTranslateApiUrl"
`);
}
}

View File

@ -0,0 +1,13 @@
export class InstanceSilence1682891890317 {
name = "InstanceSilence1682891890317";
async up(queryRunner) {
await queryRunner.query(
`ALTER TABLE "meta" ADD "silencedHosts" character varying(256) array NOT NULL DEFAULT '{}'`,
);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "silencedHosts"`);
}
}

View File

@ -89,6 +89,11 @@ export type Source = {
authKey?: string; authKey?: string;
isPro?: boolean; isPro?: boolean;
}; };
libreTranslate: {
managed?: boolean;
apiUrl?: string;
apiKey?: string;
};
email: { email: {
managed?: boolean; managed?: boolean;
address?: string; address?: string;

View File

@ -18,3 +18,21 @@ export async function shouldBlockInstance(
(blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`), (blockedHost) => host === blockedHost || host.endsWith(`.${blockedHost}`),
); );
} }
/**
* Returns whether a specific host (punycoded) should be limited.
*
* @param host punycoded instance host
* @param meta a resolved Meta table
* @returns whether the given host should be limited
*/
export async function shouldSilenceInstance(
host: Instance["host"],
meta?: Meta,
): Promise<boolean> {
const { silencedHosts } = meta ?? (await fetchMeta());
return silencedHosts.some(
(silencedHost) =>
host === silencedHost || host.endsWith(`.${silencedHost}`),
);
}

View File

@ -97,6 +97,11 @@ export class Meta {
}) })
public blockedHosts: string[]; public blockedHosts: string[];
@Column('varchar', {
length: 256, array: true, default: '{}',
})
public silencedHosts: string[];
@Column('boolean', { @Column('boolean', {
default: false, default: false,
}) })
@ -386,6 +391,18 @@ export class Meta {
}) })
public deeplIsPro: boolean; public deeplIsPro: boolean;
@Column('varchar', {
length: 512,
nullable: true,
})
public libreTranslateApiUrl: string | null;
@Column('varchar', {
length: 128,
nullable: true,
})
public libreTranslateApiKey: string | null;
@Column('varchar', { @Column('varchar', {
length: 512, length: 512,
nullable: true, nullable: true,

View File

@ -1,12 +1,13 @@
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { Instance } from "@/models/entities/instance.js"; import { Instance } from "@/models/entities/instance.js";
import type { Packed } from "@/misc/schema.js"; import type { Packed } from "@/misc/schema.js";
import { fetchMeta } from "@/misc/fetch-meta.js"; import {
import { shouldBlockInstance } from "@/misc/should-block-instance.js"; shouldBlockInstance,
shouldSilenceInstance,
} from "@/misc/should-block-instance.js";
export const InstanceRepository = db.getRepository(Instance).extend({ export const InstanceRepository = db.getRepository(Instance).extend({
async pack(instance: Instance): Promise<Packed<"FederationInstance">> { async pack(instance: Instance): Promise<Packed<"FederationInstance">> {
const meta = await fetchMeta();
return { return {
id: instance.id, id: instance.id,
caughtAt: instance.caughtAt.toISOString(), caughtAt: instance.caughtAt.toISOString(),
@ -22,6 +23,7 @@ export const InstanceRepository = db.getRepository(Instance).extend({
isNotResponding: instance.isNotResponding, isNotResponding: instance.isNotResponding,
isSuspended: instance.isSuspended, isSuspended: instance.isSuspended,
isBlocked: await shouldBlockInstance(instance.host), isBlocked: await shouldBlockInstance(instance.host),
isSilenced: await shouldSilenceInstance(instance.host),
softwareName: instance.softwareName, softwareName: instance.softwareName,
softwareVersion: instance.softwareVersion, softwareVersion: instance.softwareVersion,
openRegistrations: instance.openRegistrations, openRegistrations: instance.openRegistrations,

View File

@ -68,6 +68,11 @@ export const packedFederationInstanceSchema = {
optional: false, optional: false,
nullable: false, nullable: false,
}, },
isSilenced: {
type: "boolean",
optional: false,
nullable: false,
},
softwareName: { softwareName: {
type: "string", type: "string",
optional: false, optional: false,

View File

@ -10,7 +10,13 @@ import { renderPerson } from "@/remote/activitypub/renderer/person.js";
import renderEmoji from "@/remote/activitypub/renderer/emoji.js"; import renderEmoji from "@/remote/activitypub/renderer/emoji.js";
import { inbox as processInbox } from "@/queue/index.js"; import { inbox as processInbox } from "@/queue/index.js";
import { isSelfHost, toPuny } from "@/misc/convert-host.js"; import { isSelfHost, toPuny } from "@/misc/convert-host.js";
import { Notes, Users, Emojis, NoteReactions } from "@/models/index.js"; import {
Notes,
Users,
Emojis,
NoteReactions,
FollowRequests,
} from "@/models/index.js";
import type { ILocalUser, User } from "@/models/entities/user.js"; import type { ILocalUser, User } from "@/models/entities/user.js";
import { renderLike } from "@/remote/activitypub/renderer/like.js"; import { renderLike } from "@/remote/activitypub/renderer/like.js";
import { getUserKeypair } from "@/misc/keypair-store.js"; import { getUserKeypair } from "@/misc/keypair-store.js";
@ -330,22 +336,68 @@ router.get("/likes/:like", async (ctx) => {
}); });
// follow // follow
router.get("/follows/:follower/:followee", async (ctx) => { router.get(
"/follows/:follower/:followee",
async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req);
if (verify !== 200) {
ctx.status = verify;
return;
}
// This may be used before the follow is completed, so we do not
// check if the following exists.
const [follower, followee] = await Promise.all([
Users.findOneBy({
id: ctx.params.follower,
host: IsNull(),
}),
Users.findOneBy({
id: ctx.params.followee,
host: Not(IsNull()),
}),
]);
if (follower == null || followee == null) {
ctx.status = 404;
return;
}
ctx.body = renderActivity(renderFollow(follower, followee));
const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
} else {
ctx.set("Cache-Control", "public, max-age=180");
}
setResponseType(ctx);
},
);
// follow request
router.get("/follows/:followRequestId", async (ctx: Router.RouterContext) => {
const verify = await checkFetch(ctx.req); const verify = await checkFetch(ctx.req);
if (verify !== 200) { if (verify !== 200) {
ctx.status = verify; ctx.status = verify;
return; return;
} }
// This may be used before the follow is completed, so we do not
// check if the following exists. const followRequest = await FollowRequests.findOneBy({
id: ctx.params.followRequestId,
});
if (followRequest == null) {
ctx.status = 404;
return;
}
const [follower, followee] = await Promise.all([ const [follower, followee] = await Promise.all([
Users.findOneBy({ Users.findOneBy({
id: ctx.params.follower, id: followRequest.followerId,
host: IsNull(), host: IsNull(),
}), }),
Users.findOneBy({ Users.findOneBy({
id: ctx.params.followee, id: followRequest.followeeId,
host: Not(IsNull()), host: Not(IsNull()),
}), }),
]); ]);
@ -355,13 +407,13 @@ router.get("/follows/:follower/:followee", async (ctx) => {
return; return;
} }
ctx.body = renderActivity(renderFollow(follower, followee));
const meta = await fetchMeta(); const meta = await fetchMeta();
if (meta.secureMode || meta.privateMode) { if (meta.secureMode || meta.privateMode) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate"); ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
} else { } else {
ctx.set("Cache-Control", "public, max-age=180"); ctx.set("Cache-Control", "public, max-age=180");
} }
ctx.body = renderActivity(renderFollow(follower, followee));
setResponseType(ctx); setResponseType(ctx);
}); });

View File

@ -30,6 +30,17 @@ export default define(meta, paramDef, async (ps, me) => {
set.deeplIsPro = config.deepl.isPro; set.deeplIsPro = config.deepl.isPro;
} }
} }
if (
config.libreTranslate.managed != null &&
config.libreTranslate.managed === true
) {
if (typeof config.libreTranslate.apiUrl === "string") {
set.libreTranslateApiUrl = config.libreTranslate.apiUrl;
}
if (typeof config.libreTranslate.apiKey === "string") {
set.libreTranslateApiKey = config.libreTranslate.apiKey;
}
}
if (config.email.managed != null && config.email.managed === true) { if (config.email.managed != null && config.email.managed === true) {
set.enableEmail = true; set.enableEmail = true;
if (typeof config.email.address === "string") { if (typeof config.email.address === "string") {

View File

@ -259,6 +259,16 @@ export const meta = {
nullable: false, nullable: false,
}, },
}, },
silencedHosts: {
type: "array",
optional: true,
nullable: false,
items: {
type: "string",
optional: false,
nullable: false,
},
},
allowedHosts: { allowedHosts: {
type: "array", type: "array",
optional: true, optional: true,
@ -512,7 +522,8 @@ export default define(meta, paramDef, async (ps, me) => {
enableGithubIntegration: instance.enableGithubIntegration, enableGithubIntegration: instance.enableGithubIntegration,
enableDiscordIntegration: instance.enableDiscordIntegration, enableDiscordIntegration: instance.enableDiscordIntegration,
enableServiceWorker: instance.enableServiceWorker, enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null, translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
pinnedPages: instance.pinnedPages, pinnedPages: instance.pinnedPages,
pinnedClipId: instance.pinnedClipId, pinnedClipId: instance.pinnedClipId,
cacheRemoteFiles: instance.cacheRemoteFiles, cacheRemoteFiles: instance.cacheRemoteFiles,
@ -523,6 +534,7 @@ export default define(meta, paramDef, async (ps, me) => {
customSplashIcons: instance.customSplashIcons, customSplashIcons: instance.customSplashIcons,
hiddenTags: instance.hiddenTags, hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts, blockedHosts: instance.blockedHosts,
silencedHosts: instance.silencedHosts,
allowedHosts: instance.allowedHosts, allowedHosts: instance.allowedHosts,
privateMode: instance.privateMode, privateMode: instance.privateMode,
secureMode: instance.secureMode, secureMode: instance.secureMode,
@ -564,6 +576,8 @@ export default define(meta, paramDef, async (ps, me) => {
objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle, objectStorageS3ForcePathStyle: instance.objectStorageS3ForcePathStyle,
deeplAuthKey: instance.deeplAuthKey, deeplAuthKey: instance.deeplAuthKey,
deeplIsPro: instance.deeplIsPro, deeplIsPro: instance.deeplIsPro,
libreTranslateApiUrl: instance.libreTranslateApiUrl,
libreTranslateApiKey: instance.libreTranslateApiKey,
enableIpLogging: instance.enableIpLogging, enableIpLogging: instance.enableIpLogging,
enableActiveEmailValidation: instance.enableActiveEmailValidation, enableActiveEmailValidation: instance.enableActiveEmailValidation,
}; };

View File

@ -61,6 +61,13 @@ export const paramDef = {
type: "string", type: "string",
}, },
}, },
silencedHosts: {
type: "array",
nullable: true,
items: {
type: "string",
},
},
allowedHosts: { allowedHosts: {
type: "array", type: "array",
nullable: true, nullable: true,
@ -124,6 +131,8 @@ export const paramDef = {
summalyProxy: { type: "string", nullable: true }, summalyProxy: { type: "string", nullable: true },
deeplAuthKey: { type: "string", nullable: true }, deeplAuthKey: { type: "string", nullable: true },
deeplIsPro: { type: "boolean" }, deeplIsPro: { type: "boolean" },
libreTranslateApiUrl: { type: "string", nullable: true },
libreTranslateApiKey: { type: "string", nullable: true },
enableTwitterIntegration: { type: "boolean" }, enableTwitterIntegration: { type: "boolean" },
twitterConsumerKey: { type: "string", nullable: true }, twitterConsumerKey: { type: "string", nullable: true },
twitterConsumerSecret: { type: "string", nullable: true }, twitterConsumerSecret: { type: "string", nullable: true },
@ -217,6 +226,15 @@ export default define(meta, paramDef, async (ps, me) => {
}); });
} }
if (Array.isArray(ps.silencedHosts)) {
let lastValue = "";
set.silencedHosts = ps.silencedHosts.sort().filter((h) => {
const lv = lastValue;
lastValue = h;
return h !== "" && h !== lv;
});
}
if (ps.themeColor !== undefined) { if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor; set.themeColor = ps.themeColor;
} }
@ -515,6 +533,22 @@ export default define(meta, paramDef, async (ps, me) => {
set.deeplIsPro = ps.deeplIsPro; set.deeplIsPro = ps.deeplIsPro;
} }
if (ps.libreTranslateApiUrl !== undefined) {
if (ps.libreTranslateApiUrl === "") {
set.libreTranslateApiUrl = null;
} else {
set.libreTranslateApiUrl = ps.libreTranslateApiUrl;
}
}
if (ps.libreTranslateApiKey !== undefined) {
if (ps.libreTranslateApiKey === "") {
set.libreTranslateApiKey = null;
} else {
set.libreTranslateApiKey = ps.libreTranslateApiKey;
}
}
if (ps.enableIpLogging !== undefined) { if (ps.enableIpLogging !== undefined) {
set.enableIpLogging = ps.enableIpLogging; set.enableIpLogging = ps.enableIpLogging;
} }

View File

@ -34,6 +34,7 @@ export const paramDef = {
notResponding: { type: "boolean", nullable: true }, notResponding: { type: "boolean", nullable: true },
suspended: { type: "boolean", nullable: true }, suspended: { type: "boolean", nullable: true },
federating: { type: "boolean", nullable: true }, federating: { type: "boolean", nullable: true },
silenced: { type: "boolean", nullable: true },
subscribing: { type: "boolean", nullable: true }, subscribing: { type: "boolean", nullable: true },
publishing: { type: "boolean", nullable: true }, publishing: { type: "boolean", nullable: true },
limit: { type: "integer", minimum: 1, maximum: 100, default: 30 }, limit: { type: "integer", minimum: 1, maximum: 100, default: 30 },
@ -115,6 +116,22 @@ export default define(meta, paramDef, async (ps, me) => {
} }
} }
if (typeof ps.silenced === "boolean") {
const meta = await fetchMeta(true);
if (ps.silenced) {
if (meta.silencedHosts.length === 0) {
return [];
}
query.andWhere("instance.host IN (:...silences)", {
silences: meta.silencedHosts,
});
} else if (meta.silencedHosts.length > 0) {
query.andWhere("instance.host NOT IN (:...silences)", {
silences: meta.silencedHosts,
});
}
}
if (typeof ps.notResponding === "boolean") { if (typeof ps.notResponding === "boolean") {
if (ps.notResponding) { if (ps.notResponding) {
query.andWhere("instance.isNotResponding = TRUE"); query.andWhere("instance.isNotResponding = TRUE");

View File

@ -482,7 +482,8 @@ export default define(meta, paramDef, async (ps, me) => {
enableServiceWorker: instance.enableServiceWorker, enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null, translatorAvailable:
instance.deeplAuthKey != null || instance.libreTranslateApiUrl != null,
defaultReaction: instance.defaultReaction, defaultReaction: instance.defaultReaction,
...(ps.detail ...(ps.detail

View File

@ -51,15 +51,54 @@ export default define(meta, paramDef, async (ps, user) => {
const instance = await fetchMeta(); const instance = await fetchMeta();
if (instance.deeplAuthKey == null) { if (instance.deeplAuthKey == null && instance.libreTranslateApiUrl == null) {
return 204; // TODO: 良い感じのエラー返す return 204; // TODO: 良い感じのエラー返す
} }
let targetLang = ps.targetLang; let targetLang = ps.targetLang;
if (targetLang.includes("-")) targetLang = targetLang.split("-")[0]; if (targetLang.includes("-")) targetLang = targetLang.split("-")[0];
if (instance.libreTranslateApiUrl != null) {
const jsonBody = {
q: note.text,
source: "auto",
target: targetLang,
format: "text",
api_key: instance.libreTranslateApiKey ?? "",
};
const url = new URL(instance.libreTranslateApiUrl);
if (url.pathname.endsWith("/")) {
url.pathname = url.pathname.slice(0, -1);
}
if (!url.pathname.endsWith("/translate")) {
url.pathname += "/translate";
}
const res = await fetch(url.toString(), {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(jsonBody),
agent: getAgentByUrl,
});
const json = (await res.json()) as {
detectedLanguage?: {
confidence: number;
language: string;
};
translatedText: string;
};
return {
sourceLang: json.detectedLanguage?.language,
text: json.translatedText,
};
}
const params = new URLSearchParams(); const params = new URLSearchParams();
params.append("auth_key", instance.deeplAuthKey); params.append("auth_key", instance.deeplAuthKey ?? "");
params.append("text", note.text); params.append("text", note.text);
params.append("target_lang", targetLang); params.append("target_lang", targetLang);

View File

@ -29,6 +29,7 @@ import {
convertId, convertId,
IdConvertType as IdType, IdConvertType as IdType,
} from "../../../native-utils/built/index.js"; } from "../../../native-utils/built/index.js";
import { convertAttachment } from "./mastodon/converters.js";
// re-export native rust id conversion (function and enum) // re-export native rust id conversion (function and enum)
export { IdType, convertId }; export { IdType, convertId };
@ -93,7 +94,7 @@ mastoFileRouter.post("/v1/media", upload.single("file"), async (ctx) => {
return; return;
} }
const data = await client.uploadMedia(multipartData); const data = await client.uploadMedia(multipartData);
ctx.body = data.data; ctx.body = convertAttachment(data.data as Entity.Attachment);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -112,7 +113,7 @@ mastoFileRouter.post("/v2/media", upload.single("file"), async (ctx) => {
return; return;
} }
const data = await client.uploadMedia(multipartData); const data = await client.uploadMedia(multipartData);
ctx.body = data.data; ctx.body = convertAttachment(data.data as Entity.Attachment);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View File

@ -8,6 +8,8 @@ import { apiTimelineMastodon } from "./endpoints/timeline.js";
import { apiNotificationsMastodon } from "./endpoints/notifications.js"; import { apiNotificationsMastodon } from "./endpoints/notifications.js";
import { apiSearchMastodon } from "./endpoints/search.js"; import { apiSearchMastodon } from "./endpoints/search.js";
import { getInstance } from "./endpoints/meta.js"; import { getInstance } from "./endpoints/meta.js";
import { convertAnnouncement, convertFilter } from "./converters.js";
import { convertId, IdType } from "../index.js";
export function getClient( export function getClient(
BASE_URL: string, BASE_URL: string,
@ -68,7 +70,9 @@ export function apiMastodonCompatible(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getInstanceAnnouncements(); const data = await client.getInstanceAnnouncements();
ctx.body = data.data; ctx.body = data.data.map((announcement) =>
convertAnnouncement(announcement),
);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -83,7 +87,9 @@ export function apiMastodonCompatible(router: Router): void {
const accessTokens = ctx.request.headers.authorization; const accessTokens = ctx.request.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.dismissInstanceAnnouncement(ctx.params.id); const data = await client.dismissInstanceAnnouncement(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -100,7 +106,7 @@ export function apiMastodonCompatible(router: Router): void {
// displayed without being logged in // displayed without being logged in
try { try {
const data = await client.getFilters(); const data = await client.getFilters();
ctx.body = data.data; ctx.body = data.data.map((filter) => convertFilter(filter));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View File

@ -0,0 +1,61 @@
import { Entity } from "@calckey/megalodon";
import { convertId, IdType } from "../index.js";
function simpleConvert(data: any) {
data.id = convertId(data.id, IdType.MastodonId);
return data;
}
export function convertAccount(account: Entity.Account) {
return simpleConvert(account);
}
export function convertAnnouncement(announcement: Entity.Announcement) {
return simpleConvert(announcement);
}
export function convertAttachment(attachment: Entity.Attachment) {
return simpleConvert(attachment);
}
export function convertFilter(filter: Entity.Filter) {
return simpleConvert(filter);
}
export function convertList(list: Entity.List) {
return simpleConvert(list);
}
export function convertNotification(notification: Entity.Notification) {
notification.account = convertAccount(notification.account);
notification.id = convertId(notification.id, IdType.MastodonId);
if (notification.status)
notification.status = convertStatus(notification.status);
return notification;
}
export function convertPoll(poll: Entity.Poll) {
return simpleConvert(poll);
}
export function convertRelationship(relationship: Entity.Relationship) {
return simpleConvert(relationship);
}
export function convertStatus(status: Entity.Status) {
status.account = convertAccount(status.account);
status.id = convertId(status.id, IdType.MastodonId);
if (status.in_reply_to_account_id)
status.in_reply_to_account_id = convertId(
status.in_reply_to_account_id,
IdType.MastodonId,
);
if (status.in_reply_to_id)
status.in_reply_to_id = convertId(status.in_reply_to_id, IdType.MastodonId);
status.media_attachments = status.media_attachments.map((attachment) =>
convertAttachment(attachment),
);
status.mentions = status.mentions.map((mention) => ({
...mention,
id: convertId(mention.id, IdType.MastodonId),
}));
if (status.poll) status.poll = convertPoll(status.poll);
if (status.reblog) status.reblog = convertStatus(status.reblog);
return status;
}

View File

@ -3,8 +3,14 @@ import { resolveUser } from "@/remote/resolve-user.js";
import Router from "@koa/router"; import Router from "@koa/router";
import { FindOptionsWhere, IsNull } from "typeorm"; import { FindOptionsWhere, IsNull } from "typeorm";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { argsToBools, limitToInt } from "./timeline.js"; import { argsToBools, convertTimelinesArgsId, limitToInt } from "./timeline.js";
import { convertId, IdType } from "../../index.js"; import { convertId, IdType } from "../../index.js";
import {
convertAccount,
convertList,
convertRelationship,
convertStatus,
} from "../converters.js";
const relationshipModel = { const relationshipModel = {
id: "", id: "",
@ -62,9 +68,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.updateCredentials( const data = await client.updateCredentials(
(ctx.request as any).body as any, (ctx.request as any).body as any,
); );
let resp = data.data; ctx.body = convertAccount(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -81,9 +85,7 @@ export function apiAccountMastodon(router: Router): void {
(ctx.request.query as any).acct, (ctx.request.query as any).acct,
"accounts", "accounts",
); );
let resp = data.data.accounts[0]; ctx.body = convertAccount(data.data.accounts[0]);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -115,11 +117,9 @@ export function apiAccountMastodon(router: Router): void {
} }
const data = await client.getRelationships(reqIds); const data = await client.getRelationships(reqIds);
let resp = data.data; ctx.body = data.data.map((relationship) =>
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { convertRelationship(relationship),
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); );
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
let data = e.response.data; let data = e.response.data;
@ -136,9 +136,7 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const calcId = convertId(ctx.params.id, IdType.CalckeyId); const calcId = convertId(ctx.params.id, IdType.CalckeyId);
const data = await client.getAccount(calcId); const data = await client.getAccount(calcId);
let resp = data.data; ctx.body = convertAccount(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -155,27 +153,9 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const data = await client.getAccountStatuses( const data = await client.getAccountStatuses(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
argsToBools(limitToInt(ctx.query as any)), convertTimelinesArgsId(argsToBools(limitToInt(ctx.query as any))),
); );
let resp = data.data; ctx.body = data.data.map((status) => convertStatus(status));
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -193,13 +173,9 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const data = await client.getAccountFollowers( const data = await client.getAccountFollowers(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
let resp = data.data; ctx.body = data.data.map((account) => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -217,13 +193,9 @@ export function apiAccountMastodon(router: Router): void {
try { try {
const data = await client.getAccountFollowing( const data = await client.getAccountFollowing(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
let resp = data.data; ctx.body = data.data.map((account) => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -239,8 +211,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountLists(ctx.params.id); const data = await client.getAccountLists(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data.map((list) => convertList(list));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -259,9 +233,8 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.followAccount( const data = await client.followAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let acct = data.data; let acct = convertRelationship(data.data);
acct.following = true; acct.following = true;
acct.id = convertId(acct.id, IdType.MastodonId);
ctx.body = acct; ctx.body = acct;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -281,8 +254,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unfollowAccount( const data = await client.unfollowAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let acct = data.data; let acct = convertRelationship(data.data);
acct.id = convertId(acct.id, IdType.MastodonId);
acct.following = false; acct.following = false;
ctx.body = acct; ctx.body = acct;
} catch (e: any) { } catch (e: any) {
@ -303,9 +275,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.blockAccount( const data = await client.blockAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -324,9 +294,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unblockAccount( const data = await client.unblockAccount(
convertId(ctx.params.id, IdType.MastodonId), convertId(ctx.params.id, IdType.MastodonId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -346,9 +314,7 @@ export function apiAccountMastodon(router: Router): void {
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request as any).body as any, (ctx.request as any).body as any,
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -367,9 +333,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.unmuteAccount( const data = await client.unmuteAccount(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -383,28 +347,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = (await client.getBookmarks( const data = await client.getBookmarks(
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
)) as any; );
let resp = data.data; ctx.body = data.data.map((status) => convertStatus(status));
for (let statIdx = 0; statIdx < resp.length; statIdx++) {
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId);
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -417,26 +363,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getFavourites(limitToInt(ctx.query as any)); const data = await client.getFavourites(
let resp = data.data; convertTimelinesArgsId(limitToInt(ctx.query as any)),
for (let statIdx = 0; statIdx < resp.length; statIdx++) { );
resp[statIdx].id = convertId(resp[statIdx].id, IdType.MastodonId); ctx.body = data.data.map((status) => convertStatus(status));
resp[statIdx].in_reply_to_account_id = resp[statIdx]
.in_reply_to_account_id
? convertId(resp[statIdx].in_reply_to_account_id, IdType.MastodonId)
: null;
resp[statIdx].in_reply_to_id = resp[statIdx].in_reply_to_id
? convertId(resp[statIdx].in_reply_to_id, IdType.MastodonId)
: null;
let mentions = resp[statIdx].mentions;
for (let mtnIdx = 0; mtnIdx < mentions.length; mtnIdx++) {
resp[statIdx].mentions[mtnIdx].id = convertId(
mentions[mtnIdx].id,
IdType.MastodonId,
);
}
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -449,12 +379,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getMutes(limitToInt(ctx.query as any)); const data = await client.getMutes(
let resp = data.data; convertTimelinesArgsId(limitToInt(ctx.query as any)),
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { );
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); ctx.body = data.data.map((account) => convertAccount(account));
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -467,12 +395,10 @@ export function apiAccountMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getBlocks(limitToInt(ctx.query as any)); const data = await client.getBlocks(
let resp = data.data; convertTimelinesArgsId(limitToInt(ctx.query as any)),
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) { );
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId); ctx.body = data.data.map((account) => convertAccount(account));
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -488,11 +414,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.getFollowRequests( const data = await client.getFollowRequests(
((ctx.query as any) || { limit: 20 }).limit, ((ctx.query as any) || { limit: 20 }).limit,
); );
let resp = data.data; ctx.body = data.data.map((account) => convertAccount(account));
for (let acctIdx = 0; acctIdx < resp.length; acctIdx++) {
resp[acctIdx].id = convertId(resp[acctIdx].id, IdType.MastodonId);
}
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -510,9 +432,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.acceptFollowRequest( const data = await client.acceptFollowRequest(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -531,9 +451,7 @@ export function apiAccountMastodon(router: Router): void {
const data = await client.rejectFollowRequest( const data = await client.rejectFollowRequest(
convertId(ctx.params.id, IdType.CalckeyId), convertId(ctx.params.id, IdType.CalckeyId),
); );
let resp = data.data; ctx.body = convertRelationship(data.data);
resp.id = convertId(resp.id, IdType.MastodonId);
ctx.body = resp;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);

View File

@ -1,6 +1,8 @@
import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import megalodon, { MegalodonInterface } from "@calckey/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { IdType, convertId } from "../../index.js";
import { convertFilter } from "../converters.js";
export function apiFilterMastodon(router: Router): void { export function apiFilterMastodon(router: Router): void {
router.get("/v1/filters", async (ctx) => { router.get("/v1/filters", async (ctx) => {
@ -10,7 +12,7 @@ export function apiFilterMastodon(router: Router): void {
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.getFilters(); const data = await client.getFilters();
ctx.body = data.data; ctx.body = data.data.map((filter) => convertFilter(filter));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -24,8 +26,10 @@ export function apiFilterMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.getFilter(ctx.params.id); const data = await client.getFilter(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertFilter(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -40,7 +44,7 @@ export function apiFilterMastodon(router: Router): void {
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.createFilter(body.phrase, body.context, body); const data = await client.createFilter(body.phrase, body.context, body);
ctx.body = data.data; ctx.body = convertFilter(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -55,11 +59,11 @@ export function apiFilterMastodon(router: Router): void {
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.updateFilter( const data = await client.updateFilter(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
body.phrase, body.phrase,
body.context, body.context,
); );
ctx.body = data.data; ctx.body = convertFilter(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -73,7 +77,9 @@ export function apiFilterMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.deleteFilter(ctx.params.id); const data = await client.deleteFilter(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View File

@ -1,8 +1,10 @@
import megalodon, { MegalodonInterface } from "@calckey/megalodon"; import megalodon, { MegalodonInterface } from "@calckey/megalodon";
import Router from "@koa/router"; import Router from "@koa/router";
import { koaBody } from "koa-body"; import { koaBody } from "koa-body";
import { convertId, IdType } from "../../index.js";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import { toTextWithReaction } from "./timeline.js"; import { convertTimelinesArgsId, toTextWithReaction } from "./timeline.js";
import { convertNotification } from "../converters.js";
function toLimitToInt(q: any) { function toLimitToInt(q: any) {
if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10); if (q.limit) if (typeof q.limit === "string") q.limit = parseInt(q.limit, 10);
return q; return q;
@ -15,9 +17,12 @@ export function apiNotificationsMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.getNotifications(toLimitToInt(ctx.query)); const data = await client.getNotifications(
convertTimelinesArgsId(toLimitToInt(ctx.query)),
);
const notfs = data.data; const notfs = data.data;
const ret = notfs.map((n) => { const ret = notfs.map((n) => {
n = convertNotification(n);
if (n.type !== "follow" && n.type !== "follow_request") { if (n.type !== "follow" && n.type !== "follow_request") {
if (n.type === "reaction") n.type = "favourite"; if (n.type === "reaction") n.type = "favourite";
n.status = toTextWithReaction( n.status = toTextWithReaction(
@ -43,8 +48,10 @@ export function apiNotificationsMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const dataRaw = await client.getNotification(ctx.params.id); const dataRaw = await client.getNotification(
const data = dataRaw.data; convertId(ctx.params.id, IdType.CalckeyId),
);
const data = convertNotification(dataRaw.data);
if (data.type !== "follow" && data.type !== "follow_request") { if (data.type !== "follow" && data.type !== "follow_request") {
if (data.type === "reaction") data.type = "favourite"; if (data.type === "reaction") data.type = "favourite";
ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0]; ctx.body = toTextWithReaction([data as any], ctx.request.hostname)[0];
@ -79,7 +86,9 @@ export function apiNotificationsMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const data = await client.dismissNotification(ctx.params.id); const data = await client.dismissNotification(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);

View File

@ -3,7 +3,8 @@ import Router from "@koa/router";
import { getClient } from "../ApiMastodonCompatibleService.js"; import { getClient } from "../ApiMastodonCompatibleService.js";
import axios from "axios"; import axios from "axios";
import { Converter } from "@calckey/megalodon"; import { Converter } from "@calckey/megalodon";
import { limitToInt } from "./timeline.js"; import { convertTimelinesArgsId, limitToInt } from "./timeline.js";
import { convertAccount, convertStatus } from "../converters.js";
export function apiSearchMastodon(router: Router): void { export function apiSearchMastodon(router: Router): void {
router.get("/v1/search", async (ctx) => { router.get("/v1/search", async (ctx) => {
@ -12,7 +13,7 @@ export function apiSearchMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const body: any = ctx.request.body; const body: any = ctx.request.body;
try { try {
const query: any = limitToInt(ctx.query); const query: any = convertTimelinesArgsId(limitToInt(ctx.query));
const type = query.type || ""; const type = query.type || "";
const data = await client.search(query.q, type, query); const data = await client.search(query.q, type, query);
ctx.body = data.data; ctx.body = data.data;
@ -27,18 +28,20 @@ export function apiSearchMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const query: any = limitToInt(ctx.query); const query: any = convertTimelinesArgsId(limitToInt(ctx.query));
const type = query.type; const type = query.type;
if (type) { if (type) {
const data = await client.search(query.q, type, query); const data = await client.search(query.q, type, query);
ctx.body = data.data; ctx.body = data.data.accounts.map((account) => convertAccount(account));
} else { } else {
const acct = await client.search(query.q, "accounts", query); const acct = await client.search(query.q, "accounts", query);
const stat = await client.search(query.q, "statuses", query); const stat = await client.search(query.q, "statuses", query);
const tags = await client.search(query.q, "hashtags", query); const tags = await client.search(query.q, "hashtags", query);
ctx.body = { ctx.body = {
accounts: acct.data.accounts, accounts: acct.data.accounts.map((account) =>
statuses: stat.data.statuses, convertAccount(account),
),
statuses: stat.data.statuses.map((status) => convertStatus(status)),
hashtags: tags.data.hashtags, hashtags: tags.data.hashtags,
}; };
} }
@ -57,7 +60,7 @@ export function apiSearchMastodon(router: Router): void {
ctx.request.hostname, ctx.request.hostname,
accessTokens, accessTokens,
); );
ctx.body = data; ctx.body = data.map((status) => convertStatus(status));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -69,12 +72,16 @@ export function apiSearchMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
try { try {
const query: any = ctx.query; const query: any = ctx.query;
const data = await getFeaturedUser( let data = await getFeaturedUser(
BASE_URL, BASE_URL,
ctx.request.hostname, ctx.request.hostname,
accessTokens, accessTokens,
query.limit || 20, query.limit || 20,
); );
data = data.map((suggestion) => {
suggestion.account = convertAccount(suggestion.account);
return suggestion;
});
console.log(data); console.log(data);
ctx.body = data; ctx.body = data;
} catch (e: any) { } catch (e: any) {

View File

@ -4,7 +4,14 @@ import { emojiRegexAtStartToEnd } from "@/misc/emoji-regex.js";
import axios from "axios"; import axios from "axios";
import querystring from "node:querystring"; import querystring from "node:querystring";
import qs from "qs"; import qs from "qs";
import { limitToInt } from "./timeline.js"; import { convertTimelinesArgsId, limitToInt } from "./timeline.js";
import { convertId, IdType } from "../../index.js";
import {
convertAccount,
convertAttachment,
convertPoll,
convertStatus,
} from "../converters.js";
function normalizeQuery(data: any) { function normalizeQuery(data: any) {
const str = querystring.stringify(data); const str = querystring.stringify(data);
@ -18,6 +25,8 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
let body: any = ctx.request.body; let body: any = ctx.request.body;
if (body.in_reply_to_id)
body.in_reply_to_id = convertId(body.in_reply_to_id, IdType.CalckeyId);
if ( if (
(!body.poll && body["poll[options][]"]) || (!body.poll && body["poll[options][]"]) ||
(!body.media_ids && body["media_ids[]"]) (!body.media_ids && body["media_ids[]"])
@ -54,7 +63,7 @@ export function apiStatusMastodon(router: Router): void {
body.sensitive = body.sensitive =
typeof sensitive === "string" ? sensitive === "true" : sensitive; typeof sensitive === "string" ? sensitive === "true" : sensitive;
const data = await client.postStatus(text, body); const data = await client.postStatus(text, body);
ctx.body = data.data; ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -66,8 +75,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getStatus(ctx.params.id); const data = await client.getStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -79,7 +90,9 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteStatus(ctx.params.id); const data = await client.deleteStatus(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e.response.data, request.params.id); console.error(e.response.data, request.params.id);
@ -100,10 +113,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const id = ctx.params.id; const id = convertId(ctx.params.id, IdType.CalckeyId);
const data = await client.getStatusContext( const data = await client.getStatusContext(
id, id,
limitToInt(ctx.query as any), convertTimelinesArgsId(limitToInt(ctx.query as any)),
); );
const status = await client.getStatus(id); const status = await client.getStatus(id);
let reqInstance = axios.create({ let reqInstance = axios.create({
@ -126,6 +139,12 @@ export function apiStatusMastodon(router: Router): void {
text, text,
), ),
); );
data.data.ancestors = data.data.ancestors.map((status) =>
convertStatus(status),
);
data.data.descendants = data.data.descendants.map((status) =>
convertStatus(status),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -141,8 +160,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getStatusRebloggedBy(ctx.params.id); const data = await client.getStatusRebloggedBy(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data.map((account) => convertAccount(account));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -165,11 +186,11 @@ export function apiStatusMastodon(router: Router): void {
const react = await getFirstReaction(BASE_URL, accessTokens); const react = await getFirstReaction(BASE_URL, accessTokens);
try { try {
const a = (await client.createEmojiReaction( const a = (await client.createEmojiReaction(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
react, react,
)) as any; )) as any;
//const data = await client.favouriteStatus(ctx.params.id) as any; //const data = await client.favouriteStatus(ctx.params.id) as any;
ctx.body = a.data; ctx.body = convertStatus(a.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -186,8 +207,11 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
const react = await getFirstReaction(BASE_URL, accessTokens); const react = await getFirstReaction(BASE_URL, accessTokens);
try { try {
const data = await client.deleteEmojiReaction(ctx.params.id, react); const data = await client.deleteEmojiReaction(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
react,
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -203,8 +227,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.reblogStatus(ctx.params.id); const data = await client.reblogStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -220,8 +246,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unreblogStatus(ctx.params.id); const data = await client.unreblogStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -237,8 +265,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.bookmarkStatus(ctx.params.id); const data = await client.bookmarkStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -254,8 +284,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = (await client.unbookmarkStatus(ctx.params.id)) as any; const data = await client.unbookmarkStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -271,8 +303,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.pinStatus(ctx.params.id); const data = await client.pinStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -288,8 +322,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.unpinStatus(ctx.params.id); const data = await client.unpinStatus(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertStatus(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -302,8 +338,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getMedia(ctx.params.id); const data = await client.getMedia(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertAttachment(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -316,10 +354,10 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.updateMedia( const data = await client.updateMedia(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
ctx.request.body as any, ctx.request.body as any,
); );
ctx.body = data.data; ctx.body = convertAttachment(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -331,8 +369,10 @@ export function apiStatusMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getPoll(ctx.params.id); const data = await client.getPoll(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertPoll(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;
@ -347,10 +387,10 @@ export function apiStatusMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.votePoll( const data = await client.votePoll(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request.body as any).choices, (ctx.request.body as any).choices,
); );
ctx.body = data.data; ctx.body = convertPoll(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
ctx.status = 401; ctx.status = 401;

View File

@ -4,6 +4,8 @@ import { getClient } from "../ApiMastodonCompatibleService.js";
import { statusModel } from "./status.js"; import { statusModel } from "./status.js";
import Autolinker from "autolinker"; import Autolinker from "autolinker";
import { ParsedUrlQuery } from "querystring"; import { ParsedUrlQuery } from "querystring";
import { convertAccount, convertList, convertStatus } from "../converters.js";
import { convertId, IdType } from "../../index.js";
export function limitToInt(q: ParsedUrlQuery) { export function limitToInt(q: ParsedUrlQuery) {
let object: any = q; let object: any = q;
@ -29,6 +31,16 @@ export function argsToBools(q: ParsedUrlQuery) {
return q; return q;
} }
export function convertTimelinesArgsId(q: ParsedUrlQuery) {
if (typeof q.min_id === "string")
q.min_id = convertId(q.min_id, IdType.CalckeyId);
if (typeof q.max_id === "string")
q.max_id = convertId(q.max_id, IdType.CalckeyId);
if (typeof q.since_id === "string")
q.since_id = convertId(q.since_id, IdType.CalckeyId);
return q;
}
export function toTextWithReaction(status: Entity.Status[], host: string) { export function toTextWithReaction(status: Entity.Status[], host: string) {
return status.map((t) => { return status.map((t) => {
if (!t) return statusModel(null, null, [], "no content"); if (!t) return statusModel(null, null, [], "no content");
@ -97,9 +109,14 @@ export function apiTimelineMastodon(router: Router): void {
try { try {
const query: any = ctx.query; const query: any = ctx.query;
const data = query.local const data = query.local
? await client.getLocalTimeline(argsToBools(limitToInt(query))) ? await client.getLocalTimeline(
: await client.getPublicTimeline(argsToBools(limitToInt(query))); convertTimelinesArgsId(argsToBools(limitToInt(query))),
ctx.body = toTextWithReaction(data.data, ctx.hostname); )
: await client.getPublicTimeline(
convertTimelinesArgsId(argsToBools(limitToInt(query))),
);
let resp = data.data.map((status) => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -116,9 +133,10 @@ export function apiTimelineMastodon(router: Router): void {
try { try {
const data = await client.getTagTimeline( const data = await client.getTagTimeline(
ctx.params.hashtag, ctx.params.hashtag,
argsToBools(limitToInt(ctx.query)), convertTimelinesArgsId(argsToBools(limitToInt(ctx.query))),
); );
ctx.body = toTextWithReaction(data.data, ctx.hostname); let resp = data.data.map((status) => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -132,8 +150,11 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getHomeTimeline(limitToInt(ctx.query)); const data = await client.getHomeTimeline(
ctx.body = toTextWithReaction(data.data, ctx.hostname); convertTimelinesArgsId(limitToInt(ctx.query)),
);
let resp = data.data.map((status) => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -149,10 +170,11 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getListTimeline( const data = await client.getListTimeline(
ctx.params.listId, convertId(ctx.params.listId, IdType.CalckeyId),
limitToInt(ctx.query), convertTimelinesArgsId(limitToInt(ctx.query)),
); );
ctx.body = toTextWithReaction(data.data, ctx.hostname); let resp = data.data.map((status) => convertStatus(status));
ctx.body = toTextWithReaction(resp, ctx.hostname);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -166,7 +188,9 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getConversationTimeline(limitToInt(ctx.query)); const data = await client.getConversationTimeline(
convertTimelinesArgsId(limitToInt(ctx.query)),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -181,7 +205,7 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getLists(); const data = await client.getLists();
ctx.body = data.data; ctx.body = data.data.map((list) => convertList(list));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -196,8 +220,10 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getList(ctx.params.id); const data = await client.getList(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = convertList(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -212,7 +238,7 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.createList((ctx.request.body as any).title); const data = await client.createList((ctx.request.body as any).title);
ctx.body = data.data; ctx.body = convertList(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -227,8 +253,11 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.updateList(ctx.params.id, (ctx.request.body as any).title); const data = await client.updateList(
ctx.body = data.data; convertId(ctx.params.id, IdType.CalckeyId),
(ctx.request.body as any).title,
);
ctx.body = convertList(data.data);
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -244,7 +273,9 @@ export function apiTimelineMastodon(router: Router): void {
const accessTokens = ctx.headers.authorization; const accessTokens = ctx.headers.authorization;
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteList(ctx.params.id); const data = await client.deleteList(
convertId(ctx.params.id, IdType.CalckeyId),
);
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
@ -262,10 +293,10 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.getAccountsInList( const data = await client.getAccountsInList(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
ctx.query as any, convertTimelinesArgsId(ctx.query as any),
); );
ctx.body = data.data; ctx.body = data.data.map((account) => convertAccount(account));
} catch (e: any) { } catch (e: any) {
console.error(e); console.error(e);
console.error(e.response.data); console.error(e.response.data);
@ -282,8 +313,10 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.addAccountsToList( const data = await client.addAccountsToList(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.query as any).account_ids, (ctx.query.account_ids as string[]).map((id) =>
convertId(id, IdType.CalckeyId),
),
); );
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {
@ -302,8 +335,10 @@ export function apiTimelineMastodon(router: Router): void {
const client = getClient(BASE_URL, accessTokens); const client = getClient(BASE_URL, accessTokens);
try { try {
const data = await client.deleteAccountsFromList( const data = await client.deleteAccountsFromList(
ctx.params.id, convertId(ctx.params.id, IdType.CalckeyId),
(ctx.query as any).account_ids, (ctx.query.account_ids as string[]).map((id) =>
convertId(id, IdType.CalckeyId),
),
); );
ctx.body = data.data; ctx.body = data.data;
} catch (e: any) { } catch (e: any) {

View File

@ -55,7 +55,7 @@ export default async (ctx: Koa.Context) => {
return; return;
} }
const available = await validateEmailForAccount(emailAddress); const { available } = await validateEmailForAccount(emailAddress);
if (!available) { if (!available) {
ctx.status = 400; ctx.status = 400;
return; return;

View File

@ -399,28 +399,33 @@ router.get("/notes/:note", async (ctx, next) => {
visibility: In(["public", "home"]), visibility: In(["public", "home"]),
}); });
if (note) { try {
const _note = await Notes.pack(note); if (note) {
const profile = await UserProfiles.findOneByOrFail({ userId: note.userId }); const _note = await Notes.pack(note);
const meta = await fetchMeta();
await ctx.render("note", {
note: _note,
profile,
avatarUrl: await Users.getAvatarUrl(
await Users.findOneByOrFail({ id: note.userId }),
),
// TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note),
instanceName: meta.name || "Calckey",
icon: meta.iconUrl,
privateMode: meta.privateMode,
themeColor: meta.themeColor,
});
ctx.set("Cache-Control", "public, max-age=15"); const profile = await UserProfiles.findOneByOrFail({
userId: note.userId,
});
const meta = await fetchMeta();
await ctx.render("note", {
note: _note,
profile,
avatarUrl: await Users.getAvatarUrl(
await Users.findOneByOrFail({ id: note.userId }),
),
// TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note),
instanceName: meta.name || "Calckey",
icon: meta.iconUrl,
privateMode: meta.privateMode,
themeColor: meta.themeColor,
});
return; ctx.set("Cache-Control", "public, max-age=15");
}
return;
}
} catch {}
await next(); await next();
}); });

View File

@ -6,11 +6,13 @@ import {
NoteThreadMutings, NoteThreadMutings,
UserProfiles, UserProfiles,
Users, Users,
Followings,
} from "@/models/index.js"; } from "@/models/index.js";
import { genId } from "@/misc/gen-id.js"; import { genId } from "@/misc/gen-id.js";
import type { User } from "@/models/entities/user.js"; import type { User } from "@/models/entities/user.js";
import type { Notification } from "@/models/entities/notification.js"; import type { Notification } from "@/models/entities/notification.js";
import { sendEmailNotification } from "./send-email-notification.js"; import { sendEmailNotification } from "./send-email-notification.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
export async function createNotification( export async function createNotification(
notifieeId: User["id"], notifieeId: User["id"],
@ -21,6 +23,26 @@ export async function createNotification(
return null; return null;
} }
if (
data.notifierId &&
["mention", "reply", "renote", "quote", "reaction"].includes(type)
) {
const notifier = await Users.findOneBy({ id: data.notifierId });
// suppress if the notifier does not exist or is silenced.
if (!notifier) return null;
// suppress if the notifier is silenced or in a silenced instance, and not followed by the notifiee.
if (
(notifier.isSilenced ||
(Users.isRemoteUser(notifier) &&
(await shouldSilenceInstance(notifier.host)))) &&
!(await Followings.exist({
where: { followerId: notifieeId, followeeId: data.notifierId },
}))
)
return null;
}
const profile = await UserProfiles.findOneBy({ userId: notifieeId }); const profile = await UserProfiles.findOneBy({ userId: notifieeId });
const isMuted = profile?.mutingNotificationTypes.includes(type); const isMuted = profile?.mutingNotificationTypes.includes(type);

View File

@ -27,6 +27,7 @@ import { isDuplicateKeyValueError } from "@/misc/is-duplicate-key-value-error.js
import type { Packed } from "@/misc/schema.js"; import type { Packed } from "@/misc/schema.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { webhookDeliver } from "@/queue/index.js"; import { webhookDeliver } from "@/queue/index.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
const logger = new Logger("following/create"); const logger = new Logger("following/create");
@ -226,13 +227,19 @@ export default async function (
}); });
// フォロー対象が鍵アカウントである or // フォロー対象が鍵アカウントである or
// The follower is silenced, or
// フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or // フォロワーがBotであり、フォロー対象がBotからのフォローに慎重である or
// フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである // フォロワーがローカルユーザーであり、フォロー対象がリモートユーザーである or
// The follower is remote, the followee is local, and the follower is in a silenced instance.
// 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく // 上記のいずれかに当てはまる場合はすぐフォローせずにフォローリクエストを発行しておく
if ( if (
followee.isLocked || followee.isLocked ||
follower.isSilenced ||
(followeeProfile.carefulBot && follower.isBot) || (followeeProfile.carefulBot && follower.isBot) ||
(Users.isLocalUser(follower) && Users.isRemoteUser(followee)) (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) ||
(Users.isRemoteUser(follower) &&
Users.isLocalUser(followee) &&
(await shouldSilenceInstance(follower.host)))
) { ) {
let autoAccept = false; let autoAccept = false;

View File

@ -6,6 +6,7 @@ import type { User } from "@/models/entities/user.js";
import { Blockings, FollowRequests, Users } from "@/models/index.js"; import { Blockings, FollowRequests, Users } from "@/models/index.js";
import { genId } from "@/misc/gen-id.js"; import { genId } from "@/misc/gen-id.js";
import { createNotification } from "../../create-notification.js"; import { createNotification } from "../../create-notification.js";
import config from "@/config/index.js";
export default async function ( export default async function (
follower: { follower: {
@ -79,7 +80,13 @@ export default async function (
} }
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) { if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
const content = renderActivity(renderFollow(follower, followee)); const content = renderActivity(
renderFollow(
follower,
followee,
requestId ?? `${config.url}/follows/${followRequest.id}`,
),
);
deliver(follower, content, followee.inbox); deliver(follower, content, followee.inbox);
} }
} }

View File

@ -39,7 +39,7 @@ import {
} from "@/models/index.js"; } from "@/models/index.js";
import type { DriveFile } from "@/models/entities/drive-file.js"; import type { DriveFile } from "@/models/entities/drive-file.js";
import type { App } from "@/models/entities/app.js"; import type { App } from "@/models/entities/app.js";
import { Not, In } from "typeorm"; import { Not, In, IsNull } from "typeorm";
import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js"; import type { User, ILocalUser, IRemoteUser } from "@/models/entities/user.js";
import { genId } from "@/misc/gen-id.js"; import { genId } from "@/misc/gen-id.js";
import { import {
@ -66,6 +66,7 @@ import { Cache } from "@/misc/cache.js";
import type { UserProfile } from "@/models/entities/user-profile.js"; import type { UserProfile } from "@/models/entities/user-profile.js";
import { db } from "@/db/postgre.js"; import { db } from "@/db/postgre.js";
import { getActiveWebhooks } from "@/misc/webhook-cache.js"; import { getActiveWebhooks } from "@/misc/webhook-cache.js";
import { shouldSilenceInstance } from "@/misc/should-block-instance.js";
const mutedWordsCache = new Cache< const mutedWordsCache = new Cache<
{ userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[] { userId: UserProfile["userId"]; mutedWords: UserProfile["mutedWords"] }[]
@ -166,6 +167,7 @@ export default async (
data: Option, data: Option,
silent = false, silent = false,
) => ) =>
// rome-ignore lint/suspicious/noAsyncPromiseExecutor: FIXME
new Promise<Note>(async (res, rej) => { new Promise<Note>(async (res, rej) => {
// If you reply outside the channel, match the scope of the target. // If you reply outside the channel, match the scope of the target.
// TODO (I think it's a process that could be done on the client side, but it's server side for now.) // TODO (I think it's a process that could be done on the client side, but it's server side for now.)
@ -203,6 +205,15 @@ export default async (
data.visibility = "home"; data.visibility = "home";
} }
// Enforce home visibility if the user is in a silenced instance.
if (
data.visibility === "public" &&
Users.isRemoteUser(user) &&
(await shouldSilenceInstance(user.host))
) {
data.visibility = "home";
}
// Reject if the target of the renote is a public range other than "Home or Entire". // Reject if the target of the renote is a public range other than "Home or Entire".
if ( if (
data.renote && data.renote &&

View File

@ -118,7 +118,7 @@ export default async (
userId: user.id, userId: user.id,
}); });
// リアクションされたユーザーがローカルユーザーなら通知を作成 // Create notification if the reaction target is a local user.
if (note.userHost === null) { if (note.userHost === null) {
createNotification(note.userId, "reaction", { createNotification(note.userId, "reaction", {
notifierId: user.id, notifierId: user.id,
@ -143,7 +143,7 @@ export default async (
} }
}); });
//#region 配信 //#region deliver
if (Users.isLocalUser(user) && !note.localOnly) { if (Users.isLocalUser(user) && !note.localOnly) {
const content = renderActivity(await renderLike(record, note)); const content = renderActivity(await renderLike(record, note));
const dm = new DeliverManager(user, content); const dm = new DeliverManager(user, content);

View File

@ -1,7 +0,0 @@
node_modules
/built
/coverage
/.eslintrc.js
/jest.config.ts
/test
/test-d

View File

@ -1,65 +0,0 @@
module.exports = {
root: true,
parser: "@typescript-eslint/parser",
parserOptions: {
tsconfigRootDir: __dirname,
project: ["./tsconfig.json"],
},
plugins: ["@typescript-eslint"],
extends: ["eslint:recommended", "plugin:@typescript-eslint/recommended"],
rules: {
indent: [
"error",
"tab",
{
SwitchCase: 1,
MemberExpression: "off",
flatTernaryExpressions: true,
ArrayExpression: "first",
ObjectExpression: "first",
},
],
"eol-last": ["error", "always"],
semi: ["error", "always"],
quotes: ["error", "single"],
"comma-dangle": ["error", "always-multiline"],
"keyword-spacing": [
"error",
{
before: true,
after: true,
},
],
"key-spacing": [
"error",
{
beforeColon: false,
afterColon: true,
},
],
"space-infix-ops": ["error"],
"space-before-blocks": ["error", "always"],
"object-curly-spacing": ["error", "always"],
"nonblock-statement-body-position": ["error", "beside"],
eqeqeq: ["error", "always", { null: "ignore" }],
"no-multiple-empty-lines": ["error", { max: 1 }],
"no-multi-spaces": ["error"],
"no-var": ["error"],
"prefer-arrow-callback": ["error"],
"no-throw-literal": ["error"],
"no-param-reassign": ["warn"],
"no-constant-condition": ["warn"],
"no-empty-pattern": ["warn"],
"@typescript-eslint/no-unnecessary-condition": ["error"],
"@typescript-eslint/no-inferrable-types": ["warn"],
"@typescript-eslint/no-non-null-assertion": ["warn"],
"@typescript-eslint/explicit-function-return-type": ["warn"],
"@typescript-eslint/no-misused-promises": [
"error",
{
checksVoidReturn: false,
},
],
"@typescript-eslint/consistent-type-imports": "error",
},
};

View File

@ -9,9 +9,8 @@
"tsd": "tsd", "tsd": "tsd",
"api": "pnpm api-extractor run --local --verbose", "api": "pnpm api-extractor run --local --verbose",
"api-prod": "pnpm api-extractor run --verbose", "api-prod": "pnpm api-extractor run --verbose",
"eslint": "eslint . --ext .js,.jsx,.ts,.tsx",
"typecheck": "tsc --noEmit", "typecheck": "tsc --noEmit",
"lint": "pnpm typecheck && pnpm eslint", "lint": "pnpm typecheck && pnpm rome check \"src/*.ts\"",
"jest": "jest --coverage --detectOpenHandles", "jest": "jest --coverage --detectOpenHandles",
"test": "pnpm jest && pnpm tsd" "test": "pnpm jest && pnpm tsd"
}, },

View File

@ -55,6 +55,7 @@ export type Endpoints = {
"admin/get-table-stats": { req: TODO; res: TODO }; "admin/get-table-stats": { req: TODO; res: TODO };
"admin/invite": { req: TODO; res: TODO }; "admin/invite": { req: TODO; res: TODO };
"admin/logs": { req: TODO; res: TODO }; "admin/logs": { req: TODO; res: TODO };
"admin/meta": { req: TODO; res: TODO };
"admin/reset-password": { req: TODO; res: TODO }; "admin/reset-password": { req: TODO; res: TODO };
"admin/resolve-abuse-user-report": { req: TODO; res: TODO }; "admin/resolve-abuse-user-report": { req: TODO; res: TODO };
"admin/resync-chart": { req: TODO; res: TODO }; "admin/resync-chart": { req: TODO; res: TODO };

View File

@ -32,7 +32,7 @@
"autosize": "5.0.2", "autosize": "5.0.2",
"blurhash": "1.1.5", "blurhash": "1.1.5",
"broadcast-channel": "4.19.1", "broadcast-channel": "4.19.1",
"browser-image-resizer": "https://github.com/misskey-dev/browser-image-resizer.git", "browser-image-resizer": "github:misskey-dev/browser-image-resizer",
"calckey-js": "workspace:*", "calckey-js": "workspace:*",
"chart.js": "4.1.1", "chart.js": "4.1.1",
"chartjs-adapter-date-fns": "2.0.1", "chartjs-adapter-date-fns": "2.0.1",

View File

@ -28,7 +28,7 @@ const emit = defineEmits<{
(ev: "update:modelValue", v: boolean): void; (ev: "update:modelValue", v: boolean): void;
}>(); }>();
const el = ref<HTMLElement>(); const el = ref<HTMLElement>();
const label = computed(() => { const label = computed(() => {
return concat([ return concat([
@ -52,7 +52,7 @@ function focus() {
} }
defineExpose({ defineExpose({
focus focus,
}); });
</script> </script>
@ -73,9 +73,46 @@ defineExpose({
} }
} }
} }
&:hover > span, &:focus > span { &:hover > span,
&:focus > span {
background: var(--cwFg) !important; background: var(--cwFg) !important;
color: var(--cwBg) !important; color: var(--cwBg) !important;
} }
&.fade {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> span {
display: inline-block;
background: var(--panel);
padding: 0.4em 1em;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> span {
background: var(--panelHighlight);
}
}
}
&.showLess {
width: 100%;
margin-top: 1em;
position: sticky;
bottom: var(--stickyBottom);
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 0 7px 7px var(--bg);
}
}
} }
</style> </style>

View File

@ -174,7 +174,7 @@ import { deviceKind } from "@/scripts/device-kind";
import { emojiCategories, instance } from "@/instance"; import { emojiCategories, instance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { FocusTrap } from 'focus-trap-vue'; import { FocusTrap } from "focus-trap-vue";
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{

View File

@ -5,6 +5,7 @@
{ {
yellow: instance.isNotResponding, yellow: instance.isNotResponding,
red: instance.isBlocked, red: instance.isBlocked,
purple: instance.isSilenced,
gray: instance.isSuspended, gray: instance.isSuspended,
}, },
]" ]"
@ -23,13 +24,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import * as misskey from "calckey-js"; import * as calckey from "calckey-js";
import MkMiniChart from "@/components/MkMiniChart.vue"; import MkMiniChart from "@/components/MkMiniChart.vue";
import * as os from "@/os"; import * as os from "@/os";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy"; import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
const props = defineProps<{ const props = defineProps<{
instance: misskey.entities.Instance; instance: calckey.entities.Instance;
}>(); }>();
let chartValues = $ref<number[] | null>(null); let chartValues = $ref<number[] | null>(null);
@ -135,6 +136,21 @@ function getInstanceIcon(instance): string {
background-size: 16px 16px; background-size: 16px 16px;
} }
&:global(.purple) {
--c: rgba(196, 0, 255, 0.15);
background-image: linear-gradient(
45deg,
var(--c) 16.67%,
transparent 16.67%,
transparent 50%,
var(--c) 50%,
var(--c) 66.67%,
transparent 66.67%,
transparent 100%
);
background-size: 16px 16px;
}
&:global(.gray) { &:global(.gray) {
--c: var(--bg); --c: var(--bg);
background-image: linear-gradient( background-image: linear-gradient(

View File

@ -139,7 +139,8 @@ function close() {
height: 100px; height: 100px;
border-radius: 10px; border-radius: 10px;
&:hover, &:focus-visible { &:hover,
&:focus-visible {
color: var(--accent); color: var(--accent);
background: var(--accentedBg); background: var(--accentedBg);
text-decoration: none; text-decoration: none;

View File

@ -1,14 +1,14 @@
<template> <template>
<div ref="el" class="sfhdhdhr" tabindex="-1"> <div ref="el" class="sfhdhdhr" tabindex="-1">
<MkMenu <MkMenu
ref="menu" ref="menu"
:items="items" :items="items"
:align="align" :align="align"
:width="width" :width="width"
:as-drawer="false" :as-drawer="false"
@close="onChildClosed" @close="onChildClosed"
/> />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>

View File

@ -1,6 +1,6 @@
<template> <template>
<FocusTrap v-bind:active="isActive"> <FocusTrap :active="false" ref="focusTrap">
<div tabindex="-1" v-focus> <div tabindex="-1">
<div <div
ref="itemsEl" ref="itemsEl"
class="rrevdjwt _popup _shadow" class="rrevdjwt _popup _shadow"
@ -14,7 +14,9 @@
<template v-for="(item, i) in items2"> <template v-for="(item, i) in items2">
<div v-if="item === null" class="divider"></div> <div v-if="item === null" class="divider"></div>
<span v-else-if="item.type === 'label'" class="label item"> <span v-else-if="item.type === 'label'" class="label item">
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
</span> </span>
<span <span
v-else-if="item.type === 'pending'" v-else-if="item.type === 'pending'"
@ -48,7 +50,9 @@
class="avatar" class="avatar"
disableLink disableLink
/> />
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
@ -75,7 +79,9 @@
:class="icon" :class="icon"
></i> ></i>
</span> </span>
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
@ -89,9 +95,11 @@
@mouseenter.passive="onItemMouseEnter(item)" @mouseenter.passive="onItemMouseEnter(item)"
@mouseleave.passive="onItemMouseLeave(item)" @mouseleave.passive="onItemMouseLeave(item)"
> >
<MkAvatar :user="item.user" class="avatar" disableLink /><MkUserName <MkAvatar
:user="item.user" :user="item.user"
/> class="avatar"
disableLink
/><MkUserName :user="item.user" />
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
@ -129,9 +137,13 @@
:class="icon" :class="icon"
></i> ></i>
</span> </span>
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span class="caret" <span class="caret"
><i class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"></i ><i
class="ph-caret-right ph-bold ph-lg ph-fw ph-lg"
></i
></span> ></span>
</button> </button>
<button <button
@ -161,7 +173,9 @@
class="avatar" class="avatar"
disableLink disableLink
/> />
<span :style="item.textStyle || ''">{{ item.text }}</span> <span :style="item.textStyle || ''">{{
item.text
}}</span>
<span v-if="item.indicate" class="indicator" <span v-if="item.indicate" class="indicator"
><i class="ph-circle ph-fill"></i ><i class="ph-circle ph-fill"></i
></span> ></span>
@ -203,9 +217,10 @@ import FormSwitch from "@/components/form/switch.vue";
import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu"; import { MenuItem, InnerMenuItem, MenuPending, MenuAction } from "@/types/menu";
import * as os from "@/os"; import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { FocusTrap } from 'focus-trap-vue'; import { FocusTrap } from "focus-trap-vue";
const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue")); const XChild = defineAsyncComponent(() => import("./MkMenu.child.vue"));
const focusTrap = ref();
const props = defineProps<{ const props = defineProps<{
items: MenuItem[]; items: MenuItem[];
@ -316,6 +331,8 @@ function focusDown() {
} }
onMounted(() => { onMounted(() => {
focusTrap.value.activate();
if (props.viaKeyboard) { if (props.viaKeyboard) {
nextTick(() => { nextTick(() => {
focusNext(itemsEl.children[0], true, false); focusNext(itemsEl.children[0], true, false);
@ -380,7 +397,8 @@ onBeforeUnmount(() => {
transform: translateY(0em); transform: translateY(0em);
} }
&:not(:disabled):hover, &:focus-visible { &:not(:disabled):hover,
&:focus-visible {
color: var(--accent); color: var(--accent);
text-decoration: none; text-decoration: none;

View File

@ -26,13 +26,16 @@
$style.root, $style.root,
{ {
[$style.drawer]: type === 'drawer', [$style.drawer]: type === 'drawer',
[$style.dialog]: type === 'dialog' || type === 'dialog:top', [$style.dialog]:
type === 'dialog' || type === 'dialog:top',
[$style.popup]: type === 'popup', [$style.popup]: type === 'popup',
}, },
]" ]"
:style="{ :style="{
zIndex, zIndex,
pointerEvents: (manualShowing != null ? manualShowing : showing) pointerEvents: (
manualShowing != null ? manualShowing : showing
)
? 'auto' ? 'auto'
: 'none', : 'none',
'--transformOrigin': transformOrigin, '--transformOrigin': transformOrigin,
@ -76,7 +79,7 @@ import * as os from "@/os";
import { isTouchUsing } from "@/scripts/touch"; import { isTouchUsing } from "@/scripts/touch";
import { defaultStore } from "@/store"; import { defaultStore } from "@/store";
import { deviceKind } from "@/scripts/device-kind"; import { deviceKind } from "@/scripts/device-kind";
import { FocusTrap } from 'focus-trap-vue'; import { FocusTrap } from "focus-trap-vue";
function getFixedContainer(el: Element | null): Element | null { function getFixedContainer(el: Element | null): Element | null {
if (el == null || el.tagName === "BODY") return null; if (el == null || el.tagName === "BODY") return null;

View File

@ -60,7 +60,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted } from "vue"; import { onMounted, onUnmounted } from "vue";
import { FocusTrap } from 'focus-trap-vue'; import { FocusTrap } from "focus-trap-vue";
import MkModal from "./MkModal.vue"; import MkModal from "./MkModal.vue";
const props = withDefaults( const props = withDefaults(

View File

@ -279,7 +279,7 @@ const isRenote =
note.poll == null; note.poll == null;
const el = ref<HTMLElement>(); const el = ref<HTMLElement>();
const footerEl = ref<HTMLElement>(); const footerEl = ref<HTMLElement>();
const menuButton = ref<HTMLElement>(); const menuButton = ref<HTMLElement>();
const starButton = ref<InstanceType<typeof XStarButton>>(); const starButton = ref<InstanceType<typeof XStarButton>>();
const renoteButton = ref<InstanceType<typeof XRenoteButton>>(); const renoteButton = ref<InstanceType<typeof XRenoteButton>>();

View File

@ -462,15 +462,26 @@ if (
props.reply && props.reply &&
["home", "followers", "specified"].includes(props.reply.visibility) ["home", "followers", "specified"].includes(props.reply.visibility)
) { ) {
visibility = props.reply.visibility; if (props.reply.visibility === "home" && visibility === "followers") {
if (props.reply.visibility === "specified") { visibility = "followers";
os.api("users/show", { } else if (
userIds: props.reply.visibleUserIds.filter( ["home", "followers"].includes(props.reply.visibility) &&
(uid) => uid !== $i.id && uid !== props.reply.userId visibility === "specified"
), ) {
}).then((users) => { visibility = "specified";
users.forEach(pushVisibleUser); } else {
}); visibility = props.reply.visibility;
}
if (visibility === "specified") {
if (props.reply.visibleUserIds) {
os.api("users/show", {
userIds: props.reply.visibleUserIds.filter(
(uid) => uid !== $i.id && uid !== props.reply.userId
),
}).then((users) => {
users.forEach(pushVisibleUser);
});
}
if (props.reply.userId !== $i.id) { if (props.reply.userId !== $i.id) {
os.api("users/show", { userId: props.reply.userId }).then( os.api("users/show", { userId: props.reply.userId }).then(

View File

@ -0,0 +1,60 @@
<template>
<button v-if="modelValue" class="fade _button" @click.stop="toggle">
<span>{{ i18n.ts.showMore }}</span>
</button>
<button v-if="!modelValue" class="showLess _button" @click.stop="toggle">
<span>{{ i18n.ts.showLess }}</span>
</button>
</template>
<script lang="ts" setup>
import { i18n } from "@/i18n";
const props = defineProps<{
modelValue: boolean;
}>();
const emit = defineEmits<{
(ev: "update:modelValue", v: boolean): void;
}>();
const toggle = () => {
emit("update:modelValue", !props.modelValue);
};
</script>
<style lang="scss" scoped>
.fade {
display: block;
position: absolute;
bottom: 0;
left: 0;
width: 100%;
> span {
display: inline-block;
background: var(--panel);
padding: 0.4em 1em;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 2px 6px rgb(0 0 0 / 20%);
}
&:hover {
> span {
background: var(--panelHighlight);
}
}
}
.showLess {
width: 100%;
margin-top: 1em;
position: sticky;
bottom: var(--stickyBottom);
> span {
display: inline-block;
background: var(--panel);
padding: 6px 10px;
font-size: 0.8em;
border-radius: 999px;
box-shadow: 0 0 7px 7px var(--bg);
}
}
</style>

View File

@ -35,10 +35,19 @@
class="content" class="content"
:class="{ collapsed, isLong, showContent: note.cw && !showContent }" :class="{ collapsed, isLong, showContent: note.cw && !showContent }"
> >
<XCwButton ref="cwButton" v-if="note.cw && !showContent" v-model="showContent" :note="note" v-on:keydown="focusFooter" /> <XCwButton
<div ref="cwButton"
v-if="note.cw && !showContent"
v-model="showContent"
:note="note"
v-on:keydown="focusFooter"
/>
<div
class="body" class="body"
v-bind="{ 'aria-label': !showContent ? '' : null, 'tabindex': !showContent ? '-1' : null }" v-bind="{
'aria-label': !showContent ? '' : null,
tabindex: !showContent ? '-1' : null,
}"
> >
<span v-if="note.deletedAt" style="opacity: 0.5" <span v-if="note.deletedAt" style="opacity: 0.5"
>({{ i18n.ts.deleted }})</span >({{ i18n.ts.deleted }})</span
@ -106,27 +115,17 @@
v-on:focus="cwButton?.focus()" v-on:focus="cwButton?.focus()"
></div> ></div>
</div> </div>
<button <XShowMoreButton
v-if="isLong && collapsed" v-if="isLong"
class="fade _button" v-model="collapsed"
@click.stop="collapsed = false" ></XShowMoreButton>
>
<span>{{ i18n.ts.showMore }}</span>
</button>
<button
v-if="isLong && !collapsed"
class="showLess _button"
@click.stop="collapsed = true"
>
<span>{{ i18n.ts.showLess }}</span>
</button>
<XCwButton v-if="note.cw" v-model="showContent" :note="note" /> <XCwButton v-if="note.cw" v-model="showContent" :note="note" />
</div> </div>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from "vue"; import { ref } from "vue";
import * as misskey from "calckey-js"; import * as misskey from "calckey-js";
import * as mfm from "mfm-js"; import * as mfm from "mfm-js";
import XNoteSimple from "@/components/MkNoteSimple.vue"; import XNoteSimple from "@/components/MkNoteSimple.vue";
@ -150,7 +149,7 @@ const emit = defineEmits<{
(ev: "focusfooter"): void; (ev: "focusfooter"): void;
}>(); }>();
const cwButton = ref<HTMLElement>(); const cwButton = ref<HTMLElement>();
const isLong = const isLong =
!props.detailedView && !props.detailedView &&
props.note.cw == null && props.note.cw == null &&
@ -163,7 +162,6 @@ const urls = props.note.text
let showContent = $ref(false); let showContent = $ref(false);
function focusFooter(ev) { function focusFooter(ev) {
if (ev.key == "Tab" && !ev.getModifierState("Shift")) { if (ev.key == "Tab" && !ev.getModifierState("Shift")) {
emit("focusfooter"); emit("focusfooter");

View File

@ -96,7 +96,8 @@ export default defineComponent({
font-size: 0.9em; font-size: 0.9em;
margin-bottom: 0.3rem; margin-bottom: 0.3rem;
&:hover, &:focus-visible { &:hover,
&:focus-visible {
text-decoration: none; text-decoration: none;
background: var(--panelHighlight); background: var(--panelHighlight);
} }

View File

@ -46,7 +46,10 @@
/></MkA> /></MkA>
<p class="username"><MkAcct :user="user" /></p> <p class="username"><MkAcct :user="user" /></p>
</div> </div>
<div class="description" :class="{ collapsed: isLong && collapsed }"> <div
class="description"
:class="{ collapsed: isLong && collapsed }"
>
<Mfm <Mfm
v-if="user.description" v-if="user.description"
:text="user.description" :text="user.description"
@ -55,20 +58,10 @@
:custom-emojis="user.emojis" :custom-emojis="user.emojis"
/> />
</div> </div>
<button <XShowMoreButton
v-if="isLong && collapsed" v-if="isLong"
class="fade _button" v-model="collapsed"
@click.stop="collapsed = false" ></XShowMoreButton>
>
<span>{{ i18n.ts.showMore }}</span>
</button>
<button
v-if="isLong && !collapsed"
class="showLess _button"
@click.stop="collapsed = true"
>
<span>{{ i18n.ts.showLess }}</span>
</button>
<div v-if="user.fields.length > 0" class="fields"> <div v-if="user.fields.length > 0" class="fields">
<dl <dl
v-for="(field, i) in user.fields" v-for="(field, i) in user.fields"
@ -149,14 +142,15 @@ let user = $ref<misskey.entities.UserDetailed | null>(null);
let top = $ref(0); let top = $ref(0);
let left = $ref(0); let left = $ref(0);
let isLong = $ref(false); let isLong = $ref(false);
let collapsed = $ref(!isLong); let collapsed = $ref(!isLong);
onMounted(() => { onMounted(() => {
if (typeof props.q === "object") { if (typeof props.q === "object") {
user = props.q; user = props.q;
isLong = (user.description.split("\n").length > 9 || user.description.length > 400); isLong =
user.description.split("\n").length > 9 ||
user.description.length > 400;
} else { } else {
const query = props.q.startsWith("@") const query = props.q.startsWith("@")
? Acct.parse(props.q.substr(1)) ? Acct.parse(props.q.substr(1))
@ -165,10 +159,11 @@ onMounted(() => {
os.api("users/show", query).then((res) => { os.api("users/show", query).then((res) => {
if (!props.showing) return; if (!props.showing) return;
user = res; user = res;
isLong = (user.description.split("\n").length > 9 || user.description.length > 400); isLong =
user.description.split("\n").length > 9 ||
user.description.length > 400;
}); });
} }
const rect = props.source.getBoundingClientRect(); const rect = props.source.getBoundingClientRect();
const x = const x =
@ -313,7 +308,7 @@ onMounted(() => {
> .fields { > .fields {
padding: 0 16px; padding: 0 16px;
font-size: .8em; font-size: 0.8em;
margin-top: 1em; margin-top: 1em;
> .field { > .field {

View File

@ -7,7 +7,7 @@
v-bind="Object.fromEntries(currentPageProps)" v-bind="Object.fromEntries(currentPageProps)"
tabindex="-1" tabindex="-1"
v-focus v-focus
style="outline: none;" style="outline: none"
/> />
<template #fallback> <template #fallback>

View File

@ -1,3 +1,3 @@
export default { export default {
mounted: (el) => el.focus() mounted: (el) => el.focus(),
} };

View File

@ -87,23 +87,11 @@ export default {
self.hideTimer = window.setTimeout(self.close, delay); self.hideTimer = window.setTimeout(self.close, delay);
} }
el.addEventListener( el.addEventListener(start, showTooltip, { passive: true });
start, showTooltip, el.addEventListener("focusin", showTooltip, { passive: true });
{ passive: true },
);
el.addEventListener(
"focusin", showTooltip,
{ passive: true },
);
el.addEventListener( el.addEventListener(end, hideTooltip, { passive: true });
end, hideTooltip, el.addEventListener("focusout", hideTooltip, { passive: true });
{ passive: true },
);
el.addEventListener(
"focusout", hideTooltip,
{ passive: true },
);
el.addEventListener("click", () => { el.addEventListener("click", () => {
window.clearTimeout(self.showTimer); window.clearTimeout(self.showTimer);

View File

@ -18,6 +18,7 @@
<option value="publishing">{{ i18n.ts.publishing }}</option> <option value="publishing">{{ i18n.ts.publishing }}</option>
<option value="suspended">{{ i18n.ts.suspended }}</option> <option value="suspended">{{ i18n.ts.suspended }}</option>
<option value="blocked">{{ i18n.ts.blocked }}</option> <option value="blocked">{{ i18n.ts.blocked }}</option>
<option value="silenced">{{ i18n.ts.silenced }}</option>
<option value="notResponding"> <option value="notResponding">
{{ i18n.ts.notResponding }} {{ i18n.ts.notResponding }}
</option> </option>
@ -105,13 +106,11 @@
<script lang="ts" setup> <script lang="ts" setup>
import { computed } from "vue"; import { computed } from "vue";
import MkButton from "@/components/MkButton.vue";
import MkInput from "@/components/form/input.vue"; import MkInput from "@/components/form/input.vue";
import MkSelect from "@/components/form/select.vue"; import MkSelect from "@/components/form/select.vue";
import MkPagination from "@/components/MkPagination.vue"; import MkPagination from "@/components/MkPagination.vue";
import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue"; import MkInstanceCardMini from "@/components/MkInstanceCardMini.vue";
import FormSplit from "@/components/form/split.vue"; import FormSplit from "@/components/form/split.vue";
import * as os from "@/os";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
let host = $ref(""); let host = $ref("");
@ -134,6 +133,8 @@ const pagination = {
? { suspended: true } ? { suspended: true }
: state === "blocked" : state === "blocked"
? { blocked: true } ? { blocked: true }
: state === "silenced"
? { silenced: true }
: state === "notResponding" : state === "notResponding"
? { notResponding: true } ? { notResponding: true }
: {}), : {}),
@ -143,6 +144,7 @@ const pagination = {
function getStatus(instance) { function getStatus(instance) {
if (instance.isSuspended) return "Suspended"; if (instance.isSuspended) return "Suspended";
if (instance.isBlocked) return "Blocked"; if (instance.isBlocked) return "Blocked";
if (instance.isSilenced) return "Silenced";
if (instance.isNotResponding) return "Error"; if (instance.isNotResponding) return "Error";
return "Alive"; return "Alive";
} }

View File

@ -313,7 +313,9 @@ onUnmounted(() => {
font-weight: normal; font-weight: normal;
opacity: 0.7; opacity: 0.7;
&:hover, &:focus-visible, &.active { &:hover,
&:focus-visible,
&.active {
opacity: 1; opacity: 1;
} }

View File

@ -3,7 +3,6 @@
<MkStickyContainer> <MkStickyContainer>
<template #header <template #header
><MkPageHeader ><MkPageHeader
v-model:tab="tab"
:actions="headerActions" :actions="headerActions"
:tabs="headerTabs" :tabs="headerTabs"
:display-back-button="true" :display-back-button="true"

View File

@ -7,13 +7,31 @@
:display-back-button="true" :display-back-button="true"
/></template> /></template>
<MkSpacer :content-max="700" :margin-min="16" :margin-max="32"> <MkSpacer :content-max="700" :margin-min="16" :margin-max="32">
<MkTab v-model="tab" class="_formBlock">
<option value="block">{{ i18n.ts.blockedInstances }}</option>
<option value="silence">{{ i18n.ts.silencedInstances }}</option>
</MkTab>
<FormSuspense :p="init"> <FormSuspense :p="init">
<FormTextarea v-model="blockedHosts" class="_formBlock"> <FormTextarea
v-if="tab === 'block'"
v-model="blockedHosts"
class="_formBlock"
>
<span>{{ i18n.ts.blockedInstances }}</span> <span>{{ i18n.ts.blockedInstances }}</span>
<template #caption>{{ <template #caption>{{
i18n.ts.blockedInstancesDescription i18n.ts.blockedInstancesDescription
}}</template> }}</template>
</FormTextarea> </FormTextarea>
<FormTextarea
v-else-if="tab === 'silence'"
v-model="silencedHosts"
class="_formBlock"
>
<span>{{ i18n.ts.silencedInstances }}</span>
<template #caption>{{
i18n.ts.silencedInstancesDescription
}}</template>
</FormTextarea>
<FormButton primary class="_formBlock" @click="save" <FormButton primary class="_formBlock" @click="save"
><i class="ph-floppy-disk-back ph-bold ph-lg"></i> ><i class="ph-floppy-disk-back ph-bold ph-lg"></i>
@ -29,21 +47,28 @@ import {} from "vue";
import FormButton from "@/components/MkButton.vue"; import FormButton from "@/components/MkButton.vue";
import FormTextarea from "@/components/form/textarea.vue"; import FormTextarea from "@/components/form/textarea.vue";
import FormSuspense from "@/components/form/suspense.vue"; import FormSuspense from "@/components/form/suspense.vue";
import MkTab from "@/components/MkTab.vue";
import * as os from "@/os"; import * as os from "@/os";
import { fetchInstance } from "@/instance"; import { fetchInstance } from "@/instance";
import { i18n } from "@/i18n"; import { i18n } from "@/i18n";
import { definePageMetadata } from "@/scripts/page-metadata"; import { definePageMetadata } from "@/scripts/page-metadata";
let blockedHosts: string = $ref(""); let blockedHosts: string = $ref("");
let silencedHosts: string = $ref("");
let tab = $ref("block");
async function init() { async function init() {
const meta = await os.api("admin/meta"); const meta = await os.api("admin/meta");
blockedHosts = meta.blockedHosts.join("\n"); if (meta) {
blockedHosts = meta.blockedHosts.join("\n");
silencedHosts = meta.silencedHosts.join("\n");
}
} }
function save() { function save() {
os.apiWithDialog("admin/update-meta", { os.apiWithDialog("admin/update-meta", {
blockedHosts: blockedHosts.split("\n").map((h) => h.trim()) || [], blockedHosts: blockedHosts.split("\n").map((h) => h.trim()) || [],
silencedHosts: silencedHosts.split("\n").map((h) => h.trim()) || [],
}).then(() => { }).then(() => {
fetchInstance(); fetchInstance();
}); });

View File

@ -12,7 +12,12 @@
class="user" class="user"
:to="`/user-info/${user.id}`" :to="`/user-info/${user.id}`"
> >
<MkAvatar :user="user" class="avatar" indicator disableLink /> <MkAvatar
:user="user"
class="avatar"
indicator
disableLink
/>
</MkA> </MkA>
</div> </div>
</Transition> </Transition>

View File

@ -371,6 +371,34 @@
<template #label>Pro account</template> <template #label>Pro account</template>
</FormSwitch> </FormSwitch>
</FormSection> </FormSection>
<FormSection>
<template #label>Libre Translate</template>
<FormInput
v-model="libreTranslateApiUrl"
class="_formBlock"
>
<template #prefix
><i class="ph-link ph-bold ph-lg"></i
></template>
<template #label
>Libre Translate API URL</template
>
</FormInput>
<FormInput
v-model="libreTranslateApiKey"
class="_formBlock"
>
<template #prefix
><i class="ph-key ph-bold ph-lg"></i
></template>
<template #label
>Libre Translate API Key</template
>
</FormInput>
</FormSection>
</div> </div>
</FormSuspense> </FormSuspense>
</MkSpacer> </MkSpacer>
@ -422,6 +450,8 @@ let swPublicKey: any = $ref(null);
let swPrivateKey: any = $ref(null); let swPrivateKey: any = $ref(null);
let deeplAuthKey: string = $ref(""); let deeplAuthKey: string = $ref("");
let deeplIsPro: boolean = $ref(false); let deeplIsPro: boolean = $ref(false);
let libreTranslateApiUrl: string = $ref("");
let libreTranslateApiKey: string = $ref("");
let defaultReaction: string = $ref(""); let defaultReaction: string = $ref("");
let defaultReactionCustom: string = $ref(""); let defaultReactionCustom: string = $ref("");
@ -456,6 +486,8 @@ async function init() {
swPrivateKey = meta.swPrivateKey; swPrivateKey = meta.swPrivateKey;
deeplAuthKey = meta.deeplAuthKey; deeplAuthKey = meta.deeplAuthKey;
deeplIsPro = meta.deeplIsPro; deeplIsPro = meta.deeplIsPro;
libreTranslateApiUrl = meta.libreTranslateApiUrl;
libreTranslateApiKey = meta.libreTranslateApiKey;
defaultReaction = ["⭐", "👍", "❤️"].includes(meta.defaultReaction) defaultReaction = ["⭐", "👍", "❤️"].includes(meta.defaultReaction)
? meta.defaultReaction ? meta.defaultReaction
: "custom"; : "custom";
@ -498,6 +530,8 @@ function save() {
swPrivateKey, swPrivateKey,
deeplAuthKey, deeplAuthKey,
deeplIsPro, deeplIsPro,
libreTranslateApiUrl,
libreTranslateApiKey,
defaultReaction, defaultReaction,
}).then(() => { }).then(() => {
fetchInstance(); fetchInstance();

View File

@ -98,6 +98,14 @@
@update:modelValue="toggleBlock" @update:modelValue="toggleBlock"
>{{ i18n.ts.blockThisInstance }}</FormSwitch >{{ i18n.ts.blockThisInstance }}</FormSwitch
> >
<FormSwitch
v-model="isSilenced"
class="_formBlock"
@update:modelValue="toggleSilence"
>{{
i18n.ts.silenceThisInstance
}}</FormSwitch
>
</FormSuspense> </FormSuspense>
<MkButton @click="refreshMetadata" <MkButton @click="refreshMetadata"
><i ><i
@ -329,7 +337,7 @@
import { watch } from "vue"; import { watch } from "vue";
import { Virtual } from "swiper"; import { Virtual } from "swiper";
import { Swiper, SwiperSlide } from "swiper/vue"; import { Swiper, SwiperSlide } from "swiper/vue";
import type * as misskey from "calckey-js"; import type * as calckey from "calckey-js";
import MkChart from "@/components/MkChart.vue"; import MkChart from "@/components/MkChart.vue";
import MkObjectView from "@/components/MkObjectView.vue"; import MkObjectView from "@/components/MkObjectView.vue";
import FormLink from "@/components/form/link.vue"; import FormLink from "@/components/form/link.vue";
@ -352,11 +360,13 @@ import "swiper/scss";
import "swiper/scss/virtual"; import "swiper/scss/virtual";
import { getProxiedImageUrlNullable } from "@/scripts/media-proxy"; import { getProxiedImageUrlNullable } from "@/scripts/media-proxy";
type AugmentedInstanceMetadata = misskey.entities.DetailedInstanceMetadata & { type AugmentedInstanceMetadata = calckey.entities.DetailedInstanceMetadata & {
blockedHosts: string[]; blockedHosts: string[];
silencedHosts: string[];
}; };
type AugmentedInstance = misskey.entities.Instance & { type AugmentedInstance = calckey.entities.Instance & {
isBlocked: boolean; isBlocked: boolean;
isSilenced: boolean;
}; };
const props = defineProps<{ const props = defineProps<{
@ -373,6 +383,7 @@ let meta = $ref<AugmentedInstanceMetadata | null>(null);
let instance = $ref<AugmentedInstance | null>(null); let instance = $ref<AugmentedInstance | null>(null);
let suspended = $ref(false); let suspended = $ref(false);
let isBlocked = $ref(false); let isBlocked = $ref(false);
let isSilenced = $ref(false);
let faviconUrl = $ref(null); let faviconUrl = $ref(null);
const usersPagination = { const usersPagination = {
@ -386,16 +397,14 @@ const usersPagination = {
offsetMode: true, offsetMode: true,
}; };
async function init() {
meta = await os.api("admin/meta");
}
async function fetch() { async function fetch() {
meta = (await os.api("admin/meta")) as AugmentedInstanceMetadata;
instance = (await os.api("federation/show-instance", { instance = (await os.api("federation/show-instance", {
host: props.host, host: props.host,
})) as AugmentedInstance; })) as AugmentedInstance;
suspended = instance.isSuspended; suspended = instance.isSuspended;
isBlocked = instance.isBlocked; isBlocked = instance.isBlocked;
isSilenced = instance.isSilenced;
faviconUrl = faviconUrl =
getProxiedImageUrlNullable(instance.faviconUrl, "preview") ?? getProxiedImageUrlNullable(instance.faviconUrl, "preview") ??
getProxiedImageUrlNullable(instance.iconUrl, "preview"); getProxiedImageUrlNullable(instance.iconUrl, "preview");
@ -417,6 +426,22 @@ async function toggleBlock() {
}); });
} }
async function toggleSilence() {
if (meta == null) return;
if (!instance) {
throw new Error(`Instance info not loaded`);
}
let silencedHosts: string[];
if (isSilenced) {
silencedHosts = meta.silencedHosts.concat([instance.host]);
} else {
silencedHosts = meta.silencedHosts.filter((x) => x !== instance!.host);
}
await os.api("admin/update-meta", {
silencedHosts,
});
}
async function toggleSuspend(v) { async function toggleSuspend(v) {
await os.api("admin/federation/update-instance", { await os.api("admin/federation/update-instance", {
host: instance.host, host: instance.host,

View File

@ -2,7 +2,7 @@
<MkStickyContainer> <MkStickyContainer>
<template #header><MkPageHeader /></template> <template #header><MkPageHeader /></template>
<MkSpacer :content-max="800"> <MkSpacer :content-max="800">
<div class="mwysmxbg"> <div :class="$style.root">
<div>{{ i18n.ts._mfm.intro }}</div> <div>{{ i18n.ts._mfm.intro }}</div>
<br /> <br />
<div class="section _block"> <div class="section _block">
@ -137,6 +137,18 @@
</div> </div>
</div> </div>
</div> </div>
<div class="section _block">
<div class="title">{{ i18n.ts._mfm.blockMath }}</div>
<div class="content">
<p>{{ i18n.ts._mfm.blockMathDescription }}</p>
<div class="preview">
<Mfm :text="preview_blockMath" />
<MkTextarea v-model="preview_blockMath"
><template #label>MFM</template></MkTextarea
>
</div>
</div>
</div>
<!-- deprecated <!-- deprecated
<div class="section _block"> <div class="section _block">
<div class="title">{{ i18n.ts._mfm.search }}</div> <div class="title">{{ i18n.ts._mfm.search }}</div>
@ -427,8 +439,11 @@ let preview_blockCode = $ref(
'```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```' '```\n~ (#i, 100) {\n\t<: ? ((i % 15) = 0) "FizzBuzz"\n\t\t.? ((i % 3) = 0) "Fizz"\n\t\t.? ((i % 5) = 0) "Buzz"\n\t\t. i\n}\n```'
); );
let preview_inlineMath = $ref("\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)"); let preview_inlineMath = $ref("\\(x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\)");
let preview_blockMath = $ref("\\[x= \\frac{-b' \\pm \\sqrt{(b')^2-ac}}{a}\\]");
let preview_quote = $ref(`> ${i18n.ts._mfm.dummy}`); let preview_quote = $ref(`> ${i18n.ts._mfm.dummy}`);
let preview_search = $ref(`${i18n.ts._mfm.dummy} 検索`); let preview_search = $ref(
`${i18n.ts._mfm.dummy} [search]\n${i18n.ts._mfm.dummy} [検索]\n${i18n.ts._mfm.dummy} 検索`
);
let preview_jelly = $ref("$[jelly 🍮] $[jelly.speed=5s 🍮]"); let preview_jelly = $ref("$[jelly 🍮] $[jelly.speed=5s 🍮]");
let preview_tada = $ref("$[tada 🍮] $[tada.speed=5s 🍮]"); let preview_tada = $ref("$[tada 🍮] $[tada.speed=5s 🍮]");
let preview_jump = $ref("$[jump 🍮] $[jump.speed=5s 🍮]"); let preview_jump = $ref("$[jump 🍮] $[jump.speed=5s 🍮]");
@ -450,9 +465,15 @@ let preview_x4 = $ref("$[x4 🍮]");
let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`); let preview_blur = $ref(`$[blur ${i18n.ts._mfm.dummy}]`);
let preview_rainbow = $ref("$[rainbow 🍮] $[rainbow.speed=5s 🍮]"); let preview_rainbow = $ref("$[rainbow 🍮] $[rainbow.speed=5s 🍮]");
let preview_sparkle = $ref("$[sparkle 🍮]"); let preview_sparkle = $ref("$[sparkle 🍮]");
let preview_rotate = $ref("$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]"); let preview_rotate = $ref(
let preview_position = $ref("$[position.y=-1 Positioning]\n$[position.x=-1 Positioning]"); "$[rotate 🍮]\n$[rotate.deg=45 🍮]\n$[rotate.x,deg=45 Hello, world!]"
let preview_scale = $ref("$[scale.x=1.3 Scaling]\n$[scale.x=1.3,y=2 Scaling]\n$[scale.y=0.3 Tiny scaling]"); );
let preview_position = $ref(
"$[position.y=-1 Positioning]\n$[position.x=-1 Positioning]"
);
let preview_scale = $ref(
"$[scale.x=1.3 Scaling]\n$[scale.x=1.3,y=2 Scaling]\n$[scale.y=0.3 Tiny scaling]"
);
let preview_fg = $ref("$[fg.color=ff0000 Text color]"); let preview_fg = $ref("$[fg.color=ff0000 Text color]");
let preview_bg = $ref("$[bg.color=ff0000 Background color]"); let preview_bg = $ref("$[bg.color=ff0000 Background color]");
let preview_plain = $ref( let preview_plain = $ref(
@ -465,8 +486,8 @@ definePageMetadata({
}); });
</script> </script>
<style lang="scss" scoped> <style lang="scss" module>
.mwysmxbg { .root {
background: var(--bg); background: var(--bg);
> .section { > .section {

View File

@ -1,7 +1,7 @@
{ {
id: '080a01c5-377d-4fbb-88cc-6bb5d04977ea', id: '080a01c5-377d-4fbb-88cc-6bb5d04977ea',
base: 'dark', base: 'dark',
name: 'Mi Astro Dark', name: 'Astro Dark',
author: 'syuilo', author: 'syuilo',
props: { props: {
bg: '#232125', bg: '#232125',

View File

@ -1,7 +1,7 @@
{ {
id: '504debaf-4912-6a4c-5059-1db08a76b737', id: '504debaf-4912-6a4c-5059-1db08a76b737',
name: 'Mi Botanical Dark', name: 'Botanical Dark',
author: 'syuilo', author: 'syuilo',
base: 'dark', base: 'dark',

View File

@ -1,7 +1,7 @@
{ {
id: 'ffcd3328-5c57-4ca3-9dac-4580cbf7742f', id: 'ffcd3328-5c57-4ca3-9dac-4580cbf7742f',
base: 'dark', base: 'dark',
name: 'Catppuccin frappe', name: 'Catppuccin Frappe',
props: { props: {
X2: ':darken<2<@panel', X2: ':darken<2<@panel',
X3: 'rgba(255, 255, 255, 0.05)', X3: 'rgba(255, 255, 255, 0.05)',

View File

@ -1,7 +1,7 @@
{ {
id: 'd413f41f-a489-48be-9e20-3532ffbb4363', id: 'd413f41f-a489-48be-9e20-3532ffbb4363',
base: 'dark', base: 'dark',
name: 'Catppuccin mocha', name: 'Catppuccin Mocha',
props: { props: {
X2: ':darken<2<@panel', X2: ':darken<2<@panel',
X3: 'rgba(255, 255, 255, 0.05)', X3: 'rgba(255, 255, 255, 0.05)',

View File

@ -1,7 +1,7 @@
{ {
id: '679b3b87-a4e9-4789-8696-b56c15cc33b0', id: '679b3b87-a4e9-4789-8696-b56c15cc33b0',
name: 'Mi Cherry Dark', name: 'Cherry Dark',
author: 'syuilo', author: 'syuilo',
base: 'dark', base: 'dark',

View File

@ -1,7 +1,7 @@
{ {
id: '32a637ef-b47a-4775-bb7b-bacbb823f865', id: '32a637ef-b47a-4775-bb7b-bacbb823f865',
name: 'Mi Future Dark', name: 'Future Dark',
author: 'syuilo', author: 'syuilo',
base: 'dark', base: 'dark',

View File

@ -1,7 +1,7 @@
{ {
id: '02816013-8107-440f-877e-865083ffe194', id: '02816013-8107-440f-877e-865083ffe194',
name: 'Mi Green+Lime Dark', name: 'Mi Dark',
author: 'syuilo', author: 'syuilo',
base: 'dark', base: 'dark',

View File

@ -1,24 +0,0 @@
{
id: 'dc489603-27b5-424a-9b25-1ff6aec9824a',
name: 'Mi Green+Orange Dark',
author: 'syuilo',
base: 'dark',
props: {
accent: '#e97f00',
bg: '#0C1210',
fg: '#dee7e4',
fgHighlighted: '#fff',
fgOnAccent: '#192320',
divider: '#e7fffb24',
panel: '#192320',
panelHeaderBg: '@panel',
panelHeaderDivider: '@divider',
popup: '#293330',
renote: '@accent',
mentionMe: '#b4e900',
link: '#24d7ce',
},
}

View File

@ -1,7 +1,7 @@
{ {
id: '66e7e5a9-cd43-42cd-837d-12f47841fa34', id: '66e7e5a9-cd43-42cd-837d-12f47841fa34',
name: 'Mi Ice Dark', name: 'Ice Dark',
author: 'syuilo', author: 'syuilo',
base: 'dark', base: 'dark',

View File

@ -1,7 +1,7 @@
{ {
id: 'c503d768-7c70-4db2-a4e6-08264304bc8d', id: 'c503d768-7c70-4db2-a4e6-08264304bc8d',
name: 'Mi Persimmon Dark', name: 'Persimmon Dark',
author: 'syuilo', author: 'syuilo',
base: 'dark', base: 'dark',

View File

@ -1,7 +1,7 @@
{ {
id: '7a5bc13b-df8f-4d44-8e94-4452f0c634bb', id: '7a5bc13b-df8f-4d44-8e94-4452f0c634bb',
base: 'dark', base: 'dark',
name: 'Mi U0 Dark', name: 'U0 Dark',
props: { props: {
X2: ':darken<2<@panel', X2: ':darken<2<@panel',
X3: 'rgba(255, 255, 255, 0.05)', X3: 'rgba(255, 255, 255, 0.05)',

View File

@ -1,7 +1,7 @@
{ {
id: '0ff48d43-aab3-46e7-ab12-8492110d2e2b', id: '0ff48d43-aab3-46e7-ab12-8492110d2e2b',
name: 'Mi Apricot Light', name: 'Apricot Light',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -0,0 +1,94 @@
{
id: "169661d2-5a17-4dfc-b71b-9938cbbbed3e",
base: "light",
name: "Catppuccin Latte",
props: {
X2: ":darken<2<@panel",
X3: "rgba(255, 255, 255, 0.05)",
X4: "rgba(255, 255, 255, 0.1)",
X5: "rgba(255, 255, 255, 0.05)",
X6: "rgba(255, 255, 255, 0.15)",
X7: "rgba(255, 255, 255, 0.05)",
X8: ":lighten<5<@accent",
X9: ":darken<5<@accent",
bg: "#dce0e8",
fg: "#4c4f69",
X10: ":alpha<0.4<@accent",
X11: "rgba(0, 0, 0, 0.3)",
X12: "rgba(255, 255, 255, 0.1)",
X13: "rgba(255, 255, 255, 0.15)",
X14: ":alpha<0.5<@navBg",
X15: ":alpha<0<@panel",
X16: ":alpha<0.7<@panel",
X17: ":alpha<0.8<@bg",
cwBg: "#bcc0cc",
cwFg: "#5c5f77",
link: "#1e66f5",
warn: "#fe640b",
badge: "#1e66f5",
error: "#d20f39",
focus: ":alpha<0.3<@accent",
navBg: "@panel",
navFg: "@fg",
panel: ":lighten<3<@bg",
popup: ":lighten<3<@panel",
accent: "#8839ef",
header: ":alpha<0.7<@panel",
infoBg: "#ccd0da",
infoFg: "#6c6f85",
renote: "#1e66f5",
shadow: "rgba(0, 0, 0, 0.3)",
divider: "rgba(255, 255, 255, 0.1)",
hashtag: "#209fb5",
mention: "@accent",
modalBg: "rgba(0, 0, 0, 0.5)",
success: "#40a02b",
buttonBg: "rgba(255, 255, 255, 0.05)",
switchBg: "rgba(255, 255, 255, 0.15)",
acrylicBg: ":alpha<0.5<@bg",
cwHoverBg: "#acb0be",
indicator: "@accent",
mentionMe: "@mention",
messageBg: "@bg",
navActive: "@accent",
accentedBg: ":alpha<0.15<@accent",
codeNumber: "#40a02b",
codeString: "#fe640b",
fgOnAccent: "#eff1f5",
infoWarnBg: "#ccd0da",
infoWarnFg: "#5c5f77",
navHoverFg: ":lighten<17<@fg",
swutchOnBg: "@accentedBg",
swutchOnFg: "@accent",
codeBoolean: "@accent",
dateLabelFg: "@fg",
deckDivider: "#9ca0b0",
inputBorder: "rgba(255, 255, 255, 0.1)",
panelBorder: "solid 1px var(--divider)",
swutchOffBg: "rgba(255, 255, 255, 0.1)",
swutchOffFg: "@fg",
accentDarken: ":darken<10<@accent",
acrylicPanel: ":alpha<0.5<@panel",
navIndicator: "@indicator",
windowHeader: ":alpha<0.85<@panel",
accentLighten: ":lighten<10<@accent",
buttonHoverBg: "rgba(255, 255, 255, 0.1)",
driveFolderBg: ":alpha<0.3<@accent",
fgHighlighted: ":lighten<3<@fg",
fgTransparent: ":alpha<0.5<@fg",
panelHeaderBg: ":lighten<3<@panel",
panelHeaderFg: "@fg",
buttonGradateA: "@accent",
buttonGradateB: ":hue<20<@accent",
htmlThemeColor: "@bg",
panelHighlight: ":lighten<3<@panel",
listItemHoverBg: "rgba(255, 255, 255, 0.03)",
scrollbarHandle: "rgba(255, 255, 255, 0.2)",
inputBorderHover: "rgba(255, 255, 255, 0.2)",
wallpaperOverlay: "rgba(0, 0, 0, 0.5)",
fgTransparentWeak: ":alpha<0.75<@fg",
panelHeaderDivider: "rgba(0, 0, 0, 0)",
scrollbarHandleHover: "rgba(255, 255, 255, 0.4)",
},
author: "somebody ¯_(ツ)_/¯",
}

View File

@ -1,7 +1,7 @@
{ {
id: 'ac168876-f737-4074-a3fc-a370c732ef48', id: 'ac168876-f737-4074-a3fc-a370c732ef48',
name: 'Mi Cherry Light', name: 'Cherry Light',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -1,7 +1,7 @@
{ {
id: '6ed80faa-74f0-42c2-98e4-a64d9e138eab', id: '6ed80faa-74f0-42c2-98e4-a64d9e138eab',
name: 'Mi Coffee Light', name: 'Coffee Light',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -1,7 +1,7 @@
{ {
id: 'a58a0abb-ff8c-476a-8dec-0ad7837e7e96', id: 'a58a0abb-ff8c-476a-8dec-0ad7837e7e96',
name: 'Mi Rainy Light', name: 'Rainy Light',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -1,7 +1,7 @@
{ {
id: '213273e5-7d20-d5f0-6e36-1b6a4f67115c', id: '213273e5-7d20-d5f0-6e36-1b6a4f67115c',
name: 'Mi Sushi Light', name: 'Sushi Light',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -1,7 +1,7 @@
{ {
id: 'e2c940b5-6e9a-4c03-b738-261c720c426d', id: 'e2c940b5-6e9a-4c03-b738-261c720c426d',
base: 'light', base: 'light',
name: 'Mi U0 Light', name: 'U0 Light',
props: { props: {
X2: ':darken<2<@panel', X2: ':darken<2<@panel',
X3: 'rgba(255, 255, 255, 0.05)', X3: 'rgba(255, 255, 255, 0.05)',

View File

@ -1,7 +1,7 @@
{ {
id: '6128c2a9-5c54-43fe-a47d-17942356470b', id: '6128c2a9-5c54-43fe-a47d-17942356470b',
name: 'Mi Vivid Light', name: 'Vivid Light',
author: 'syuilo', author: 'syuilo',
base: 'light', base: 'light',

View File

@ -440,7 +440,7 @@ function more(ev: MouseEvent) {
color: var(--navActive); color: var(--navActive);
} }
&:hover, &:hover,
&:focus-within, &:focus-within,
&.active { &.active {
color: var(--accent); color: var(--accent);

View File

@ -26,6 +26,9 @@
"@jovikowi@calckey.social", "@jovikowi@calckey.social",
"@padraig@calckey.social", "@padraig@calckey.social",
"@pancakes@cats.city", "@pancakes@cats.city",
"@theresmiling@calckey.social",
"@AlderForrest@calckey.social",
"@kristian@calckey.social",
"Interkosmos Link" "Interkosmos Link"
] ]
} }

View File

@ -24,7 +24,7 @@ importers:
version: 7.2.0 version: 7.2.0
focus-trap-vue: focus-trap-vue:
specifier: ^4.0.1 specifier: ^4.0.1
version: 4.0.1(focus-trap@7.2.0)(vue@3.2.45) version: 4.0.1(focus-trap@7.2.0)(vue@3.2.47)
js-yaml: js-yaml:
specifier: 4.1.0 specifier: 4.1.0
version: 4.1.0 version: 4.1.0
@ -726,8 +726,8 @@ importers:
specifier: 4.19.1 specifier: 4.19.1
version: 4.19.1 version: 4.19.1
browser-image-resizer: browser-image-resizer:
specifier: https://github.com/misskey-dev/browser-image-resizer.git specifier: github:misskey-dev/browser-image-resizer
version: github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c version: github.com/misskey-dev/browser-image-resizer/56f504427ad7f6500e141a6d9f3aee42023d7f3e
calckey-js: calckey-js:
specifier: workspace:* specifier: workspace:*
version: link:../calckey-js version: link:../calckey-js
@ -1071,6 +1071,11 @@ packages:
resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==} resolution: {integrity: sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
/@babel/helper-string-parser@7.21.5:
resolution: {integrity: sha512-5pTUx3hAJaZIdW99sJ6ZUUgWq/Y+Hja7TowEnLNMm1VivRgZQL3vpBY3qUACVsvw+yQU6+YgfBVmcbLaZtrA1w==}
engines: {node: '>=6.9.0'}
dev: false
/@babel/helper-validator-identifier@7.19.1: /@babel/helper-validator-identifier@7.19.1:
resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==}
engines: {node: '>=6.9.0'} engines: {node: '>=6.9.0'}
@ -1114,6 +1119,14 @@ packages:
'@babel/types': 7.21.4 '@babel/types': 7.21.4
dev: true dev: true
/@babel/parser@7.21.5:
resolution: {integrity: sha512-J+IxH2IsxV4HbnTrSWgMAQj0UEo61hDA4Ny8h8PCX0MLXiibqHbqIOVneqdocemSBc22VpBKxt4J6FQzy9HarQ==}
engines: {node: '>=6.0.0'}
hasBin: true
dependencies:
'@babel/types': 7.21.5
dev: false
/@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4): /@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.21.4):
resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==} resolution: {integrity: sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==}
peerDependencies: peerDependencies:
@ -1300,6 +1313,15 @@ packages:
to-fast-properties: 2.0.0 to-fast-properties: 2.0.0
dev: true dev: true
/@babel/types@7.21.5:
resolution: {integrity: sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q==}
engines: {node: '>=6.9.0'}
dependencies:
'@babel/helper-string-parser': 7.21.5
'@babel/helper-validator-identifier': 7.19.1
to-fast-properties: 2.0.0
dev: false
/@bcoe/v8-coverage@0.2.3: /@bcoe/v8-coverage@0.2.3:
resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==}
dev: true dev: true
@ -3809,12 +3831,30 @@ packages:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
estree-walker: 2.0.2 estree-walker: 2.0.2
source-map: 0.6.1 source-map: 0.6.1
dev: true
/@vue/compiler-core@3.2.47:
resolution: {integrity: sha512-p4D7FDnQb7+YJmO2iPEv0SQNeNzcbHdGByJDsT4lynf63AFkOTFN07HsiRSvjGo0QrxR/o3d0hUyNCUnBU2Tig==}
dependencies:
'@babel/parser': 7.21.5
'@vue/shared': 3.2.47
estree-walker: 2.0.2
source-map: 0.6.1
dev: false
/@vue/compiler-dom@3.2.45: /@vue/compiler-dom@3.2.45:
resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==}
dependencies: dependencies:
'@vue/compiler-core': 3.2.45 '@vue/compiler-core': 3.2.45
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true
/@vue/compiler-dom@3.2.47:
resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==}
dependencies:
'@vue/compiler-core': 3.2.47
'@vue/shared': 3.2.47
dev: false
/@vue/compiler-sfc@2.7.14: /@vue/compiler-sfc@2.7.14:
resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==} resolution: {integrity: sha512-aNmNHyLPsw+sVvlQFQ2/8sjNuLtK54TC6cuKnVzAY93ks4ZBrvwQSnkkIh7bsbNhum5hJBS00wSDipQ937f5DA==}
@ -3837,12 +3877,36 @@ packages:
magic-string: 0.25.9 magic-string: 0.25.9
postcss: 8.4.21 postcss: 8.4.21
source-map: 0.6.1 source-map: 0.6.1
dev: true
/@vue/compiler-sfc@3.2.47:
resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==}
dependencies:
'@babel/parser': 7.21.5
'@vue/compiler-core': 3.2.47
'@vue/compiler-dom': 3.2.47
'@vue/compiler-ssr': 3.2.47
'@vue/reactivity-transform': 3.2.47
'@vue/shared': 3.2.47
estree-walker: 2.0.2
magic-string: 0.25.9
postcss: 8.4.23
source-map: 0.6.1
dev: false
/@vue/compiler-ssr@3.2.45: /@vue/compiler-ssr@3.2.45:
resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==}
dependencies: dependencies:
'@vue/compiler-dom': 3.2.45 '@vue/compiler-dom': 3.2.45
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true
/@vue/compiler-ssr@3.2.47:
resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==}
dependencies:
'@vue/compiler-dom': 3.2.47
'@vue/shared': 3.2.47
dev: false
/@vue/reactivity-transform@3.2.45: /@vue/reactivity-transform@3.2.45:
resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==}
@ -3852,17 +3916,43 @@ packages:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
estree-walker: 2.0.2 estree-walker: 2.0.2
magic-string: 0.25.9 magic-string: 0.25.9
dev: true
/@vue/reactivity-transform@3.2.47:
resolution: {integrity: sha512-m8lGXw8rdnPVVIdIFhf0LeQ/ixyHkH5plYuS83yop5n7ggVJU+z5v0zecwEnX7fa7HNLBhh2qngJJkxpwEEmYA==}
dependencies:
'@babel/parser': 7.21.5
'@vue/compiler-core': 3.2.47
'@vue/shared': 3.2.47
estree-walker: 2.0.2
magic-string: 0.25.9
dev: false
/@vue/reactivity@3.2.45: /@vue/reactivity@3.2.45:
resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==}
dependencies: dependencies:
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true
/@vue/reactivity@3.2.47:
resolution: {integrity: sha512-7khqQ/75oyyg+N/e+iwV6lpy1f5wq759NdlS1fpAhFXa8VeAIKGgk2E/C4VF59lx5b+Ezs5fpp/5WsRYXQiKxQ==}
dependencies:
'@vue/shared': 3.2.47
dev: false
/@vue/runtime-core@3.2.45: /@vue/runtime-core@3.2.45:
resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==}
dependencies: dependencies:
'@vue/reactivity': 3.2.45 '@vue/reactivity': 3.2.45
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true
/@vue/runtime-core@3.2.47:
resolution: {integrity: sha512-RZxbLQIRB/K0ev0K9FXhNbBzT32H9iRtYbaXb0ZIz2usLms/D55dJR2t6cIEUn6vyhS3ALNvNthI+Q95C+NOpA==}
dependencies:
'@vue/reactivity': 3.2.47
'@vue/shared': 3.2.47
dev: false
/@vue/runtime-dom@3.2.45: /@vue/runtime-dom@3.2.45:
resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==}
@ -3870,6 +3960,15 @@ packages:
'@vue/runtime-core': 3.2.45 '@vue/runtime-core': 3.2.45
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
csstype: 2.6.21 csstype: 2.6.21
dev: true
/@vue/runtime-dom@3.2.47:
resolution: {integrity: sha512-ArXrFTjS6TsDei4qwNvgrdmHtD930KgSKGhS5M+j8QxXrDJYLqYw4RRcDy1bz1m1wMmb6j+zGLifdVHtkXA7gA==}
dependencies:
'@vue/runtime-core': 3.2.47
'@vue/shared': 3.2.47
csstype: 2.6.21
dev: false
/@vue/server-renderer@3.2.45(vue@3.2.45): /@vue/server-renderer@3.2.45(vue@3.2.45):
resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==}
@ -3879,9 +3978,25 @@ packages:
'@vue/compiler-ssr': 3.2.45 '@vue/compiler-ssr': 3.2.45
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
vue: 3.2.45 vue: 3.2.45
dev: true
/@vue/server-renderer@3.2.47(vue@3.2.47):
resolution: {integrity: sha512-dN9gc1i8EvmP9RCzvneONXsKfBRgqFeFZLurmHOveL7oH6HiFXJw5OGu294n1nHc/HMgTy6LulU/tv5/A7f/LA==}
peerDependencies:
vue: 3.2.47
dependencies:
'@vue/compiler-ssr': 3.2.47
'@vue/shared': 3.2.47
vue: 3.2.47
dev: false
/@vue/shared@3.2.45: /@vue/shared@3.2.45:
resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==}
dev: true
/@vue/shared@3.2.47:
resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==}
dev: false
/@webassemblyjs/ast@1.11.1: /@webassemblyjs/ast@1.11.1:
resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==} resolution: {integrity: sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==}
@ -7439,14 +7554,14 @@ packages:
readable-stream: 2.3.7 readable-stream: 2.3.7
dev: true dev: true
/focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.45): /focus-trap-vue@4.0.1(focus-trap@7.2.0)(vue@3.2.47):
resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==} resolution: {integrity: sha512-2iqOeoSvgq7Um6aL+255a/wXPskj6waLq2oKCa4gOnMORPo15JX7wN6J5bl1SMhMlTlkHXGSrQ9uJPJLPZDl5w==}
peerDependencies: peerDependencies:
focus-trap: ^7.0.0 focus-trap: ^7.0.0
vue: ^3.0.0 vue: ^3.0.0
dependencies: dependencies:
focus-trap: 7.2.0 focus-trap: 7.2.0
vue: 3.2.45 vue: 3.2.47
dev: false dev: false
/focus-trap@7.2.0: /focus-trap@7.2.0:
@ -7606,7 +7721,7 @@ packages:
resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==}
engines: {node: '>= 0.10'} engines: {node: '>= 0.10'}
dependencies: dependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.10
through2: 2.0.5 through2: 2.0.5
dev: true dev: true
@ -9695,7 +9810,7 @@ packages:
/jsonfile@4.0.0: /jsonfile@4.0.0:
resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.10
/jsonfile@5.0.0: /jsonfile@5.0.0:
resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==} resolution: {integrity: sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w==}
@ -9709,7 +9824,7 @@ packages:
dependencies: dependencies:
universalify: 2.0.0 universalify: 2.0.0
optionalDependencies: optionalDependencies:
graceful-fs: 4.2.11 graceful-fs: 4.2.10
dev: true dev: true
/jsonld@6.0.0: /jsonld@6.0.0:
@ -10832,6 +10947,12 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
/nanoid@3.3.6:
resolution: {integrity: sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
dev: false
/nanomatch@1.2.13: /nanomatch@1.2.13:
resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==}
engines: {node: '>=0.10.0'} engines: {node: '>=0.10.0'}
@ -11953,6 +12074,15 @@ packages:
picocolors: 1.0.0 picocolors: 1.0.0
source-map-js: 1.0.2 source-map-js: 1.0.2
/postcss@8.4.23:
resolution: {integrity: sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==}
engines: {node: ^10 || ^12 || >=14}
dependencies:
nanoid: 3.3.6
picocolors: 1.0.0
source-map-js: 1.0.2
dev: false
/postgres-array@2.0.0: /postgres-array@2.0.0:
resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==}
engines: {node: '>=4'} engines: {node: '>=4'}
@ -14675,7 +14805,7 @@ packages:
dependencies: dependencies:
append-buffer: 1.0.2 append-buffer: 1.0.2
convert-source-map: 1.9.0 convert-source-map: 1.9.0
graceful-fs: 4.2.11 graceful-fs: 4.2.10
normalize-path: 2.1.1 normalize-path: 2.1.1
now-and-later: 2.0.1 now-and-later: 2.0.1
remove-bom-buffer: 3.0.0 remove-bom-buffer: 3.0.0
@ -14788,6 +14918,17 @@ packages:
'@vue/runtime-dom': 3.2.45 '@vue/runtime-dom': 3.2.45
'@vue/server-renderer': 3.2.45(vue@3.2.45) '@vue/server-renderer': 3.2.45(vue@3.2.45)
'@vue/shared': 3.2.45 '@vue/shared': 3.2.45
dev: true
/vue@3.2.47:
resolution: {integrity: sha512-60188y/9Dc9WVrAZeUVSDxRQOZ+z+y5nO2ts9jWXSTkMvayiWxCWOWtBQoYjLeccfXkiiPZWAHcV+WTPhkqJHQ==}
dependencies:
'@vue/compiler-dom': 3.2.47
'@vue/compiler-sfc': 3.2.47
'@vue/runtime-dom': 3.2.47
'@vue/server-renderer': 3.2.47(vue@3.2.47)
'@vue/shared': 3.2.47
dev: false
/vuedraggable@4.1.0(vue@3.2.45): /vuedraggable@4.1.0(vue@3.2.45):
resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==}
@ -15402,10 +15543,10 @@ packages:
resolution: {integrity: sha512-+MLeeUcLTlnzVo5xDn9+LVN9oX4esvgZ7qfZczBN+YVUvZBafIrPPVyG2WdjMWU2Qkb2ZAh2M8lpqf1wIoGqJQ==} resolution: {integrity: sha512-+MLeeUcLTlnzVo5xDn9+LVN9oX4esvgZ7qfZczBN+YVUvZBafIrPPVyG2WdjMWU2Qkb2ZAh2M8lpqf1wIoGqJQ==}
dev: false dev: false
github.com/misskey-dev/browser-image-resizer/0380d12c8e736788ea7f4e6e985175521ea7b23c: github.com/misskey-dev/browser-image-resizer/56f504427ad7f6500e141a6d9f3aee42023d7f3e:
resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/0380d12c8e736788ea7f4e6e985175521ea7b23c} resolution: {tarball: https://codeload.github.com/misskey-dev/browser-image-resizer/tar.gz/56f504427ad7f6500e141a6d9f3aee42023d7f3e}
name: browser-image-resizer name: browser-image-resizer
version: 2.2.1-misskey.3 version: 2.2.1-misskey.4
dev: true dev: true
github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658: github.com/sampotts/plyr/d434c9af16e641400aaee93188594208d88f2658: