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

View File

@ -30,12 +30,12 @@ function commentsOrderByClause (me, models, sort) {
return `ORDER BY COALESCE(
personal_hot_score,
${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 {
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 {
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
// 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) {
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
freebieClauses.push(`"Item"."userId" = ${me.id}`)
investmentClause = `(("Item".cost + "Item".boost + ("Item".msats / 1000)) >= ${user.satsFilter} OR "Item"."userId" = ${me.id})`
if (user.wildWestMode) {
return investmentClause
}
}
const freebieClause = '(' + freebieClauses.join(' OR ') + ')'
// handle outlawed
// 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 ') + ')'
return [freebieClause, outlawClause]
return [investmentClause, outlawClause]
}
function typeClause (type) {
@ -268,7 +262,7 @@ function typeClause (type) {
case 'comments':
return '"Item"."parentId" IS NOT NULL'
case 'freebies':
return '"Item".freebie'
return '"Item".cost = 0'
case 'outlawed':
return `"Item"."weightedVotes" - "Item"."weightedDownVotes" <= -${ITEM_FILTER_THRESHOLD} OR "Item".outlawed`
case 'borderland':
@ -470,10 +464,10 @@ export default {
'"Item".bio = false',
activeOrMine(me),
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
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)
}
@ -1054,6 +1048,9 @@ export default {
freedFreebie: async (item) => {
return item.weightedVotes - item.weightedDownVotes > 0
},
freebie: async (item) => {
return item.cost === 0
},
meSats: async (item, args, { me, models }) => {
if (!me) return 0
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
if (differentSub) {
const sub = await models.sub.findUnique({ where: { name: subName } })
if (old.freebie) {
if (old.cost === 0) {
if (!sub.allowFreebies) {
throw new GraphQLError(`~${subName} does not allow freebies`, { extensions: { code: 'BAD_INPUT' } })
}

View File

@ -71,7 +71,7 @@ export default gql`
diagnostics: Boolean!
noReferralLinks: Boolean!
fiatCurrency: String!
greeterMode: Boolean!
satsFilter: Int!
hideBookmarks: Boolean!
hideCowboyHat: Boolean!
hideGithub: Boolean!
@ -140,7 +140,7 @@ export default gql`
diagnostics: Boolean!
noReferralLinks: Boolean!
fiatCurrency: String!
greeterMode: Boolean!
satsFilter: Int!
hideBookmarks: Boolean!
hideCowboyHat: Boolean!
hideGithub: Boolean!
@ -192,7 +192,7 @@ export default gql`
twitterId: String
nostrAuthPubkey: String
}
type NameValue {
name: String!
value: Float!

View File

@ -97,7 +97,7 @@ export default function Comment ({
}) {
const [edit, setEdit] = useState()
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(
(isHiddenFreebie || item?.user?.meMute || (item?.outlawed && !me?.privates?.wildWestMode)) && !includeParent
? 'yep'

View File

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

View File

@ -579,6 +579,7 @@ export const settingsSchema = object().shape({
diagnostics: boolean(),
noReferralLinks: 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')
// exclude from cyclic analysis. see https://github.com/jquense/yup/issues/720
}, [['tipRandomMax', 'tipRandomMin']])

View File

@ -140,7 +140,7 @@ export default function Settings ({ ssrData }) {
hideTwitter: settings?.hideTwitter,
imgproxyOnly: settings?.imgproxyOnly,
wildWestMode: settings?.wildWestMode,
greeterMode: settings?.greeterMode,
satsFilter: settings?.satsFilter,
nsfwMode: settings?.nsfwMode,
nostrPubkey: settings?.nostrPubkey ? bech32encode(settings.nostrPubkey) : '',
nostrCrossposting: settings?.nostrCrossposting,
@ -152,7 +152,11 @@ export default function Settings ({ ssrData }) {
noReferralLinks: settings?.noReferralLinks
}}
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) {
nostrPubkey = null
} else {
@ -172,6 +176,7 @@ export default function Settings ({ ssrData }) {
tipRandomMin: tipRandom ? Number(tipRandomMin) : null,
tipRandomMax: tipRandom ? Number(tipRandomMax) : null,
withdrawMaxFeeDefault: Number(withdrawMaxFeeDefault),
satsFilter: Number(satsFilter),
zapUndos: zapUndosEnabled ? Number(zapUndos) : null,
nostrPubkey,
nostrRelays: nostrRelaysFiltered,
@ -467,7 +472,27 @@ export default function Settings ({ ssrData }) {
label={<>don't create referral links on copy</>}
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
label={
<div className='d-flex align-items-center'>wild west mode
@ -482,21 +507,6 @@ export default function Settings ({ ssrData }) {
name='wildWestMode'
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
label={
<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)
hideInvoiceDesc Boolean @default(false)
wildWestMode Boolean @default(false)
greeterMode Boolean @default(false)
satsFilter Int @default(10)
nsfwMode Boolean @default(false)
fiatCurrency String @default("USD")
withdrawMaxFeeDefault Int @default(10)
@ -425,6 +425,7 @@ model Item {
lastZapAt DateTime?
ncomments Int @default(0)
msats BigInt @default(0)
cost Int @default(0)
weightedDownVotes Float @default(0)
bio Boolean @default(false)
freebie Boolean @default(false)
@ -489,6 +490,7 @@ model Item {
@@index([weightedVotes], map: "Item.weightedVotes_index")
@@index([invoiceId])
@@index([invoiceActionState])
@@index([cost])
}
// we use this to denormalize a user's aggregated interactions (zaps) with an item