replace greeter mode with investment filter (#1291)

* replace greeter mode with investment filter

* change name to satsFilter

* drop freebie column

---------

Co-authored-by: k00b <k00b@stacker.news>
This commit is contained in:
Keyan 2024-08-11 18:47:03 -05:00 committed by GitHub
parent e897a2d1dc
commit c5f043c625
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 73 additions and 48 deletions

View File

@ -1,7 +1,7 @@
import { ANON_ITEM_SPAM_INTERVAL, ITEM_SPAM_INTERVAL, USER_ID } from '@/lib/constants' import { ANON_ITEM_SPAM_INTERVAL, ITEM_SPAM_INTERVAL, USER_ID } from '@/lib/constants'
import { notifyItemMention, notifyItemParents, notifyMention, notifyTerritorySubscribers, notifyUserSubscribers } from '@/lib/webPush' import { notifyItemMention, notifyItemParents, notifyMention, notifyTerritorySubscribers, notifyUserSubscribers } from '@/lib/webPush'
import { getItemMentions, getMentions, performBotBehavior } from './lib/item' import { getItemMentions, getMentions, performBotBehavior } from './lib/item'
import { satsToMsats } from '@/lib/format' import { msatsToSats, satsToMsats } from '@/lib/format'
export const anonable = true export const anonable = true
export const supportsPessimism = true export const supportsPessimism = true
@ -51,8 +51,7 @@ export async function perform (args, context) {
itemActs.push({ itemActs.push({
msats: cost - boostMsats, act: 'FEE', userId: data.userId, ...invoiceData msats: cost - boostMsats, act: 'FEE', userId: data.userId, ...invoiceData
}) })
} else { data.cost = msatsToSats(cost - boostMsats)
data.freebie = true
} }
const mentions = await getMentions(args, context) const mentions = await getMentions(args, context)

View File

@ -30,12 +30,12 @@ function commentsOrderByClause (me, models, sort) {
return `ORDER BY COALESCE( return `ORDER BY COALESCE(
personal_hot_score, personal_hot_score,
${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3)) DESC NULLS LAST, ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3)) DESC NULLS LAST,
"Item".msats DESC, ("Item".freebie IS FALSE) DESC, "Item".id DESC` "Item".msats DESC, ("Item".cost > 0) DESC, "Item".id DESC`
} else { } else {
if (sort === 'top') { if (sort === 'top') {
return `ORDER BY ${orderByNumerator(models, 0)} DESC NULLS LAST, "Item".msats DESC, ("Item".freebie IS FALSE) DESC, "Item".id DESC` return `ORDER BY ${orderByNumerator(models, 0)} DESC NULLS LAST, "Item".msats DESC, ("Item".cost > 0) DESC, "Item".id DESC`
} else { } else {
return `ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".freebie IS FALSE) DESC, "Item".id DESC` return `ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".cost > 0) DESC, "Item".id DESC`
} }
} }
} }
@ -225,22 +225,16 @@ export async function filterClause (me, models, type) {
// handle freebies // handle freebies
// by default don't include freebies unless they have upvotes // by default don't include freebies unless they have upvotes
let freebieClauses = ['NOT "Item".freebie', '"Item"."weightedVotes" - "Item"."weightedDownVotes" > 0'] let investmentClause = '("Item".cost + "Item".boost + ("Item".msats / 1000)) >= 10'
if (me) { if (me) {
const user = await models.user.findUnique({ where: { id: me.id } }) const user = await models.user.findUnique({ where: { id: me.id } })
// wild west mode has everything
if (user.wildWestMode) {
return ''
}
// greeter mode includes freebies if feebies haven't been flagged
if (user.greeterMode) {
freebieClauses = ['NOT "Item".freebie', '"Item"."weightedVotes" - "Item"."weightedDownVotes" >= 0']
}
// always include if it's mine investmentClause = `(("Item".cost + "Item".boost + ("Item".msats / 1000)) >= ${user.satsFilter} OR "Item"."userId" = ${me.id})`
freebieClauses.push(`"Item"."userId" = ${me.id}`)
if (user.wildWestMode) {
return investmentClause
}
} }
const freebieClause = '(' + freebieClauses.join(' OR ') + ')'
// handle outlawed // handle outlawed
// if the item is above the threshold or is mine // if the item is above the threshold or is mine
@ -250,7 +244,7 @@ export async function filterClause (me, models, type) {
} }
const outlawClause = '(' + outlawClauses.join(' OR ') + ')' const outlawClause = '(' + outlawClauses.join(' OR ') + ')'
return [freebieClause, outlawClause] return [investmentClause, outlawClause]
} }
function typeClause (type) { function typeClause (type) {
@ -268,7 +262,7 @@ function typeClause (type) {
case 'comments': case 'comments':
return '"Item"."parentId" IS NOT NULL' return '"Item"."parentId" IS NOT NULL'
case 'freebies': case 'freebies':
return '"Item".freebie' return '"Item".cost = 0'
case 'outlawed': case 'outlawed':
return `"Item"."weightedVotes" - "Item"."weightedDownVotes" <= -${ITEM_FILTER_THRESHOLD} OR "Item".outlawed` return `"Item"."weightedVotes" - "Item"."weightedDownVotes" <= -${ITEM_FILTER_THRESHOLD} OR "Item".outlawed`
case 'borderland': case 'borderland':
@ -470,10 +464,10 @@ export default {
'"Item".bio = false', '"Item".bio = false',
activeOrMine(me), activeOrMine(me),
await filterClause(me, models, type))} await filterClause(me, models, type))}
ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".freebie IS FALSE) DESC, "Item".id DESC ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".cost > 0) DESC, "Item".id DESC
OFFSET $1 OFFSET $1
LIMIT $2`, LIMIT $2`,
orderBy: `ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".freebie IS FALSE) DESC, "Item".id DESC` orderBy: `ORDER BY ${orderByNumerator(models, 0)}/POWER(GREATEST(3, EXTRACT(EPOCH FROM (now_utc() - "Item".created_at))/3600), 1.3) DESC NULLS LAST, "Item".msats DESC, ("Item".cost > 0) DESC, "Item".id DESC`
}, decodedCursor.offset, limit, ...subArr) }, decodedCursor.offset, limit, ...subArr)
} }
@ -1054,6 +1048,9 @@ export default {
freedFreebie: async (item) => { freedFreebie: async (item) => {
return item.weightedVotes - item.weightedDownVotes > 0 return item.weightedVotes - item.weightedDownVotes > 0
}, },
freebie: async (item) => {
return item.cost === 0
},
meSats: async (item, args, { me, models }) => { meSats: async (item, args, { me, models }) => {
if (!me) return 0 if (!me) return 0
if (typeof item.meMsats !== 'undefined') { if (typeof item.meMsats !== 'undefined') {
@ -1255,7 +1252,7 @@ export const updateItem = async (parent, { sub: subName, forward, ...item }, { m
const differentSub = subName && old.subName !== subName const differentSub = subName && old.subName !== subName
if (differentSub) { if (differentSub) {
const sub = await models.sub.findUnique({ where: { name: subName } }) const sub = await models.sub.findUnique({ where: { name: subName } })
if (old.freebie) { if (old.cost === 0) {
if (!sub.allowFreebies) { if (!sub.allowFreebies) {
throw new GraphQLError(`~${subName} does not allow freebies`, { extensions: { code: 'BAD_INPUT' } }) throw new GraphQLError(`~${subName} does not allow freebies`, { extensions: { code: 'BAD_INPUT' } })
} }

View File

@ -71,7 +71,7 @@ export default gql`
diagnostics: Boolean! diagnostics: Boolean!
noReferralLinks: Boolean! noReferralLinks: Boolean!
fiatCurrency: String! fiatCurrency: String!
greeterMode: Boolean! satsFilter: Int!
hideBookmarks: Boolean! hideBookmarks: Boolean!
hideCowboyHat: Boolean! hideCowboyHat: Boolean!
hideGithub: Boolean! hideGithub: Boolean!
@ -140,7 +140,7 @@ export default gql`
diagnostics: Boolean! diagnostics: Boolean!
noReferralLinks: Boolean! noReferralLinks: Boolean!
fiatCurrency: String! fiatCurrency: String!
greeterMode: Boolean! satsFilter: Int!
hideBookmarks: Boolean! hideBookmarks: Boolean!
hideCowboyHat: Boolean! hideCowboyHat: Boolean!
hideGithub: Boolean! hideGithub: Boolean!

View File

@ -97,7 +97,7 @@ export default function Comment ({
}) { }) {
const [edit, setEdit] = useState() const [edit, setEdit] = useState()
const me = useMe() const me = useMe()
const isHiddenFreebie = !me?.privates?.wildWestMode && !me?.privates?.greeterMode && !item.mine && item.freebie && !item.freedFreebie const isHiddenFreebie = me?.privates?.satsFilter !== 0 && !item.mine && item.freebie && !item.freedFreebie
const [collapse, setCollapse] = useState( const [collapse, setCollapse] = useState(
(isHiddenFreebie || item?.user?.meMute || (item?.outlawed && !me?.privates?.wildWestMode)) && !includeParent (isHiddenFreebie || item?.user?.meMute || (item?.outlawed && !me?.privates?.wildWestMode)) && !includeParent
? 'yep' ? 'yep'

View File

@ -15,7 +15,7 @@ export const ME = gql`
diagnostics diagnostics
noReferralLinks noReferralLinks
fiatCurrency fiatCurrency
greeterMode satsFilter
hideCowboyHat hideCowboyHat
hideFromTopUsers hideFromTopUsers
hideGithub hideGithub
@ -104,7 +104,7 @@ export const SETTINGS_FIELDS = gql`
nostrCrossposting nostrCrossposting
nostrRelays nostrRelays
wildWestMode wildWestMode
greeterMode satsFilter
nsfwMode nsfwMode
authMethods { authMethods {
lightning lightning

View File

@ -579,6 +579,7 @@ export const settingsSchema = object().shape({
diagnostics: boolean(), diagnostics: boolean(),
noReferralLinks: boolean(), noReferralLinks: boolean(),
hideIsContributor: boolean(), hideIsContributor: boolean(),
satsFilter: intValidator.required('required').min(0, 'must be at least 0').max(1000, 'must be at most 1000'),
zapUndos: intValidator.nullable().min(0, 'must be greater or equal to 0') zapUndos: intValidator.nullable().min(0, 'must be greater or equal to 0')
// exclude from cyclic analysis. see https://github.com/jquense/yup/issues/720 // exclude from cyclic analysis. see https://github.com/jquense/yup/issues/720
}, [['tipRandomMax', 'tipRandomMin']]) }, [['tipRandomMax', 'tipRandomMin']])

View File

@ -140,7 +140,7 @@ export default function Settings ({ ssrData }) {
hideTwitter: settings?.hideTwitter, hideTwitter: settings?.hideTwitter,
imgproxyOnly: settings?.imgproxyOnly, imgproxyOnly: settings?.imgproxyOnly,
wildWestMode: settings?.wildWestMode, wildWestMode: settings?.wildWestMode,
greeterMode: settings?.greeterMode, satsFilter: settings?.satsFilter,
nsfwMode: settings?.nsfwMode, nsfwMode: settings?.nsfwMode,
nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '', nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '',
nostrCrossposting: settings?.nostrCrossposting, nostrCrossposting: settings?.nostrCrossposting,
@ -152,7 +152,11 @@ export default function Settings ({ ssrData }) {
noReferralLinks: settings?.noReferralLinks noReferralLinks: settings?.noReferralLinks
}} }}
schema={settingsSchema} schema={settingsSchema}
onSubmit={async ({ tipDefault, tipRandom, tipRandomMin, tipRandomMax, withdrawMaxFeeDefault, zapUndos, zapUndosEnabled, nostrPubkey, nostrRelays, ...values }) => { onSubmit={async ({
tipDefault, tipRandom, tipRandomMin, tipRandomMax, withdrawMaxFeeDefault,
zapUndos, zapUndosEnabled, nostrPubkey, nostrRelays, satsFilter,
...values
}) => {
if (nostrPubkey.length === 0) { if (nostrPubkey.length === 0) {
nostrPubkey = null nostrPubkey = null
} else { } else {
@ -172,6 +176,7 @@ export default function Settings ({ ssrData }) {
tipRandomMin: tipRandom ? Number(tipRandomMin) : null, tipRandomMin: tipRandom ? Number(tipRandomMin) : null,
tipRandomMax: tipRandom ? Number(tipRandomMax) : null, tipRandomMax: tipRandom ? Number(tipRandomMax) : null,
withdrawMaxFeeDefault: Number(withdrawMaxFeeDefault), withdrawMaxFeeDefault: Number(withdrawMaxFeeDefault),
satsFilter: Number(satsFilter),
zapUndos: zapUndosEnabled ? Number(zapUndos) : null, zapUndos: zapUndosEnabled ? Number(zapUndos) : null,
nostrPubkey, nostrPubkey,
nostrRelays: nostrRelaysFiltered, nostrRelays: nostrRelaysFiltered,
@ -467,7 +472,27 @@ export default function Settings ({ ssrData }) {
label={<>don't create referral links on copy</>} label={<>don't create referral links on copy</>}
name='noReferralLinks' name='noReferralLinks'
/> />
<div className='form-label'>content</div> <h4>content</h4>
<Input
label={
<div className='d-flex align-items-center'>filter by sats
<Info>
<ul className='fw-bold'>
<li>hide the post if the sum of these is less than your setting:</li>
<ul>
<li>posting cost</li>
<li>total sats from zaps</li>
<li>boost</li>
</ul>
<li>set to zero to be a greeter, with the tradeoff of seeing more spam</li>
</ul>
</Info>
</div>
}
name='satsFilter'
required
append={<InputGroup.Text className='text-monospace'>sats</InputGroup.Text>}
/>
<Checkbox <Checkbox
label={ label={
<div className='d-flex align-items-center'>wild west mode <div className='d-flex align-items-center'>wild west mode
@ -482,21 +507,6 @@ export default function Settings ({ ssrData }) {
name='wildWestMode' name='wildWestMode'
groupClassName='mb-0' groupClassName='mb-0'
/> />
<Checkbox
label={
<div className='d-flex align-items-center'>greeter mode
<Info>
<ul className='fw-bold'>
<li>see and screen free posts and comments</li>
<li>help onboard new stackers to SN and Lightning</li>
<li>you might be subject to more spam</li>
</ul>
</Info>
</div>
}
name='greeterMode'
groupClassName='mb-0'
/>
<Checkbox <Checkbox
label={ label={
<div className='d-flex align-items-center'>nsfw mode <div className='d-flex align-items-center'>nsfw mode

View File

@ -0,0 +1,16 @@
-- AlterTable
ALTER TABLE "Item" ADD COLUMN "cost" INTEGER NOT NULL DEFAULT 0;
-- use existing "ItemAct".act = FEE AND "Item"."userId" = "ItemAct"."userId" to calculate the cost for existing "Item"s
UPDATE "Item" SET "cost" = "ItemAct"."msats" / 1000
FROM "ItemAct"
WHERE "Item"."id" = "ItemAct"."itemId" AND "ItemAct"."act" = 'FEE' AND "Item"."userId" = "ItemAct"."userId";
ALTER TABLE "users" ADD COLUMN "satsFilter" INTEGER NOT NULL DEFAULT 10;
UPDATE "users" SET "satsFilter" = 0 WHERE "greeterMode";
ALTER TABLE "users" DROP COLUMN "greeterMode";
-- CreateIndex
CREATE INDEX "Item_cost_idx" ON "Item"("cost");

View File

@ -54,7 +54,7 @@ model User {
upvoteTrust Float @default(0) upvoteTrust Float @default(0)
hideInvoiceDesc Boolean @default(false) hideInvoiceDesc Boolean @default(false)
wildWestMode Boolean @default(false) wildWestMode Boolean @default(false)
greeterMode Boolean @default(false) satsFilter Int @default(10)
nsfwMode Boolean @default(false) nsfwMode Boolean @default(false)
fiatCurrency String @default("USD") fiatCurrency String @default("USD")
withdrawMaxFeeDefault Int @default(10) withdrawMaxFeeDefault Int @default(10)
@ -425,6 +425,7 @@ model Item {
lastZapAt DateTime? lastZapAt DateTime?
ncomments Int @default(0) ncomments Int @default(0)
msats BigInt @default(0) msats BigInt @default(0)
cost Int @default(0)
weightedDownVotes Float @default(0) weightedDownVotes Float @default(0)
bio Boolean @default(false) bio Boolean @default(false)
freebie Boolean @default(false) freebie Boolean @default(false)
@ -489,6 +490,7 @@ model Item {
@@index([weightedVotes], map: "Item.weightedVotes_index") @@index([weightedVotes], map: "Item.weightedVotes_index")
@@index([invoiceId]) @@index([invoiceId])
@@index([invoiceActionState]) @@index([invoiceActionState])
@@index([cost])
} }
// we use this to denormalize a user's aggregated interactions (zaps) with an item // we use this to denormalize a user's aggregated interactions (zaps) with an item