2022-03-14 20:29:32 +00:00
|
|
|
import _ from 'lodash'
|
|
|
|
|
|
|
|
import { Answer } from '../../../common/answer'
|
|
|
|
import { Bet } from '../../../common/bet'
|
|
|
|
import { getOutcomeProbability } from '../../../common/calculate'
|
|
|
|
import { Comment } from '../../../common/comment'
|
2022-03-15 22:27:51 +00:00
|
|
|
import {
|
|
|
|
Contract,
|
|
|
|
DPM,
|
|
|
|
FreeResponse,
|
|
|
|
FullContract,
|
|
|
|
} from '../../../common/contract'
|
2022-03-14 20:29:32 +00:00
|
|
|
import { User } from '../../../common/user'
|
|
|
|
import { mapCommentsByBetId } from '../../lib/firebase/comments'
|
|
|
|
|
|
|
|
export type ActivityItem =
|
|
|
|
| DescriptionItem
|
|
|
|
| QuestionItem
|
|
|
|
| BetItem
|
|
|
|
| CommentItem
|
|
|
|
| CreateAnswerItem
|
|
|
|
| BetGroupItem
|
|
|
|
| AnswerGroupItem
|
|
|
|
| CloseItem
|
|
|
|
| ResolveItem
|
|
|
|
|
|
|
|
type BaseActivityItem = {
|
|
|
|
id: string
|
|
|
|
contract: Contract
|
|
|
|
}
|
|
|
|
|
|
|
|
export type DescriptionItem = BaseActivityItem & {
|
|
|
|
type: 'description'
|
|
|
|
}
|
|
|
|
|
|
|
|
export type QuestionItem = BaseActivityItem & {
|
|
|
|
type: 'question'
|
|
|
|
showDescription: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type BetItem = BaseActivityItem & {
|
|
|
|
type: 'bet'
|
|
|
|
bet: Bet
|
|
|
|
hideOutcome: boolean
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar: boolean
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export type CommentItem = BaseActivityItem & {
|
|
|
|
type: 'comment'
|
|
|
|
comment: Comment
|
|
|
|
bet: Bet
|
|
|
|
hideOutcome: boolean
|
|
|
|
truncate: boolean
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar: boolean
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export type CreateAnswerItem = BaseActivityItem & {
|
|
|
|
type: 'createanswer'
|
|
|
|
answer: Answer
|
|
|
|
}
|
|
|
|
|
|
|
|
export type BetGroupItem = BaseActivityItem & {
|
|
|
|
type: 'betgroup'
|
|
|
|
bets: Bet[]
|
|
|
|
hideOutcome: boolean
|
|
|
|
}
|
|
|
|
|
|
|
|
export type AnswerGroupItem = BaseActivityItem & {
|
|
|
|
type: 'answergroup'
|
|
|
|
answer: Answer
|
|
|
|
items: ActivityItem[]
|
|
|
|
}
|
|
|
|
|
|
|
|
export type CloseItem = BaseActivityItem & {
|
|
|
|
type: 'close'
|
|
|
|
}
|
|
|
|
|
|
|
|
export type ResolveItem = BaseActivityItem & {
|
|
|
|
type: 'resolve'
|
|
|
|
}
|
|
|
|
|
|
|
|
const DAY_IN_MS = 24 * 60 * 60 * 1000
|
|
|
|
|
|
|
|
// Group together bets that are:
|
2022-03-14 22:11:10 +00:00
|
|
|
// - Within a day of the first in the group
|
|
|
|
// (Unless the bets are older: then are grouped by 7-days.)
|
2022-03-14 20:29:32 +00:00
|
|
|
// - Do not have a comment
|
|
|
|
// - Were not created by this user or the contract creator
|
|
|
|
// Return a list of ActivityItems
|
|
|
|
function groupBets(
|
|
|
|
bets: Bet[],
|
|
|
|
comments: Comment[],
|
|
|
|
contract: Contract,
|
|
|
|
userId: string | undefined,
|
|
|
|
options: {
|
|
|
|
hideOutcome: boolean
|
|
|
|
abbreviated: boolean
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar: boolean
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
) {
|
2022-03-14 21:56:53 +00:00
|
|
|
const { hideOutcome, abbreviated, smallAvatar } = options
|
2022-03-14 20:29:32 +00:00
|
|
|
|
|
|
|
const commentsMap = mapCommentsByBetId(comments)
|
|
|
|
const items: ActivityItem[] = []
|
|
|
|
let group: Bet[] = []
|
|
|
|
|
|
|
|
// Turn the current group into an ActivityItem
|
|
|
|
function pushGroup() {
|
|
|
|
if (group.length == 1) {
|
|
|
|
items.push(toActivityItem(group[0]))
|
|
|
|
} else if (group.length > 1) {
|
|
|
|
items.push({
|
|
|
|
type: 'betgroup',
|
|
|
|
bets: [...group],
|
|
|
|
id: group[0].id,
|
|
|
|
contract,
|
|
|
|
hideOutcome,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
group = []
|
|
|
|
}
|
|
|
|
|
|
|
|
function toActivityItem(bet: Bet): ActivityItem {
|
|
|
|
const comment = commentsMap[bet.id]
|
|
|
|
return comment
|
|
|
|
? {
|
|
|
|
type: 'comment' as const,
|
|
|
|
id: bet.id,
|
|
|
|
comment,
|
|
|
|
bet,
|
|
|
|
contract,
|
|
|
|
hideOutcome,
|
|
|
|
truncate: abbreviated,
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar,
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
: {
|
|
|
|
type: 'bet' as const,
|
|
|
|
id: bet.id,
|
|
|
|
bet,
|
|
|
|
contract,
|
|
|
|
hideOutcome,
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar,
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const bet of bets) {
|
|
|
|
const isCreator = userId === bet.userId
|
|
|
|
|
2022-03-14 22:11:10 +00:00
|
|
|
// If first bet in group is older than 3 days, group by 7 days. Otherwise, group by 1 day.
|
|
|
|
const windowMs =
|
|
|
|
Date.now() - (group[0]?.createdTime ?? bet.createdTime) > DAY_IN_MS * 3
|
|
|
|
? DAY_IN_MS * 7
|
|
|
|
: DAY_IN_MS
|
|
|
|
|
2022-03-14 20:29:32 +00:00
|
|
|
if (commentsMap[bet.id] || isCreator) {
|
|
|
|
pushGroup()
|
|
|
|
// Create a single item for this
|
|
|
|
items.push(toActivityItem(bet))
|
|
|
|
} else {
|
|
|
|
if (
|
|
|
|
group.length > 0 &&
|
|
|
|
bet.createdTime - group[0].createdTime > windowMs
|
|
|
|
) {
|
|
|
|
// More than `windowMs` has passed; start a new group
|
|
|
|
pushGroup()
|
|
|
|
}
|
|
|
|
group.push(bet)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (group.length > 0) {
|
|
|
|
pushGroup()
|
|
|
|
}
|
|
|
|
return abbreviated ? items.slice(-3) : items
|
|
|
|
}
|
|
|
|
|
|
|
|
function getAnswerGroups(
|
2022-03-15 22:27:51 +00:00
|
|
|
contract: FullContract<DPM, FreeResponse>,
|
2022-03-14 20:29:32 +00:00
|
|
|
bets: Bet[],
|
|
|
|
comments: Comment[],
|
|
|
|
user: User | undefined | null,
|
|
|
|
options: {
|
|
|
|
sortByProb: boolean
|
|
|
|
abbreviated: boolean
|
|
|
|
}
|
|
|
|
) {
|
|
|
|
const { sortByProb, abbreviated } = options
|
|
|
|
|
|
|
|
let outcomes = _.uniq(bets.map((bet) => bet.outcome)).filter(
|
2022-03-15 22:27:51 +00:00
|
|
|
(outcome) => getOutcomeProbability(contract, outcome) > 0.01
|
2022-03-14 20:29:32 +00:00
|
|
|
)
|
2022-03-14 21:39:59 +00:00
|
|
|
if (abbreviated) {
|
|
|
|
const lastComment = _.last(comments)
|
|
|
|
const lastCommentOutcome = bets.find(
|
|
|
|
(bet) => bet.id === lastComment?.betId
|
|
|
|
)?.outcome
|
2022-03-15 00:39:40 +00:00
|
|
|
const lastBetOutcome = _.last(bets)?.outcome
|
|
|
|
if (lastCommentOutcome && lastBetOutcome) {
|
|
|
|
outcomes = _.uniq([
|
|
|
|
...outcomes.filter(
|
|
|
|
(outcome) =>
|
|
|
|
outcome !== lastCommentOutcome && outcome !== lastBetOutcome
|
|
|
|
),
|
2022-03-14 21:39:59 +00:00
|
|
|
lastCommentOutcome,
|
2022-03-15 00:39:40 +00:00
|
|
|
lastBetOutcome,
|
|
|
|
])
|
2022-03-14 21:39:59 +00:00
|
|
|
}
|
|
|
|
outcomes = outcomes.slice(-2)
|
|
|
|
}
|
2022-03-14 20:29:32 +00:00
|
|
|
if (sortByProb) {
|
|
|
|
outcomes = _.sortBy(
|
|
|
|
outcomes,
|
2022-03-15 22:27:51 +00:00
|
|
|
(outcome) => -1 * getOutcomeProbability(contract, outcome)
|
2022-03-14 20:29:32 +00:00
|
|
|
)
|
2022-03-14 21:39:59 +00:00
|
|
|
} else {
|
|
|
|
// Sort by recent bet.
|
|
|
|
outcomes = _.sortBy(outcomes, (outcome) =>
|
|
|
|
_.findLastIndex(bets, (bet) => bet.outcome === outcome)
|
|
|
|
)
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
|
2022-03-14 21:08:08 +00:00
|
|
|
const answerGroups = outcomes
|
|
|
|
.map((outcome) => {
|
|
|
|
const answerBets = bets.filter((bet) => bet.outcome === outcome)
|
|
|
|
const answerComments = comments.filter((comment) =>
|
|
|
|
answerBets.some((bet) => bet.id === comment.betId)
|
|
|
|
)
|
|
|
|
const answer = contract.answers?.find(
|
|
|
|
(answer) => answer.id === outcome
|
|
|
|
) as Answer
|
|
|
|
|
2022-03-14 22:11:10 +00:00
|
|
|
let items = groupBets(answerBets, answerComments, contract, user?.id, {
|
|
|
|
hideOutcome: true,
|
|
|
|
abbreviated,
|
|
|
|
smallAvatar: true,
|
|
|
|
})
|
2022-03-14 20:29:32 +00:00
|
|
|
|
2022-03-14 21:08:08 +00:00
|
|
|
if (abbreviated) items = items.slice(-2)
|
2022-03-14 20:29:32 +00:00
|
|
|
|
2022-03-14 21:08:08 +00:00
|
|
|
return {
|
|
|
|
id: outcome,
|
|
|
|
type: 'answergroup' as const,
|
|
|
|
contract,
|
|
|
|
answer,
|
|
|
|
items,
|
|
|
|
user,
|
|
|
|
}
|
|
|
|
})
|
|
|
|
.filter((group) => group.answer)
|
2022-03-14 20:29:32 +00:00
|
|
|
|
|
|
|
return answerGroups
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getAllContractActivityItems(
|
|
|
|
contract: Contract,
|
|
|
|
bets: Bet[],
|
|
|
|
comments: Comment[],
|
|
|
|
user: User | null | undefined,
|
|
|
|
filterToOutcome: string | undefined,
|
|
|
|
options: {
|
|
|
|
abbreviated: boolean
|
|
|
|
}
|
|
|
|
) {
|
|
|
|
const { abbreviated } = options
|
|
|
|
const { outcomeType } = contract
|
|
|
|
|
|
|
|
bets =
|
|
|
|
outcomeType === 'BINARY'
|
2022-03-16 22:55:11 +00:00
|
|
|
? bets.filter((bet) => !bet.isAnte && !bet.isRedemption)
|
2022-03-14 20:29:32 +00:00
|
|
|
: bets.filter((bet) => !(bet.isAnte && (bet.outcome as string) === '0'))
|
|
|
|
|
|
|
|
let answer: Answer | undefined
|
|
|
|
if (filterToOutcome) {
|
|
|
|
bets = bets.filter((bet) => bet.outcome === filterToOutcome)
|
2022-03-15 22:27:51 +00:00
|
|
|
answer = (contract as FullContract<DPM, FreeResponse>).answers?.find(
|
|
|
|
(answer) => answer.id === filterToOutcome
|
|
|
|
)
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
const items: ActivityItem[] =
|
|
|
|
filterToOutcome && answer
|
|
|
|
? [{ type: 'createanswer', id: answer.id, contract, answer }]
|
|
|
|
: abbreviated
|
|
|
|
? [{ type: 'question', id: '0', contract, showDescription: false }]
|
|
|
|
: [{ type: 'description', id: '0', contract }]
|
|
|
|
|
|
|
|
items.push(
|
|
|
|
...(outcomeType === 'FREE_RESPONSE' && !filterToOutcome
|
2022-03-15 22:27:51 +00:00
|
|
|
? getAnswerGroups(
|
|
|
|
contract as FullContract<DPM, FreeResponse>,
|
|
|
|
bets,
|
|
|
|
comments,
|
|
|
|
user,
|
|
|
|
{
|
|
|
|
sortByProb: true,
|
|
|
|
abbreviated,
|
|
|
|
}
|
|
|
|
)
|
2022-03-14 22:11:10 +00:00
|
|
|
: groupBets(bets, comments, contract, user?.id, {
|
2022-03-14 20:29:32 +00:00
|
|
|
hideOutcome: !!filterToOutcome,
|
|
|
|
abbreviated,
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar: !!filterToOutcome,
|
2022-03-14 20:29:32 +00:00
|
|
|
}))
|
|
|
|
)
|
|
|
|
|
|
|
|
if (contract.closeTime && contract.closeTime <= Date.now()) {
|
|
|
|
items.push({ type: 'close', id: `${contract.closeTime}`, contract })
|
|
|
|
}
|
|
|
|
if (contract.resolution) {
|
|
|
|
items.push({ type: 'resolve', id: `${contract.resolutionTime}`, contract })
|
|
|
|
}
|
|
|
|
|
|
|
|
return items
|
|
|
|
}
|
|
|
|
|
|
|
|
export function getRecentContractActivityItems(
|
|
|
|
contract: Contract,
|
|
|
|
bets: Bet[],
|
|
|
|
comments: Comment[],
|
|
|
|
user: User | null | undefined
|
|
|
|
) {
|
2022-03-16 22:55:11 +00:00
|
|
|
bets = bets
|
|
|
|
.filter((bet) => !bet.isRedemption)
|
|
|
|
.sort((b1, b2) => b1.createdTime - b2.createdTime)
|
2022-03-14 20:29:32 +00:00
|
|
|
comments = comments.sort((c1, c2) => c1.createdTime - c2.createdTime)
|
|
|
|
|
|
|
|
const questionItem: QuestionItem = {
|
|
|
|
type: 'question',
|
|
|
|
id: '0',
|
|
|
|
contract,
|
|
|
|
showDescription: false,
|
|
|
|
}
|
|
|
|
|
2022-03-14 21:39:59 +00:00
|
|
|
const items =
|
2022-03-14 20:29:32 +00:00
|
|
|
contract.outcomeType === 'FREE_RESPONSE'
|
2022-03-15 22:27:51 +00:00
|
|
|
? getAnswerGroups(
|
|
|
|
contract as FullContract<DPM, FreeResponse>,
|
|
|
|
bets,
|
|
|
|
comments,
|
|
|
|
user,
|
|
|
|
{
|
|
|
|
sortByProb: false,
|
|
|
|
abbreviated: true,
|
|
|
|
}
|
|
|
|
)
|
2022-03-14 22:11:10 +00:00
|
|
|
: groupBets(bets, comments, contract, user?.id, {
|
2022-03-14 20:29:32 +00:00
|
|
|
hideOutcome: false,
|
|
|
|
abbreviated: true,
|
2022-03-14 21:56:53 +00:00
|
|
|
smallAvatar: false,
|
2022-03-14 20:29:32 +00:00
|
|
|
})
|
|
|
|
|
2022-03-14 21:39:59 +00:00
|
|
|
return [questionItem, ...items]
|
2022-03-14 20:29:32 +00:00
|
|
|
}
|