Merge branch 'develop' into feat/drive-file-usage-hints

This commit is contained in:
naskya 2024-04-21 10:42:25 +09:00
commit 4d34e14dd8
No known key found for this signature in database
GPG key ID: 712D413B3A9FED5C
28 changed files with 129 additions and 169 deletions

View file

@ -5,6 +5,10 @@ Critical security updates are indicated by the :warning: icon.
- Server administrators should check [notice-for-admins.md](./notice-for-admins.md) as well.
- Third-party client/bot developers may want to check [api-change.md](./api-change.md) as well.
## [v20240421](https://firefish.dev/firefish/firefish/-/merge_requests/10756/commits)
- Fix bugs
## [v20240413](https://firefish.dev/firefish/firefish/-/merge_requests/10741/commits)
- Add "Media" tab to user page

View file

@ -1,6 +1,6 @@
{
"name": "firefish",
"version": "20240413",
"version": "20240421",
"repository": {
"type": "git",
"url": "https://firefish.dev/firefish/firefish.git"

View file

@ -17,7 +17,7 @@ regenerate-entities:
attribute=$$(printf 'cfg_attr(feature = "napi", napi_derive::napi(object, js_name = "%s", use_nullable = true))' "$${jsname}"); \
sed -i "s/NAPI_EXTRA_ATTR_PLACEHOLDER/$${attribute}/" "$${file}"; \
done
sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi)]/' \
sed -i 's/#\[derive(Debug, Clone, PartialEq, Eq, EnumIter, DeriveActiveEnum)\]/#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]\n#[cfg_attr(not(feature = "napi"), derive(Clone))]\n#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]/' \
src/model/entity/sea_orm_active_enums.rs
cargo fmt --all --

View file

@ -772,81 +772,81 @@ export interface ReplyMuting {
muteeId: string
muterId: string
}
export const enum AntennaSrcEnum {
All = 0,
Group = 1,
Home = 2,
Instances = 3,
List = 4,
Users = 5
export enum AntennaSrcEnum {
All = 'all',
Group = 'group',
Home = 'home',
Instances = 'instances',
List = 'list',
Users = 'users'
}
export const enum MutedNoteReasonEnum {
Manual = 0,
Other = 1,
Spam = 2,
Word = 3
export enum MutedNoteReasonEnum {
Manual = 'manual',
Other = 'other',
Spam = 'spam',
Word = 'word'
}
export const enum NoteVisibilityEnum {
Followers = 0,
Hidden = 1,
Home = 2,
Public = 3,
Specified = 4
export enum NoteVisibilityEnum {
Followers = 'followers',
Hidden = 'hidden',
Home = 'home',
Public = 'public',
Specified = 'specified'
}
export const enum NotificationTypeEnum {
App = 0,
Follow = 1,
FollowRequestAccepted = 2,
GroupInvited = 3,
Mention = 4,
PollEnded = 5,
PollVote = 6,
Quote = 7,
Reaction = 8,
ReceiveFollowRequest = 9,
Renote = 10,
Reply = 11
export enum NotificationTypeEnum {
App = 'app',
Follow = 'follow',
FollowRequestAccepted = 'followRequestAccepted',
GroupInvited = 'groupInvited',
Mention = 'mention',
PollEnded = 'pollEnded',
PollVote = 'pollVote',
Quote = 'quote',
Reaction = 'reaction',
ReceiveFollowRequest = 'receiveFollowRequest',
Renote = 'renote',
Reply = 'reply'
}
export const enum PageVisibilityEnum {
Followers = 0,
Public = 1,
Specified = 2
export enum PageVisibilityEnum {
Followers = 'followers',
Public = 'public',
Specified = 'specified'
}
export const enum PollNotevisibilityEnum {
Followers = 0,
Home = 1,
Public = 2,
Specified = 3
export enum PollNotevisibilityEnum {
Followers = 'followers',
Home = 'home',
Public = 'public',
Specified = 'specified'
}
export const enum RelayStatusEnum {
Accepted = 0,
Rejected = 1,
Requesting = 2
export enum RelayStatusEnum {
Accepted = 'accepted',
Rejected = 'rejected',
Requesting = 'requesting'
}
export const enum UserEmojimodpermEnum {
Add = 0,
Full = 1,
Mod = 2,
Unauthorized = 3
export enum UserEmojimodpermEnum {
Add = 'add',
Full = 'full',
Mod = 'mod',
Unauthorized = 'unauthorized'
}
export const enum UserProfileFfvisibilityEnum {
Followers = 0,
Private = 1,
Public = 2
export enum UserProfileFfvisibilityEnum {
Followers = 'followers',
Private = 'private',
Public = 'public'
}
export const enum UserProfileMutingnotificationtypesEnum {
App = 0,
Follow = 1,
FollowRequestAccepted = 2,
GroupInvited = 3,
Mention = 4,
PollEnded = 5,
PollVote = 6,
Quote = 7,
Reaction = 8,
ReceiveFollowRequest = 9,
Renote = 10,
Reply = 11
export enum UserProfileMutingnotificationtypesEnum {
App = 'app',
Follow = 'follow',
FollowRequestAccepted = 'followRequestAccepted',
GroupInvited = 'groupInvited',
Mention = 'mention',
PollEnded = 'pollEnded',
PollVote = 'pollVote',
Quote = 'quote',
Reaction = 'reaction',
ReceiveFollowRequest = 'receiveFollowRequest',
Renote = 'renote',
Reply = 'reply'
}
export interface Signin {
id: string

View file

@ -33,8 +33,8 @@
},
"scripts": {
"artifacts": "napi artifacts",
"build": "napi build --features napi --platform --release ./built/",
"build:debug": "napi build --features napi --platform ./built/",
"build": "napi build --features napi --no-const-enum --platform --release ./built/",
"build:debug": "napi build --features napi --no-const-enum --platform ./built/",
"prepublishOnly": "napi prepublish -t npm",
"test": "pnpm run cargo:test && pnpm run build:debug && ava",
"universal": "napi universal",

View file

@ -4,7 +4,7 @@ use sea_orm::entity::prelude::*;
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "antenna_src_enum")]
pub enum AntennaSrcEnum {
#[sea_orm(string_value = "all")]
@ -22,7 +22,7 @@ pub enum AntennaSrcEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -40,7 +40,7 @@ pub enum MutedNoteReasonEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -60,7 +60,7 @@ pub enum NoteVisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -94,7 +94,7 @@ pub enum NotificationTypeEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -110,7 +110,7 @@ pub enum PageVisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -128,7 +128,7 @@ pub enum PollNotevisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(rs_type = "String", db_type = "Enum", enum_name = "relay_status_enum")]
pub enum RelayStatusEnum {
#[sea_orm(string_value = "accepted")]
@ -140,7 +140,7 @@ pub enum RelayStatusEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -158,7 +158,7 @@ pub enum UserEmojimodpermEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",
@ -174,7 +174,7 @@ pub enum UserProfileFfvisibilityEnum {
}
#[derive(Debug, PartialEq, Eq, EnumIter, DeriveActiveEnum)]
#[cfg_attr(not(feature = "napi"), derive(Clone))]
#[cfg_attr(feature = "napi", napi_derive::napi)]
#[cfg_attr(feature = "napi", napi_derive::napi(string_enum = "camelCase"))]
#[sea_orm(
rs_type = "String",
db_type = "Enum",

View file

@ -1,6 +1,6 @@
import { redisClient } from "@/db/redis.js";
import { encode, decode } from "msgpackr";
import { ChainableCommander } from "ioredis";
import type { ChainableCommander } from "ioredis";
export class Cache<T> {
private ttl: number;

View file

@ -34,7 +34,7 @@ export function initialize<T>(name: string, limitPerSec = -1) {
function apBackoff(attemptsMade: number, err: Error) {
const baseDelay = 60 * 1000; // 1min
const maxBackoff = 8 * 60 * 60 * 1000; // 8hours
let backoff = (Math.pow(2, attemptsMade) - 1) * baseDelay;
let backoff = (2 ** attemptsMade - 1) * baseDelay;
backoff = Math.min(backoff, maxBackoff);
backoff += Math.round(backoff * Math.random() * 0.2);
return backoff;

View file

@ -32,7 +32,7 @@ import Followers from "./activitypub/followers.js";
import Outbox, { packActivity } from "./activitypub/outbox.js";
import { serverLogger } from "./index.js";
import config from "@/config/index.js";
import Koa from "koa";
import type Koa from "koa";
import * as crypto from "node:crypto";
import { inspect } from "node:util";
import type { IActivity } from "@/remote/activitypub/type.js";

View file

@ -1,6 +1,6 @@
import type * as http from "node:http";
import { EventEmitter } from "events";
import type { ParsedUrlQuery } from "querystring";
import { EventEmitter } from "node:events";
import type { ParsedUrlQuery } from "node:querystring";
import * as websocket from "websocket";
import { subscriber as redisClient } from "@/db/redis.js";

View file

@ -1,4 +1,4 @@
import { Readable, ReadableOptions } from "node:stream";
import { Readable, type ReadableOptions } from "node:stream";
import { Buffer } from "node:buffer";
import * as fs from "node:fs";

View file

@ -54,6 +54,10 @@ app.use(async (ctx, next) => {
const url = decodeURI(ctx.path);
if (url === bullBoardPath || url.startsWith(`${bullBoardPath}/`)) {
if (!url.startsWith(`${bullBoardPath}/static/`)) {
ctx.set("Cache-Control", "private, max-age=0, must-revalidate");
}
const token = ctx.cookies.get("token");
if (token == null) {
ctx.status = 401;

View file

@ -1,4 +1,3 @@
import type { KVs } from "../core.js";
import Chart from "../core.js";
import type { User } from "@/models/entities/user.js";
import { name, schema } from "./entities/active-users.js";

View file

@ -1,4 +1,5 @@
import { Window } from "happy-dom";
import type { HTMLAnchorElement, HTMLLinkElement } from "happy-dom";
import config from "@/config/index.js";
async function getRelMeLinks(url: string): Promise<string[]> {

View file

@ -28,9 +28,9 @@ export default class Logger {
if (config.syslog) {
this.syslogClient = new SyslogPro.RFC5424({
applacationName: "Firefish",
applicationName: "Firefish",
timestamp: true,
encludeStructuredData: true,
includeStructuredData: true,
color: true,
extendedColor: true,
server: {
@ -144,12 +144,12 @@ export default class Logger {
}
}
// Used when the process can't continue (fatal error)
public error(
x: string | Error,
data?: Record<string, any> | null,
important = false,
): void {
// 実行を継続できない状況で使う
if (x instanceof Error) {
data = data || {};
data.e = x;
@ -166,30 +166,30 @@ export default class Logger {
}
}
// Used when the process can continue but some action should be taken
public warn(
message: string,
data?: Record<string, any> | null,
important = false,
): void {
// 実行を継続できるが改善すべき状況で使う
this.log("warning", message, data, important);
}
// Used when something is successful
public succ(
message: string,
data?: Record<string, any> | null,
important = false,
): void {
// 何かに成功した状況で使う
this.log("success", message, data, important);
}
// Used for debugging (information necessary for developers but unnecessary for users)
public debug(
message: string,
data?: Record<string, any> | null,
important = false,
): void {
// Used for debugging (information necessary for developers but unnecessary for users)
// Fixed if statement is ignored when logLevel includes debug
if (
config.logLevel?.includes("debug") ||
@ -200,12 +200,12 @@ export default class Logger {
}
}
// Other generic logs
public info(
message: string,
data?: Record<string, any> | null,
important = false,
): void {
// それ以外
this.log("info", message, data, important);
}
}

View file

@ -1,12 +1,7 @@
import { publishMainStream } from "@/services/stream.js";
import type { Note } from "@/models/entities/note.js";
import type { User } from "@/models/entities/user.js";
import {
NoteUnreads,
Users,
Followings,
ChannelFollowings,
} from "@/models/index.js";
import { NoteUnreads, Followings, ChannelFollowings } from "@/models/index.js";
import { Not, IsNull, In } from "typeorm";
import type { Channel } from "@/models/entities/channel.js";
import { readNotificationByQuery } from "@/server/api/common/read-notification.js";
@ -120,34 +115,4 @@ export default async function (
]),
});
}
// if (readAntennaNotes.length > 0) {
// await AntennaNotes.update(
// {
// antennaId: In(myAntennas.map((a) => a.id)),
// noteId: In(readAntennaNotes.map((n) => n.id)),
// },
// {
// read: true,
// },
// );
// // TODO: まとめてクエリしたい
// for (const antenna of myAntennas) {
// const count = await AntennaNotes.countBy({
// antennaId: antenna.id,
// read: false,
// });
// if (count === 0) {
// publishMainStream(userId, "readAntenna", antenna);
// }
// }
// Users.getHasUnreadAntenna(userId).then((unread) => {
// if (!unread) {
// publishMainStream(userId, "readAllAntennas");
// }
// });
// }
}

View file

@ -28,11 +28,10 @@
</template>
<script lang="ts" setup>
import type { Ref } from "vue";
import MkTooltip from "./MkTooltip.vue";
const props = defineProps<{
showing: Ref<boolean>;
showing: boolean;
x: number;
y: number;
title?: string;

View file

@ -231,15 +231,9 @@ const unicodeEmojiSkinToneLabels = [
i18n.ts._skinTones?.dark ?? "Dark",
];
const size = computed(() =>
props.asReactionPicker ? reactionPickerSize.value : 1,
);
const width = computed(() =>
props.asReactionPicker ? reactionPickerWidth.value : 3,
);
const height = computed(() =>
props.asReactionPicker ? reactionPickerHeight.value : 2,
);
const size = reactionPickerSize;
const width = reactionPickerWidth;
const height = reactionPickerHeight;
const customEmojiCategories = emojiCategories;
const customEmojis = instance.emojis;
const q = ref<string | null>(null);

View file

@ -39,7 +39,7 @@ import { defaultStore } from "@/store";
withDefaults(
defineProps<{
manualShowing?: boolean | null;
src?: HTMLElement;
src?: HTMLElement | null;
showPinned?: boolean;
asReactionPicker?: boolean;
}>(),

View file

@ -42,7 +42,7 @@ useTooltip(el, (showing) => {
os.popup(
defineAsyncComponent(() => import("@/components/MkUrlPreviewPopup.vue")),
{
showing: showing.value,
showing,
url: props.url,
source: el.value,
},

View file

@ -1188,7 +1188,7 @@ async function insertEmoji(ev: MouseEvent) {
os.openEmojiPicker(
(ev.currentTarget ?? ev.target) as HTMLElement,
{},
textareaEl.value,
textareaEl.value!,
);
}

View file

@ -19,13 +19,12 @@
</template>
<script lang="ts" setup>
import type { Ref } from "vue";
import type { entities } from "firefish-js";
import MkTooltip from "./MkTooltip.vue";
import XReactionIcon from "@/components/MkReactionIcon.vue";
defineProps<{
showing: Ref<boolean>;
showing: boolean;
reaction: string;
emojis: entities.EmojiLite[];
targetElement: HTMLElement;

View file

@ -30,13 +30,12 @@
</template>
<script lang="ts" setup>
import type { Ref } from "vue";
import type { entities } from "firefish-js";
import MkTooltip from "./MkTooltip.vue";
import XReactionIcon from "@/components/MkReactionIcon.vue";
defineProps<{
showing: Ref<boolean>;
showing: boolean;
reaction: string;
users: entities.User[]; // TODO
count: number;

View file

@ -5,7 +5,7 @@
@after-leave="emit('closed')"
>
<div
v-show="unref(showing)"
v-show="showing"
ref="el"
class="buebdbiu _acrylic _shadow"
:style="{ zIndex, maxWidth: maxWidth + 'px' }"
@ -19,21 +19,14 @@
</template>
<script lang="ts" setup>
import {
type MaybeRef,
nextTick,
onMounted,
onUnmounted,
ref,
unref,
} from "vue";
import { nextTick, onMounted, onUnmounted, ref } from "vue";
import * as os from "@/os";
import { calcPopupPosition } from "@/scripts/popup-position";
import { defaultStore } from "@/store";
const props = withDefaults(
defineProps<{
showing: MaybeRef<boolean>;
showing: boolean;
targetElement?: HTMLElement | null;
x?: number;
y?: number;

View file

@ -19,12 +19,11 @@
</template>
<script lang="ts" setup>
import type { Ref } from "vue";
import type { entities } from "firefish-js";
import MkTooltip from "./MkTooltip.vue";
defineProps<{
showing: Ref<boolean>;
showing: boolean;
users: entities.User[];
count: number;
targetElement?: HTMLElement;

View file

@ -13,7 +13,7 @@
]"
>
<i
v-if="unref(success)"
v-if="success"
:class="[$style.icon, $style.success, iconify('ph-check')]"
></i>
<MkLoading
@ -29,16 +29,15 @@
</template>
<script lang="ts" setup>
import type { MaybeRef } from "vue";
import { shallowRef, unref, watch } from "vue";
import { shallowRef, watch } from "vue";
import MkModal from "@/components/MkModal.vue";
import iconify from "@/scripts/icon";
const modal = shallowRef<InstanceType<typeof MkModal>>();
const props = defineProps<{
success: MaybeRef<boolean>;
showing: MaybeRef<boolean>;
success: boolean;
showing: boolean;
text?: string;
}>();

View file

@ -3,7 +3,7 @@
import { EventEmitter } from "eventemitter3";
import { type Endpoints, type entities, api as firefishApi } from "firefish-js";
import insertTextAtCursor from "insert-text-at-cursor";
import type { Component, Ref } from "vue";
import type { Component, MaybeRef, Ref } from "vue";
import { defineAsyncComponent, markRaw, ref } from "vue";
import { i18n } from "./i18n";
import MkDialog from "@/components/MkDialog.vue";
@ -213,9 +213,13 @@ interface VueComponentConstructor<P, E> {
type NonArrayAble<A> = A extends Array<unknown> ? never : A;
type CanUseRef<T> = {
[K in keyof T]: MaybeRef<T[K]>;
};
export async function popup<Props, Emits>(
component: VueComponentConstructor<Props, Emits>,
props: Props,
props: CanUseRef<Props>,
events: Partial<NonArrayAble<NonNullable<Emits>>> = {},
disposeEvent?: keyof Partial<NonArrayAble<NonNullable<Emits>>>,
) {
@ -240,6 +244,7 @@ export async function popup<Props, Emits>(
id,
};
// Hint: Vue will automatically resolve ref here, so it is safe to use ref in props
popups.value.push(state);
return {

View file

@ -24,14 +24,14 @@ class ReactionPicker {
},
{
done: (reaction) => {
this.onChosen!(reaction);
this.onChosen?.(reaction);
},
close: () => {
this.manualShowing.value = false;
},
closed: () => {
this.src.value = null;
this.onClosed!();
this.onClosed?.();
},
},
);