123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- import { Injectable } from "@angular/core";
- import { TwitterApiProvider } from "../twitter-api/twitter-api";
- import { P2pDatabaseGunProvider } from "../p2p-database-gun/p2p-database-gun";
- import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs";
- import { CryptoProvider } from "../crypto/crypto";
- import { PgpKeyServerProvider } from "../../providers/pgp-key-server/pgp-key-server";
- import { Storage } from "@ionic/storage";
- @Injectable()
- export class FeedProvider {
- friends;
- userId: string;
- constructor(
- private twitter: TwitterApiProvider,
- private gun: P2pDatabaseGunProvider,
- private ipfs: P2pStorageIpfsProvider,
- private cryptoUtils: CryptoProvider,
- private opnpgp: PgpKeyServerProvider,
- private storage: Storage
- ) {
- this.storage.get("userId").then(userId => (this.userId = userId));
- }
- /**
- * Retrives the public and private tweets for a user
- * Since it is loaded in batches of 20 public tweets, public and private tweet are used as reference to load next 20 tweets
- * @param userId user id
- * @param oldestPublicTweet oldest public tweet
- * @param oldestPrivateTweet oldest private tweet
- */
- public async loadUserTimeline(
- userId,
- oldestPublicTweet ? ,
- oldestPrivateTweet ?
- ) {
- const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
- // Fetch tweets from Twitter
- let tweets = await this.twitter.fetchUserTimeline(userId, maxId);
- if (tweets.length === 0) return tweets;
- tweets = tweets.filter(tweet => tweet.id_str != maxId);
- // Determine start and end of time interval to look for private tweets
- const intervalStart: Date = oldestPrivateTweet ?
- new Date(oldestPrivateTweet["created_at"]) :
- new Date();
- const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);
- // Fetch private tweet hashs from P2P DB for corresponding interval
- const privateTweetHashs: object[] = await this.gun.fetchPrivateTweetHashsForUserInInterval(
- userId,
- intervalStart,
- intervalEnd
- );
- if (privateTweetHashs.length > 0) {
- const privateTweets = await this.fetchPrivateTweets(privateTweetHashs);
- // Combine and sort tweets
- return tweets
- .concat(privateTweets)
- .sort((a, b) => this.sortByDateAsc(a, b));
- } else {
- return tweets;
- }
- }
- /**
- * Retrieves the home feed for the logged in user
- * Since it is loaded in batches of 20 public tweets, public and private tweet are used as reference to load next 20 tweets
- * @param oldestPublicTweet oldest public tweet
- * @param oldestPrivateTweet oldest private tweet
- */
- public async loadHomeTimeline(oldestPublicTweet ? , oldestPrivateTweet ? ) {
- // Fetch tweets from Twitter
- const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
- let tweets = await this.twitter.fetchHomeFeed(maxId);
- tweets = tweets.filter(tweet => tweet.id_str != maxId);
- // Determine start and end of time interval to look for private tweets
- const intervalStart: Date = oldestPrivateTweet ?
- new Date(oldestPrivateTweet["created_at"]) :
- new Date();
- const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);
- // Fetch user's friends
- const friends = await this.getCachedFriends(this.userId);
- // Extract friends user ids and add own user id
- const friendsAndUserIds = friends
- .map(friend => friend.id_str)
- .concat([this.userId]);
- console.log('fetch hometimeline private tweets');
- // Fetch ipfs hashs for period
- const promises: Promise < object[] > [] = friendsAndUserIds.map(accountId => {
- return this.gun.fetchPrivateTweetHashsForUserInInterval(
- accountId,
- intervalStart,
- intervalEnd
- );
- });
- console.log('fetched prvt tweet hashes ');
- const resolvedPromises = await Promise.all(promises);
- const privateTweetHashs = resolvedPromises.reduce(
- (privateTweets, el) => privateTweets.concat(el),
- []
- );
- if (privateTweetHashs.length > 0) {
- const privateTweets = await this.fetchPrivateTweets(privateTweetHashs);
- if(!privateTweets) return tweets;
- // Combine and sort tweets
- return tweets
- .concat(privateTweets)
- .sort((a, b) => this.sortByDateAsc(a, b));
- } else {
- return tweets;
- }
- }
- private async fetchPrivateTweets(privateTweetsData: object[]) {
- console.log('fetching private tweets');
- const privateTweets = [];
- // Load private tweets from P2P storage
- for (let i = 0; i < privateTweetsData.length; i++) {
- console.log("privateTweetsData.length:",privateTweetsData.length);
- const hash = privateTweetsData[i]["hash"];
- const userId = privateTweetsData[i]["userId"];
- const timestamp = privateTweetsData[i]["created_at"];
- // fetch from IPFS
- const encryptedTweet = await this.ipfs.fetchTweet(hash);
- console.log('fetching private tweet',encryptedTweet);
- if(!encryptedTweet){
- console.log('encrypted tweet:',encryptedTweet)
- return;
- }
- // Fetch public key history for user
-
- console.log('decrypting private tweets');
- let pvtKey = await this.storage.get("privateKey");
- const decryptedTweet = await this.opnpgp.decrypt(encryptedTweet,pvtKey);
- if(decryptedTweet)
- privateTweets.push(JSON.parse(decryptedTweet));
- }
- if(privateTweets.length>0){
- // Add retweeted/quoted status
- privateTweets.map(async tweet => await this.addQuotedStatusToTweet(tweet));
- // Add original status (reply to)
- privateTweets.map(
- async tweet => await this.addOriginalStatusToTweet(tweet)
- );
- // Add user object to private tweets
- return await Promise.all(
- privateTweets.map(async tweet => await this.addUserToTweet(tweet))
- );
- }
-
- }
- private getPublicKeyAt(
- timestamp: string,
- publicKeyHistory: object[]
- ): string {
- const timestampTweet = new Date(timestamp).getTime();
- for (let key of publicKeyHistory) {
- const timestampKey = new Date(key["validFrom"]).getTime();
- if (timestampTweet > timestampKey) {
- return key["key"];
- }
- }
- // todo: throw error
- return "";
- }
- private async addUserToTweet(tweet: object): Promise < object > {
- tweet["user"] = await this.twitter.fetchUser(tweet["user_id"]);
- return tweet;
- }
- private async addQuotedStatusToTweet(tweet: object): Promise < object > {
- if (!tweet["quoted_status_id"]) return tweet;
- const quoted_status = await this.twitter.fetchTweet(
- tweet["quoted_status_id"]
- );
- tweet["quoted_status"] = quoted_status["data"];
- return tweet;
- }
- private async addOriginalStatusToTweet(tweet: object): Promise < object > {
- if (!tweet["in_reply_to_status_id"]) return tweet;
- const originalTweet = await this.twitter.fetchTweet(
- tweet["in_reply_to_status_id"]
- );
- tweet["in_reply_to_screen_name"] =
- originalTweet["data"]["user"]["screen_name"];
- return tweet;
- }
- private getOldestTweetTimestamp(tweets): Date {
- if (tweets.length < 15) {
- // End of timeline is reached - load all private tweets
- return new Date("2018-04-01T00:00:00");
- } else {
- const lastTweetTimestamp = tweets[tweets.length - 1].created_at;
- return new Date(lastTweetTimestamp);
- }
- }
- private sortByDateAsc(a, b) {
- const dateA = new Date(a.created_at);
- const dateB = new Date(b.created_at);
- if (dateA > dateB) {
- return -1;
- } else if (dateA < dateB) {
- return 1;
- } else {
- return 0;
- }
- }
- private async getCachedFriends(userId) {
- // Cache friends for 15 minutes to avoid unnecessary API calls
- if (!this.friends || (Date.now() - this.friends.lastUpdate) / 900000 > 15) {
- this.friends = {
- friendList: await this.twitter.fetchFriends(userId),
- lastUpdate: Date.now()
- };
- }
- return this.friends.friendList;
- }
- }
|