Notification fetching base
This commit is contained in:
parent
3c16574d03
commit
475c33e57e
|
@ -1617,6 +1617,7 @@ dependencies = [
|
|||
"serde",
|
||||
"serde_json",
|
||||
"serde_urlencoded",
|
||||
"strum",
|
||||
"ts-rs",
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
pub mod emoji;
|
||||
pub mod model_ext;
|
||||
pub mod note_model;
|
||||
pub mod notification_model;
|
||||
pub mod poll;
|
||||
pub mod user_model;
|
||||
|
||||
|
@ -10,6 +11,7 @@ pub use sea_orm;
|
|||
use user_model::UserResolver;
|
||||
|
||||
use crate::note_model::NoteResolver;
|
||||
use crate::notification_model::NotificationResolver;
|
||||
use chrono::Utc;
|
||||
use ext_calckey_model_migration::{Migrator, MigratorTrait};
|
||||
use futures_util::StreamExt;
|
||||
|
@ -296,6 +298,14 @@ impl CalckeyModel {
|
|||
Ok(meta)
|
||||
}
|
||||
|
||||
pub fn get_notification_resolver(&self) -> NotificationResolver {
|
||||
NotificationResolver::new(
|
||||
self.clone(),
|
||||
self.get_user_resolver(),
|
||||
self.get_note_resolver(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_note_resolver(&self) -> NoteResolver {
|
||||
NoteResolver::new(self.clone(), self.get_user_resolver())
|
||||
}
|
||||
|
|
|
@ -25,6 +25,7 @@ const USER: &str = "user.";
|
|||
const REPLY: &str = "reply.";
|
||||
const RENOTE: &str = "renote.";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NoteResolver {
|
||||
db: CalckeyModel,
|
||||
user_resolver: UserResolver,
|
||||
|
@ -207,7 +208,7 @@ impl NoteResolver {
|
|||
|
||||
*/
|
||||
|
||||
fn attach_note(
|
||||
pub fn attach_note(
|
||||
&self,
|
||||
q: &mut SelectStatement,
|
||||
note_tbl: &MagIden,
|
||||
|
|
|
@ -0,0 +1,162 @@
|
|||
use crate::model_ext::{
|
||||
AliasColumnExt, AliasSourceExt, AliasSuffixExt, CursorPaginationExt, EntityPrefixExt, MagIden,
|
||||
ModelPagination, SelectColumnsExt,
|
||||
};
|
||||
use crate::note_model::data::NoteData;
|
||||
use crate::note_model::{NoteResolveOptions, NoteResolver};
|
||||
use crate::user_model::{UserData, UserResolveOptions, UserResolver};
|
||||
use crate::{CalckeyDbError, CalckeyModel};
|
||||
use chrono::{DateTime, Utc};
|
||||
use ck::{access_token, notification, user};
|
||||
use ext_calckey_model_migration::{JoinType, SelectStatement};
|
||||
use magnetar_sdk::types::SpanFilter;
|
||||
use sea_orm::Iden;
|
||||
use sea_orm::{DbErr, EntityTrait, FromQueryResult, QueryFilter, QueryResult, QuerySelect};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct NotificationData {
|
||||
pub notification: notification::Model,
|
||||
pub access_token: Option<access_token::Model>,
|
||||
pub notifier: Option<UserData>,
|
||||
pub notification_note: Option<NoteData>,
|
||||
}
|
||||
|
||||
impl FromQueryResult for NotificationData {
|
||||
fn from_query_result(res: &QueryResult, prefix: &str) -> Result<Self, DbErr> {
|
||||
let prefix = if prefix.is_empty() {
|
||||
notification::Entity.base_prefix()
|
||||
} else {
|
||||
MagIden::alias(prefix)
|
||||
};
|
||||
|
||||
Ok(NotificationData {
|
||||
notification: notification::Model::from_query_result(res, &prefix.to_string())?,
|
||||
access_token: access_token::Model::from_query_result_optional(
|
||||
res,
|
||||
&prefix.join_str_as_str(ACCESS_TOKEN),
|
||||
)?,
|
||||
notification_note: NoteData::from_query_result_optional(
|
||||
res,
|
||||
&prefix.join_str_as_str(NOTIFICATION_NOTE),
|
||||
)?,
|
||||
notifier: UserData::from_query_result_optional(res, &prefix.join_str_as_str(NOTIFIER))?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl ModelPagination for NotificationData {
|
||||
fn id(&self) -> &str {
|
||||
&self.notification.id
|
||||
}
|
||||
|
||||
fn time(&self) -> DateTime<Utc> {
|
||||
self.notification.created_at.into()
|
||||
}
|
||||
}
|
||||
|
||||
const NOTIFICATION: &str = "notification.";
|
||||
const NOTIFIER: &str = "notifier.";
|
||||
const NOTIFICATION_NOTE: &str = "note.";
|
||||
const ACCESS_TOKEN: &str = "access_token.";
|
||||
|
||||
pub struct NotificationResolveOptions {}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct NotificationResolver {
|
||||
db: CalckeyModel,
|
||||
note_resolver: NoteResolver,
|
||||
user_resolver: UserResolver,
|
||||
}
|
||||
|
||||
impl NotificationResolver {
|
||||
pub(crate) fn new(
|
||||
db: CalckeyModel,
|
||||
user_resolver: UserResolver,
|
||||
note_resolver: NoteResolver,
|
||||
) -> Self {
|
||||
Self {
|
||||
db,
|
||||
note_resolver,
|
||||
user_resolver,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve(
|
||||
&self,
|
||||
q: &mut SelectStatement,
|
||||
notification_tbl: &MagIden,
|
||||
note_options: &NoteResolveOptions,
|
||||
user_options: &UserResolveOptions,
|
||||
note_resolver: &NoteResolver,
|
||||
user_resolver: &UserResolver,
|
||||
) {
|
||||
let notifier_tbl = notification_tbl.join_str(NOTIFIER);
|
||||
q.add_aliased_columns::<user::Entity>(¬ifier_tbl);
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
notification::Relation::User2.with_from_alias(notification_tbl),
|
||||
¬ifier_tbl,
|
||||
);
|
||||
user_resolver.resolve(q, ¬ifier_tbl, &user_options);
|
||||
|
||||
let token_tbl = notification_tbl.join_str(ACCESS_TOKEN);
|
||||
q.add_aliased_columns::<access_token::Entity>(&token_tbl);
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
notification::Relation::AccessToken.with_from_alias(notification_tbl),
|
||||
&token_tbl,
|
||||
);
|
||||
|
||||
let note_tbl = notification_tbl.join_str(NOTIFICATION_NOTE);
|
||||
q.join_columns(
|
||||
JoinType::LeftJoin,
|
||||
notification::Relation::Note.with_from_alias(notification_tbl),
|
||||
¬e_tbl,
|
||||
);
|
||||
note_resolver.attach_note(q, ¬e_tbl, 1, 1, note_options, &self.user_resolver);
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
&self,
|
||||
note_options: &NoteResolveOptions,
|
||||
user_options: &UserResolveOptions,
|
||||
user_id: &str,
|
||||
pagination: &SpanFilter,
|
||||
prev: &mut Option<SpanFilter>,
|
||||
next: &mut Option<SpanFilter>,
|
||||
limit: u64,
|
||||
) -> Result<Vec<NotificationData>, CalckeyDbError> {
|
||||
let notification_tbl = notification::Entity.base_prefix();
|
||||
|
||||
let mut select = notification::Entity::find();
|
||||
|
||||
let query = QuerySelect::query(&mut select);
|
||||
self.resolve(
|
||||
query,
|
||||
¬ification_tbl,
|
||||
note_options,
|
||||
user_options,
|
||||
&self.note_resolver,
|
||||
&self.user_resolver,
|
||||
);
|
||||
|
||||
let notifications = select
|
||||
.filter(
|
||||
notification_tbl
|
||||
.col(notification::Column::NotifieeId)
|
||||
.eq(user_id),
|
||||
)
|
||||
.get_paginated_model::<NotificationData, _, _>(
|
||||
&self.db.0,
|
||||
(notification::Column::CreatedAt, notification::Column::Id),
|
||||
pagination,
|
||||
prev,
|
||||
next,
|
||||
limit,
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(notifications)
|
||||
}
|
||||
}
|
|
@ -115,6 +115,7 @@ const PROFILE: &str = "profile.";
|
|||
const AVATAR: &str = "avatar.";
|
||||
const BANNER: &str = "banner.";
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct UserResolver {
|
||||
db: CalckeyModel,
|
||||
}
|
||||
|
|
|
@ -14,3 +14,14 @@ export { PackUserBase } from "./types/packed/PackUserBase";
|
|||
export { PackUserMaybeAll } from "./types/packed/PackUserMaybeAll";
|
||||
export { PackUserSelf } from "./types/packed/PackUserSelf";
|
||||
export { PackUserSelfMaybeAll } from "./types/packed/PackUserSelfMaybeAll";
|
||||
export { PackNotificationFollow } from "./types/packed/PackNotificationFollow";
|
||||
export { PackNotificationFollowRequestReceived } from "./types/packed/PackNotificationFollowRequestReceived";
|
||||
export { PackNotificationFollowRequestAccepted } from "./types/packed/PackNotificationFollowRequestAccepted";
|
||||
export { PackNotificationMention } from "./types/packed/PackNotificationMention";
|
||||
export { PackNotificationReply } from "./types/packed/PackNotificationReply";
|
||||
export { PackNotificationRenote } from "./types/packed/PackNotificationRenote";
|
||||
export { PackNotificationReaction } from "./types/packed/PackNotificationReaction";
|
||||
export { PackNotificationQuote } from "./types/packed/PackNotificationQuote";
|
||||
export { PackNotificationPollEnd } from "./types/packed/PackNotificationPollEnd";
|
||||
export { PackNotificationApp } from "./types/packed/PackNotificationApp";
|
||||
export { PackNotification } from "./types/PackNotification";
|
||||
|
|
|
@ -49,3 +49,8 @@ export { NoFilter } from "./types/NoFilter";
|
|||
export { TimelineType } from "./types/TimelineType";
|
||||
export { Empty } from "./types/Empty";
|
||||
export { PaginationShape } from "./types/PaginationShape";
|
||||
export { NotificationBase } from "./types/NotificationBase";
|
||||
export { NotificationAppExt } from "./types/NotificationAppExt";
|
||||
export { NotificationNoteExt } from "./types/NotificationNoteExt";
|
||||
export { NotificationReactionExt } from "./types/NotificationReactionExt";
|
||||
export { NotificationUserExt } from "./types/NotificationUserExt";
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface NotificationAppExt { body: string, header: string, icon: string, }
|
|
@ -0,0 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export interface NotificationBase { id: string, created_at: string, is_read: boolean, }
|
|
@ -0,0 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackNoteMaybeFull } from "./packed/PackNoteMaybeFull";
|
||||
|
||||
export interface NotificationNoteExt { note: PackNoteMaybeFull, }
|
|
@ -0,0 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Reaction } from "./Reaction";
|
||||
|
||||
export interface NotificationReactionExt { reaction: Reaction, }
|
|
@ -1,3 +1,3 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
|
||||
export type NotificationType = "Follow" | "Mention" | "Reply" | "Renote" | "Quote" | "Reaction" | "PollVote" | "PollEnded" | "FollowRequest" | "FollowRequestAccepted" | "App";
|
||||
export type NotificationType = "Follow" | "FollowRequestReceived" | "FollowRequestAccepted" | "Mention" | "Reply" | "Renote" | "Reaction" | "Quote" | "PollEnd" | "App";
|
|
@ -0,0 +1,4 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackUserBase } from "./packed/PackUserBase";
|
||||
|
||||
export interface NotificationUserExt { user: PackUserBase, }
|
|
@ -0,0 +1,13 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { PackNotificationApp } from "./packed/PackNotificationApp";
|
||||
import type { PackNotificationFollow } from "./packed/PackNotificationFollow";
|
||||
import type { PackNotificationFollowRequestAccepted } from "./packed/PackNotificationFollowRequestAccepted";
|
||||
import type { PackNotificationFollowRequestReceived } from "./packed/PackNotificationFollowRequestReceived";
|
||||
import type { PackNotificationMention } from "./packed/PackNotificationMention";
|
||||
import type { PackNotificationPollEnd } from "./packed/PackNotificationPollEnd";
|
||||
import type { PackNotificationQuote } from "./packed/PackNotificationQuote";
|
||||
import type { PackNotificationReaction } from "./packed/PackNotificationReaction";
|
||||
import type { PackNotificationRenote } from "./packed/PackNotificationRenote";
|
||||
import type { PackNotificationReply } from "./packed/PackNotificationReply";
|
||||
|
||||
export type PackNotification = { "type": "Follow" } & PackNotificationFollow | { "type": "FollowRequestReceived" } & PackNotificationFollowRequestReceived | { "type": "FollowRequestAccepted" } & PackNotificationFollowRequestAccepted | { "type": "Mention" } & PackNotificationMention | { "type": "Reply" } & PackNotificationReply | { "type": "Renote" } & PackNotificationRenote | { "type": "Reaction" } & PackNotificationReaction | { "type": "Quote" } & PackNotificationQuote | { "type": "PollEnd" } & PackNotificationPollEnd | { "type": "App" } & PackNotificationApp;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationAppExt } from "../NotificationAppExt";
|
||||
|
||||
export type PackNotificationApp = Id & NotificationAppExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationUserExt } from "../NotificationUserExt";
|
||||
|
||||
export type PackNotificationFollow = Id & NotificationUserExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationUserExt } from "../NotificationUserExt";
|
||||
|
||||
export type PackNotificationFollowRequestAccepted = Id & NotificationUserExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationUserExt } from "../NotificationUserExt";
|
||||
|
||||
export type PackNotificationFollowRequestReceived = Id & NotificationUserExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationNoteExt } from "../NotificationNoteExt";
|
||||
|
||||
export type PackNotificationMention = Id & NotificationNoteExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationNoteExt } from "../NotificationNoteExt";
|
||||
|
||||
export type PackNotificationPollEnd = Id & NotificationNoteExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationNoteExt } from "../NotificationNoteExt";
|
||||
|
||||
export type PackNotificationQuote = Id & NotificationNoteExt;
|
|
@ -0,0 +1,6 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationReactionExt } from "../NotificationReactionExt";
|
||||
import type { NotificationUserExt } from "../NotificationUserExt";
|
||||
|
||||
export type PackNotificationReaction = Id & NotificationReactionExt & NotificationUserExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationNoteExt } from "../NotificationNoteExt";
|
||||
|
||||
export type PackNotificationRenote = Id & NotificationNoteExt;
|
|
@ -0,0 +1,5 @@
|
|||
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
|
||||
import type { Id } from "../Id";
|
||||
import type { NotificationNoteExt } from "../NotificationNoteExt";
|
||||
|
||||
export type PackNotificationReply = Id & NotificationNoteExt;
|
|
@ -15,6 +15,8 @@ serde = { workspace = true, features = ["derive"] }
|
|||
serde_json = { workspace = true }
|
||||
serde_urlencoded = { workspace = true }
|
||||
|
||||
strum = { workspace = true, features = ["derive"] }
|
||||
|
||||
ts-rs = { workspace = true, features = ["chrono", "chrono-impl"] }
|
||||
|
||||
unicode-segmentation = { workspace = true }
|
||||
|
|
|
@ -2,9 +2,11 @@ pub mod drive;
|
|||
pub mod emoji;
|
||||
pub mod instance;
|
||||
pub mod note;
|
||||
pub mod notification;
|
||||
pub mod timeline;
|
||||
pub mod user;
|
||||
|
||||
use crate::types::notification::NotificationType;
|
||||
use crate::util_types::U64Range;
|
||||
use chrono::{DateTime, Utc};
|
||||
use serde::de::Error;
|
||||
|
@ -213,22 +215,6 @@ impl<T: AsRef<str>> From<T> for Id {
|
|||
#[repr(transparent)]
|
||||
pub struct MmXml(pub String);
|
||||
|
||||
#[derive(Copy, Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub enum NotificationType {
|
||||
Follow,
|
||||
Mention,
|
||||
Reply,
|
||||
Renote,
|
||||
Quote,
|
||||
Reaction,
|
||||
PollVote,
|
||||
PollEnded,
|
||||
FollowRequest,
|
||||
FollowRequestAccepted,
|
||||
App,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationSettings {
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
use crate::types::note::{
|
||||
PackNoteMaybeFull, Reaction, ReactionShortcode, ReactionUnicode, ReactionUnknown,
|
||||
};
|
||||
use crate::types::user::PackUserBase;
|
||||
use crate::types::Id;
|
||||
use crate::{Packed, Required};
|
||||
use chrono::{DateTime, Utc};
|
||||
use magnetar_sdk_macros::pack;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::EnumDiscriminants;
|
||||
use ts_rs::TS;
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationBase {
|
||||
pub id: String,
|
||||
pub created_at: DateTime<Utc>,
|
||||
pub is_read: bool,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationNoteExt {
|
||||
pub note: PackNoteMaybeFull,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationUserExt {
|
||||
pub user: PackUserBase,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationReactionExt {
|
||||
pub reaction: Reaction,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS)]
|
||||
#[ts(export)]
|
||||
pub struct NotificationAppExt {
|
||||
pub body: String,
|
||||
pub header: String,
|
||||
pub icon: String,
|
||||
}
|
||||
|
||||
pack!(PackNotificationFollow, Required<Id> as id & Required<NotificationUserExt> as follower);
|
||||
pack!(PackNotificationFollowRequestReceived, Required<Id> as id & Required<NotificationUserExt> as follower);
|
||||
pack!(PackNotificationFollowRequestAccepted, Required<Id> as id & Required<NotificationUserExt> as follower);
|
||||
pack!(PackNotificationMention, Required<Id> as id & Required<NotificationNoteExt> as note);
|
||||
pack!(PackNotificationReply, Required<Id> as id & Required<NotificationNoteExt> as note);
|
||||
pack!(PackNotificationRenote, Required<Id> as id & Required<NotificationNoteExt> as note);
|
||||
pack!(PackNotificationReaction, Required<Id> as id & Required<NotificationReactionExt> as reaction & Required<NotificationUserExt> as user);
|
||||
pack!(PackNotificationQuote, Required<Id> as id & Required<NotificationNoteExt> as note);
|
||||
pack!(PackNotificationPollEnd, Required<Id> as id & Required<NotificationNoteExt> as note);
|
||||
pack!(PackNotificationApp, Required<Id> as id & Required<NotificationAppExt> as custom);
|
||||
|
||||
#[derive(Clone, Debug, Deserialize, Serialize, TS, EnumDiscriminants)]
|
||||
#[strum_discriminants(name(NotificationType))]
|
||||
#[strum_discriminants(ts(export))]
|
||||
#[strum_discriminants(derive(Deserialize, Serialize, TS))]
|
||||
#[ts(export)]
|
||||
#[serde(tag = "type")]
|
||||
pub enum PackNotification {
|
||||
Follow(PackNotificationFollow),
|
||||
FollowRequestReceived(PackNotificationFollowRequestReceived),
|
||||
FollowRequestAccepted(PackNotificationFollowRequestAccepted),
|
||||
Mention(PackNotificationMention),
|
||||
Reply(PackNotificationReply),
|
||||
Renote(PackNotificationRenote),
|
||||
Reaction(PackNotificationReaction),
|
||||
Quote(PackNotificationQuote),
|
||||
PollEnd(PackNotificationPollEnd),
|
||||
App(PackNotificationApp),
|
||||
}
|
Loading…
Reference in New Issue