mirror of
https://github.com/BewlyBewly/BewlyBewly.git
synced 2025-04-14 13:15:29 +00:00
Co-authored-by: star knight <64941905+starknt@users.noreply.github.com> Co-authored-by: hakadao <a578457889743@gmail.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import type { List as WatchListItem, WatchListResult } from '~/models/anime/watc
|
||||
import type { List as PopularAnimeItem, PopularAnimeResult } from '~/models/anime/popular'
|
||||
import type { ItemSubItem as RecommendationItem, RecommendationResult } from '~/models/anime/recommendation'
|
||||
|
||||
const api = useApiClient()
|
||||
const animeWatchList = reactive<WatchListItem[]>([])
|
||||
const recommendAnimeList = reactive<RecommendationItem[]>([])
|
||||
const popularAnimeList = reactive<PopularAnimeItem[]>([])
|
||||
@@ -58,13 +59,11 @@ function initPageAction() {
|
||||
|
||||
function getAnimeWatchList() {
|
||||
isLoadingAnimeWatchList.value = true
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: 'getAnimeWatchList',
|
||||
vmid: getUserID() ?? 0,
|
||||
pn: 1,
|
||||
ps: 30,
|
||||
})
|
||||
api.anime.getWatchList({
|
||||
vmid: getUserID() ?? 0,
|
||||
pn: 1,
|
||||
ps: 30,
|
||||
})
|
||||
.then((response: WatchListResult) => {
|
||||
const {
|
||||
code,
|
||||
@@ -81,11 +80,9 @@ function getAnimeWatchList() {
|
||||
|
||||
function getRecommendAnimeList() {
|
||||
isLoadingRecommendAnime.value = true
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: 'getRecommendAnimeList',
|
||||
coursor: cursor.value,
|
||||
})
|
||||
api.anime.getRecommendAnimeList({
|
||||
coursor: cursor.value,
|
||||
})
|
||||
.then((response: RecommendationResult) => {
|
||||
const {
|
||||
code,
|
||||
@@ -109,10 +106,7 @@ function getRecommendAnimeList() {
|
||||
|
||||
function getPopularAnimeList() {
|
||||
isLoadingPopularAnime.value = true
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: 'getPopularAnimeList',
|
||||
})
|
||||
api.anime.getPopularAnimeList()
|
||||
.then((response: PopularAnimeResult) => {
|
||||
const {
|
||||
code,
|
||||
|
||||
@@ -6,7 +6,7 @@ import { removeHttpFromUrl } from '~/utils/main'
|
||||
import type { Result as TimetableItem, TimetableResult } from '~/models/anime/timeTable'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const api = useApiClient()
|
||||
const animeTimeTable = reactive<TimetableItem[]>([])
|
||||
const animeTimeTableWrap = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
|
||||
@@ -32,10 +32,7 @@ function refreshAnimeTimeTable() {
|
||||
}
|
||||
|
||||
function getAnimeTimeTable() {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: 'getAnimeTimeTable',
|
||||
})
|
||||
api.anime.getAnimeTimeTable()
|
||||
.then((res: TimetableResult) => {
|
||||
const { code, result } = res
|
||||
if (code === 0)
|
||||
|
||||
@@ -6,9 +6,9 @@ import emitter from '~/utils/mitt'
|
||||
import { settings } from '~/logic'
|
||||
import type { Media as FavoriteItem, FavoritesResult } from '~/models/video/favorite'
|
||||
import type { List as CategoryItem, FavoritesCategoryResult } from '~/models/video/favoriteCategory'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApiClient()
|
||||
|
||||
const favoriteCategories = reactive<CategoryItem[]>([])
|
||||
const favoriteResources = reactive<FavoriteItem[]>([])
|
||||
@@ -73,11 +73,9 @@ function initPageAction() {
|
||||
}
|
||||
|
||||
async function getFavoriteCategories() {
|
||||
await browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.FAVORITE.GET_FAVORITE_CATEGORIES,
|
||||
up_mid: getUserID(),
|
||||
})
|
||||
await api.favorite.getFavoriteCategories({
|
||||
up_mid: getUserID(),
|
||||
})
|
||||
.then((res: FavoritesCategoryResult) => {
|
||||
if (res.code === 0) {
|
||||
Object.assign(favoriteCategories, res.data.list)
|
||||
@@ -108,13 +106,11 @@ async function getFavoriteResources(
|
||||
isFullPageLoading.value = true
|
||||
isLoading.value = true
|
||||
try {
|
||||
const res: FavoritesResult = await browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.FAVORITE.GET_FAVORITE_RESOURCES,
|
||||
media_id,
|
||||
pn,
|
||||
keyword,
|
||||
})
|
||||
const res: FavoritesResult = await api.favorite.getFavoriteResources({
|
||||
media_id,
|
||||
pn,
|
||||
keyword,
|
||||
})
|
||||
|
||||
if (res.code === 0) {
|
||||
activatedCategoryCover.value = res.data.info.cover
|
||||
@@ -158,8 +154,7 @@ function jumpToLoginPage() {
|
||||
}
|
||||
|
||||
function handleUnfavorite(favoriteResource: FavoriteResource) {
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.FAVORITE.PATCH_DEL_FAVORITE_RESOURCES,
|
||||
api.favorite.patchDelFavoriteResources({
|
||||
resources: `${favoriteResource.id}:${favoriteResource.type}`,
|
||||
media_id: selectedCategory.value?.id,
|
||||
csrf: getCSRF(),
|
||||
|
||||
@@ -7,10 +7,9 @@ import { calcCurrentTime } from '~/utils/dataFormatter'
|
||||
import { Business } from '~/models/history/history'
|
||||
import type { List as HistoryItem, HistoryResult } from '~/models/history/history'
|
||||
import type { List as HistorySearchItem, HistorySearchResult } from '~/models/video/historySearch'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const api = useApiClient()
|
||||
const isLoading = ref<boolean>()
|
||||
const noMoreContent = ref<boolean>(false)
|
||||
const historyList = reactive<Array<HistoryItem>>([])
|
||||
@@ -56,15 +55,13 @@ function initPageAction() {
|
||||
*/
|
||||
function getHistoryList() {
|
||||
isLoading.value = true
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.HISTORY.GET_HISTORY_LIST,
|
||||
type: 'all',
|
||||
view_at:
|
||||
api.history.getHistoryList({
|
||||
type: 'all',
|
||||
view_at:
|
||||
historyList.length > 0
|
||||
? historyList[historyList.length - 1].view_at
|
||||
: 0,
|
||||
})
|
||||
})
|
||||
.then((res: HistoryResult) => {
|
||||
if (res.code === 0) {
|
||||
if (Array.isArray(res.data.list) && res.data.list.length > 0)
|
||||
@@ -84,12 +81,10 @@ function getHistoryList() {
|
||||
|
||||
function searchHistoryList() {
|
||||
isLoading.value = true
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.HISTORY.SEARCH_HISTORY_LIST,
|
||||
pn: currentPageNum.value++,
|
||||
keyword: keyword.value,
|
||||
})
|
||||
api.history.searchHistoryList({
|
||||
pn: currentPageNum.value++,
|
||||
keyword: keyword.value,
|
||||
})
|
||||
.then((res: HistorySearchResult) => {
|
||||
if (res.code === 0) {
|
||||
if (historyList.length !== 0 && res.data.list.length < 20) {
|
||||
@@ -118,12 +113,10 @@ function handleSearch() {
|
||||
}
|
||||
|
||||
function deleteHistoryItem(index: number, historyItem: HistoryItem) {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.HISTORY.DELETE_HISTORY_ITEM,
|
||||
kid: `${historyItem.history.business}_${historyItem.history.oid}`,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
api.history.deleteHistoryItem({
|
||||
kid: `${historyItem.history.business}_${historyItem.history.oid}`,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0)
|
||||
historyList.splice(index, 1)
|
||||
@@ -169,10 +162,7 @@ function getHistoryItemCover(item: HistoryItem) {
|
||||
}
|
||||
|
||||
function getHistoryPauseStatus() {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.HISTORY.GET_HISTORY_PAUSE_STATUS,
|
||||
})
|
||||
api.history.getHistoryPauseStatus()
|
||||
.then((res) => {
|
||||
if (res.code === 0)
|
||||
historyStatus.value = res.data
|
||||
@@ -180,12 +170,10 @@ function getHistoryPauseStatus() {
|
||||
}
|
||||
|
||||
function setHistoryPauseStatus(isPause: boolean) {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.HISTORY.SET_HISTORY_PAUSE_STATUS,
|
||||
csrf: getCSRF(),
|
||||
switch: isPause,
|
||||
})
|
||||
api.history.setHistoryPauseStatus({
|
||||
csrf: getCSRF(),
|
||||
switch: isPause,
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0)
|
||||
getHistoryPauseStatus()
|
||||
@@ -193,11 +181,9 @@ function setHistoryPauseStatus(isPause: boolean) {
|
||||
}
|
||||
|
||||
function clearAllHistory() {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.HISTORY.CLEAR_ALL_HISTORY,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
api.history.clearAllHistory({
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0)
|
||||
historyList.length = 0
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { DataItem as MomentItem, MomentResult } from '~/models/moment/moment'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
@@ -21,6 +20,8 @@ const gridValue = computed((): string => {
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const api = useApiClient()
|
||||
|
||||
const videoList = reactive<MomentItem[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
@@ -82,8 +83,7 @@ async function getFollowedUsersVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response: MomentResult = await browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.MOMENT.GET_MOMENTS,
|
||||
const response: MomentResult = await api.moment.getMoments({
|
||||
type: 'video',
|
||||
offset: offset.value,
|
||||
update_baseline: updateBaseline.value,
|
||||
|
||||
@@ -8,7 +8,6 @@ import type { Item as VideoItem, forYouResult } from '~/models/video/forYou'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import { accessKey, settings } from '~/logic'
|
||||
import { LanguageType } from '~/enums/appEnums'
|
||||
import API from '~/background/msg.define'
|
||||
import { TVAppKey, getTvSign } from '~/utils/authProvider'
|
||||
import { isVerticalVideo } from '~/utils/uriParse'
|
||||
|
||||
@@ -30,7 +29,7 @@ const gridValue = computed((): string => {
|
||||
})
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const api = useApiClient()
|
||||
const videoList = reactive<VideoItem[]>([])
|
||||
const appVideoList = reactive<AppVideoItem[]>([])
|
||||
const isLoading = ref<boolean>(true)
|
||||
@@ -140,8 +139,7 @@ async function getRecommendVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response: forYouResult = await browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.VIDEO.GET_RECOMMEND_VIDEOS,
|
||||
const response: forYouResult = await api.video.getRecommendVideos({
|
||||
fresh_idx: refreshIdx.value++,
|
||||
})
|
||||
|
||||
@@ -180,8 +178,7 @@ async function getAppRecommendVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response: AppForYouResult = await browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.VIDEO.GET_APP_RECOMMEND_VIDEOS,
|
||||
const response: AppForYouResult = await api.video.getAppRecommendVideos({
|
||||
access_key: accessKey.value,
|
||||
s_locale: settings.value.language === LanguageType.Mandarin_TW || settings.value.language === LanguageType.Cantonese ? 'zh-Hant_TW' : 'zh-Hans_CN',
|
||||
c_locale: settings.value.language === LanguageType.Mandarin_TW || settings.value.language === LanguageType.Cantonese ? 'zh-Hant_TW' : 'zh-Hans_CN',
|
||||
@@ -300,8 +297,7 @@ function handleAppDislike() {
|
||||
appkey: TVAppKey.appkey,
|
||||
}
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.VIDEO.DISLIKE_VIDEO,
|
||||
api.video.dislikeVideo({
|
||||
...params,
|
||||
sign: getTvSign(params),
|
||||
})
|
||||
@@ -336,8 +332,7 @@ function handleAppUndoDislike(video: AppVideoItem) {
|
||||
appkey: TVAppKey.appkey,
|
||||
}
|
||||
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.VIDEO.UNDO_DISLIKE_VIDEO,
|
||||
api.video.undoDislikeVideo({
|
||||
...params,
|
||||
sign: getTvSign(params),
|
||||
}).then((res) => {
|
||||
|
||||
@@ -6,7 +6,6 @@ import type { List as RankingPgcItem, RankingPgcResult } from '~/models/video/ra
|
||||
import type { GridLayout } from '~/logic'
|
||||
import { settings } from '~/logic'
|
||||
import emitter from '~/utils/mitt'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
@@ -18,6 +17,7 @@ const emit = defineEmits<{
|
||||
}>()
|
||||
|
||||
const { t } = useI18n()
|
||||
const api = useApiClient()
|
||||
const { handleBackToTop, handlePageRefresh } = useBewlyApp()
|
||||
|
||||
const gridValue = computed((): string => {
|
||||
@@ -129,8 +129,7 @@ function getRankingVideos() {
|
||||
videoList.length = 0
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.RANKING.GET_RANKING_VIDEOS,
|
||||
api.ranking.getRankingVideos({
|
||||
rid: activatedRankingType.value.rid,
|
||||
type: 'type' in activatedRankingType.value ? activatedRankingType.value.type : 'all',
|
||||
}).then((response: RankingResult) => {
|
||||
@@ -147,8 +146,7 @@ function getRankingVideos() {
|
||||
function getRankingPgc() {
|
||||
PgcList.length = 0
|
||||
isLoading.value = true
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.RANKING.GET_RANKING_PGC,
|
||||
api.ranking.getRankingPgc({
|
||||
season_type: activatedRankingType.value.seasonType,
|
||||
}).then((response: RankingPgcResult) => {
|
||||
if (response.code === 0)
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { DataItem as MomentItem, MomentResult } from '~/models/moment/moment'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
@@ -20,7 +19,7 @@ const gridValue = computed((): string => {
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const api = useApiClient()
|
||||
const momentList = reactive<MomentItem[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const needToLoginFirst = ref<boolean>(false)
|
||||
@@ -86,8 +85,7 @@ async function getFollowedUsersVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response: MomentResult = await browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.MOMENT.GET_MOMENTS,
|
||||
const response: MomentResult = await api.moment.getMoments({
|
||||
type: 'pgc',
|
||||
offset: offset.value,
|
||||
update_baseline: updateBaseline.value,
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { Ref } from 'vue'
|
||||
import type { GridLayout } from '~/logic'
|
||||
import type { TrendingResult, List as VideoItem } from '~/models/video/trending'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const props = defineProps<{
|
||||
gridLayout: GridLayout
|
||||
@@ -20,7 +19,7 @@ const gridValue = computed((): string => {
|
||||
return '~ cols-1 xl:cols-2 gap-4'
|
||||
return '~ cols-1 gap-4'
|
||||
})
|
||||
|
||||
const api = useApiClient()
|
||||
const videoList = reactive<VideoItem[]>([])
|
||||
const isLoading = ref<boolean>(false)
|
||||
const containerRef = ref<HTMLElement>() as Ref<HTMLElement>
|
||||
@@ -66,8 +65,7 @@ async function getTrendingVideos() {
|
||||
emit('beforeLoading')
|
||||
isLoading.value = true
|
||||
try {
|
||||
const response: TrendingResult = await browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.VIDEO.GET_POPULAR_VIDEOS,
|
||||
const response: TrendingResult = await api.video.getPopularVideos({
|
||||
pn: pn.value++,
|
||||
ps: 30,
|
||||
})
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { Comment, UserCardInfo, VideoInfo } from './types'
|
||||
import { getCSRF, removeHttpFromUrl } from '~/utils/main'
|
||||
import { calcTimeSince, numFormatter } from '~/utils/dataFormatter'
|
||||
|
||||
const api = useApiClient()
|
||||
const videoContent = ref() as Ref<HTMLElement>
|
||||
// const commentContent = ref() as Ref<HTMLElement>
|
||||
const danmukuContent = ref() as Ref<HTMLElement>
|
||||
@@ -71,9 +72,9 @@ async function getVideoInfo() {
|
||||
|
||||
let res
|
||||
if (isBV)
|
||||
res = await browser.runtime.sendMessage({ contentScriptQuery: 'getVideoInfo', videoId })
|
||||
res = await api.video.getVideoInfo({ videoId })
|
||||
else
|
||||
res = await browser.runtime.sendMessage({ contentScriptQuery: 'getVideoInfo', aid: videoId.replace('av', '') })
|
||||
res = await api.video.getVideoInfo({ aid: videoId.replace('av', '') })
|
||||
if (res.code === 0) {
|
||||
Object.assign(videoInfo, res.data.View)
|
||||
Object.assign(userCardInfo, res.data.Card)
|
||||
@@ -134,8 +135,7 @@ async function getVideoInfo() {
|
||||
// }
|
||||
|
||||
function getVideoComments() {
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: 'getVideoComments',
|
||||
api.video.getVideoComments({
|
||||
csrf: getCSRF(),
|
||||
oid: videoInfo.aid,
|
||||
pn: 1,
|
||||
|
||||
@@ -4,10 +4,9 @@ import { useI18n } from 'vue-i18n'
|
||||
import { getCSRF, openLinkToNewTab, removeHttpFromUrl } from '~/utils/main'
|
||||
import { calcCurrentTime } from '~/utils/dataFormatter'
|
||||
import type { List as VideoItem, WatchLaterResult } from '~/models/video/watchLater'
|
||||
import API from '~/background/msg.define'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
const api = useApiClient()
|
||||
const isLoading = ref<boolean>()
|
||||
const noMoreContent = ref<boolean>()
|
||||
const watchLaterList = reactive<VideoItem[]>([])
|
||||
@@ -34,10 +33,7 @@ function initPageAction() {
|
||||
function getAllWatchLaterList() {
|
||||
isLoading.value = true
|
||||
watchLaterList.length = 0
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.WATCHLATER.GET_ALL_WATCHLATER_LIST,
|
||||
})
|
||||
api.watchlater.getAllWatchlaterList()
|
||||
.then((res: WatchLaterResult) => {
|
||||
if (res.code === 0)
|
||||
Object.assign(watchLaterList, res.data.list)
|
||||
@@ -47,12 +43,10 @@ function getAllWatchLaterList() {
|
||||
}
|
||||
|
||||
function deleteWatchLaterItem(index: number, aid: number) {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.WATCHLATER.REMOVE_FROM_WATCHLATER,
|
||||
aid,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
api.watchlater.removeFromWatchLater({
|
||||
aid,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0)
|
||||
watchLaterList.splice(index, 1)
|
||||
@@ -65,8 +59,7 @@ function handleClearAllWatchLater() {
|
||||
)
|
||||
if (result) {
|
||||
isLoading.value = true
|
||||
browser.runtime.sendMessage({
|
||||
contentScriptQuery: API.WATCHLATER.CLEAR_ALL_WATCHLATER,
|
||||
api.watchlater.clearAllWatchLater({
|
||||
csrf: getCSRF(),
|
||||
}).then((res) => {
|
||||
if (res.code === 0)
|
||||
@@ -82,12 +75,10 @@ function handleRemoveWatchedVideos() {
|
||||
t('watch_later.remove_watched_videos_confirm'),
|
||||
)
|
||||
if (result) {
|
||||
browser.runtime
|
||||
.sendMessage({
|
||||
contentScriptQuery: API.WATCHLATER.REMOVE_FROM_WATCHLATER,
|
||||
viewed: true,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
api.watchlater.removeFromWatchLater({
|
||||
viewed: true,
|
||||
csrf: getCSRF(),
|
||||
})
|
||||
.then((res) => {
|
||||
if (res.code === 0)
|
||||
getAllWatchLaterList()
|
||||
|
||||
Reference in New Issue
Block a user