diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 8abca405fb..2625cf75d3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,18 @@ version: 2 updates: - - package-ecosystem: "npm" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "daily" +- package-ecosystem: npm + directory: "/" + schedule: + interval: daily + open-pull-requests-limit: 0 +- package-ecosystem: npm + directory: "/packages/backend" + schedule: + interval: daily + open-pull-requests-limit: 0 +- package-ecosystem: npm + directory: "/packages/client" + schedule: + interval: daily + open-pull-requests-limit: 0 diff --git a/.node-version b/.node-version index 971a6537e5..53a42214a4 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -v16.6.2 +v16.13.2 diff --git a/CHANGELOG.md b/CHANGELOG.md index 10a3941423..6b1ac4fb05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,18 @@ You should also include the user name that made the change. --> +## 12.106.0 (2022/02/11) + +### Improvements +- Improve federation chart @syuilo +- クライアント: リアクションピッカーのサイズを設定できるように @syuilo +- クライアント: リアクションピッカーの幅、高さ制限を緩和 @syuilo +- Docker: Update to Node v16.13.2 @mei23 +- Update dependencies + +### Bugfixes +- validate regular expressions in word mutes @Johann150 + ## 12.105.0 (2022/02/09) ### Improvements diff --git a/Dockerfile b/Dockerfile index df86034301..9a651b6c2d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM node:16.6.2-alpine3.13 AS base +FROM node:16.13.2-alpine3.15 AS base ENV NODE_ENV=production diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index fa18a8b3d0..5cc2bb91b6 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -595,6 +595,8 @@ smtpSecure: "SMTP সংযোগের জন্য SSL/TLS ব্যবহা smtpSecureInfo: "STARTTLS ব্যবহার করার সময় এটি বন্ধ করুন।" testEmail: "ইমেল বিতরণ পরীক্ষা করুন" wordMute: "বিশেষ কোন শব্দকে মিউট করুন" +regexpError: "রেগুলার এক্সপ্রেশন ত্রুটি" +regexpErrorDescription: "{tab} ওয়ার্ড মিউটের {line} লাইনে রেগুলার এক্সপ্রেশনে একটি ত্রুটি ছিল:" instanceMute: "মিউট করা ইন্সত্যান্সগুলি" userSaysSomething: "{name} কিছু বলেছে" makeActive: "সক্রিয় করা" @@ -824,6 +826,11 @@ leaveGroupConfirm: "\"{name}\" গ্রুপ ছেড়ে চলে যেত useDrawerReactionPickerForMobile: "মোবাইলে রিঅ্যাকশন পিকারকে ড্রয়ারে প্রদর্শন করুন" welcomeBackWithName: "আবার স্বাগতম, {name}" clickToFinishEmailVerification: " [{ok}] ক্লিক করার মাধ্যমে আপনার ইমেল ঠিকানা নিশ্চিত করুন।" +overridedDeviceKind: "ডিভাইসের ধরন" +smartphone: "স্মার্টফোন" +tablet: "ট্যাবলেট" +auto: "স্বয়ংক্রিয়" +themeColor: "থিমের রং" _emailUnavailable: used: "এই ইমেইল ঠিকানাটি ইতোমধ্যে ব্যবহৃত হয়েছে" format: "এই ইমেল ঠিকানাটি সঠিকভাবে লিখা হয়নি" @@ -1112,10 +1119,47 @@ _2fa: registerDevice: "নতুন ডিভাইস নিবন্ধন করুন" registerKey: "সিকিউরিটি কী নিবন্ধন করুন" step1: "প্রথমে, আপনার ডিভাইসে {a} বা {b} এর মতো একটি অথেনটিকেশন অ্যাপ ইনস্টল করুন৷" + step2: "এরপরে, অ্যাপের সাহায্যে প্রদর্শিত QR কোডটি স্ক্যান করুন।" + step3: "অ্যাপে প্রদর্শিত টোকেনটি লিখুন এবং আপনার কাজ শেষ।" + step4: "আপনাকে এখন থেকে লগ ইন করার সময়, এইভাবে টোকেন লিখতে হবে।" + securityKeyInfo: "আপনি একটি হার্ডওয়্যার সিকিউরিটি কী ব্যবহার করে লগ ইন করতে পারেন যা FIDO2 বা ডিভাইসের ফিঙ্গারপ্রিন্ট সেন্সর বা পিন সমর্থন করে৷" _permissions: + "read:account": "অ্যাকাউন্টের তথ্য দেখুন" + "write:account": "অ্যাকাউন্টের তথ্য সম্পাদন করুন" + "read:blocks": "ব্লক করা ব্যাবহারকারীদের তালিকা দেখুন" + "write:blocks": "ব্লক করা ব্যাবহারকারীদের তালিকা সম্পাদনা করুন" + "read:drive": "ড্রাইভের ফাইল এবং ফোল্ডারসমূহ পড়া" + "write:drive": "ড্রাইভের ফাইল এবং ফোল্ডারসমূহ সম্পাদনা করা" + "read:favorites": "পছন্দের তালিকা পড়া" + "write:favorites": "পছন্দের তালিকা সম্পাদনা করা" + "read:following": "অনুসরণ তথ্য দেখুন" + "write:following": "অনুসরণ তথ্য সম্পাদনা করা" + "read:messaging": "চ্যাটগুলি দেখুন" + "write:messaging": "চ্যাটগুলি সম্পাদনা করুন" + "read:mutes": "মিউটের লিস্ট দেখুন" + "write:mutes": "মিউটের লিস্ট সম্পাদনা করুন" + "write:notes": "নোট লিখা" + "read:notifications": "বিজ্ঞপ্তিগুলি দেখুন" + "write:notifications": "বিজ্ঞপ্তি নিয়ে কাজ করে" + "read:reactions": "রিঅ্যাকশনগুলি দেখুন" + "write:reactions": "রিঅ্যাকশনগুলি সম্পাদনা করুন" + "write:votes": "ভোট দিন" "read:pages": "আপনার পেজগুলি দেখুন" "write:pages": "পেজগুলি সম্পাদনা বা ডিলিট করুন" + "read:page-likes": "পৃষ্ঠায় দেয়া পছন্দগুলি দেখুন" + "write:page-likes": "পৃষ্ঠায় দেয়া পছন্দগুলি সম্পাদনা করুন" + "read:user-groups": "ব্যাবহারকারী গ্রুপগুলি দেখুন" + "write:user-groups": "ব্যাবহারকারী গ্রুপগুলি সম্পাদনা করুন" + "read:channels": "চ্যানেলগুলি দেখুন" + "write:channels": "চ্যানেলগুলি সম্পাদনা করুন" + "read:gallery": "গ্যালারী দেখুন" + "write:gallery": "গ্যালারী সম্পাদনা করুন" + "read:gallery-likes": "গ্যালারীর পছন্দগুলি দেখুন" + "write:gallery-likes": "গ্যালারীর পছন্দগুলি সম্পাদনা করুন" _auth: + shareAccess: "\"{name}\" কে অ্যাকাউন্টের অ্যাক্সেস দিবেন?" + shareAccessAsk: "অ্যাপ্লিকেশনটিকে অ্যাকাউন্টের অ্যাক্সেস দিবেন?" + permissionAsk: "এই অ্যাপ্লিকেশনটি নিম্নলিখিত অনুমতি চাই" pleaseGoBack: "দয়া করে অ্যাপ্লিকেশনে ফিরে যান" callback: "অ্যাপ্লিকেশনে ফিরে যাচ্ছি" denied: "প্রবেশ নিষেধ" @@ -1178,48 +1222,342 @@ _poll: closed: "শেষ হয়ে গেছে" remainingDays: "আর {d} দিন {h} ঘণ্টা বাকি আছে" remainingHours: "আর {h} ঘণ্টা {m} মিনিট বাকি আছে" + remainingMinutes: "আর বাকি আছে {m} মিনিট {s} সেকেন্ড" + remainingSeconds: "আর বাকি আছে {s} সেকেন্ড" _visibility: + public: "সর্বজনীন" + publicDescription: "সবাই আপনার নোটগুলি দেখতে পাবে" home: "মূল পাতা" + homeDescription: "শুধুমাত্র হোম টাইমলাইনে আপনার নোটগুলি পোস্ট করুন" followers: "অনুসরণকারী" + followersDescription: "শুধুমাত্র আপনার অনুসরণকারীদের নিকট পোস্ট করুন" + specified: "ডাইরেক্ট নোট" + specifiedDescription: "শুধুমাত্র নির্দিষ্ট ব্যাবহারকারীর নিকট পাঠান" + localOnly: "শুধুমাত্র লোকাল" + localOnlyDescription: "রিমোট ব্যাবহারকারীদের নিকট দৃশ্যমান নয়" +_postForm: + replyPlaceholder: "নোটটির জবাব দিন..." + quotePlaceholder: "নোটটিকে উদ্ধৃত করুন..." + channelPlaceholder: "চ্যানেলে পোস্ট করুন..." + _placeholders: + a: "আপনি এখন কি করছেন?" + b: "আপনার আশে পাশে কি হচ্ছে?" + c: "আপনি কি ভাবছেন?" + d: "আপনি কি বলতে চান?" + e: "লেখা শুরু করুন..." + f: "আপনার লেখার জন্য অপেক্ষা করছি..." _profile: name: "নাম" username: "ব্যবহারকারীর নাম" + description: "আপনার সম্পর্কে" + youCanIncludeHashtags: "হ্যাশট্যাগ অন্তর্ভুক্ত করা যেতে পারে।" + metadata: "অতিরিক্ত তথ্য" + metadataEdit: "অতিরিক্ত তথ্য সম্পাদনা করুন" + metadataDescription: "আপনি আপনার প্রোফাইলে একটি টেবিল হিসাবে চারটি অতিরিক্ত তথ্য দেখাতে পারেন।" + metadataLabel: "লেবেল" + metadataContent: "বিষয়বস্তু" + changeAvatar: "অ্যাভাটার পরিবর্তন করুন" + changeBanner: "ব্যানার পরিবর্তন করুন" _exportOrImport: allNotes: "সকল নোট" followingList: "অনুসরণ করা হচ্ছে" muteList: "মিউট" blockingList: "ব্লক" userLists: "লিস্ট" + excludeMutingUsers: "মিউটকৃত ব্যবহারকারীদের বাদ দিন" + excludeInactiveUsers: "অব্যাবহৃত অ্যাকাউন্ট বাদ দিন" _charts: federation: "ফেডিভার্স" + apRequest: "অনুরোধসমূহ" + usersIncDec: "ব্যবহারকারীদের সংখ্যার পরিবর্তন" + usersTotal: "ব্যবহারকারীদের সংখ্যা" + activeUsers: "সক্রিয় ব্যাবহারকারী" + notesIncDec: "নোটের সংখ্যার পরিবর্তন" + localNotesIncDec: "লোকাল নোটের সংখ্যার পরিবর্তন" + remoteNotesIncDec: "রিমোট নোটের সংখ্যার পরিবর্তন" + notesTotal: "নোটের সংখ্যা" + filesIncDec: "ফাইলের সংখ্যার পরিবর্তন" + filesTotal: "ফাইলের সংখ্যা" + storageUsageIncDec: "স্টোরেজের ব্যাবহারের পরিবর্তন" + storageUsageTotal: "মোট স্টোরেজের ব্যাবহার" +_instanceCharts: + requests: "অনুরোধসমূহ" + users: "ব্যবহারকারীদের সংখ্যার পরিবর্তন" + usersTotal: "ক্রমবর্ধমান ব্যবহারকারীদের সংখ্যা" + notes: "নোটের সংখ্যার পরিবর্তন" + notesTotal: "ক্রমবর্ধমান নোটের সংখ্যা" + ff: "অনুসরণকারী / অনুসরণ করা ব্যাবহারকারীদের সংখ্যার পরিবর্তন" + ffTotal: "অনুসরণকারী / অনুসরণ করা ব্যাবহারকারীদের ক্রমবর্ধমান সংখ্যা" + cacheSize: "ক্যাশ সাইজের পরিবর্তন" + cacheSizeTotal: "ক্রমবর্ধমান ক্যাশ সাইজ" + files: "ফাইলের সংখ্যার পরিবর্তন" + filesTotal: "ক্রমবর্ধমান ফাইলের সংখ্যা" _timelines: home: "মূল পাতা" + local: "স্থানীয়" + social: "সামাজিক" + global: "গ্লোবাল" _pages: + newPage: "নতুন পৃষ্ঠা বানান" + editPage: "পৃষ্ঠাটি সম্পাদনা করুন" + readPage: "উৎস দেখছেন" + created: "পৃষ্ঠা তৈরি করা হয়েছে" + updated: "পৃষ্ঠা সম্পাদনা করা হয়েছে" + deleted: "পৃষ্ঠা মুছে ফেলা হয়েছে" + pageSetting: "পৃষ্ঠার সেটিংস" + nameAlreadyExists: "পৃষ্ঠার URLটি ইতিমধ্যেই ব্যাবহার করা হয়েছে" + invalidNameTitle: "পৃষ্ঠার URL অবৈধ" + invalidNameText: "নিশ্চিত করুন যে এটি ফাঁকা নয়" + editThisPage: "পৃষ্ঠাটি সম্পাদনা করুন" + viewSource: "উৎস দেখুন" viewPage: "আপনার পেজগুলি দেখুন" + like: "পছন্দ" + unlike: "পছন্দ সরান" + my: "আমার পৃষ্ঠাগুলি" + liked: "পছন্দ করা পৃষ্ঠাগুলি" + featured: "জনপ্রিয়" + inspector: "ইনিস্পেক্টর" + contents: "বিষয়বস্তু" + content: "পৃষ্ঠার ব্লক" + variables: "চলকগুলি" + title: "শিরোনাম" + url: "পৃষ্ঠার URL" + summary: "পৃষ্ঠার বর্ণনা" + alignCenter: "সেন্টার" + hideTitleWhenPinned: "পিন করা হলে টাইটেল লুকান" + font: "ফন্ট" + fontSerif: "সেরিফ" + fontSansSerif: "স্যান্স সেরিফ" + eyeCatchingImageSet: "থাম্বনেইল সেট করুন" + eyeCatchingImageRemove: "থাম্বনেইল সরান" + chooseBlock: "ব্লক যোগ করুন" + selectType: "ধরন নির্বাচন করুন" + enterVariableName: "চলকের নাম লিখুন" + variableNameIsAlreadyUsed: "চলকের নামটি ইতিপূর্বে ব্যাবহৃত হয়েছে" + contentBlocks: "বিষয়বস্তু" + inputBlocks: "ইনপুট" + specialBlocks: "বিশেষ" blocks: + text: "লেখা" + textarea: "টেক্সট এরিয়া" + section: "বিভাগ" image: "ছবি" + button: "বাটন" + if: "যদি" + _if: + variable: "চলকগুলি" + post: "নোট লিখুন" + _post: + text: "বিষয়বস্তু" + attachCanvasImage: "ক্যানভাস ছবিসহ পোস্ট করুন" + canvasId: "ক্যানভাস ID" + textInput: "টেক্সট ইনপুট" + _textInput: + name: "চলকের নাম" + text: "শিরোনাম" + default: "ডিফল্ট মান" + textareaInput: "একাধিক লাইনের টেক্সট ইনপুট" + _textareaInput: + name: "চলকের নাম" + text: "শিরোনাম" + default: "ডিফল্ট মান" + numberInput: "সংখ্যা ইনপুট" + _numberInput: + name: "চলকের নাম" + text: "শিরোনাম" + default: "ডিফল্ট মান" + canvas: "ক্যানভাস" + _canvas: + id: "ক্যানভাস ID" + width: "প্রস্থ" + height: "উচ্চতা" + note: "এম্বেড নোট" + _note: + id: "নোট ID" + idDescription: "আপনি এর বদলে নোটের URL পেস্ট করতে পারেন." + detailed: "বিস্তারিত দেখুন" + switch: "সুইচ" + _switch: + name: "চলকের নাম" + text: "শিরোনাম" + default: "ডিফল্ট মান" + counter: "কাউন্টার" + _counter: + name: "চলকের নাম" + text: "শিরোনাম" + inc: "এভাবে মান বাড়ান" + _button: + text: "শিরোনাম" + colored: "রঙ্গিন" + action: "বাটনে ক্লিক করলে যা হবে" + _action: + dialog: "ডায়ালগ দেখান " + _dialog: + content: "বিষয়বস্তু" + resetRandom: "র‍্যানডম সিড রিসেট করুন" + pushEvent: "ইভেন্ট পাঠান" + _pushEvent: + event: "ইভেন্টের নাম" + message: "চালু হলে প্রদর্শনের জন্য বার্তা" + variable: "পাঠানো চলক" + no-variable: "কিছুই না" + callAiScript: "AiScript চালান" + _callAiScript: + functionName: "ফাংশনের নাম" + radioButton: "বহুনির্বাচনী" + _radioButton: + name: "চলকের নাম" + title: "শিরোনাম" + values: "বিকল্পগুলিকে আলাদা লাইনে লিখুন" + default: "ডিফল্ট মান" script: categories: + flow: "নিয়ন্ত্রণ" + logical: "লজিক্যাল অপারেশন" + operation: "হিসাব-নিকাশ" + comparison: "তুলনা" + random: "র‍্যান্ডম" + value: "মান" + fn: "ফাংশন" + text: "টেক্সট ম্যানিপুলেশন" + convert: "রুপান্তর" list: "লিস্ট" blocks: + text: "লেখা" + multiLineText: "লেখা (একাধিক লাইন)" + textList: "লেখার লিস্ট" + _textList: + info: "প্রতিটি এন্ট্রিকে আলাদা লাইনে লিখুন" + strLen: "লেখার দৈর্ঘ্য" + _strLen: + arg1: "লেখা" + strPick: "অক্ষর বের করে আনুন" + _strPick: + arg1: "লেখা" + arg2: "অক্ষরের অবস্থান" + strReplace: "লেখা প্রতিস্থাপন" + _strReplace: + arg1: "লেখা" + arg2: "যে লেখা প্রতিস্থাপন করা হবে" + arg3: "যা দ্বারা প্রতিস্থাপন করা হবে" + strReverse: "লেখা উল্টান" + _strReverse: + arg1: "লেখা" + join: "লেখা যুক্ত করুন" _join: arg1: "লিস্ট" + arg2: "বিভাজক" + add: "যোগ" + _add: + arg1: "A" + arg2: "B" + subtract: "বিয়োগ" + _subtract: + arg1: "A" + arg2: "B" + multiply: "গুন" + _multiply: + arg1: "A" + arg2: "B" + divide: "ভাগ" + _divide: + arg1: "A" + arg2: "B" + mod: "ভাগশেষ" + _mod: + arg1: "A" + arg2: "B" + round: "দশমিক রাউন্ড করুন" + _round: + arg1: "সংখ্যা" + eq: "A ও B সমান" + _eq: + arg1: "A" + arg2: "B" + notEq: "A ও B সমান না" + _notEq: + arg1: "A" + arg2: "B" + and: "A এবং B" + _and: + arg1: "A" + arg2: "B" + or: "A অথবা B" + _or: + arg1: "A" + arg2: "B" + lt: "< A , B হতে কম" + _lt: + arg1: "A" + arg2: "B" + gt: "> A , B হতে বেশী" + _gt: + arg1: "A" + arg2: "B" + ltEq: "<= A , B হতে কম বা সমান" + _ltEq: + arg1: "A" + arg2: "B" + gtEq: ">= A , B হতে বেশী বা সমান" + _gtEq: + arg1: "A" + arg2: "B" + if: "যদি" + _if: + arg1: "যদি" + arg2: "তাহলে" + arg3: "তাছাড়া" + not: "না" + _not: + arg1: "না" + random: "র‍্যান্ডম" + _random: + arg1: "সম্ভাব্যতা" + rannum: "র‍্যানডম সংখ্যা" + _rannum: + arg1: "ন্যূনতম মান" + arg2: "সর্বোচ্চ মান" + randomPick: "তালিকা থেকে দৈবচয়ন করুন" _randomPick: arg1: "লিস্ট" + dailyRandom: "র‍্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)" _dailyRandom: arg1: "সম্ভাব্যতা" dailyRannum: "র‍্যান্ডম সংখ্যা (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)" _dailyRannum: arg1: "ন্যূনতম মান" arg2: "সর্বোচ্চ মান" + dailyRandomPick: "তালিকা থেকে এলোমেলোভাবে নির্বাচন করুন (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন পরিবর্তীত হয়)" _dailyRandomPick: arg1: "লিস্ট" + seedRandom: "র‍্যানডম (সীড দ্বারা)" + _seedRandom: + arg1: "সীড" + arg2: "সম্ভাব্যতা" + seedRannum: "র‍্যানডম সংখ্যা (সীড দ্বারা)" + _seedRannum: + arg1: "সীড" + arg2: "ন্যূনতম মান" + arg3: "সর্বোচ্চ মান" + seedRandomPick: "তালিকা থেকে দৈবচয়ন করুন (সীড দ্বারা)" _seedRandomPick: + arg1: "সীড" arg2: "লিস্ট" + DRPWPM: "সম্ভাব্যতা সহ একটি তালিকা থেকে এলোমেলোভাবে নির্বাচন করুন (প্রতিটি ব্যবহারকারীর জন্য প্রতিদিন)" + _DRPWPM: + arg1: "লেখার লিস্ট" + pick: "তালিকা থেকে নির্বাচন করুন" _pick: arg1: "লিস্ট" + arg2: "অবস্থান" + listLen: "লিস্টের দৈর্ঘ্য পান" _listLen: arg1: "লিস্ট" + number: "সংখ্যা" + stringToNumber: "পাঠ্য থেকে সংখ্যা" + _stringToNumber: + arg1: "লেখা" + numberToString: "সংখ্যা থেকে পাঠ্য" _numberToString: arg1: "সংখ্যা" splitStrByLine: "পাঠ্যকে লাইনে বিভক্ত করুন" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index cec230e10d..bd93e53012 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -595,6 +595,8 @@ smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden" smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest" testEmail: "Email-Versand testen" wordMute: "Wort-Stummschaltung" +regexpError: "Regular Expression error" +regexpErrorDescription: "Error in the regular expression on line {line} in your {tab} word mutes:" instanceMute: "Instanzstummschaltungen" userSaysSomething: "{name} hat etwas gesagt" makeActive: "Aktivieren" @@ -828,6 +830,7 @@ overridedDeviceKind: "Gerätetyp" smartphone: "Smartphone" tablet: "Tablet" auto: "Automatisch" +themeColor: "Instanzfarbe" _emailUnavailable: used: "Diese Email-Adresse wird bereits verwendet" format: "Das Format dieser Email-Adresse ist ungültig" diff --git a/locales/en-US.yml b/locales/en-US.yml index 43ecf98206..5f7bc2a533 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -828,6 +828,7 @@ overridedDeviceKind: "Device type" smartphone: "Smartphone" tablet: "Tablet" auto: "Auto" +themeColor: "Theme Color" _emailUnavailable: used: "This email address is already being used" format: "The format of this email address is invalid" diff --git a/locales/eo-UY.yml b/locales/eo-UY.yml index 286a0d857f..062bf85aa9 100644 --- a/locales/eo-UY.yml +++ b/locales/eo-UY.yml @@ -9,7 +9,7 @@ username: "Uzantnomo" password: "Pasvorto" forgotPassword: "Ĉu vi forgesis pasvorton?" fetchingAsApObject: "Informpetado de la Fediverso…" -ok: "OK" +ok: "Bone" gotIt: "Kompreni" cancel: "Nuligi" enterUsername: "Entajpu uzantnomon" @@ -75,7 +75,7 @@ following: "Sekvata" followers: "Sekvantoj" followsYou: "Sekvas vin" createList: "Krei liston" -manageLists: "Bonteni liston" +manageLists: "Bonteni la listojn" error: "Eraro" somethingHappened: "Problemo okazis" retry: "Provi denove" @@ -137,6 +137,7 @@ settingGuide: "Agordaj rekomendoj" cacheRemoteFiles: "Stapli forajn dosierojn" flagAsBot: "Marki kiel esti uzanto de roboto" flagAsCat: "Marki kiel esti kato" +flagAsCatDescription: "Flagu por montri ke la konton havas kato." flagShowTimelineReplies: "Montri respondon de notoj en templinio." autoAcceptFollowed: "Aŭtomate akcepti la peton de sekvado far uzantoj kiujn vi sekvas" addAccount: "Aldoni konton" @@ -184,7 +185,7 @@ noUsers: "Neniu uzanto" editProfile: "Redakti profilon" noteDeleteConfirm: "Ĉu vi certas ke vi volas forviŝi la noton?" pinLimitExceeded: "Vi ne povas alpingli pli" -intro: "Instalado de Misskey finiĝis! Kreu administran konton." +intro: "La instalado de Misskey finiĝis! Volu krei administran konton." done: "Fini" processing: "Prilaborado…" preview: "Antaŭmontro" @@ -223,6 +224,7 @@ resetAreYouSure: "Ĉu vi certas restarigi?" saved: "Konservita" messaging: "Retbabili" upload: "Alŝuti" +keepOriginalUploading: "Konservi la originalan bildon" fromDrive: "De la disko" fromUrl: "De URL" uploadFromUrl: "Alŝuti de URL" @@ -491,7 +493,7 @@ deletedNote: "Forviŝita noto" invisibleNote: "Malpublikigita noto" enableInfiniteScroll: "Ebligi infinitan rulumon" visibility: "Videbleco" -poll: "Balotujo" +poll: "Enketo" useCw: "Kaŝi enhavo" enablePlayer: "Vidigi la filmeton" disablePlayer: "Malfermi la filmeton" @@ -537,7 +539,7 @@ overview: "Resumo" logs: "Protokoloj" delayed: "Prokrasto " database: "Datumbazo" -channel: "Kanalo" +channel: "Kanaloj" create: "Krei" notificationSetting: "Agordoj de sciigoj" useGlobalSetting: "Oni uzas malloka agordo" @@ -560,6 +562,7 @@ createNew: "Krei novan" optional: "Opciaj" public: "Publika" i18nInfo: "Misskey estas tradukata en diversaj lingvoj de volontuloj. Oni povas kontribui ĉe {link}." +manageAccessTokens: "Bonteni la aŭtentikigajn pecojn" accountInfo: "Kontaj Informoj" notesCount: "La nombro de notoj" repliesCount: "La nombro de respondoj senditaj" @@ -571,6 +574,7 @@ followersCount: "La nombro de sekvantoj" sentReactionsCount: "La nombro de la reagoj senditaj" receivedReactionsCount: "La nombro de la reagoj ricevitaj" pollVotesCount: "Nombro de voĉdonado" +pollVotedCount: "La nombro de la voĉoj ricevitaj en siaj enketoj" yes: "Jes" no: "Ne" driveFilesCount: "La nombro de la dosieroj sur la disko" @@ -615,7 +619,7 @@ inUse: "Uzata" editCode: "Redakti kodon" receiveAnnouncementFromInstance: "Ricevi informojn sciigintajn de la nodo" emailNotification: "Sciigoj per retpoŝto" -inChannelSearch: "Serĉi en kanalo" +inChannelSearch: "Serĉi en la kanalo" useReactionPickerForContextMenu: "Dekstre-klaki por malfermi la elektilon de reagoj" typingUsers: "{users} nun skribas…" clear: "Vakigi" @@ -635,7 +639,7 @@ administration: "Bontenado" accounts: "Kontoj" configure: "Agordi" recentPosts: "Novaj afiŝoj" -popularPosts: "Populara noto" +popularPosts: "Plej viditaj" shareWithNote: "Kundividi en noto" ads: "Reklamaĵo" expiration: "Limtempo" @@ -646,6 +650,7 @@ low: "Malalta" emailNotConfiguredWarning: "Vi ne agordis retpoŝtadreso." customCss: "Personecigita CSS" global: "Malloka" +squareAvatars: "Montri bildsimbolon kiel kvadrata" sent: "Sendi" received: "Ricevita" searchResult: "Serĉorezultoj" @@ -674,10 +679,14 @@ ffVisibilityDescription: "Oni permesas agordi tiuln kiuj povas vidi la homojn ki continueThread: "Pli vidi la mesaĝaron" incorrectPassword: "Nevalida pasvorto" voteConfirm: "Ĉu vi voĉdonas {choice}n?" +hide: "Kaŝi" leaveGroup: "Eliĝi el la grupo" leaveGroupConfirm: "Ĉu vi certas ke vi volas eliĝi el la grupo {name}?" welcomeBackWithName: "Bonrevenon, {name}!" clickToFinishEmailVerification: "Volu klaki [{ok}] por fini la konfirmon de vian retadreson" +smartphone: "Saĝtelefono" +tablet: "Platkomputilo" +auto: "Aŭtomate" _emailUnavailable: used: "La retpoŝto jam estas uzita." format: "Nevalida formato." @@ -688,6 +697,7 @@ _ffVisibility: followers: "Nur al sekvantoj" private: "Malpublikigita" _signup: + almostThere: "Preskaŭ plenumita" emailAddressInfo: "Entajpu vian retpoŝton" _accountDelete: accountDelete: "Forigi konton" @@ -698,7 +708,7 @@ _ad: _forgotPassword: enterEmail: "Entajpu la retpoŝton kiun vi registrigis al via konto. Ligilo por restarigi pasvorton estos sendita al la retadreso." _gallery: - my: "Mia afiŝo" + my: "Miaj afiŝoj" liked: "Ŝatitaj notoj" like: "Ŝati" _email: @@ -877,14 +887,18 @@ _cw: chars: "{count} literoj" files: "{count} dosiero(j)" _poll: - choiceN: "Balotilo {n}" - noMore: "Oni ne povas aldoni pli." + choiceN: "Ebla voĉdono {n}" + noMore: "Oni ne povas aldoni pli" + canMultipleVote: "Permesi plurelekton" expiration: "Limtempo" - infinite: "Neniam" deadlineTime: "hor" - votesCount: "{n} balotiloj" - vote: "Baloti" - closed: "Oni jam balotis ĝin" + duration: "Daŭro" + votesCount: "{n} voĉoj" + totalVotes: "Sume {n} voĉoj" + vote: "Voĉdoni" + showResult: "Vidi la rezultojn" + voted: "Voĉdonita" + closed: "Finita" _visibility: public: "Publika" publicDescription: "Publikigi al ĉiuj en la Fediverso" @@ -1089,7 +1103,7 @@ _notification: youGotReply: "{name} respondis" youGotQuote: "{name} citis" youRenoted: "{name} plusendis" - youGotPoll: "{name} balotis" + youGotPoll: "{name} voĉdonis" youGotMessagingMessageFromUser: "{name} sendis al vi mesaĝon" youGotMessagingMessageFromGroup: "Oni sendis al la grupo {name} mesaĝon" youWereFollowed: "Eksekvis vin" @@ -1104,6 +1118,7 @@ _notification: renote: "Plusendoj" quote: "Citi" reaction: "Reagoj" + pollVote: "Voĉdonoj en balotoj" receiveFollowRequest: "Ricevi peton de sekvado" followRequestAccepted: "Akceptita peto de sekvado" groupInvited: "Invitita al grupo" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 62d5587043..f4c9d19981 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -595,6 +595,8 @@ smtpSecure: "SMTP 接続に暗黙的なSSL/TLSを使用する" smtpSecureInfo: "STARTTLS使用時はオフにします。" testEmail: "配信テスト" wordMute: "ワードミュート" +regexpError: "正規表現エラー" +regexpErrorDescription: "{tab}ワードミュートの{line}行目の正規表現にエラーが発生しました:" instanceMute: "インスタンスミュート" userSaysSomething: "{name}が何かを言いました" makeActive: "アクティブにする" @@ -829,6 +831,8 @@ smartphone: "スマートフォン" tablet: "タブレット" auto: "自動" themeColor: "テーマカラー" +size: "サイズ" +numberOfColumn: "列の数" _emailUnavailable: used: "既に使用されています" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 7ee4f5be79..643770ca31 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -594,6 +594,8 @@ smtpSecure: "Použiť implicitné SSL/TLS pre SMTP spojenia" smtpSecureInfo: "Toto vypnite keď používate STARTTLS" testEmail: "Doručenie testovacieho emailu" wordMute: "Stíšenie slova" +regexpError: "Chyba v regulárnom výraze" +regexpErrorDescription: "Na riadku {line} sa vyskytla chyba v stíšenom slove {tab}." instanceMute: "Stíšené servery" userSaysSomething: "{name} niečo povedal/a" makeActive: "Aktivovať" @@ -827,6 +829,7 @@ overridedDeviceKind: "Typ zariadenia" smartphone: "Smartfón" tablet: "Tablet" auto: "Automaticky" +themeColor: "Farba témy" _emailUnavailable: used: "Táto emailová adresa sa už používa" format: "Formát emailovej adresy je nesprávny" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index f4f1680caa..568470c7b2 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -595,6 +595,8 @@ smtpSecure: "在 SMTP 连接中使用隐式 SSL / TLS" smtpSecureInfo: "使用STARTTLS时关闭。" testEmail: "邮件发送测试" wordMute: "文字屏蔽" +regexpError: "正则表达式错误" +regexpErrorDescription: "{tab} 屏蔽文字的第 {line} 行的正则表达式有错误:" instanceMute: "实例的屏蔽" userSaysSomething: "{name}说了什么" makeActive: "启用" @@ -828,6 +830,7 @@ overridedDeviceKind: "设备类型" smartphone: "智能手机" tablet: "平板" auto: "自动" +themeColor: "主题颜色" _emailUnavailable: used: "已经被使用过" format: "无效的格式" diff --git a/package.json b/package.json index 31a440da18..19ea4ef9ad 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "12.105.0", + "version": "12.106.0", "codename": "indigo", "repository": { "type": "git", diff --git a/packages/backend/migration/1644010796173-convert-hard-mutes.js b/packages/backend/migration/1644010796173-convert-hard-mutes.js new file mode 100644 index 0000000000..f06da65670 --- /dev/null +++ b/packages/backend/migration/1644010796173-convert-hard-mutes.js @@ -0,0 +1,64 @@ +const RE2 = require('re2'); +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class convertHardMutes1644010796173 { + name = 'convertHardMutes1644010796173' + + async up(queryRunner) { + let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile"`); + for(let i = 0; i < entries.length; i++) { + let words = entries[i].mutedWords + .map(line => { + const regexp = line.join(" ").match(/^\/(.+)\/(.*)$/); + if (regexp) { + // convert regexp's + try { + new RE2(regexp[1], regexp[2]); + return `/${regexp[1]}/${regexp[2]}`; + } catch (err) { + // invalid regex, ignore it + return []; + } + } else { + // remove empty segments + return line.filter(x => x !== ''); + } + }) + // remove empty lines + .filter(x => !(Array.isArray(x) && x.length === 0)); + + await queryRunner.connection.createQueryBuilder() + .update('user_profile') + .set({ + mutedWords: words + }) + .where('userId = :id', { id: entries[i].userId }) + .execute(); + } + } + + async down(queryRunner) { + let entries = await queryRunner.query(`SELECT "userId", "mutedWords" FROM "user_profile"`); + for(let i = 0; i < entries.length; i++) { + let words = entries[i].mutedWords + .map(line => { + if (Array.isArray(line)) { + return line; + } else { + // do not split regex at spaces again + return [line]; + } + }) + // remove empty lines + .filter(x => !(Array.isArray(x) && x.length === 0)); + + await queryRunner.connection.createQueryBuilder() + .update('user_profile') + .set({ + mutedWords: words + }) + .where('userId = :id', { id: entries[i].userId }) + .execute(); + } + } +} diff --git a/packages/backend/migration/1644481657998-chart-v15.js b/packages/backend/migration/1644481657998-chart-v15.js new file mode 100644 index 0000000000..4eee83f612 --- /dev/null +++ b/packages/backend/migration/1644481657998-chart-v15.js @@ -0,0 +1,31 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class chartV151644481657998 { + name = 'chartV151644481657998' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_total"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_inc"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___instance_dec"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_total"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_inc"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___instance_dec"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___sub" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___pub" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___sub" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___pub" smallint NOT NULL DEFAULT '0'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___pub"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" DROP COLUMN "___sub"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___pub"`); + await queryRunner.query(`ALTER TABLE "__chart__federation" DROP COLUMN "___sub"`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_dec" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_inc" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart_day__federation" ADD "___instance_total" integer NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_dec" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_inc" smallint NOT NULL DEFAULT '0'`); + await queryRunner.query(`ALTER TABLE "__chart__federation" ADD "___instance_total" integer NOT NULL DEFAULT '0'`); + } +} diff --git a/packages/backend/migration/1644551208096-following-indexes.js b/packages/backend/migration/1644551208096-following-indexes.js new file mode 100644 index 0000000000..6c149d8069 --- /dev/null +++ b/packages/backend/migration/1644551208096-following-indexes.js @@ -0,0 +1,15 @@ +const { MigrationInterface, QueryRunner } = require("typeorm"); + +module.exports = class followingIndexes1644551208096 { + name = 'followingIndexes1644551208096' + + async up(queryRunner) { + await queryRunner.query(`CREATE INDEX "IDX_4ccd2239268ebbd1b35e318754" ON "following" ("followerHost") `); + await queryRunner.query(`CREATE INDEX "IDX_fcdafee716dfe9c3b5fde90f30" ON "following" ("followeeHost") `); + } + + async down(queryRunner) { + await queryRunner.query(`DROP INDEX "public"."IDX_fcdafee716dfe9c3b5fde90f30"`); + await queryRunner.query(`DROP INDEX "public"."IDX_4ccd2239268ebbd1b35e318754"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index d81688cbfe..c331da7324 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -123,7 +123,7 @@ "ms": "3.0.0-canary.1", "multer": "1.4.4", "nested-property": "4.0.0", - "node-fetch": "2.6.1", + "node-fetch": "2.6.7", "nodemailer": "6.7.2", "os-utils": "0.0.14", "parse5": "6.0.1", diff --git a/packages/backend/src/misc/check-word-mute.ts b/packages/backend/src/misc/check-word-mute.ts index e2e871dd2b..dedda3cdf6 100644 --- a/packages/backend/src/misc/check-word-mute.ts +++ b/packages/backend/src/misc/check-word-mute.ts @@ -11,26 +11,31 @@ type UserLike = { id: User['id']; }; -export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: string[][]): Promise { +export async function checkWordMute(note: NoteLike, me: UserLike | null | undefined, mutedWords: Array): Promise { // 自分自身 if (me && (note.userId === me.id)) return false; - const words = mutedWords - // Clean up - .map(xs => xs.filter(x => x !== '')) - .filter(xs => xs.length > 0); - - if (words.length > 0) { + if (mutedWords.length > 0) { if (note.text == null) return false; - const matched = words.some(and => - and.every(keyword => { - const regexp = keyword.match(/^\/(.+)\/(.*)$/); - if (regexp) { + const matched = mutedWords.some(filter => { + if (Array.isArray(filter)) { + return filter.every(keyword => note.text!.includes(keyword)); + } else { + // represents RegExp + const regexp = filter.match(/^\/(.+)\/(.*)$/); + + // This should never happen due to input sanitisation. + if (!regexp) return false; + + try { return new RE2(regexp[1], regexp[2]).test(note.text!); + } catch (err) { + // This should never happen due to input sanitisation. + return false; } - return note.text!.includes(keyword); - })); + } + }); if (matched) return true; } diff --git a/packages/backend/src/models/entities/following.ts b/packages/backend/src/models/entities/following.ts index c3631e8501..ad387e5188 100644 --- a/packages/backend/src/models/entities/following.ts +++ b/packages/backend/src/models/entities/following.ts @@ -41,6 +41,7 @@ export class Following { public follower: User | null; //#region Denormalized fields + @Index() @Column('varchar', { length: 128, nullable: true, comment: '[Denormalized]', @@ -59,6 +60,7 @@ export class Following { }) public followerSharedInbox: string | null; + @Index() @Column('varchar', { length: 128, nullable: true, comment: '[Denormalized]', diff --git a/packages/backend/src/queue/index.ts b/packages/backend/src/queue/index.ts index 16acabfbf8..62f372f3a8 100644 --- a/packages/backend/src/queue/index.ts +++ b/packages/backend/src/queue/index.ts @@ -258,6 +258,11 @@ export default function() { processDb(dbQueue); processObjectStorage(objectStorageQueue); + systemQueue.add('tickCharts', { + }, { + repeat: { cron: '55 * * * *' }, + }); + systemQueue.add('resyncCharts', { }, { repeat: { cron: '0 0 * * *' }, diff --git a/packages/backend/src/queue/processors/system/index.ts b/packages/backend/src/queue/processors/system/index.ts index 636fefc402..1513ea4a84 100644 --- a/packages/backend/src/queue/processors/system/index.ts +++ b/packages/backend/src/queue/processors/system/index.ts @@ -1,8 +1,10 @@ import * as Bull from 'bull'; +import { tickCharts } from './tick-charts'; import { resyncCharts } from './resync-charts'; import { cleanCharts } from './clean-charts'; const jobs = { + tickCharts, resyncCharts, cleanCharts, } as Record> | Bull.ProcessPromiseFunction>>; diff --git a/packages/backend/src/queue/processors/system/tick-charts.ts b/packages/backend/src/queue/processors/system/tick-charts.ts new file mode 100644 index 0000000000..d53089f89c --- /dev/null +++ b/packages/backend/src/queue/processors/system/tick-charts.ts @@ -0,0 +1,28 @@ +import * as Bull from 'bull'; + +import { queueLogger } from '../../logger'; +import { activeUsersChart, driveChart, federationChart, hashtagChart, instanceChart, notesChart, perUserDriveChart, perUserFollowingChart, perUserNotesChart, perUserReactionsChart, usersChart, apRequestChart } from '@/services/chart/index'; + +const logger = queueLogger.createSubLogger('tick-charts'); + +export async function tickCharts(job: Bull.Job>, done: any): Promise { + logger.info(`Tick charts...`); + + await Promise.all([ + federationChart.tick(false), + notesChart.tick(false), + usersChart.tick(false), + activeUsersChart.tick(false), + instanceChart.tick(false), + perUserNotesChart.tick(false), + driveChart.tick(false), + perUserReactionsChart.tick(false), + hashtagChart.tick(false), + perUserFollowingChart.tick(false), + perUserDriveChart.tick(false), + apRequestChart.tick(false), + ]); + + logger.succ(`All charts successfully ticked.`); + done(); +} diff --git a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts b/packages/backend/src/server/api/endpoints/admin/resync-chart.ts deleted file mode 100644 index d80d2b0426..0000000000 --- a/packages/backend/src/server/api/endpoints/admin/resync-chart.ts +++ /dev/null @@ -1,22 +0,0 @@ -import define from '../../define'; -import { driveChart, notesChart, usersChart } from '@/services/chart/index'; -import { insertModerationLog } from '@/services/insert-moderation-log'; - -export const meta = { - tags: ['admin'], - - requireCredential: true, - requireModerator: true, -} as const; - -// eslint-disable-next-line import/no-default-export -export default define(meta, async (ps, me) => { - insertModerationLog(me, 'chartResync'); - - driveChart.resync(); - notesChart.resync(); - usersChart.resync(); - - // TODO: ユーザーごとのチャートもキューに入れて更新する - // TODO: インスタンスごとのチャートもキューに入れて更新する -}); diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index eb57aa2bfc..aec7bbd2e1 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -1,3 +1,4 @@ +const RE2 = require('re2'); import $ from 'cafy'; import * as mfm from 'mfm-js'; import { ID } from '@/misc/cafy-id'; @@ -117,7 +118,7 @@ export const meta = { }, mutedWords: { - validator: $.optional.arr($.arr($.str)), + validator: $.optional.arr($.either($.arr($.str.min(1)).min(1), $.str)), }, mutedInstances: { @@ -163,6 +164,12 @@ export const meta = { code: 'NO_SUCH_PAGE', id: '8e01b590-7eb9-431b-a239-860e086c408e', }, + + invalidRegexp: { + message: 'Invalid Regular Expression.', + code: 'INVALID_REGEXP', + id: '0d786918-10df-41cd-8f33-8dec7d9a89a5', + } }, res: { @@ -191,6 +198,18 @@ export default define(meta, async (ps, _user, token) => { if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId; if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId; if (ps.mutedWords !== undefined) { + // validate regular expression syntax + ps.mutedWords.filter(x => !Array.isArray(x)).forEach(x => { + const regexp = x.match(/^\/(.+)\/(.*)$/); + if (!regexp) throw new ApiError(meta.errors.invalidRegexp); + + try { + new RE2(regexp[1], regexp[2]); + } catch (err) { + throw new ApiError(meta.errors.invalidRegexp); + } + }); + profileUpdates.mutedWords = ps.mutedWords; profileUpdates.enableWordMute = ps.mutedWords.length > 0; } diff --git a/packages/backend/src/services/chart/charts/active-users.ts b/packages/backend/src/services/chart/charts/active-users.ts index 87dd95f4dc..5baf46f772 100644 --- a/packages/backend/src/services/chart/charts/active-users.ts +++ b/packages/backend/src/services/chart/charts/active-users.ts @@ -18,7 +18,12 @@ export default class ActiveUsersChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/ap-request.ts b/packages/backend/src/services/chart/charts/ap-request.ts index bac5e425c8..ca763c8847 100644 --- a/packages/backend/src/services/chart/charts/ap-request.ts +++ b/packages/backend/src/services/chart/charts/ap-request.ts @@ -12,7 +12,12 @@ export default class ApRequestChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/drive.ts b/packages/backend/src/services/chart/charts/drive.ts index 2f00adae2b..288689784e 100644 --- a/packages/backend/src/services/chart/charts/drive.ts +++ b/packages/backend/src/services/chart/charts/drive.ts @@ -15,7 +15,12 @@ export default class DriveChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/entities/federation.ts b/packages/backend/src/services/chart/charts/entities/federation.ts index 0c8c20991d..6b2089f0b4 100644 --- a/packages/backend/src/services/chart/charts/entities/federation.ts +++ b/packages/backend/src/services/chart/charts/entities/federation.ts @@ -3,12 +3,11 @@ import Chart from '../../core'; export const name = 'federation'; export const schema = { - 'instance.total': { accumulate: true }, - 'instance.inc': { range: 'small' }, - 'instance.dec': { range: 'small' }, 'deliveredInstances': { uniqueIncrement: true, range: 'small' }, 'inboxInstances': { uniqueIncrement: true, range: 'small' }, 'stalled': { uniqueIncrement: true, range: 'small' }, + 'sub': { accumulate: true, range: 'small' }, + 'pub': { accumulate: true, range: 'small' }, } as const; export const entity = Chart.schemaToEntity(name, schema); diff --git a/packages/backend/src/services/chart/charts/federation.ts b/packages/backend/src/services/chart/charts/federation.ts index 19c75c98ad..211ba1debc 100644 --- a/packages/backend/src/services/chart/charts/federation.ts +++ b/packages/backend/src/services/chart/charts/federation.ts @@ -1,6 +1,6 @@ import autobind from 'autobind-decorator'; import Chart, { KVs } from '../core'; -import { Instances } from '@/models/index'; +import { Followings } from '@/models/index'; import { name, schema } from './entities/federation'; /** @@ -13,23 +13,30 @@ export default class FederationChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { - const [total] = await Promise.all([ - Instances.count({}), - ]); - + protected async tickMajor(): Promise>> { return { - 'instance.total': total, }; } @autobind - public async update(isAdditional: boolean): Promise { - await this.commit({ - 'instance.total': isAdditional ? 1 : -1, - 'instance.inc': isAdditional ? 1 : 0, - 'instance.dec': isAdditional ? 0 : 1, - }); + protected async tickMinor(): Promise>> { + const [sub, pub] = await Promise.all([ + Followings.createQueryBuilder('following') + .select('COUNT(DISTINCT following.followeeHost)') + .where('following.followeeHost IS NOT NULL') + .getRawOne() + .then(x => parseInt(x.count, 10)), + Followings.createQueryBuilder('following') + .select('COUNT(DISTINCT following.followerHost)') + .where('following.followerHost IS NOT NULL') + .getRawOne() + .then(x => parseInt(x.count, 10)), + ]); + + return { + 'sub': sub, + 'pub': pub, + }; } @autobind diff --git a/packages/backend/src/services/chart/charts/hashtag.ts b/packages/backend/src/services/chart/charts/hashtag.ts index 0b7bc467d2..cbae686833 100644 --- a/packages/backend/src/services/chart/charts/hashtag.ts +++ b/packages/backend/src/services/chart/charts/hashtag.ts @@ -14,7 +14,12 @@ export default class HashtagChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/instance.ts b/packages/backend/src/services/chart/charts/instance.ts index 5ea4d567e1..930ac4729b 100644 --- a/packages/backend/src/services/chart/charts/instance.ts +++ b/packages/backend/src/services/chart/charts/instance.ts @@ -16,7 +16,7 @@ export default class InstanceChart extends Chart { } @autobind - protected async queryCurrentState(group: string): Promise>> { + protected async tickMajor(group: string): Promise>> { const [ notesCount, usersCount, @@ -42,6 +42,11 @@ export default class InstanceChart extends Chart { }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async requestReceived(host: string): Promise { await this.commit({ diff --git a/packages/backend/src/services/chart/charts/notes.ts b/packages/backend/src/services/chart/charts/notes.ts index 5c56a9a718..624ee5db28 100644 --- a/packages/backend/src/services/chart/charts/notes.ts +++ b/packages/backend/src/services/chart/charts/notes.ts @@ -15,7 +15,7 @@ export default class NotesChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { const [localCount, remoteCount] = await Promise.all([ Notes.count({ userHost: null }), Notes.count({ userHost: Not(IsNull()) }), @@ -27,6 +27,11 @@ export default class NotesChart extends Chart { }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async update(note: Note, isAdditional: boolean): Promise { const prefix = note.userHost === null ? 'local' : 'remote'; diff --git a/packages/backend/src/services/chart/charts/per-user-drive.ts b/packages/backend/src/services/chart/charts/per-user-drive.ts index 969ed018f9..ae9e8c5694 100644 --- a/packages/backend/src/services/chart/charts/per-user-drive.ts +++ b/packages/backend/src/services/chart/charts/per-user-drive.ts @@ -14,7 +14,7 @@ export default class PerUserDriveChart extends Chart { } @autobind - protected async queryCurrentState(group: string): Promise>> { + protected async tickMajor(group: string): Promise>> { const [count, size] = await Promise.all([ DriveFiles.count({ userId: group }), DriveFiles.calcDriveUsageOf(group), @@ -26,6 +26,11 @@ export default class PerUserDriveChart extends Chart { }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async update(file: DriveFile, isAdditional: boolean): Promise { const fileSizeKb = file.size / 1000; diff --git a/packages/backend/src/services/chart/charts/per-user-following.ts b/packages/backend/src/services/chart/charts/per-user-following.ts index cdd0aad947..0b39881c14 100644 --- a/packages/backend/src/services/chart/charts/per-user-following.ts +++ b/packages/backend/src/services/chart/charts/per-user-following.ts @@ -15,7 +15,7 @@ export default class PerUserFollowingChart extends Chart { } @autobind - protected async queryCurrentState(group: string): Promise>> { + protected async tickMajor(group: string): Promise>> { const [ localFollowingsCount, localFollowersCount, @@ -36,6 +36,11 @@ export default class PerUserFollowingChart extends Chart { }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async update(follower: { id: User['id']; host: User['host']; }, followee: { id: User['id']; host: User['host']; }, isFollow: boolean): Promise { const prefixFollower = Users.isLocalUser(follower) ? 'local' : 'remote'; diff --git a/packages/backend/src/services/chart/charts/per-user-notes.ts b/packages/backend/src/services/chart/charts/per-user-notes.ts index 6a4f0363b2..01a2785158 100644 --- a/packages/backend/src/services/chart/charts/per-user-notes.ts +++ b/packages/backend/src/services/chart/charts/per-user-notes.ts @@ -15,7 +15,7 @@ export default class PerUserNotesChart extends Chart { } @autobind - protected async queryCurrentState(group: string): Promise>> { + protected async tickMajor(group: string): Promise>> { const [count] = await Promise.all([ Notes.count({ userId: group }), ]); @@ -25,6 +25,11 @@ export default class PerUserNotesChart extends Chart { }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async update(user: { id: User['id'] }, note: Note, isAdditional: boolean): Promise { await this.commit({ diff --git a/packages/backend/src/services/chart/charts/per-user-reactions.ts b/packages/backend/src/services/chart/charts/per-user-reactions.ts index 2ec347f40a..59af0e86c0 100644 --- a/packages/backend/src/services/chart/charts/per-user-reactions.ts +++ b/packages/backend/src/services/chart/charts/per-user-reactions.ts @@ -15,7 +15,12 @@ export default class PerUserReactionsChart extends Chart { } @autobind - protected async queryCurrentState(group: string): Promise>> { + protected async tickMajor(group: string): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/test-grouped.ts b/packages/backend/src/services/chart/charts/test-grouped.ts index 5f0b1aafdc..19b2135849 100644 --- a/packages/backend/src/services/chart/charts/test-grouped.ts +++ b/packages/backend/src/services/chart/charts/test-grouped.ts @@ -14,12 +14,17 @@ export default class TestGroupedChart extends Chart { } @autobind - protected async queryCurrentState(group: string): Promise>> { + protected async tickMajor(group: string): Promise>> { return { 'foo.total': this.total[group], }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async increment(group: string): Promise { if (this.total[group] == null) this.total[group] = 0; diff --git a/packages/backend/src/services/chart/charts/test-intersection.ts b/packages/backend/src/services/chart/charts/test-intersection.ts index c6ba71a956..6fd780f9b9 100644 --- a/packages/backend/src/services/chart/charts/test-intersection.ts +++ b/packages/backend/src/services/chart/charts/test-intersection.ts @@ -12,7 +12,12 @@ export default class TestIntersectionChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/test-unique.ts b/packages/backend/src/services/chart/charts/test-unique.ts index e67036acef..2c9cc2fd6a 100644 --- a/packages/backend/src/services/chart/charts/test-unique.ts +++ b/packages/backend/src/services/chart/charts/test-unique.ts @@ -12,7 +12,12 @@ export default class TestUniqueChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { + return {}; + } + + @autobind + protected async tickMinor(): Promise>> { return {}; } diff --git a/packages/backend/src/services/chart/charts/test.ts b/packages/backend/src/services/chart/charts/test.ts index 878acd51be..b539625c10 100644 --- a/packages/backend/src/services/chart/charts/test.ts +++ b/packages/backend/src/services/chart/charts/test.ts @@ -14,12 +14,17 @@ export default class TestChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { return { 'foo.total': this.total, }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async increment(): Promise { this.total++; diff --git a/packages/backend/src/services/chart/charts/users.ts b/packages/backend/src/services/chart/charts/users.ts index 50fca3a8d6..70ef89f8cd 100644 --- a/packages/backend/src/services/chart/charts/users.ts +++ b/packages/backend/src/services/chart/charts/users.ts @@ -15,7 +15,7 @@ export default class UsersChart extends Chart { } @autobind - protected async queryCurrentState(): Promise>> { + protected async tickMajor(): Promise>> { const [localCount, remoteCount] = await Promise.all([ Users.count({ host: null }), Users.count({ host: Not(IsNull()) }), @@ -27,6 +27,11 @@ export default class UsersChart extends Chart { }; } + @autobind + protected async tickMinor(): Promise>> { + return {}; + } + @autobind public async update(user: { id: User['id'], host: User['host'] }, isAdditional: boolean): Promise { const prefix = Users.isLocalUser(user) ? 'local' : 'remote'; diff --git a/packages/backend/src/services/chart/core.ts b/packages/backend/src/services/chart/core.ts index 5888e1a144..61a7575706 100644 --- a/packages/backend/src/services/chart/core.ts +++ b/packages/backend/src/services/chart/core.ts @@ -81,7 +81,15 @@ export default abstract class Chart { protected repositoryForHour: Repository>; protected repositoryForDay: Repository>; - protected abstract queryCurrentState(group: string | null): Promise>>; + /** + * 1日に一回程度実行されれば良いような計算処理を入れる(主にCASCADE削除などアプリケーション側で感知できない変動によるズレの修正用) + */ + protected abstract tickMajor(group: string | null): Promise>>; + + /** + * 少なくとも最小スパン内に1回は実行されて欲しい計算処理を入れる + */ + protected abstract tickMinor(group: string | null): Promise>>; @autobind private static convertSchemaToColumnDefinitions(schema: Schema): Record { @@ -445,8 +453,8 @@ export default abstract class Chart { } @autobind - public async resync(group: string | null = null): Promise { - const data = await this.queryCurrentState(group); + public async tick(major: boolean, group: string | null = null): Promise { + const data = major ? await this.tickMajor(group) : await this.tickMinor(group); const columns = {} as Record; for (const [k, v] of Object.entries(data)) { @@ -480,6 +488,11 @@ export default abstract class Chart { update(logHour, logDay)); } + @autobind + public resync(group: string | null = null): Promise { + return this.tick(true, group); + } + @autobind public async clean(): Promise { const current = dateUTC(Chart.getCurrentDate()); diff --git a/packages/backend/src/services/register-or-fetch-instance-doc.ts b/packages/backend/src/services/register-or-fetch-instance-doc.ts index 18b42ed15b..c42506a2ed 100644 --- a/packages/backend/src/services/register-or-fetch-instance-doc.ts +++ b/packages/backend/src/services/register-or-fetch-instance-doc.ts @@ -1,6 +1,5 @@ import { Instance } from '@/models/entities/instance'; import { Instances } from '@/models/index'; -import { federationChart } from '@/services/chart/index'; import { genId } from '@/misc/gen-id'; import { toPuny } from '@/misc/convert-host'; import { Cache } from '@/misc/cache'; @@ -23,8 +22,6 @@ export async function registerOrFetchInstanceDoc(host: string): Promise Instances.findOneOrFail(x.identifiers[0])); - federationChart.update(true); - cache.set(host, i); return i; } else { diff --git a/packages/backend/yarn.lock b/packages/backend/yarn.lock index c4afef8b5c..878ba11f7f 100644 --- a/packages/backend/yarn.lock +++ b/packages/backend/yarn.lock @@ -3134,9 +3134,9 @@ github-from-package@0.0.0: integrity sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4= glob-parent@^5.1.0, glob-parent@~5.1.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.1.tgz#b6c1ef417c4e5663ea498f1c45afac6916bbc229" - integrity sha512-FnI+VGOpnlGHWZxthPGR+QhR78fuiK0sNLkHQv+bL9fQi57lNNdquIbna/WrfROrolq8GK5Ek6BiMwqL/voRYQ== + version "5.1.2" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" + integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== dependencies: is-glob "^4.0.1" @@ -3741,14 +3741,7 @@ is-generator-function@^1.0.7: resolved "https://registry.yarnpkg.com/is-generator-function/-/is-generator-function-1.0.7.tgz#d2132e529bb0000a7f80794d4bdf5cd5e5813522" integrity sha512-YZc5EwyO4f2kWCax7oegfuSr9mFz1ZvieNYBEjmukLxgXfBUbxAWGVF7GZf0zidYtoBl3WvC07YK0wT76a+Rtw== -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@~4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.1.tgz#7567dbe9f2f5e2467bc77ab83c4a29482407a5dc" - integrity sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg== - dependencies: - is-extglob "^2.1.1" - -is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -4643,17 +4636,10 @@ minipass-sized@^1.0.3: dependencies: minipass "^3.0.0" -minipass@^3.0.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.1.tgz#7607ce778472a185ad6d89082aa2070f79cedcd5" - integrity sha512-UFqVihv6PQgwj8/yTGvl9kPz7xIAY+R5z6XYjRInD3Gk3qx6QGSD6zEcpeG4Dy/lQnv1J6zv8ejV90hyYIKf3w== - dependencies: - yallist "^4.0.0" - -minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: - version "3.1.3" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.3.tgz#7d42ff1f39635482e15f9cdb53184deebd5815fd" - integrity sha512-Mgd2GdMVzY+x3IJ+oHnVM+KG3lA5c8tnabyJKmHSaG2kAGpudxuOf8ToDkhumF7UzME7DecbQE9uOZhNm7PuJg== +minipass@^3.0.0, minipass@^3.1.0, minipass@^3.1.1, minipass@^3.1.3: + version "3.1.6" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-3.1.6.tgz#3b8150aa688a711a1521af5e8779c1d3bb4f45ee" + integrity sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ== dependencies: yallist "^4.0.0" @@ -4882,10 +4868,12 @@ node-fetch@*: fetch-blob "^3.1.4" formdata-polyfill "^4.0.10" -node-fetch@2.6.1, node-fetch@^2.6.1: - version "2.6.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052" - integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw== +node-fetch@2.6.7, node-fetch@^2.6.1: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" node-fetch@3.0.0-beta.9: version "3.0.0-beta.9" @@ -4959,9 +4947,9 @@ normalize-path@^3.0.0, normalize-path@~3.0.0: integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== normalize-url@^4.1.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.0.tgz#453354087e6ca96957bd8f5baf753f5982142129" - integrity sha512-2s47yzUxdexf1OhyRi4Em83iQk0aPvwTddtFz4hnSSw9dCEsLEGf6SwIO8ss/19S9iBb5sJaOuTvTGDeZI00BQ== + version "4.5.1" + resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a" + integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA== npm-run-path@^5.0.1: version "5.0.1" @@ -5293,9 +5281,9 @@ path-key@^4.0.0: integrity sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ== path-parse@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" - integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== + version "1.0.7" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" + integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== path-to-regexp@^6.1.0: version "6.1.0" @@ -6169,20 +6157,11 @@ signal-exit@^3.0.5: integrity sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ== simple-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" - integrity sha1-c0TLuLbib7J9ZrL8hvn21Zl1IcY= + version "1.0.1" + resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" + integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.0.tgz#73fa628278d21de83dadd5512d2cc1f4872bd675" - integrity sha512-ZalZGexYr3TA0SwySsr5HlgOOinS4Jsa8YB2GJ6lUNAazyAu4KG/VmzMTwAt2YVXzzVj8QmefmAonZIK2BSGcQ== - dependencies: - decompress-response "^6.0.0" - once "^1.3.1" - simple-concat "^1.0.0" - -simple-get@^4.0.1: +simple-get@^4.0.0, simple-get@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== @@ -6542,19 +6521,7 @@ tar-stream@^2.1.4, tar-stream@^2.2.0: inherits "^2.0.3" readable-stream "^3.1.1" -tar@^6.0.2: - version "6.0.5" - resolved "https://registry.yarnpkg.com/tar/-/tar-6.0.5.tgz#bde815086e10b39f1dcd298e89d596e1535e200f" - integrity sha512-0b4HOimQHj9nXNEAA7zWwMM91Zhhba3pspja6sQbgTpynOJf+bkjBnfybNYzbpLbnwXnbyB4LOREvlyXLkCHSg== - dependencies: - chownr "^2.0.0" - fs-minipass "^2.0.0" - minipass "^3.0.0" - minizlib "^2.1.1" - mkdirp "^1.0.3" - yallist "^4.0.0" - -tar@^6.1.2: +tar@^6.0.2, tar@^6.1.2: version "6.1.11" resolved "https://registry.yarnpkg.com/tar/-/tar-6.1.11.tgz#6760a38f003afa1b2ffd0ffe9e9abbd0eab3d621" integrity sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA== @@ -6653,6 +6620,11 @@ tr46@^3.0.0: dependencies: punycode "^2.1.1" +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= + trace-redirect@1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/trace-redirect/-/trace-redirect-1.0.6.tgz#ac629b5bf8247d30dde5a35fe9811b811075b504" @@ -6998,6 +6970,11 @@ web-streams-polyfill@^3.0.3: resolved "https://registry.yarnpkg.com/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz#a6b74026b38e4885869fb5c589e90b95ccfc7965" integrity sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA== +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= + webidl-conversions@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" @@ -7035,6 +7012,14 @@ whatwg-url@^10.0.0: tr46 "^3.0.0" webidl-conversions "^7.0.0" +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha1-lmRU6HZUYuN2RNNib2dCzotwll0= + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" diff --git a/packages/client/src/components/chart.vue b/packages/client/src/components/chart.vue index ced0d481c4..b90c790c3f 100644 --- a/packages/client/src/components/chart.vue +++ b/packages/client/src/components/chart.vue @@ -213,17 +213,18 @@ export default defineComponent({ data: x.data.slice().reverse(), tension: 0.3, pointRadius: 0, - borderWidth: 2, + borderWidth: props.bar ? 0 : 2, borderColor: x.color ? x.color : getColor(i), borderDash: x.borderDash || [], borderJoinStyle: 'round', - backgroundColor: alpha(x.color ? x.color : getColor(i), 0.1), - gradient: { + borderRadius: props.bar ? 3 : undefined, + backgroundColor: props.bar ? (x.color ? x.color : getColor(i)) : alpha(x.color ? x.color : getColor(i), 0.1), + gradient: props.bar ? undefined : { backgroundColor: { axis: 'y', colors: { 0: alpha(x.color ? x.color : getColor(i), 0), - [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.1), + [maxes[i]]: alpha(x.color ? x.color : getColor(i), 0.15), }, }, }, @@ -248,6 +249,7 @@ export default defineComponent({ x: { type: 'time', stacked: props.stacked, + offset: false, time: { stepSize: 1, unit: props.span === 'day' ? 'month' : 'day', @@ -271,6 +273,7 @@ export default defineComponent({ y: { position: 'left', stacked: props.stacked, + suggestedMax: 100, grid: { color: gridColor, borderColor: 'rgb(0, 0, 0, 0)', @@ -308,7 +311,7 @@ export default defineComponent({ }, external: externalTooltipHandler, }, - zoom: { + zoom: props.detailed ? { pan: { enabled: true, }, @@ -334,7 +337,7 @@ export default defineComponent({ max: 'original', }, } - }, + } : undefined, gradient, }, }, @@ -370,14 +373,14 @@ export default defineComponent({ const raw = await os.api('charts/federation', { limit: props.limit, span: props.span }); return { series: [{ - name: 'Total', + name: 'Sub', type: 'area', - data: format(raw.instance.total), - color: '#888888', + data: format(raw.sub), + color: colors.orange, }, { - name: 'Inc/Dec', + name: 'Pub', type: 'area', - data: format(sum(raw.instance.inc, negate(raw.instance.dec))), + data: format(raw.pub), color: colors.purple, }, { name: 'Received', @@ -426,7 +429,6 @@ export default defineComponent({ series: [{ name: 'All', type: 'line', - borderDash: [5, 5], data: format(type == 'combined' ? sum(raw.local.inc, negate(raw.local.dec), raw.remote.inc, negate(raw.remote.dec)) : sum(raw[type].inc, negate(raw[type].dec)) @@ -750,20 +752,28 @@ export default defineComponent({ series: [...(props.args.withoutAll ? [] : [{ name: 'All', type: 'line', - borderDash: [5, 5], data: format(sum(raw.inc, negate(raw.dec))), + color: '#888888', }]), { + name: 'With file', + type: 'area', + data: format(raw.diffs.withFile), + color: colors.purple, + }, { name: 'Renotes', type: 'area', data: format(raw.diffs.renote), + color: colors.green, }, { name: 'Replies', type: 'area', data: format(raw.diffs.reply), + color: colors.yellow, }, { name: 'Normal', type: 'area', data: format(raw.diffs.normal), + color: colors.blue, }], }; }; diff --git a/packages/client/src/components/emoji-picker.vue b/packages/client/src/components/emoji-picker.vue index 3e1208979f..8601ea121c 100644 --- a/packages/client/src/components/emoji-picker.vue +++ b/packages/client/src/components/emoji-picker.vue @@ -1,5 +1,5 @@