feed.ts 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. import { Injectable } from "@angular/core";
  2. import { TwitterApiProvider } from "../twitter-api/twitter-api";
  3. import { P2pDatabaseGunProvider } from "../p2p-database-gun/p2p-database-gun";
  4. import { P2pStorageIpfsProvider } from "../p2p-storage-ipfs/p2p-storage-ipfs";
  5. import { CryptoProvider } from "../crypto/crypto";
  6. import { Storage } from "@ionic/storage";
  7. @Injectable()
  8. export class FeedProvider {
  9. friends;
  10. userId: string;
  11. constructor(
  12. private twitter: TwitterApiProvider,
  13. private gun: P2pDatabaseGunProvider,
  14. private ipfs: P2pStorageIpfsProvider,
  15. private cryptoUtils: CryptoProvider,
  16. private storage: Storage
  17. ) {
  18. this.storage.get("userId").then(userId => (this.userId = userId));
  19. }
  20. /**
  21. * Retrives the public and private tweets for a user
  22. * Since it is loaded in batches of 20 public tweets, public and private tweet are used as reference to load next 20 tweets
  23. * @param userId user id
  24. * @param oldestPublicTweet oldest public tweet
  25. * @param oldestPrivateTweet oldest private tweet
  26. */
  27. public async loadUserTimeline(
  28. userId,
  29. oldestPublicTweet?,
  30. oldestPrivateTweet?
  31. ) {
  32. console.log('loading tweets for loadUserTimeline');
  33. const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
  34. // Fetch tweets from Twitter
  35. let tweets = await this.twitter.fetchUserTimeline(userId, maxId);
  36. if (tweets.length === 0) return tweets;
  37. tweets = tweets.filter(tweet => tweet.id_str != maxId);
  38. // Determine start and end of time interval to look for private tweets
  39. const intervalStart: Date = oldestPrivateTweet
  40. ? new Date(oldestPrivateTweet["created_at"])
  41. : new Date();
  42. const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);
  43. // Fetch private tweet hashs from P2P DB for corresponding interval
  44. const privateTweetHashs: object[] = await this.gun.fetchPrivateTweetHashsForUserInInterval(
  45. userId,
  46. intervalStart,
  47. intervalEnd
  48. );
  49. if (privateTweetHashs.length > 0) {
  50. const privateTweets = await this.fetchPrivateTweets(privateTweetHashs);
  51. // Combine and sort tweets
  52. return tweets
  53. .concat(privateTweets)
  54. .sort((a, b) => this.sortByDateAsc(a, b));
  55. } else {
  56. return tweets;
  57. }
  58. }
  59. /**
  60. * Retrieves the home feed for the logged in user
  61. * Since it is loaded in batches of 20 public tweets, public and private tweet are used as reference to load next 20 tweets
  62. * @param oldestPublicTweet oldest public tweet
  63. * @param oldestPrivateTweet oldest private tweet
  64. */
  65. public async loadHomeTimeline(oldestPublicTweet?, oldestPrivateTweet?) {
  66. // Fetch tweets from Twitter
  67. console.log('loading tweets for hometimeline');
  68. const maxId = oldestPublicTweet ? oldestPublicTweet["id_str"] : undefined;
  69. let tweets = await this.twitter.fetchHomeFeed(maxId);
  70. tweets = tweets.filter(tweet => tweet.id_str != maxId);
  71. console.log('tweets are:',tweets);
  72. // Determine start and end of time interval to look for private tweets
  73. const intervalStart: Date = oldestPrivateTweet
  74. ? new Date(oldestPrivateTweet["created_at"])
  75. : new Date();
  76. const intervalEnd: Date = this.getOldestTweetTimestamp(tweets);
  77. // Fetch user's friends
  78. const friends = await this.getCachedFriends(this.userId);
  79. // Extract friends user ids and add own user id
  80. const friendsAndUserIds = friends
  81. .map(friend => friend.id_str)
  82. .concat([this.userId]);
  83. // Fetch ipfs hashs for period
  84. const promises: Promise<object[]>[] = friendsAndUserIds.map(accountId => {
  85. return this.gun.fetchPrivateTweetHashsForUserInInterval(
  86. accountId,
  87. intervalStart,
  88. intervalEnd
  89. );
  90. });
  91. const resolvedPromises = await Promise.all(promises);
  92. const privateTweetHashs = resolvedPromises.reduce(
  93. (privateTweets, el) => privateTweets.concat(el),
  94. []
  95. );
  96. if (privateTweetHashs.length > 0) {
  97. const privateTweets = await this.fetchPrivateTweets(privateTweetHashs);
  98. // Combine and sort tweets
  99. return tweets
  100. .concat(privateTweets)
  101. .sort((a, b) => this.sortByDateAsc(a, b));
  102. } else {
  103. console.log('in else tweets are: ',tweets);
  104. return tweets;
  105. }
  106. }
  107. private async fetchPrivateTweets(privateTweetsData: object[]) {
  108. const privateTweets = [];
  109. console.log('loading tweets for fetchPrivateTweets');
  110. // Load private tweets from P2P storage
  111. for (let i = 0; i < privateTweetsData.length; i++) {
  112. const hash = privateTweetsData[i]["hash"];
  113. const userId = privateTweetsData[i]["userId"];
  114. const timestamp = privateTweetsData[i]["created_at"];
  115. // fetch from IPFS
  116. const encryptedTweet = await this.ipfs.fetchTweet(hash);
  117. // Fetch public key history for user
  118. const publicKeyHistory: object[] = await this.cryptoUtils.fetchPublicKeyHistoryForUser(
  119. userId
  120. );
  121. // Decrypt tweets
  122. const decryptedTweet = this.cryptoUtils.decrypt(
  123. encryptedTweet,
  124. this.getPublicKeyAt(timestamp, publicKeyHistory)
  125. );
  126. privateTweets.push(JSON.parse(decryptedTweet));
  127. }
  128. // Add retweeted/quoted status
  129. privateTweets.map(async tweet => await this.addQuotedStatusToTweet(tweet));
  130. // Add original status (reply to)
  131. privateTweets.map(
  132. async tweet => await this.addOriginalStatusToTweet(tweet)
  133. );
  134. // Add user object to private tweets
  135. return await Promise.all(
  136. privateTweets.map(async tweet => await this.addUserToTweet(tweet))
  137. );
  138. }
  139. private getPublicKeyAt(
  140. timestamp: string,
  141. publicKeyHistory: object[]
  142. ): string {
  143. const timestampTweet = new Date(timestamp).getTime();
  144. for (let key of publicKeyHistory) {
  145. const timestampKey = new Date(key["validFrom"]).getTime();
  146. if (timestampTweet > timestampKey) {
  147. return key["key"];
  148. }
  149. }
  150. // todo: throw error
  151. return "";
  152. }
  153. private async addUserToTweet(tweet: object): Promise<object> {
  154. tweet["user"] = await this.twitter.fetchUser(tweet["user_id"]);
  155. return tweet;
  156. }
  157. private async addQuotedStatusToTweet(tweet: object): Promise<object> {
  158. if (!tweet["quoted_status_id"]) return tweet;
  159. const quoted_status = await this.twitter.fetchTweet(
  160. tweet["quoted_status_id"]
  161. );
  162. tweet["quoted_status"] = quoted_status["data"];
  163. return tweet;
  164. }
  165. private async addOriginalStatusToTweet(tweet: object): Promise<object> {
  166. if (!tweet["in_reply_to_status_id"]) return tweet;
  167. const originalTweet = await this.twitter.fetchTweet(
  168. tweet["in_reply_to_status_id"]
  169. );
  170. tweet["in_reply_to_screen_name"] =
  171. originalTweet["data"]["user"]["screen_name"];
  172. return tweet;
  173. }
  174. private getOldestTweetTimestamp(tweets): Date {
  175. if (tweets.length < 15) {
  176. // End of timeline is reached - load all private tweets
  177. return new Date("2018-04-01T00:00:00");
  178. } else {
  179. const lastTweetTimestamp = tweets[tweets.length - 1].created_at;
  180. return new Date(lastTweetTimestamp);
  181. }
  182. }
  183. private sortByDateAsc(a, b) {
  184. const dateA = new Date(a.created_at);
  185. const dateB = new Date(b.created_at);
  186. if (dateA > dateB) {
  187. return -1;
  188. } else if (dateA < dateB) {
  189. return 1;
  190. } else {
  191. return 0;
  192. }
  193. }
  194. private async getCachedFriends(userId) {
  195. // Cache friends for 15 minutes to avoid unnecessary API calls
  196. if (!this.friends || (Date.now() - this.friends.lastUpdate) / 900000 > 15) {
  197. this.friends = {
  198. friendList: await this.twitter.fetchFriends(userId),
  199. lastUpdate: Date.now()
  200. };
  201. }
  202. return this.friends.friendList;
  203. }
  204. }