Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Code linting changes, additional comments, and a variable rename #10

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions src/features/FeatureStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export default class FeatureStorage extends Storage {
static async getTopFavs(api: mastodon.rest.Client): Promise<accFeatureType> {
const topFavs: accFeatureType = await this.get(Key.TOP_FAVS) as accFeatureType;
console.log(topFavs);

if (topFavs != null && await this.getOpenings() % 10 < 9) {
return topFavs;
} else {
Expand All @@ -23,6 +24,7 @@ export default class FeatureStorage extends Storage {
static async getTopReblogs(api: mastodon.rest.Client): Promise<accFeatureType> {
const topReblogs: accFeatureType = await this.get(Key.TOP_REBLOGS) as accFeatureType;
console.log(topReblogs);

if (topReblogs != null && await this.getOpenings() % 10 < 9) {
return topReblogs;
} else {
Expand All @@ -36,6 +38,7 @@ export default class FeatureStorage extends Storage {
static async getTopInteracts(api: mastodon.rest.Client): Promise<accFeatureType> {
const topInteracts: accFeatureType = await this.get(Key.TOP_INTERACTS) as accFeatureType;
console.log(topInteracts);

if (topInteracts != null && await this.getOpenings() % 10 < 9) {
return topInteracts;
} else {
Expand All @@ -45,9 +48,11 @@ export default class FeatureStorage extends Storage {
}
}

// Returns the Mastodon server the user is currently logged in to
static async getCoreServer(api: mastodon.rest.Client): Promise<serverFeatureType> {
const coreServer: serverFeatureType = await this.get(Key.CORE_SERVER) as serverFeatureType;
console.log(coreServer);

if (coreServer != null && await this.getOpenings() % 10 != 9) {
return coreServer;
} else {
Expand All @@ -57,5 +62,4 @@ export default class FeatureStorage extends Storage {
return server;
}
}

}
};
21 changes: 10 additions & 11 deletions src/feeds/topPostsFeed.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
import { mastodon } from "masto";
import FeatureStore from "../features/FeatureStore";
import { StatusType } from "../types";
import Storage from "../Storage";
import { mastodon } from "masto";
import { StatusType } from "../types";
import { _transformKeys, mastodonFetch } from "../helpers";

export default async function getTopPostFeed(api: mastodon.rest.Client): Promise<StatusType[]> {
const core_servers = await FeatureStore.getCoreServer(api)
let results: StatusType[][] = [];


//Get Top Servers
const servers = Object.keys(core_servers).sort((a, b) => {
return core_servers[b] - core_servers[a]
return core_servers[b] - core_servers[a];
}).slice(0, 10)

if (servers.length === 0) {
Expand All @@ -27,16 +26,16 @@ export default async function getTopPostFeed(api: mastodon.rest.Client): Promise
).map((status: StatusType) => {
status.topPost = true;
return status;
}).slice(0, 10) ?? []
}).slice(0, 10) ?? [];
}))
console.log(results)
console.log(results);

const lastOpened = new Date((await Storage.getLastOpened() ?? 0) - 28800000)
const lastOpened = new Date((await Storage.getLastOpened() ?? 0) - 28800000);
return results.flat().filter((status: StatusType) => new Date(status.createdAt) > lastOpened).map((status: StatusType) => {
const acct = status.account.acct
const acct = status.account.acct;
if (acct && !acct.includes("@")) {
status.account.acct = `${acct}@${status.account.url.split("/")[2]}`
status.account.acct = `${acct}@${status.account.url.split("/")[2]}`;
}
return status
return status;
})
}
}
57 changes: 34 additions & 23 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,27 @@
import { mastodon } from "masto";
import { FeedFetcher, StatusType, weightsType } from "./types";
import {
diversityFeedScorer,
favsFeatureScorer,
FeatureScorer,
FeedScorer,
interactsFeatureScorer,
reblogsFeatureScorer,
diversityFeedScorer,
reblogsFeedScorer,
FeatureScorer,
FeedScorer,
topPostFeatureScorer
} from "./scorer";
import weightsStore from "./weights/weightsStore";
import chaosFeatureScorer from "./scorer/feature/chaosFeatureScorer";
import getHomeFeed from "./feeds/homeFeed";
import topPostsFeed from "./feeds/topPostsFeed";
import Paginator from "./Paginator";
import Storage from "./Storage";
import Paginator from "./Paginator"
import chaosFeatureScorer from "./scorer/feature/chaosFeatureScorer";
import topPostsFeed from "./feeds/topPostsFeed";
import weightsStore from "./weights/weightsStore";
//import getRecommenderFeed from "./feeds/recommenderFeed";

export default class TheAlgorithm {
user: mastodon.v1.Account;
fetchers = [getHomeFeed, topPostsFeed];
featureScorer = [
featureScorers = [
new favsFeatureScorer(),
new reblogsFeatureScorer(),
new interactsFeatureScorer(),
Expand All @@ -48,29 +48,29 @@ export default class TheAlgorithm {
feedScorer: Array<FeedScorer>
) {
this.fetchers = fetchers;
this.featureScorer = featureScorer;
this.featureScorers = featureScorer;
this.feedScorer = feedScorer;
return this.getFeed();
}

async getFeed(): Promise<StatusType[]> {
const { fetchers, featureScorer, feedScorer } = this;
const { fetchers, featureScorers, feedScorer } = this;
const response = await Promise.all(fetchers.map(fetcher => fetcher(this.api, this.user)))
this.feed = response.flat();

// Load and Prepare Features
await Promise.all(featureScorer.map(scorer => scorer.getFeature(this.api)));
await Promise.all(featureScorers.map(scorer => scorer.getFeature(this.api)));
await Promise.all(feedScorer.map(scorer => scorer.setFeed(this.feed)));

// Get Score Names
const scoreNames = featureScorer.map(scorer => scorer.getVerboseName());
const scoreNames = featureScorers.map(scorer => scorer.getVerboseName());
const feedScoreNames = feedScorer.map(scorer => scorer.getVerboseName());

// Score Feed
let scoredFeed: StatusType[] = []
for (const status of this.feed) {
// Load Scores for each status
const featureScore = await Promise.all(featureScorer.map(scorer => scorer.score(this.api, status)));
const featureScore = await Promise.all(featureScorers.map(scorer => scorer.score(this.api, status)));
const feedScore = await Promise.all(feedScorer.map(scorer => scorer.score(status)));
// Turn Scores into Weight Objects
const featureScoreObj = this._getScoreObj(scoreNames, featureScore);
Expand Down Expand Up @@ -107,7 +107,7 @@ export default class TheAlgorithm {
//Remove duplicates
scoredFeed = [...new Map(scoredFeed.map((item: StatusType) => [item["uri"], item])).values()];

this.feed = scoredFeed
this.feed = scoredFeed;
return this.feed;
}

Expand All @@ -128,18 +128,18 @@ export default class TheAlgorithm {
}

getWeightNames(): string[] {
const scorers = [...this.featureScorer, ...this.feedScorer];
const scorers = [...this.featureScorers, ...this.feedScorer];
return [...scorers.map(scorer => scorer.getVerboseName())]
}

async setDefaultWeights(): Promise<void> {
//Set Default Weights if they don't exist
const scorers = [...this.featureScorer, ...this.feedScorer];
const scorers = [...this.featureScorers, ...this.feedScorer];
Promise.all(scorers.map(scorer => weightsStore.defaultFallback(scorer.getVerboseName(), scorer.getDefaultWeight())))
}

getWeightDescriptions(): string[] {
const scorers = [...this.featureScorer, ...this.feedScorer];
const scorers = [...this.featureScorers, ...this.feedScorer];
return [...scorers.map(scorer => scorer.getDescription())]
}

Expand Down Expand Up @@ -171,20 +171,31 @@ export default class TheAlgorithm {
}

getDescription(verboseName: string): string {
const scorers = [...this.featureScorer, ...this.feedScorer];
const scorers = [...this.featureScorers, ...this.feedScorer];
const scorer = scorers.find(scorer => scorer.getVerboseName() === verboseName);
if (scorer) {
return scorer.getDescription();
}
return "";
}

//Adjust post weights based on user's chosen slider values
async weightAdjust(statusWeights: weightsType, step = 0.001): Promise<weightsType | undefined> {
//Adjust Weights based on user interaction
if (statusWeights == undefined) return;
const mean = Object.values(statusWeights).filter((value: number) => !isNaN(value)).reduce((accumulator, currentValue) => accumulator + Math.abs(currentValue), 0) / Object.values(statusWeights).length;

// Compute the total and mean score (AKA 'weight') of all the posts we are weighting
const total = Object.values(statusWeights)
.filter((value: number) => !isNaN(value))
.reduce((accumulator, currentValue) => accumulator + Math.abs(currentValue), 0);
const mean = total / Object.values(statusWeights).length;

// Compute the sum and mean of the preferred weighting configured by the user with the weight sliders
const currentWeight: weightsType = await this.getWeights()
const currentMean = Object.values(currentWeight).filter((value: number) => !isNaN(value)).reduce((accumulator, currentValue) => accumulator + currentValue, 0) / Object.values(currentWeight).length;
const currentTotal = Object.values(currentWeight)
.filter((value: number) => !isNaN(value))
.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
const currentMean = currentTotal / Object.values(currentWeight).length;

for (const key in currentWeight) {
const reweight = 1 - (Math.abs(statusWeights[key]) / mean) / (currentWeight[key] / currentMean);
currentWeight[key] = currentWeight[key] - step * currentWeight[key] * reweight;
Expand All @@ -194,6 +205,6 @@ export default class TheAlgorithm {
}

list() {
return new Paginator(this.feed)
return new Paginator(this.feed);
}
}
}
15 changes: 7 additions & 8 deletions src/scorer/feature/favsFeatureScorer.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,19 @@
import FeatureScorer from '../FeatureScorer'
import { StatusType } from '../../types'
import { mastodon } from 'masto'
import FeatureStorage from '../../features/FeatureStore'
import FeatureScorer from '../FeatureScorer';
import FeatureStorage from '../../features/FeatureStore';
import { mastodon } from 'masto';
import { StatusType } from '../../types';

export default class favsFeatureScorer extends FeatureScorer {

constructor() {
super({
featureGetter: (api: mastodon.rest.Client) => FeatureStorage.getTopFavs(api),
verboseName: "Favs",
description: "Posts that are from your most favorited users",
description: "Favor posts from users whose posts you have favorited a lot in the past",
defaultWeight: 1,
})
}

async score(_api: mastodon.rest.Client, status: StatusType) {
return (status.account.acct in this.feature) ? this.feature[status.account.acct] : 0
return (status.account.acct in this.feature) ? this.feature[status.account.acct] : 0;
}
}
};
14 changes: 9 additions & 5 deletions src/scorer/feature/interactsFeatureScorer.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
/*
* Gives higher weight to posts from users that have often interacted with your posts.
*/
import FeatureScorer from "../FeatureScorer";
import { StatusType } from "../../types";
import { mastodon } from "masto";
import FeatureStorage from "../../features/FeatureStore";
import { mastodon } from "masto";
import { StatusType } from "../../types";


export default class interactsFeatureScorer extends FeatureScorer {
constructor() {
super({
featureGetter: (api: mastodon.rest.Client) => { return FeatureStorage.getTopInteracts(api) },
verboseName: "Interacts",
description: "Posts that are from users, that often interact with your posts",
description: "Favor posts from users that most frequently interact with your posts",
defaultWeight: 2,
})
}

async score(_api: mastodon.rest.Client, status: StatusType) {
return (status.account.acct in this.feature) ? this.feature[status.account.acct] : 0
return (status.account.acct in this.feature) ? this.feature[status.account.acct] : 0;
}
}
};
10 changes: 5 additions & 5 deletions src/scorer/feature/reblogsFeatureScorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,14 @@ export default class reblogsFeatureScorer extends FeatureScorer {
super({
featureGetter: (api: mastodon.rest.Client) => { return FeatureStorage.getTopReblogs(api) },
verboseName: "Reblogs",
description: "Posts that are from your most reblogger users",
description: "Favor posts from accounts you have retooted a lot",
defaultWeight: 3,
})
}

async score(_api: mastodon.rest.Client, status: StatusType) {
const authorScore = (status.account.acct in this.feature) ? this.feature[status.account.acct] : 0
const reblogScore = (status.reblog && status.reblog.account.acct in this.feature) ? this.feature[status.reblog.account.acct] : 0
return authorScore + reblogScore
const authorScore = (status.account.acct in this.feature) ? this.feature[status.account.acct] : 0;
const reblogScore = (status.reblog && status.reblog.account.acct in this.feature) ? this.feature[status.reblog.account.acct] : 0;
return authorScore + reblogScore;
}
}
};
8 changes: 4 additions & 4 deletions src/scorer/feature/topPostFeatureScorer.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import FeatureScorer from '../FeatureScorer';
import { StatusType, } from "../../types";
import { mastodon } from "masto";
import { StatusType, } from "../../types";

export default class topPostFeatureScorer extends FeatureScorer {
constructor() {
super({
featureGetter: (_api: mastodon.rest.Client) => { return Promise.resolve({}) },
verboseName: "TopPosts",
description: "Posts that are trending on multiple of your most popular instances",
description: "Favor posts that are trending in the Fediverse",
defaultWeight: 1,
})
}

async score(_api: mastodon.rest.Client, status: StatusType) {
return status.topPost ? 1 : 0
return status.topPost ? 1 : 0;
}
}
};
10 changes: 5 additions & 5 deletions src/scorer/feed/diversityFeedScorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StatusType } from "../../types";

export default class diversityFeedScorer extends FeedScorer {
constructor() {
super("Diversity", "Downranks posts from users that you have seen a lot of posts from");
super("Diversity", "Disfavor posts from users that you have seen a lot of posts from already");
}

feedExtractor(feed: StatusType[]): Record<string, number> {
Expand All @@ -17,8 +17,8 @@ export default class diversityFeedScorer extends FeedScorer {

async score(status: StatusType) {
super.score(status);
const frequ = this.features[status.account.acct]
this.features[status.account.acct] = frequ + 1
return frequ + 1
const frequ = this.features[status.account.acct];
this.features[status.account.acct] = frequ + 1;
return frequ + 1;
}
}
}
4 changes: 2 additions & 2 deletions src/scorer/feed/reblogsFeedScorer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { StatusType } from "../../types";

export default class reblogsFeedScorer extends FeedScorer {
constructor() {
super("reblogsFeed", "More Weight to posts that are reblogged a lot", 6);
super("reblogsFeed", "Favor posts that have been retooted many times", 6);
}

feedExtractor(feed: StatusType[]) {
Expand All @@ -26,4 +26,4 @@ export default class reblogsFeedScorer extends FeedScorer {
}
return features[status.uri] || 0;
}
}
}
Loading