From ef04a786344ff50cdfeefc79722dafd9c52dbf86 Mon Sep 17 00:00:00 2001
From: Henry Jameson <me@hjkos.com>
Date: Thu, 16 Aug 2018 13:12:31 +0300
Subject: [PATCH] added workaround for broken favorites

---
 src/modules/api.js                            |  4 ++
 src/modules/statuses.js                       | 37 +++++++++++++++----
 src/modules/users.js                          |  2 +
 src/services/api/api.service.js               |  3 ++
 .../backend_interactor_service.js             | 11 ++++++
 .../timeline_fetcher.service.js               |  4 +-
 6 files changed, 51 insertions(+), 10 deletions(-)

diff --git a/src/modules/api.js b/src/modules/api.js
index a61340c2..20586f5c 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -46,6 +46,10 @@ const api = {
         store.commit('addFetcher', {timeline, fetcher})
       }
     },
+    fetchOldPost (store, { postId }) {
+      console.log(store)
+      store.state.backendInteractor.fetchOldPost({ store, postId })
+    },
     stopFetching (store, timeline) {
       const fetcher = store.state.fetchers[timeline]
       window.clearInterval(fetcher)
diff --git a/src/modules/statuses.js b/src/modules/statuses.js
index bc3799dd..bd6b968f 100644
--- a/src/modules/statuses.js
+++ b/src/modules/statuses.js
@@ -27,7 +27,8 @@ export const defaultState = {
     maxId: 0,
     maxSavedId: 0,
     minId: Number.POSITIVE_INFINITY,
-    data: []
+    data: [],
+    brokenFavorites: {}
   },
   favorites: new Set(),
   error: false,
@@ -35,6 +36,7 @@ export const defaultState = {
     mentions: emptyTl(),
     public: emptyTl(),
     user: emptyTl(),
+    own: emptyTl(),
     publicAndExternal: emptyTl(),
     friends: emptyTl(),
     tag: emptyTl()
@@ -140,6 +142,12 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
     const result = mergeOrAdd(allStatuses, allStatusesObject, status)
     status = result.item
 
+    const brokenFavorites = state.notifications.brokenFavorites[status.id] || []
+    brokenFavorites.forEach((fav) => {
+      fav.status = status
+    })
+    delete state.notifications.brokenFavorites[status.id]
+
     if (result.new) {
       // We are mentioned in a post
       if (statusType(status) === 'status' && find(status.attentions, { id: user.id })) {
@@ -174,7 +182,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
     return status
   }
 
-  const favoriteStatus = (favorite) => {
+  const favoriteStatus = (favorite, counter) => {
     const status = find(allStatuses, { id: toInteger(favorite.in_reply_to_status_id) })
     if (status) {
       status.fave_num += 1
@@ -258,7 +266,7 @@ const addNewStatuses = (state, { statuses, showImmediately = false, timeline, us
   }
 }
 
-const addNewNotifications = (state, { notifications, older }) => {
+const addNewNotifications = (state, { dispatch, notifications, older }) => {
   const allStatuses = state.allStatuses
   each(notifications, (notification) => {
     const action = notification.notice
@@ -267,18 +275,31 @@ const addNewNotifications = (state, { notifications, older }) => {
       state.notifications.maxId = Math.max(notification.id, state.notifications.maxId)
       state.notifications.minId = Math.min(notification.id, state.notifications.minId)
 
-      console.log(notification)
       const fresh = !older && !notification.is_seen && notification.id > state.notifications.maxSavedId
       const status = notification.ntype === 'like'
             ? find(allStatuses, { id: action.in_reply_to_status_id })
             : action
-      state.notifications.data.push({
+
+      const result = {
         type: notification.ntype,
         status,
         action,
         // Always assume older notifications as seen
         seen: !fresh
-      })
+      }
+
+      if (notification.ntype === 'like' && !status) {
+        let broken = state.notifications.brokenFavorites[action.in_reply_to_status_id]
+        if (broken) {
+          broken.push(result)
+        } else {
+          dispatch('fetchOldPost', { postId: action.in_reply_to_status_id })
+          broken = [ result ]
+          state.notifications.brokenFavorites[action.in_reply_to_status_id] = broken
+        }
+      }
+
+      state.notifications.data.push(result)
 
       if ('Notification' in window && window.Notification.permission === 'granted') {
         const title = action.user.name
@@ -370,8 +391,8 @@ const statuses = {
     addNewStatuses ({ rootState, commit }, { statuses, showImmediately = false, timeline = false, noIdUpdate = false }) {
       commit('addNewStatuses', { statuses, showImmediately, timeline, noIdUpdate, user: rootState.users.currentUser })
     },
-    addNewNotifications ({ rootState, commit }, { notifications, older }) {
-      commit('addNewNotifications', { notifications, older })
+    addNewNotifications ({ rootState, commit, dispatch }, { notifications, older }) {
+      commit('addNewNotifications', { dispatch, notifications, older })
     },
     setError ({ rootState, commit }, { value }) {
       commit('setError', { value })
diff --git a/src/modules/users.js b/src/modules/users.js
index 8303ecc1..03686c60 100644
--- a/src/modules/users.js
+++ b/src/modules/users.js
@@ -103,6 +103,8 @@ const users = {
 
                   // Start getting fresh tweets.
                   store.dispatch('startFetching', 'friends')
+                  // Start getting our own posts, only really needed for mitigating broken favorites
+                  store.dispatch('startFetching', ['own', user.id])
 
                   // Get user mutes and follower info
                   store.rootState.api.backendInteractor.fetchMutes().then((mutedUsers) => {
diff --git a/src/services/api/api.service.js b/src/services/api/api.service.js
index 9a09e503..351f88ca 100644
--- a/src/services/api/api.service.js
+++ b/src/services/api/api.service.js
@@ -305,6 +305,9 @@ const fetchTimeline = ({timeline, credentials, since = false, until = false, use
     notifications: QVITTER_USER_NOTIFICATIONS_URL,
     'publicAndExternal': PUBLIC_AND_EXTERNAL_TIMELINE_URL,
     user: QVITTER_USER_TIMELINE_URL,
+    // separate timeline for own posts, so it won't break due to user timeline bugs
+    // really needed only for broken favorites
+    own: QVITTER_USER_TIMELINE_URL,
     tag: TAG_TIMELINE_URL
   }
 
diff --git a/src/services/backend_interactor_service/backend_interactor_service.js b/src/services/backend_interactor_service/backend_interactor_service.js
index dbfb54f9..f65ad43e 100644
--- a/src/services/backend_interactor_service/backend_interactor_service.js
+++ b/src/services/backend_interactor_service/backend_interactor_service.js
@@ -54,6 +54,16 @@ const backendInteractorService = (credentials) => {
     return timelineFetcherService.startFetching({timeline, store, credentials, userId})
   }
 
+  const fetchOldPost = ({store, postId}) => {
+    return timelineFetcherService.fetchAndUpdate({
+      store,
+      credentials,
+      timeline: 'own',
+      older: true,
+      until: postId
+    })
+  }
+
   const setUserMute = ({id, muted = true}) => {
     return apiService.setUserMute({id, muted, credentials})
   }
@@ -86,6 +96,7 @@ const backendInteractorService = (credentials) => {
     fetchAllFollowing,
     verifyCredentials: apiService.verifyCredentials,
     startFetching,
+    fetchOldPost,
     setUserMute,
     fetchMutes,
     register,
diff --git a/src/services/timeline_fetcher/timeline_fetcher.service.js b/src/services/timeline_fetcher/timeline_fetcher.service.js
index bb5fdc2e..0e3e32d2 100644
--- a/src/services/timeline_fetcher/timeline_fetcher.service.js
+++ b/src/services/timeline_fetcher/timeline_fetcher.service.js
@@ -14,13 +14,13 @@ const update = ({store, statuses, timeline, showImmediately}) => {
   })
 }
 
-const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false}) => {
+const fetchAndUpdate = ({store, credentials, timeline = 'friends', older = false, showImmediately = false, userId = false, tag = false, until}) => {
   const args = { timeline, credentials }
   const rootState = store.rootState || store.state
   const timelineData = rootState.statuses.timelines[camelCase(timeline)]
 
   if (older) {
-    args['until'] = timelineData.minVisibleId
+    args['until'] = until || timelineData.minVisibleId
   } else {
     args['since'] = timelineData.maxId
   }