forked from FoundKeyGang/FoundKey
Compare commits
16 commits
76b4e4ee08
...
3fc984ad2d
Author | SHA1 | Date | |
---|---|---|---|
3fc984ad2d | |||
c3a6dcd6f0 | |||
3d1a991051 | |||
0ad0ab7589 | |||
|
7972540e3a | ||
ba77a81cc0 | |||
b04ef21b6e | |||
965ee4b041 | |||
34d55e2dda | |||
|
1d99657a45 | ||
|
bd598cd9ba | ||
b374a79eb1 | |||
c978a687e9 | |||
4dc8822d05 | |||
ff4b6d932e | |||
a786fd99fc |
18 changed files with 200 additions and 92 deletions
|
@ -12,7 +12,7 @@ password: "Kata sandi"
|
|||
forgotPassword: "Lupa Kata Sandi"
|
||||
fetchingAsApObject: "Mengambil data dari Fediverse..."
|
||||
ok: "OK"
|
||||
gotIt: "Saya mengerti"
|
||||
gotIt: "Saya mengerti!"
|
||||
cancel: "Batalkan"
|
||||
renotedBy: "direnote oleh {user}"
|
||||
noNotes: "Tidak ada catatan"
|
||||
|
@ -24,7 +24,7 @@ otherSettings: "Pengaturan lainnya"
|
|||
openInWindow: "Buka di jendela"
|
||||
profile: "Profil"
|
||||
timeline: "Linimasa"
|
||||
noAccountDescription: "Pengguna ini belum menulis bio"
|
||||
noAccountDescription: "Pengguna ini belum menulis biodata mereka."
|
||||
login: "Masuk"
|
||||
loggingIn: "Sedang masuk"
|
||||
logout: "Keluar"
|
||||
|
@ -62,8 +62,8 @@ files: "Berkas"
|
|||
download: "Unduh"
|
||||
driveFileDeleteConfirm: "Hapus {name}? Catatan dengan berkas terkait juga akan terhapus."
|
||||
unfollowConfirm: "Berhenti mengikuti {name}?"
|
||||
exportRequested: "Kamu telah meminta ekspor. Ini akan memakan waktu sesaat. Setelah\
|
||||
\ ekspor selesai, berkas yang dihasilkan akan ditambahkan ke Drive"
|
||||
exportRequested: "Anda telah meminta ekspor. Ini mungkin memerlukan waktu beberapa\
|
||||
\ saat. File ini akan ditambahkan ke Drive Anda setelah selesai."
|
||||
importRequested: "Kamu telah meminta impor. Ini akan memakan waktu sesaat."
|
||||
lists: "Daftar"
|
||||
note: "Catat"
|
||||
|
@ -100,8 +100,8 @@ clickToShow: "Klik untuk melihat"
|
|||
sensitive: "Konten sensitif"
|
||||
add: "Tambahkan"
|
||||
reaction: "Reaksi"
|
||||
reactionSettingDescription2: "Geser untuk memindah urutkan, klik untuk menghapus,\
|
||||
\ tekan \"+\" untuk menambahkan"
|
||||
reactionSettingDescription2: "Seret untuk menyusun ulang, klik untuk menghapus, tekan\
|
||||
\ \"+\" untuk menambahkan."
|
||||
attachCancel: "Hapus lampiran"
|
||||
markAsSensitive: "Tandai sebagai konten sensitif"
|
||||
unmarkAsSensitive: "Hapus tanda konten sensitif"
|
||||
|
@ -193,10 +193,10 @@ blockedUsers: "Pengguna yang diblokir"
|
|||
noUsers: "Tidak ada pengguna"
|
||||
editProfile: "Sunting profil"
|
||||
noteDeleteConfirm: "Apakah kamu yakin ingin menghapus catatan ini?"
|
||||
pinLimitExceeded: "Kamu tidak dapat menyematkan catatan lagi"
|
||||
pinLimitExceeded: "Anda tidak dapat menyematkan catatan lagi."
|
||||
intro: "Instalasi FoundKey telah selesai! Mohon untuk membuat pengguna admin."
|
||||
done: "Selesai"
|
||||
processing: "Memproses"
|
||||
processing: "Pemrosesan..."
|
||||
preview: "Pratinjau"
|
||||
default: "Bawaan"
|
||||
noCustomEmojis: "Tidak ada emoji kustom"
|
||||
|
@ -210,7 +210,7 @@ publishing: "Sedang menyiarkan langsung"
|
|||
notResponding: "Tidak ada respon"
|
||||
changePassword: "Ubah kata sandi"
|
||||
security: "Keamanan"
|
||||
retypedNotMatch: "Input tidak sama"
|
||||
retypedNotMatch: "Input tidak cocok."
|
||||
currentPassword: "Kata sandi saat ini"
|
||||
newPassword: "Kata sandi baru"
|
||||
newPasswordRetype: "Ulangi kata sandi baru"
|
||||
|
@ -237,7 +237,7 @@ fromUrl: "Dari URL"
|
|||
uploadFromUrl: "Unggah dari URL"
|
||||
uploadFromUrlDescription: "URL berkas yang ingin kamu unggah"
|
||||
uploadFromUrlRequested: "Pengunggahan telah diminta"
|
||||
uploadFromUrlMayTakeTime: "Membutuhkan beberapa waktu hingga pengunggahan selesai"
|
||||
uploadFromUrlMayTakeTime: "Mungkin diperlukan waktu hingga unggahan selesai."
|
||||
explore: "Jelajahi"
|
||||
messageRead: "Telah dibaca"
|
||||
noMoreHistory: "Tidak ada sejarah lagi"
|
||||
|
@ -403,7 +403,7 @@ newMessageExists: "Kamu mendapatkan pesan baru"
|
|||
onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan"
|
||||
signinRequired: "Silahkan login"
|
||||
invitationCode: "Kode undangan"
|
||||
checking: "Memeriksa"
|
||||
checking: "Memeriksa..."
|
||||
available: "Tersedia"
|
||||
unavailable: "Tidak tersedia"
|
||||
usernameInvalidFormat: "Hanya dapat menerima karakter a-z, A-Z dan angka 0-9."
|
||||
|
@ -445,11 +445,10 @@ showFeaturedNotesInTimeline: "Tampilkan catatan yang diunggulkan di linimasa"
|
|||
objectStorage: "Object Storage"
|
||||
useObjectStorage: "Gunakan object storage"
|
||||
objectStorageBaseUrl: "Base URL"
|
||||
objectStorageBaseUrlDesc: "Prefix URL digunakan untuk mengkonstruksi URL ke object\
|
||||
\ (media) referencing. Tentukan URL jika kamu menggunakan CDN atau Proxy, jika tidak\
|
||||
\ tentukan alamat yang dapat diakses secara publik sesuai dengan panduan dari layanan\
|
||||
\ yang akan kamu gunakan, contohnya. 'https://<bucket>.s3.amazonaws.com' untuk AWS\
|
||||
\ S3, dan 'https://storage.googleapis.com/<bucket>' untuk GCS."
|
||||
objectStorageBaseUrlDesc: "URL yang digunakan sebagai referensi. Tentukan URL CDN\
|
||||
\ atau Proksi Anda jika Anda menggunakan keduanya.\nUntuk S3 gunakan 'https://<bucket>.s3.amazonaws.com'\
|
||||
\ dan untuk GCS atau layanan yang setara gunakan 'https://storage.googleapis.com/<bucket>',\
|
||||
\ dst."
|
||||
objectStorageBucket: "Bucket"
|
||||
objectStorageBucketDesc: "Mohon tentukan nama bucket yang digunakan pada layanan yang\
|
||||
\ telah dikonfigurasi."
|
||||
|
@ -647,8 +646,8 @@ contact: "Kontak"
|
|||
useSystemFont: "Gunakan font bawaan sistem operasi"
|
||||
clips: "Klip"
|
||||
makeExplorable: "Buat akun tampil di \"Jelajahi\""
|
||||
makeExplorableDescription: "Jika kamu mematikan ini, akun kamu tidak akan muncul di\
|
||||
\ bagian \"Jelajahi:"
|
||||
makeExplorableDescription: "Jika Anda menonaktifkannya, akun Anda tidak akan muncul\
|
||||
\ di bagian \"Jelajahi\"."
|
||||
showGapBetweenNotesInTimeline: "Tampilkan jarak diantara catatan pada linimasa"
|
||||
duplicate: "Duplikat"
|
||||
left: "Kiri"
|
||||
|
@ -759,7 +758,7 @@ ffVisibility: "Visibilitas Mengikuti/Pengikut"
|
|||
ffVisibilityDescription: "Mengatur siapa yang dapat melihat pengikutmu dan yang kamu\
|
||||
\ ikuti."
|
||||
continueThread: "Lihat lanjutan thread"
|
||||
deleteAccountConfirm: "Akun akan dihapus. Apakah kamu yakin?"
|
||||
deleteAccountConfirm: "Ini akan menghapuskan akun {handle} secara permanen. Lanjutkan?"
|
||||
incorrectPassword: "Kata sandi salah."
|
||||
voteConfirm: "Konfirmasi suara kamu untuk ({choice})?"
|
||||
hide: "Sembunyikan"
|
||||
|
@ -1079,7 +1078,7 @@ _auth:
|
|||
shareAccess: "Apakah kamu ingin mengijinkan \"{name}\" untuk mengakses akun ini?"
|
||||
shareAccessAsk: "Apakah kamu ingin mengijinkan aplikasi ini untuk mengakses akun\
|
||||
\ kamu?"
|
||||
permissionAsk: "Aplikasi ini membutuhkan beberapa ijin, yaitu:"
|
||||
permissionAsk: "Aplikasi ini meminta izin berikut ini"
|
||||
pleaseGoBack: "Mohon kembali ke aplikasi kamu"
|
||||
callback: "Mengembalikan kamu ke aplikasi"
|
||||
denied: "Akses ditolak"
|
||||
|
@ -1263,7 +1262,7 @@ _notification:
|
|||
youWereFollowed: "Mengikuti kamu"
|
||||
youReceivedFollowRequest: "Kamu menerima permintaan mengikuti"
|
||||
yourFollowRequestAccepted: "Permintaan mengikuti kamu telah diterima"
|
||||
youWereInvitedToGroup: "Telah diundang ke grup"
|
||||
youWereInvitedToGroup: "{userName} mengundang Anda ke grup"
|
||||
pollEnded: "Hasil Kuesioner telah keluar"
|
||||
emptyPushNotificationMessage: "Pembaruan notifikasi dorong"
|
||||
_types:
|
||||
|
|
|
@ -1295,7 +1295,7 @@ _remoteInteract:
|
|||
description: 今すぐにこのアクションを実行することはできません。あなたのインスタンス上で、またはログインして行う必要があるかもしれません。
|
||||
movedTo: このユーザーは {handle} に引っ越しました。
|
||||
uploadFailedDescription: ファイルをアップロードできませんでした。
|
||||
uploadFailedSize: このファイルは大きすぎるためアップロードできません。
|
||||
uploadFailedSize: ファイルサイズが大きすぎるためアップロードできません。
|
||||
uploadFailed: アップロード失敗
|
||||
showAttachedNotes: 添付ノートを表示
|
||||
attachedToNotes: このファイルが添付されたノート
|
||||
|
|
|
@ -181,14 +181,15 @@ clearCachedFiles: "Очистить кэш"
|
|||
clearCachedFilesConfirm: "Удалить все закэшированные файлы с других сайтов?"
|
||||
blockedInstances: "Заблокированные инстансы"
|
||||
blockedInstancesDescription: "Введите список инстансов, которые хотите заблокировать.\
|
||||
\ Они больше не смогут обмениваться с вашим инстансом."
|
||||
\ Они больше не смогут обмениваться с вашим инстансом. Не-ASCII доменные имена должны\
|
||||
\ быть переведены в punycode. Субдомены тоже будут заблокированы"
|
||||
muteAndBlock: "Скрытие и блокировка"
|
||||
mutedUsers: "Скрытые пользователи"
|
||||
blockedUsers: "Заблокированные пользователи"
|
||||
noUsers: "Нет ни одного пользователя"
|
||||
editProfile: "Редактировать профиль"
|
||||
noteDeleteConfirm: "Вы хотите удалить эту заметку?"
|
||||
pinLimitExceeded: "Нельзя закрепить ещё больше заметок"
|
||||
pinLimitExceeded: "Нельзя закрепить ещё больше заметок."
|
||||
intro: "Установка FoundKey завершена! А теперь создайте учетную запись администратора."
|
||||
done: "Готово"
|
||||
processing: "Обработка"
|
||||
|
@ -722,7 +723,7 @@ misskeyUpdated: "FoundKey обновился!"
|
|||
whatIsNew: "Что новенького?"
|
||||
translate: "Перевод"
|
||||
translatedFrom: "Перевод. Язык оригинала — {x}"
|
||||
accountDeletionInProgress: "В настоящее время выполняется удаление учетной записи"
|
||||
accountDeletionInProgress: "В настоящее время выполняется удаление учетной записи."
|
||||
usernameInfo: "Имя, которое отличает вашу учетную запись от других на этом сервере.\
|
||||
\ Вы можете использовать алфавит (a~z, A~Z), цифры (0~9) или символы подчеркивания\
|
||||
\ (_). Имена пользователей не могут быть изменены позже."
|
||||
|
@ -749,7 +750,7 @@ ffVisibility: "Видимость подписок и подписчиков"
|
|||
ffVisibilityDescription: "Здесь можно настроить, кто будет видеть ваши подписки и\
|
||||
\ подписчиков."
|
||||
continueThread: "Показать следующие ответы"
|
||||
deleteAccountConfirm: "Учётная запись будет безвозвратно удалена. Подтверждаете?"
|
||||
deleteAccountConfirm: "Учётная запись {handle} будет безвозвратно удалена. Подтверждаете?"
|
||||
incorrectPassword: "Пароль неверен."
|
||||
voteConfirm: "Отдать голос за «{choice}»?"
|
||||
hide: "Спрятать"
|
||||
|
@ -1268,3 +1269,81 @@ _deck:
|
|||
mentions: "Упоминания"
|
||||
direct: "Личное"
|
||||
_services: {}
|
||||
botFollowRequiresApproval: Запросы на подписку от аккаунтов помеченных как бот требуют
|
||||
подтверждения
|
||||
showLess: Показать меньше
|
||||
exportAll: Экспортировать всё
|
||||
exportSelected: Экспортировать выбранное
|
||||
cannotAttachFileWhenAccountSwitched: Вы не можете прикрепить файл, перейдя в другую
|
||||
учетную запись.
|
||||
cannotSwitchAccountWhenFileAttached: Вы не можете переключать учетные записи, пока
|
||||
файлы прикреплены.
|
||||
deleteAccount: Удалить аккаунт
|
||||
isSystemAccount: Учетная запись, созданная системой и автоматически управляемая ею.
|
||||
oneDay: Один день
|
||||
cropImage: Обрезать изображение
|
||||
documentation: Документация
|
||||
movedTo: Этот пользователь перешел на {handle}.
|
||||
typeToConfirm: Пожалуйста введите {x} чтобы подтвердить
|
||||
rateLimitExceeded: Лимит превышен
|
||||
numberOfPageCache: Количество кэшированных страниц
|
||||
numberOfPageCacheDescription: Увеличение этого числа повысит удобство для пользователей,
|
||||
но приведет к увеличению нагрузки на сервер, а также к использованию большего объема
|
||||
памяти.
|
||||
file: Файл
|
||||
unclip: Удалить из подборки
|
||||
translationSettings: Настройки перевода
|
||||
translationService: Служба перевода
|
||||
threadMuteNotificationsDesc: Выберите уведомления, которые вы хотите просмотреть в
|
||||
этом треде. Также применяются глобальные настройки уведомлений. Отключение имеет
|
||||
приоритет.
|
||||
reflectMayTakeTime: Это может занять некоторое время чтобы вступило в силу.
|
||||
failedToFetchAccountInformation: Не удалось получить информацию о аккаунте
|
||||
instanceDefaultThemeDescription: Введите код темы в формате объекта.
|
||||
tenMinutes: 10 минут
|
||||
oneHour: Один час
|
||||
oneWeek: Одна неделя
|
||||
cropImageAsk: Вы хотите обрезать это изображение?
|
||||
recentNHours: Последние {n} часов
|
||||
recentNDays: Последние {n} дней
|
||||
confirmToUnclipAlreadyClippedNote: Эта заметка уже является частью подборки "{name}".
|
||||
Вы хотите вместо этого удалить это из этой подборки?
|
||||
noEmailServerWarning: Сервер электронной почты не настроен.
|
||||
setTag: Установить метку
|
||||
addTag: Добавить метку
|
||||
removeTag: Удалить метку
|
||||
externalCssSnippets: Несколько фрагментов CSS для вашего вдохновения (не управляются
|
||||
FoundKey)
|
||||
oauthErrorGoBack: Произошла ошибка при попытке аутентификации стороннего приложения.
|
||||
Пожалуйста, вернитесь и попробуйте еще раз.
|
||||
appAuthorization: Авторизация приложения
|
||||
noPermissionsRequested: (Никаких разрешений не требуется.)
|
||||
selectMode: Выберите несколько
|
||||
selectAll: Выбрать все
|
||||
setCategory: Установить категорию
|
||||
thereIsUnresolvedAbuseReportWarning: Есть нерасмотренные жалобы.
|
||||
recommended: Рекомендовано
|
||||
check: Проверка
|
||||
unlimited: Неограниченный
|
||||
mutePeriod: Длительность глушения
|
||||
uploadFailed: Загрузка не удалась
|
||||
uploadFailedDescription: Файл не может быть загружен.
|
||||
uploadFailedSize: Файл слишком большой для загрузки.
|
||||
renoteUnmute: Показать репосты
|
||||
stopActivityDeliveryDescription: Локальная активнось не будет отправлена на этот сервер.
|
||||
Получение активностей работает как раньше.
|
||||
renoteMute: Скрыть репосты
|
||||
unrenoteAllConfirm: Вы уверены что хотите отменить все репосты данной замети?
|
||||
unrenoteAll: Отменить все репосты
|
||||
blockThisInstanceDescription: Локальная активность не будет отправлена на этот сервер.
|
||||
Активность этого сервера будет выброшена.
|
||||
attachedToNotes: Заметки с этим файлом
|
||||
showAttachedNotes: Показать заметки с этим файлом
|
||||
signinHistoryExpires: Данные о прошлых попытках войти будут автоматически удалены
|
||||
после 60 дней для соблюдения правил конфиденциальности.
|
||||
deleteAllFiles: Удалить все файлы
|
||||
federateBlocks: Федерировать блоки
|
||||
federateBlocksDescription: Если выключено, то активности типа "блок" не будут отправлены.
|
||||
regexpErrorDescription: 'Произошла ошибка в регулярном выражении на строке {line}
|
||||
ваших {tab} заглушенных слов:'
|
||||
reporter: Подавший жалобу
|
||||
|
|
|
@ -4,6 +4,7 @@ import config from '@/config/index.js';
|
|||
import { UserProfiles } from '@/models/index.js';
|
||||
import { extractMentions } from '@/misc/extract-mentions.js';
|
||||
import { intersperse } from '@/prelude/array.js';
|
||||
import { toPunyNullable } from '@/misc/convert-host.js';
|
||||
|
||||
// Transforms MFM to HTML, given the MFM text and a list of user IDs that are
|
||||
// mentioned in the text. If the list of mentions is not given, all mentions
|
||||
|
@ -14,6 +15,19 @@ export async function toHtml(mfmText: string, mentions?: string[]): Promise<stri
|
|||
return null;
|
||||
}
|
||||
|
||||
let mentionedUsers = [];
|
||||
const ids = mentions ?? extractMentions(nodes);
|
||||
if (ids.length > 0) {
|
||||
mentionedUsers = await UserProfiles.createQueryBuilder('user_profile')
|
||||
.leftJoin('user_profile.user', 'user')
|
||||
.select('user.usernameLower', 'username')
|
||||
.addSelect('user.host', 'host')
|
||||
// links should preferably use user friendly urls, only fall back to AP ids
|
||||
.addSelect('COALESCE(user_profile.url, user.uri)', 'url')
|
||||
.where('"userId" IN (:...ids)', { ids })
|
||||
.getRawMany();
|
||||
}
|
||||
|
||||
const doc = new JSDOM('').window.document;
|
||||
|
||||
const handlers: { [K in mfm.MfmNode['type']]: (node: mfm.NodeType<K>) => Promise<Node> } = {
|
||||
|
@ -103,30 +117,28 @@ export async function toHtml(mfmText: string, mentions?: string[]): Promise<stri
|
|||
},
|
||||
|
||||
async mention(node): Promise<HTMLElement | Text> {
|
||||
const { username, host, acct } = node.props;
|
||||
const ids = mentions ?? extractMentions(nodes);
|
||||
if (ids.length > 0) {
|
||||
const mentionedUsers = await UserProfiles.createQueryBuilder('user_profile')
|
||||
.leftJoin('user_profile.user', 'user')
|
||||
.select('user.username', 'username')
|
||||
.addSelect('user.host', 'host')
|
||||
// links should preferably use user friendly urls, only fall back to AP ids
|
||||
.addSelect('COALESCE(user_profile.url, user.uri)', 'url')
|
||||
.where('"userId" IN (:...ids)', { ids })
|
||||
.getRawMany();
|
||||
const userInfo = mentionedUsers.find(user => user.username === username && user.host === host);
|
||||
if (userInfo != null) {
|
||||
// Mastodon microformat: span.h-card > a.u-url.mention
|
||||
const a = doc.createElement('a');
|
||||
a.href = userInfo.url ?? `${config.url}/${acct}`;
|
||||
a.className = 'u-url mention';
|
||||
a.textContent = acct;
|
||||
let { username, host, acct } = node.props;
|
||||
// normalize username and host for searching the user
|
||||
username = username.toLowerCase();
|
||||
host = toPunyNullable(host);
|
||||
// Discard host if it is the local host. Otherwise mentions of local users where the
|
||||
// hostname is not omitted are not handled correctly.
|
||||
if (host == config.hostname) {
|
||||
host = null;
|
||||
}
|
||||
const userInfo = mentionedUsers.find(user => user.username === username && user.host === host);
|
||||
if (userInfo != null) {
|
||||
// Mastodon microformat: span.h-card > a.u-url.mention
|
||||
const a = doc.createElement('a');
|
||||
// The fallback will only be used for local users, so the host part can be discarded.
|
||||
a.href = userInfo.url ?? `${config.url}/@${username}`;
|
||||
a.className = 'u-url mention';
|
||||
a.textContent = acct;
|
||||
|
||||
const card = doc.createElement('span');
|
||||
card.className = 'h-card';
|
||||
card.appendChild(a);
|
||||
return card;
|
||||
}
|
||||
const card = doc.createElement('span');
|
||||
card.className = 'h-card';
|
||||
card.appendChild(a);
|
||||
return card;
|
||||
}
|
||||
// this user does not actually exist
|
||||
return doc.createTextNode(acct);
|
||||
|
|
|
@ -7,6 +7,7 @@ import { Webhook, webhookEventTypes } from '@/models/entities/webhook.js';
|
|||
import { IActivity } from '@/remote/activitypub/type.js';
|
||||
import { envOption } from '@/env.js';
|
||||
import { MINUTE } from '@/const.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
|
||||
import processDeliver from './processors/deliver.js';
|
||||
import processInbox from './processors/inbox.js';
|
||||
|
@ -83,24 +84,47 @@ webhookDeliverQueue
|
|||
.on('error', (job: any, err: Error) => webhookLogger.error(`error ${err}`, { job, e: renderError(err) }))
|
||||
.on('stalled', (job) => webhookLogger.warn(`stalled ${getJobInfo(job)} to=${job.data.to}`));
|
||||
|
||||
export function deliver(content: unknown, to: string | null) {
|
||||
export async function deliver(content: IActivity|IActivity[], to: string | null) {
|
||||
if (content == null) return null;
|
||||
if (to == null) return null;
|
||||
|
||||
const data = {
|
||||
content,
|
||||
to,
|
||||
};
|
||||
// group activities by actor
|
||||
const contentArray = Array.isArray(content) ? content : [content];
|
||||
let byActor = contentArray.reduce((acc, activity) => {
|
||||
if (activity.actor == null) throw new Error("Cannot deliver activity without actor.");
|
||||
if (!(activity.actor in acc)) {
|
||||
acc[activity.actor] = [];
|
||||
}
|
||||
acc[activity.actor].push(activity);
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return deliverQueue.add(data, {
|
||||
attempts: config.deliverJobMaxAttempts,
|
||||
timeout: MINUTE,
|
||||
backoff: {
|
||||
type: 'apBackoff',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
});
|
||||
// add groups to deliver queue
|
||||
const dbResolver = new DbResolver();
|
||||
for (const actor in byActor) {
|
||||
// extract user from the Activity
|
||||
const user = await dbResolver.getUserFromApId(actor);
|
||||
if (!user) throw new Error("Actor not found, cannot deliver.");
|
||||
if (user.host != null) throw new Error("Cannot deliver for remote actor.");
|
||||
|
||||
// add item to deliver queue
|
||||
const data = {
|
||||
user: {
|
||||
id: user.id,
|
||||
},
|
||||
content: byActor[actor],
|
||||
to,
|
||||
};
|
||||
deliverQueue.add(data, {
|
||||
attempts: config.deliverJobMaxAttempts,
|
||||
timeout: MINUTE,
|
||||
backoff: {
|
||||
type: 'apBackoff',
|
||||
},
|
||||
removeOnComplete: true,
|
||||
removeOnFail: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function inbox(activity: IActivity, signature: httpSignature.IParsedSignature) {
|
||||
|
|
|
@ -9,7 +9,6 @@ import { toPuny } from '@/misc/convert-host.js';
|
|||
import { StatusError } from '@/misc/fetch.js';
|
||||
import { shouldSkipInstance } from '@/misc/skipped-instances.js';
|
||||
import { DeliverJobData } from '@/queue/types.js';
|
||||
import { DbResolver } from '@/remote/activitypub/db-resolver.js';
|
||||
|
||||
const logger = new Logger('deliver');
|
||||
|
||||
|
@ -19,20 +18,13 @@ export default async (job: Bull.Job<DeliverJobData>) => {
|
|||
|
||||
if (await shouldSkipInstance(puny)) return 'skip';
|
||||
|
||||
// get user/actor for signing
|
||||
const userUri = job.data.content.actor;
|
||||
if (userUri == null) return 'error: missing actor';
|
||||
const user = await new DbResolver().getUserFromApId(userUri);
|
||||
if (user == null) return 'error: actor not found';
|
||||
if (user.host != null) return 'error: actor not local';
|
||||
|
||||
try {
|
||||
if (Array.isArray(job.data.content)) {
|
||||
await Promise.all(
|
||||
job.data.content.map(x => request(user, job.data.to, x))
|
||||
job.data.content.map(x => request(job.data.user, job.data.to, x))
|
||||
);
|
||||
} else {
|
||||
await request(user, job.data.to, job.data.content);
|
||||
await request(job.data.user, job.data.to, job.data.content);
|
||||
}
|
||||
|
||||
// Update stats
|
||||
|
|
|
@ -6,7 +6,9 @@ import { Webhook } from '@/models/entities/webhook.js';
|
|||
import { IActivity } from '@/remote/activitypub/type.js';
|
||||
|
||||
export type DeliverJobData = {
|
||||
/** Activity, containing the actor URI */
|
||||
/** Actor */
|
||||
user: ThinUser;
|
||||
/** Activity */
|
||||
content: IActivity;
|
||||
/** inbox URL to deliver */
|
||||
to: string;
|
||||
|
|
|
@ -156,7 +156,7 @@ export class DeliverManager {
|
|||
// skip instances as indicated
|
||||
if (instancesToSkip.includes(new URL(inbox).host)) continue;
|
||||
|
||||
deliver(this.activity, inbox);
|
||||
await deliver(this.activity, inbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ export default define(meta, paramDef, async (ps, me) => {
|
|||
const actor = await getInstanceActor();
|
||||
const targetUser = await Users.findOneByOrFail({ id: report.targetUserId });
|
||||
|
||||
deliver(renderActivity(renderFlag(actor, report)), targetUser.inbox);
|
||||
await deliver(renderActivity(renderFlag(actor, report)), targetUser.inbox);
|
||||
}
|
||||
|
||||
await AbuseUserReports.update(report.id, {
|
||||
|
|
|
@ -133,7 +133,7 @@ export default define(meta, paramDef, async (ps, user) => {
|
|||
if (note.userHost != null) {
|
||||
const pollOwner = await Users.findOneByOrFail({ id: note.userId }) as IRemoteUser;
|
||||
|
||||
deliver(renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox);
|
||||
await deliver(renderActivity(await renderVote(user, vote, note, poll, pollOwner)), pollOwner.inbox);
|
||||
}
|
||||
|
||||
// リモートフォロワーにUpdate配信
|
||||
|
|
|
@ -29,6 +29,6 @@ export default async function(blocker: CacheableUser, blockee: CacheableUser) {
|
|||
// deliver if remote bloking
|
||||
if (Users.isLocalUser(blocker) && Users.isRemoteUser(blockee)) {
|
||||
const content = renderActivity(renderUndo(renderBlock(blocking), blocker));
|
||||
deliver(content, blockee.inbox);
|
||||
await deliver(content, blockee.inbox);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ export async function acceptFollowRequest(followee: User, follower: User): Promi
|
|||
|
||||
if (Users.isRemoteUser(follower) && Users.isLocalUser(followee)) {
|
||||
const content = renderActivity(renderAccept(renderFollow(follower, followee, request.requestId!), followee));
|
||||
deliver(content, follower.inbox);
|
||||
await deliver(content, follower.inbox);
|
||||
}
|
||||
|
||||
Users.pack(followee.id, followee, {
|
||||
|
|
|
@ -17,7 +17,7 @@ export async function cancelFollowRequest(followee: User, follower: User): Promi
|
|||
const content = renderActivity(renderUndo(renderFollow(follower, followee), follower));
|
||||
|
||||
if (Users.isLocalUser(follower)) {
|
||||
deliver(content, followee.inbox);
|
||||
await deliver(content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,6 @@ export async function createFollowRequest(follower: User, followee: User, reques
|
|||
|
||||
if (Users.isLocalUser(follower) && Users.isRemoteUser(followee)) {
|
||||
const content = renderActivity(renderFollow(follower, followee));
|
||||
deliver(content, followee.inbox);
|
||||
await deliver(content, followee.inbox);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ export async function createMessage(user: { id: User['id']; host: User['host'];
|
|||
|
||||
const activity = renderActivity(renderCreate(await renderNote(note, false, true), note));
|
||||
|
||||
deliver(activity, recipientUser.inbox);
|
||||
await deliver(activity, recipientUser.inbox);
|
||||
}
|
||||
return messageObj;
|
||||
}
|
||||
|
|
|
@ -22,7 +22,7 @@ async function postDeleteMessage(message: MessagingMessage): Promise<void> {
|
|||
|
||||
if (Users.isLocalUser(user) && Users.isRemoteUser(recipient)) {
|
||||
const activity = renderActivity(renderDelete(renderTombstone(`${config.url}/notes/${message.id}`), user));
|
||||
deliver(activity, recipient.inbox);
|
||||
await deliver(activity, recipient.inbox);
|
||||
}
|
||||
} else if (message.groupId) {
|
||||
publishGroupMessagingStream(message.groupId, 'deleted', message.id);
|
||||
|
|
|
@ -37,7 +37,7 @@ export async function addRelay(inbox: string): Promise<Relay> {
|
|||
|
||||
const relayActor = await getRelayActor();
|
||||
const activity = renderActivity(renderFollowRelay(relay, relayActor));
|
||||
deliver(activity, relay.inbox);
|
||||
await deliver(activity, relay.inbox);
|
||||
|
||||
return relay;
|
||||
}
|
||||
|
@ -53,7 +53,7 @@ export async function removeRelay(inbox: string): Promise<void> {
|
|||
|
||||
const relayActor = await getRelayActor();
|
||||
const activity = renderActivity(renderUndo(renderFollowRelay(relay, relayActor), relayActor));
|
||||
deliver(activity, relay.inbox);
|
||||
await deliver(activity, relay.inbox);
|
||||
|
||||
await Relays.delete(relay.id);
|
||||
}
|
||||
|
@ -89,9 +89,9 @@ export async function deliverToRelays(user: { id: User['id']; host: null; }, act
|
|||
|
||||
const signed = await attachLdSignature(copy, user);
|
||||
|
||||
for (const relay of relays) {
|
||||
deliver(signed, relay.inbox);
|
||||
}
|
||||
await Promise.all(relays.map(relay =>
|
||||
deliver(signed, relay.inbox)
|
||||
));
|
||||
}
|
||||
|
||||
export async function deliverMultipleToRelays(user: User, activities: any[]): Promise<void> {
|
||||
|
@ -107,7 +107,7 @@ export async function deliverMultipleToRelays(user: User, activities: any[]): Pr
|
|||
return attachLdSignature(copy, user);
|
||||
}));
|
||||
|
||||
for (const relay of relays) {
|
||||
deliver(content, relay.inbox);
|
||||
}
|
||||
await Promise.all(relays.map(relay =>
|
||||
deliver(content, relay.inbox)
|
||||
));
|
||||
}
|
||||
|
|
|
@ -32,7 +32,7 @@ export async function doPostUnsuspend(user: User): Promise<void> {
|
|||
}
|
||||
|
||||
for (const inbox of queue) {
|
||||
deliver(content, inbox);
|
||||
await deliver(content, inbox);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue