From 4f8cac6eeee56cc0ba0d34621f700648a9e4fa77 Mon Sep 17 00:00:00 2001 From: daniyal Date: Mon, 18 Nov 2024 23:21:05 +0500 Subject: [PATCH] fix: improve wot logic Improve data structure for storing WoT Also improve WoT calculation logic --- src/hooks/useFilteredMods.ts | 20 ++++++-- src/layout/header.tsx | 34 +++++++++---- src/layout/index.tsx | 9 ++-- src/pages/home.tsx | 9 +++- src/store/reducers/wot.ts | 56 ++++++++------------- src/utils/wot.ts | 98 ++++++++++++++++-------------------- 6 files changed, 116 insertions(+), 110 deletions(-) diff --git a/src/hooks/useFilteredMods.ts b/src/hooks/useFilteredMods.ts index a7654d9..7c46c76 100644 --- a/src/hooks/useFilteredMods.ts +++ b/src/hooks/useFilteredMods.ts @@ -11,6 +11,7 @@ import { } from 'types' import { npubToHex } from 'utils' import { useAppSelector } from './redux' +import { isInWoT } from 'utils/wot' export const useFilteredMods = ( mods: ModDetails[], @@ -23,7 +24,9 @@ export const useFilteredMods = ( }, author?: string | undefined ) => { - const { siteWot, userWot } = useAppSelector((state) => state.wot) + const { siteWot, siteWotLevel, userWot, userWotLevel } = useAppSelector( + (state) => state.wot + ) return useMemo(() => { const nsfwFilter = (mods: ModDetails[]) => { @@ -56,13 +59,18 @@ export const useFilteredMods = ( case WOTFilterOptions.None: return mods case WOTFilterOptions.Site_Only: - return mods.filter((mod) => siteWot.includes(mod.author)) + return mods.filter((mod) => + isInWoT(siteWot, siteWotLevel, mod.author) + ) case WOTFilterOptions.Mine_Only: - return mods.filter((mod) => userWot.includes(mod.author)) + return mods.filter((mod) => + isInWoT(userWot, userWotLevel, mod.author) + ) case WOTFilterOptions.Site_And_Mine: return mods.filter( (mod) => - siteWot.includes(mod.author) || userWot.includes(mod.author) + isInWoT(siteWot, siteWotLevel, mod.author) || + isInWoT(userWot, userWotLevel, mod.author) ) } } @@ -114,6 +122,8 @@ export const useFilteredMods = ( muteLists, nsfwList, siteWot, - userWot + siteWotLevel, + userWot, + userWotLevel ]) } diff --git a/src/layout/header.tsx b/src/layout/header.tsx index 0d0c571..f131dc6 100644 --- a/src/layout/header.tsx +++ b/src/layout/header.tsx @@ -21,7 +21,7 @@ import '../styles/popup.css' import { npubToHex } from '../utils' import logo from '../assets/img/DEG Mods Logo With Text.svg' import placeholder from '../assets/img/DEG Mods Default PP.png' -import { setUserWot } from 'store/reducers/wot' +import { resetUserWot } from 'store/reducers/wot' export const Header = () => { const dispatch = useAppDispatch() @@ -49,7 +49,7 @@ export const Header = () => { if (opts.type === 'logout') { dispatch(setAuth(null)) dispatch(setUser(null)) - dispatch(setUserWot([])) + dispatch(resetUserWot()) } else { dispatch( setAuth({ @@ -381,13 +381,29 @@ const RegisterButtonWithDialog = () => { Browser Extensions (Windows)

- Once you create your "account" on any of these, come back and click login, then sign-in with - extension. Here's a quick video guide, and here's a guide post to help with this process.

-
- + Once you create your "account" on any of these, come + back and click login, then sign-in with extension. + Here's a quick video guide, and here's a{' '} + + guide post + {' '} + to help with this process. +

+
+
{ const hexPubkey = npubToHex(SITE_WOT_NPUB) if (hexPubkey) { dispatch(setSiteWotStatus(WOTStatus.LOADING)) - calculateWot(hexPubkey, ndk, siteWotLevel) + calculateWot(hexPubkey, ndk) .then((wot) => { - dispatch(setSiteWot(Array.from(wot))) - dispatch(setSiteWotStatus(WOTStatus.LOADED)) + dispatch(setSiteWot(wot)) }) .catch((err) => { console.trace('An error occurred in calculating site WOT', err) @@ -54,9 +53,9 @@ export const Layout = () => { if (ndk && userState.user?.pubkey) { const hexPubkey = npubToHex(userState.user.pubkey as string) if (hexPubkey) - calculateWot(hexPubkey, ndk, userWotLevel) + calculateWot(hexPubkey, ndk) .then((wot) => { - dispatch(setUserWot(Array.from(wot))) + dispatch(setUserWot(wot)) }) .catch((err) => { console.trace('An error occurred in calculating user WOT', err) diff --git a/src/pages/home.tsx b/src/pages/home.tsx index 72168a1..78f722c 100644 --- a/src/pages/home.tsx +++ b/src/pages/home.tsx @@ -38,6 +38,7 @@ import 'swiper/css/navigation' import 'swiper/css/pagination' import { LoadingSpinner } from 'components/LoadingSpinner' import { Spinner } from 'components/Spinner' +import { isInWoT } from 'utils/wot' export const HomePage = () => { const navigate = useNavigate() @@ -248,7 +249,9 @@ const DisplayMod = ({ naddr }: DisplayModProps) => { const DisplayLatestMods = () => { const navigate = useNavigate() const { fetchMods } = useNDKContext() - const { siteWot, userWot } = useAppSelector((state) => state.wot) + const { siteWot, siteWotLevel, userWot, userWotLevel } = useAppSelector( + (state) => state.wot + ) const [isFetchingLatestMods, setIsFetchingLatestMods] = useState(true) const [latestMods, setLatestMods] = useState([]) @@ -261,7 +264,9 @@ const DisplayLatestMods = () => { // Sort by the latest (published_at descending) mods.sort((a, b) => b.published_at - a.published_at) const wotFilteredMods = mods.filter( - (mod) => siteWot.includes(mod.author) || userWot.includes(mod.author) + (mod) => + isInWoT(siteWot, siteWotLevel, mod.author) || + isInWoT(userWot, userWotLevel, mod.author) ) setLatestMods(wotFilteredMods) }) diff --git a/src/store/reducers/wot.ts b/src/store/reducers/wot.ts index 4288c65..3cc5e02 100644 --- a/src/store/reducers/wot.ts +++ b/src/store/reducers/wot.ts @@ -8,19 +8,19 @@ export enum WOTStatus { } export interface IWOT { - siteWot: string[] + siteWot: Record siteWotStatus: WOTStatus siteWotLevel: number - userWot: string[] + userWot: Record userWotStatus: WOTStatus userWotLevel: number } const initialState: IWOT = { - siteWot: [], + siteWot: {}, siteWotStatus: WOTStatus.IDLE, siteWotLevel: 0, - userWot: [], + userWot: {}, userWotStatus: WOTStatus.IDLE, userWotLevel: 0 } @@ -29,45 +29,30 @@ export const wotSlice = createSlice({ name: 'wot', initialState, reducers: { - setSiteWot(state, action: PayloadAction) { - state = { - ...state, - siteWot: action.payload, - siteWotStatus: WOTStatus.LOADED - } - return state + setSiteWot(state, action: PayloadAction>) { + state.siteWot = action.payload + state.siteWotStatus = WOTStatus.LOADED }, - setUserWot(state, action: PayloadAction) { - state = { - ...state, - userWot: action.payload, - userWotStatus: WOTStatus.LOADED - } - return state + setUserWot(state, action: PayloadAction>) { + state.userWot = action.payload + state.userWotStatus = WOTStatus.LOADED + }, + resetUserWot(state) { + state.userWot = {} + state.userWotStatus = WOTStatus.IDLE + state.userWotLevel = 0 }, setSiteWotStatus(state, action: PayloadAction) { - return { - ...state, - siteWotStatus: action.payload - } + state.siteWotStatus = action.payload }, setUserWotStatus(state, action: PayloadAction) { - return { - ...state, - userWotStatus: action.payload - } + state.userWotStatus = action.payload }, setSiteWotLevel(state, action: PayloadAction) { - return { - ...state, - siteWotLevel: action.payload - } + state.siteWotLevel = action.payload }, setUserWotLevel(state, action: PayloadAction) { - return { - ...state, - userWotLevel: action.payload - } + state.userWotLevel = action.payload } } }) @@ -78,7 +63,8 @@ export const { setSiteWotStatus, setUserWotStatus, setSiteWotLevel, - setUserWotLevel + setUserWotLevel, + resetUserWot } = wotSlice.actions export default wotSlice.reducer diff --git a/src/utils/wot.ts b/src/utils/wot.ts index f894f96..be3b481 100644 --- a/src/utils/wot.ts +++ b/src/utils/wot.ts @@ -14,19 +14,26 @@ interface UserRelations { type Network = Map -export const calculateWot = async ( - pubkey: Hexpubkey, - ndk: NDK, - targetWOTScore: number -) => { - const network: Network = new Map() - const WOT = new Set() +export const calculateWot = async (pubkey: Hexpubkey, ndk: NDK) => { + const WoT: Record = {} const userRelations = await findFollowsAndMuteUsers(pubkey, ndk) - network.set(pubkey, userRelations) + const follows = Array.from(userRelations.follows) + const muted = Array.from(userRelations.muted) + + // Add all the following users to WoT with score set to Positive_Infinity + follows.forEach((f) => { + WoT[f] = Number.POSITIVE_INFINITY + }) + + // Add all the muted users to WoT with score set to Negative_Infinity + muted.forEach((m) => { + WoT[m] = Number.NEGATIVE_INFINITY + }) + + const network: Network = new Map() // find the userRelations of every user in follow list - const follows = Array.from(userRelations.follows) const promises = follows.map((user) => findFollowsAndMuteUsers(user, ndk).then((userRelations) => { network.set(user, userRelations) @@ -34,61 +41,35 @@ export const calculateWot = async ( ) await Promise.all(promises) - // add all the following users to WOT - follows.forEach((f) => { - WOT.add(f) + // make a list of all the users in the network either mutes or followed + const users = new Set() + const userRelationsArray = Array.from(network.values()) + userRelationsArray.forEach(({ follows, muted }) => { + follows.forEach((f) => users.add(f)) + muted.forEach((m) => users.add(m)) }) - // construct a list of users not being followed directly - const indirectFollows = new Set() - follows.forEach((hexpubkey) => { - const relations = network.get(hexpubkey) - if (relations) { - relations.follows.forEach((f) => { - indirectFollows.add(f) - }) + users.forEach((user) => { + // Only calculate if it's not already added to WoT + if (!(user in WoT)) { + const wotScore = calculateWoTScore(user, network) + WoT[user] = wotScore } }) - indirectFollows.forEach((targetHexPubkey) => { - // if any of the indirect followed user is in direct mute list - // we'll not include it in WOT - if (userRelations.muted.has(targetHexPubkey)) return - - const wotScore = calculateWoTScore(pubkey, targetHexPubkey, network) - if (wotScore >= targetWOTScore) { - WOT.add(targetHexPubkey) - } - }) - - return WOT + return WoT } -export const calculateWoTScore = ( - user: Hexpubkey, - targetUser: Hexpubkey, - network: Network -): number => { - const userRelations = network.get(user) - if (!userRelations) return 0 - +export const calculateWoTScore = (user: Hexpubkey, network: Network) => { let wotScore = 0 - // Check each user followed - for (const followedUser of userRelations.follows) { - const followedUserRelations = network.get(followedUser) - if (!followedUserRelations) continue + // iterate over all the entries in the network and increment/decrement + // wotScore based on the list user in which exists (followed/mutes) + network.forEach(({ follows, muted }) => { + if (follows.has(user)) wotScore += 1 - // Positive Score: +1 if followedUser also follows targetId - if (followedUserRelations.follows.has(targetUser)) { - wotScore += 1 - } - - // Negative Score: -1 if followedUser has muted targetId - if (followedUserRelations.muted.has(targetUser)) { - wotScore -= 1 - } - } + if (muted.has(user)) wotScore -= 1 + }) return wotScore } @@ -145,3 +126,12 @@ export const filterValidPTags = (tags: NDKTag[]) => return false } }) + +export const isInWoT = ( + WoT: Record, + targetScore: number, + targetUser: string +): boolean => { + const wotScore = WoT[targetUser] ?? 0 // Default to 0 if the user is not in the record + return wotScore >= targetScore +}