import { defineStore } from 'pinia'; import { ref } from 'vue'; import { watch } from 'vue'; import { compareByCreatedAt, getGroupName, getWorldName } from '../shared/utils'; import { database } from '../service/database'; import { useFriendStore } from './friend'; import { useInstanceStore } from './instance'; import { useLocationStore } from './location'; import { useModerationStore } from './moderation'; import { useNotificationStore } from './notification'; import { useNotificationsSettingsStore } from './settings/notifications'; import { useUserStore } from './user'; import { useWristOverlaySettingsStore } from './settings/wristOverlay'; import { watchState } from '../service/watchState'; export const useSharedFeedStore = defineStore('SharedFeed', () => { const friendStore = useFriendStore(); const notificationsSettingsStore = useNotificationsSettingsStore(); const locationStore = useLocationStore(); const userStore = useUserStore(); const wristOverlaySettingsStore = useWristOverlaySettingsStore(); const instanceStore = useInstanceStore(); const moderationStore = useModerationStore(); const notificationStore = useNotificationStore(); const onPlayerJoining = ref([]); async function rebuildOnPlayerJoining() { let newOnPlayerJoining = []; for (const ref of userStore.currentTravelers.values()) { const isFavorite = friendStore.localFavoriteFriends.has(ref.id); if ( locationStore.lastLocation.playerList.has(ref.id) || (notificationsSettingsStore.sharedFeedFilters.wrist .OnPlayerJoining === 'VIP' && !isFavorite) ) { continue; } if (ref.$location.tag === locationStore.lastLocation.location) { const feedEntry = { ...ref, isFavorite, isFriend: true, type: 'OnPlayerJoining' }; newOnPlayerJoining.unshift(feedEntry); continue; } const worldName = await getWorldName(ref.$location.worldId); const groupName = await getGroupName(ref.$location.groupId); const feedEntry = { created_at: ref.created_at, type: 'GPS', userId: ref.id, displayName: ref.displayName, location: ref.$location.tag, worldName, groupName, previousLocation: '', isFavorite, time: 0, isFriend: true, isTraveling: true }; newOnPlayerJoining.unshift(feedEntry); } onPlayerJoining.value = newOnPlayerJoining; sharedFeedData.value = sharedFeedData.value.filter( (ctx) => !( ctx.type === 'OnPlayerJoining' || (ctx.type === 'GPS' && ctx.isTraveling) ) ); sharedFeedData.value.unshift(...onPlayerJoining.value); if (sharedFeedData.value.length > maxEntries) { sharedFeedData.value.splice(maxEntries); } sendSharedFeed(); } watch( () => userStore.currentTravelers, () => rebuildOnPlayerJoining(), { deep: true } ); watch( () => watchState.isLoggedIn, (isLoggedIn) => { if (isLoggedIn) { sharedFeedData.value = []; loadSharedFeed(); } }, { flush: 'sync' } ); const sharedFeedData = ref([]); const maxEntries = 25; async function loadSharedFeed() { let newFeed = []; const wristFilter = notificationsSettingsStore.sharedFeedFilters.wrist; // run after fav and friendlist init const vipList = Array.from(friendStore.localFavoriteFriends.values()); const friendList = Array.from(friendStore.friends.keys()); // Filters const vipFilters = Object.keys(wristFilter).filter( (key) => wristFilter[key] === 'VIP' ); const friendsFilters = Object.keys(wristFilter).filter( (key) => wristFilter[key] === 'Friends' ); const everyoneFilters = Object.keys(wristFilter).filter( (key) => wristFilter[key] === 'On' || wristFilter[key] === 'Everyone' ); const everyoneAndFriendsFilters = Object.keys(wristFilter).filter( (key) => wristFilter[key] === 'Friends' || wristFilter[key] === 'On' || wristFilter[key] === 'Everyone' ); // Feed if (vipFilters.length) { const vipFeedRows = await database.lookupFeedDatabase( vipFilters, vipList, maxEntries ); newFeed = newFeed.concat(vipFeedRows); } if (everyoneAndFriendsFilters.length) { const friendsFeedRows = await database.lookupFeedDatabase( everyoneAndFriendsFilters, [], maxEntries ); newFeed = newFeed.concat(friendsFeedRows); } // GameLog if (vipFilters.length) { const vipGameLogRows = await database.lookupGameLogDatabase( '', vipFilters, vipList, maxEntries ); newFeed = newFeed.concat(vipGameLogRows); } if (friendsFilters.length) { const friendsGameLogRows = await database.lookupGameLogDatabase( '', friendsFilters, friendList, maxEntries ); newFeed = newFeed.concat(friendsGameLogRows); } if (everyoneFilters.length) { const everyoneGameLogRows = await database.lookupGameLogDatabase( '', everyoneFilters, [], maxEntries ); newFeed = newFeed.concat(everyoneGameLogRows); } // Notifications if (vipFilters.length) { const vipNotificationRows = await database.lookupNotificationDatabase( '', vipFilters, vipList, maxEntries ); newFeed = newFeed.concat(vipNotificationRows); } if (everyoneAndFriendsFilters.length) { const friendsNotificationRows = await database.lookupNotificationDatabase( '', everyoneAndFriendsFilters, [], maxEntries ); newFeed = newFeed.concat(friendsNotificationRows); } // hide private worlds from feed if (wristOverlaySettingsStore.hidePrivateFromFeed) { newFeed = newFeed.filter( (ctx) => !(ctx.type === 'GPS' && ctx.location === 'private') ); } // remove current user newFeed = newFeed.filter( (ctx) => ctx.userId !== userStore.currentUser.id ); // FriendLog, Moderations Against (nope, not worth it) newFeed.sort(compareByCreatedAt); newFeed.splice(maxEntries); for (const entry of newFeed) { const userId = entry.userId || entry.senderUserId; entry.isFriend = false; entry.isFavorite = false; entry.tagColour = ''; if (userId) { entry.isFriend = friendStore.friends.has(userId); entry.isFavorite = friendStore.localFavoriteFriends.has(userId); entry.tagColour = userStore.customUserTags.get(userId)?.colour ?? ''; } // tack on instance names const location = entry.location || entry.details?.location; if (location) { entry.instanceDisplayName = await instanceStore.getInstanceName(location); } } sharedFeedData.value = newFeed; rebuildOnPlayerJoining(); // also sends updated feed } async function addEntry(data) { const ctx = { ...data }; const userId = ctx.userId || ctx.senderUserId; const wristFilter = notificationsSettingsStore.sharedFeedFilters.wrist; if (userId === userStore.currentUser.id) { return; } if ( wristOverlaySettingsStore.hidePrivateFromFeed && ctx.type === 'GPS' && ctx.location === 'private' ) { return; } if (ctx.type === 'FriendRequest' || ctx.type === 'Avatar') { return; } let isFriend = false; let isFavorite = false; let tagColour = ''; if (userId) { isFriend = friendStore.friends.has(userId); isFavorite = friendStore.localFavoriteFriends.has(userId); tagColour = userStore.customUserTags.get(userId)?.colour ?? ''; } // tack on instance names const location = ctx.location || ctx.details?.location; if (location) { ctx.instanceDisplayName = await instanceStore.getInstanceName(location); } // TODO: videoPlay come through before it gets video name // TODO: On Location change remove OnPlayerLeft & OnPlayerJoined { // on Location change remove OnPlayerLeft // if (ctx.type === 'LocationDestination') { // if (!ctxTime) { // ctxTime = Date.parse(ctx.created_at); // } // currentUserLeaveTime = ctxTime; // const currentUserLeaveTimeOffset = currentUserLeaveTime + 5 * 1000; // for (var k = w - 1; k > -1; k--) { // var feedItem = wristArr[k]; // const feedItemTime = Date.parse(feedItem.created_at); // if ( // (feedItem.type === 'OnPlayerLeft' || // feedItem.type === 'BlockedOnPlayerLeft' || // feedItem.type === 'MutedOnPlayerLeft') && // feedItemTime >= currentUserLeaveTime && // feedItemTime <= currentUserLeaveTimeOffset // ) { // wristArr.splice(k, 1); // w--; // } // } // } // on Location change remove OnPlayerJoined // if (ctx.type === 'Location') { // if (!ctxTime) { // ctxTime = Date.parse(ctx.created_at); // } // locationJoinTime = ctxTime; // const locationJoinTimeOffset = locationJoinTime + 20 * 1000; // for (let k = w - 1; k > -1; k--) { // let feedItem = wristArr[k]; // const feedItemTime = Date.parse(feedItem.created_at); // if ( // (feedItem.type === 'OnPlayerJoined' || // feedItem.type === 'BlockedOnPlayerJoined' || // feedItem.type === 'MutedOnPlayerJoined') && // feedItemTime >= locationJoinTime && // feedItemTime <= locationJoinTimeOffset // ) { // wristArr.splice(k, 1); // w--; // } // } // } } if (ctx.type === 'OnPlayerJoined' || ctx.type === 'OnPlayerLeft') { moderationAgainstCheck({ ...ctx, isFavorite, isFriend, tagColour }); } if ( wristFilter[ctx.type] && (wristFilter[ctx.type] === 'On' || wristFilter[ctx.type] === 'Everyone' || (wristFilter[ctx.type] === 'Friends' && isFriend) || (wristFilter[ctx.type] === 'VIP' && isFavorite)) ) { addToSharedFeed({ ...ctx, isFavorite, isFriend, tagColour }); } } function addToSharedFeed(ref) { sharedFeedData.value.unshift(ref); if (sharedFeedData.value.length > maxEntries) { sharedFeedData.value.splice(maxEntries); } sendSharedFeed(); } function moderationAgainstCheck(ctx) { const wristFilter = notificationsSettingsStore.sharedFeedFilters.wrist; // BlockedOnPlayerJoined, BlockedOnPlayerLeft, MutedOnPlayerJoined, MutedOnPlayerLeft for (const ref of moderationStore.cachedPlayerModerations.values()) { if (ref.sourceUserId !== ctx.userId) { continue; } let type = ''; if (ref.type === 'block') { type = `Blocked${ctx.type}`; } else if (ref.type === 'mute') { type = `Muted${ctx.type}`; } else { continue; } const entry = { created_at: ctx.created_at, type, displayName: ctx.displayName, userId: ctx.userId, isFavorite: ctx.isFavorite, isFriend: ctx.isFriend, tagColour: ctx.tagColour }; notificationStore.queueGameLogNoty(entry); if ( wristFilter[type] && (wristFilter[type] === 'Everyone' || (wristFilter[type] === 'Friends' && ctx.isFriend) || (wristFilter[type] === 'VIP' && ctx.isFavorite)) ) { addToSharedFeed(entry); } } } function addTag(userId, colour) { let changed = false; for (const entry of sharedFeedData.value) { if (entry.userId === userId) { entry.tagColour = colour; changed = true; } } if (changed) { sendSharedFeed(); } } async function sendSharedFeed() { await AppApi.ExecuteVrOverlayFunction( 'wristFeedUpdate', JSON.stringify(sharedFeedData.value) ); } return { loadSharedFeed, sendSharedFeed, addEntry, addTag, sharedFeedData }; });