76 lines
2.7 KiB
JavaScript
76 lines
2.7 KiB
JavaScript
(function () {
|
|
'use strict';
|
|
|
|
const root = window.PixelIslandModules ||= {};
|
|
const utils = root.CoreUtils || {};
|
|
const fnv1a = utils.fnv1a || ((value) => {
|
|
let hash = 0x811c9dc5;
|
|
for (const ch of String(value)) {
|
|
hash ^= ch.charCodeAt(0);
|
|
hash = Math.imul(hash, 0x01000193);
|
|
}
|
|
return (hash >>> 0).toString(16).padStart(8, '0');
|
|
});
|
|
|
|
const DEFAULT_CONFIG = {
|
|
defaultDisplayLimit: 250,
|
|
newArrivalSlots: 150,
|
|
revivalSlots: 100,
|
|
publishLimitFirstDay: 5,
|
|
publishLimitTrusted: 10,
|
|
upvoteDelaySlots: 20,
|
|
downvoteAdvanceSlots: 25,
|
|
upvoteRankCap: 50
|
|
};
|
|
|
|
function objectPublicAt(object) {
|
|
return Number(object?.publishedAt || object?.placedAt || object?.createdAt || Date.now());
|
|
}
|
|
|
|
function entry(kind, object, baseIndex, votes, config = DEFAULT_CONFIG) {
|
|
const rawUp = Number(votes?.up) || 0;
|
|
const up = Math.min(rawUp, config.upvoteRankCap);
|
|
const down = Number(votes?.down) || 0;
|
|
return {
|
|
kind,
|
|
object,
|
|
id: object?.id,
|
|
assetId: object?.assetId,
|
|
publicAt: objectPublicAt(object),
|
|
baseIndex,
|
|
up,
|
|
rawUp,
|
|
down,
|
|
effectiveSlot: baseIndex + up * config.upvoteDelaySlots - down * config.downvoteAdvanceSlots
|
|
};
|
|
}
|
|
|
|
function seededScore(id, salt, rotationAt = Date.now()) {
|
|
const day = Math.floor((rotationAt || Date.now()) / (24 * 60 * 60 * 1000));
|
|
return parseInt(fnv1a(`${id}|${salt}|${day}`).slice(0, 8), 16) / 0xffffffff;
|
|
}
|
|
|
|
function buckets(entries, localLimit, config = DEFAULT_CONFIG, rotationAt = Date.now()) {
|
|
const newCap = Math.min(config.newArrivalSlots, localLimit);
|
|
const revivalCap = Math.max(0, Math.min(config.revivalSlots, localLimit - newCap));
|
|
const newest = entries
|
|
.slice()
|
|
.sort((a, b) => b.effectiveSlot - a.effectiveSlot || b.publicAt - a.publicAt || String(b.id).localeCompare(String(a.id)))
|
|
.slice(0, newCap);
|
|
const newestIds = new Set(newest.map((item) => item.id));
|
|
const revival = entries
|
|
.filter((item) => !newestIds.has(item.id))
|
|
.sort((a, b) => seededScore(b.id, 'revival', rotationAt) - seededScore(a.id, 'revival', rotationAt) || b.up - a.up || String(b.id).localeCompare(String(a.id)))
|
|
.slice(0, revivalCap);
|
|
return { newest, revival, entries, visibleIds: new Set([...newest, ...revival].map((item) => item.id)) };
|
|
}
|
|
|
|
function publishLimit(account, now = Date.now(), config = DEFAULT_CONFIG) {
|
|
if (!account?.createdAt) return 0;
|
|
return now - Number(account.createdAt) < 24 * 60 * 60 * 1000
|
|
? config.publishLimitFirstDay
|
|
: config.publishLimitTrusted;
|
|
}
|
|
|
|
root.RotationPolicy = { DEFAULT_CONFIG, objectPublicAt, entry, buckets, seededScore, publishLimit };
|
|
})();
|