soscripted / sox

Stack Overflow Extras: a userscript for the Stack Exchange websites to add a bunch of optional toggle-able features
http://stackapps.com/q/6091/
MIT License
72 stars 15 forks source link

"You cannot change your vote ..." is wrong #335

Closed polyluxus closed 6 years ago

polyluxus commented 6 years ago

Installed Version: 2.2.1DEV Environment: Tampermonkey

Current Behaviour

The script locks the vote buttons, if the post was edited longer than 5 minutes ago, giving the following tooltip

You cannot change your vote on posts that were last edited more than 5 minutes ago.

That is incorrect. As explained in What are the limits on how I can cast, change, and retract votes?:

  • In general, once you have voted, you cannot change your vote. There are two exceptions.
    • Exception one: you may change your vote a practically unlimited number of times within a five minutes window from the first vote you cast on that post. Note that after changing your vote ≈60 times, it will also be locked in.
    • Exception two: you may change your vote after every time the post is edited. A new window starts with the first vote you cast after each edit.

It does not matter when the post was edited, the votes remain unlocked until you vote again (and five minutes thereafter).

votes


Steps to reproduce

  1. Go to a post and vote on it
  2. Wait five minutes for the vote to lock
  3. Edit the post
  4. Wait five minutes to trigger the extension lock, see the tool tip
  5. (Disable option "Voting-disableVoteButtons" and change your vote.)

Features Enabled

["Appearance-addAuthorNameToInboxNotifications","Appearance-alignBadgesByClass","Appearance-answerTagsSearch","Appearance-colorAnswerer","Appearance-dragBounty","Appearance-isQuestionHot","Appearance-localTimestamps","Appearance-markEmployees","Appearance-metaChatBlogStackExchangeButton","Appearance-scrollToTop","Appearance-spoilerTip","Appearance-standOutDupeCloseMigrated","Appearance-topAnswers","Appearance-unspoil","Comments-autoShowCommentImages","Comments-commentReplies","Comments-commentShortcuts","Comments-confirmNavigateAway","Comments-copyCommentsLink","Comments-moveBounty","Comments-hiddenCommentsIndicator","Editing-addSBSBtn","Editing-editComment","Editing-editReasonTooltip","Editing-kbdAndBullets","Editing-titleEditDiff","Editing-inlineEditorEverywhere","Editing-downvotedPostsEditAlert","Flags-flagOutcomeTime","Flags-flagPercentages","Flags-flagPercentageBar","Sidebar-hideHireMe","Sidebar-linkedToFrom","Sidebar-hotNetworkQuestionsFiltering","Chat-chatEasyAccess","Chat-replyToOwnChatMessages","Chat-renameChat","Voting-betterCSS","Voting-stickyVoteButtons","Voting-disableVoteButtons","Extras-alwaysShowImageUploadLinkBox","Extras-linkedPostsInline","Extras-parseCrossSiteLinks","Extras-quickAuthorInfo","Extras-shareLinksMarkdown","Extras-sortByBountyAmount","Extras-warnNotLoggedIn","Extras-showMetaReviewCount","Extras-copyCode","Extras-dailyReviewBar","Extras-showQuestionStateInSuggestedEditReviewQueue"]
shu8 commented 6 years ago

@polyluxus thanks, looks like I overlooked that when I added the feature! Sorry! I'll get it fixed ASAP

shu8 commented 6 years ago

Looking into this, I can't see a way to find a user's vote time with the API.

I might ask on Stack Apps if I can't see find a way, but if there isn't I might not be able to fix this :/

GaurangTandon commented 6 years ago

The problem is apparently right after this comment:

//Grays out votes on posts which haven't been edited in the last 5 minutes

doesn't seem to be a bug though, as the script works as intended :P (jk)

Anyway, the simple fix seems to be simply (REMOVED - see better fix below)

GaurangTandon commented 6 years ago

Or perhaps here's a better method that works across user sessions, by using localStorage to track timestamps:

  1. construct voteTimestamp hashmap with structure as {post_id : timestamp}. Store it in localStorage. This is intended to keep track of vote buttons within five minute editing window across page loads.
  2. construct a local hashmap (say voteTimeouts) with structure as {post_id: timeouts} to keep track of currently running timeouts
  3. on page load, go through every vote button that has a pre-casted vote, and look up its associated post id in the hashmap.
    • If the entry is present in the hashmap, that means that vote button belongs to the five minute editing window. in such case, setTimeout for the remaining time the user has (Date.now() - timestamp). add the timeout to voteTimeouts. if that vote button is used again, clearTimeout and reset it as per condition.
    • otherwise, the vote button is past the five minute editing window and now should be disabled
  4. once page is loaded, watch $(document).on("sox-vote") (might have to create it if it doesn't exist). for every event, store its timestamp and timeout in the respective hashmaps, and proceed as in 3.

This is perfect, except that there's absolutely no way to tell if the post was edited after the vote button is past the five minute edit window. The only way to do so is to have the voteTimestamp hashmap stored permanently.

What are the implications of storing a timestamp hashmap permanently?

A user of SOX is generally supposed to be a very active SE user. Considering that, their voting activity (https://SITE.stackexchange.com/users/ID/NAME?tab=topactivity) would easily be around 4000-5000 votes on every SE site they are proactive in.

post_id is generally 5 to 8 chars long depending on SE site. Any timestamp will continue to be 13 chars long until we die. That is an overhead of nearly 20 chars per vote. Or 100k chars overall for one SE site. Add more SE sites in and you get the picture.

I think if there's a feature advertised, then it should work as intended, so if the overhead is required, it has to be done. On Chrome 67, the max size on localStorage is 5200000 characters. So, storage shouldn't be much of an issue. Still, I believe a helpful warning for the users who enable this feature would suffice.


Sorry, for the really long description though, I was just trying to make it as comprehensive as possible. I'll just let it be here for some more time. If no one else comes up with better ideas, I'll try implement this then.

shu8 commented 6 years ago

@GaurangTandon nice idea! Instead of localStorage it could probably be another GM value so it doesn't get deleted as easily.

However, I think a much simpler (not sure about clean...) method would be to just scrape the votes page by GETting https://meta.stackexchange.com/users/current?tab=votes&sort=upvote. Chances of a user upvoting 30 posts (the amount listed on the page) in 5 minutes are slim, and even if they do, the next page can be fetched too. The page could be requested just once, and a list of the post IDs on the current page can be found and compared to

I think it will be simpler because we won't need to store (possibly quite a bit) extra data and it wouldn't require us to log votes ourselves and manage when to delete them. It also has the advantage that we won't need to store stuff like which site the vote was on

GaurangTandon commented 6 years ago

@shu8 nice! That page has the timestamp values in the title attribute of .date-brick, so that isn't an issue. Also, the point about "we won't need to store stuff like which site the vote was on" is spot on.

I get it that this won't require us to store timestamps anywhere, but then, would you fetch that page every time I load any SE question page? I believe if I am concerned about storage, I am also equally concerned about bandwidth, and considering that I browse SE for hours daily, that page really looks expensive :O

You would only want to fetch it once per day for every site imo. But in that case as well, you'd still need to store that fetched value. So, the problem that you were trying to tackle (not using storage) remains.

That brings me to my more important point, I miscalculated the size of voteTimestamp hashmap Yeah, the actual size required for voteTimestamp would not even be 800 chars for one site at maximum usage, or just 0.8KB. The reason is that voteTimestamp stores post_ids for those posts which were voted upon in the past five minutes only. Once that time period is over, or the vote is cancelled by the user, we can delete the entry for that post_id from voteTimestamp.

Considering that rarely anyone goes on a forty vote ("Vox Populi") sprint in five minutes, the average size of voteTimestamp is expected to be around 20 * 10 = 200 chars.

Thoughts?

polyluxus commented 6 years ago

@shu8 I'm not sure I follow here, I don't see how that page can be of any help. I am not a programmer, so I might not get the full picture here.

This is going to be a little pseudo-cody, I hope you understand. From what I see, you would have to

  1. determine when the post was edited,
  2. you would have to scrape the votes page until that timestamp,
    • if you don't find a vote during that time, the buttons should be unlocked,
    • if you do find a vote:
      • check if the vote is older than five minutes -> locked
      • the vote was within the last five minutes -> unlocked

Given that you sometimes return to a post a year ago and see it changed quite a bit, and then you consider to change your vote (usually going from down to up, or at least remove the vote) I guess that produces quite a bit of overhead.

Then again I have not crawled through any code, so I don't really know anything.

GaurangTandon commented 6 years ago

@polyluxus

I'm not sure I follow here, I don't see how that page can be of any help.

Actually, there's a bit of a trick here. At first, I too found it a bit counter-intuitive. But, press F12. Press Ctrl+F in the DevTools Elements Panel. Type .date_brick and it will focus on to the first vote. Then, you'll notice that its title attribute has the exact timestamp ;)

image

shu8 commented 6 years ago

Sorry, I forgot to actually mention that!

@GaurangTandon sure, your method would use less bandwidth, so I think its worth trying :)

I have asked on Stack Apps though to see if it is possible to get the vote time, so we could maybe wait till I get a response?

polyluxus commented 6 years ago

@GaurangTandon

Type .date_brick and it will focus on to the first vote.

Exactly, it'll give you the timestamp of the votes, starting with the most recent first. It will not give you the time stamp of the vote you are actually interested in, at least not in an iffy. You'll still have to find that vote in the entire history up to the point when the post was last edited, to determine whether the voting is locked or not.
If you revisit a post you have voted on more than a year ago, and you now notice that an edit has substantially changed it, and you would like to change your vote, you are able to. In that case you'll have to travel back quite a few pages. I see no handy way of doing that in a time frame that actually makes sense.

The last five minutes of the voting history are actually the least important ones. The extension doesn't really need to do anything. It is any vote that was cast before that, but after the last edit, that must trigger the locking of the buttons.

@shu8 Care to share a link of the question?

GaurangTandon commented 6 years ago

You'll still have to find that vote in the entire history up to the point when the post was last edited

Ah I see what you mean! That's indeed true.

@polyluxus This is the question. Afaict, this approach is very complicated. So, I will build a working model of my approach via the voteTimestamps hashmap, and then share here by tomorrow ASAP.

shu8 commented 6 years ago

@polyluxus You're right! I overlooked that part!

@GaurangTandon it does look unnecessarily complicated so your idea would be perfect!

GaurangTandon commented 6 years ago

I'm afraid, but after having written nearly a hundred and twenty lines of code for enabling everything, I realized that my implementation was missing the most crucial point - which @polyluxus had just elaborated. I have no way to tell if the vote - that was cast more than five minutes ago - was cast before or after the post was edited.

Therefore, the fetch mechanism is the way to go for now. However, that should be coupled with the localStorage mechanism, to cache pre-fetched values.

That said though, this also brings us to the point that we might have to fetch gazillions of vote-pages before we finally arrive at the two year old (say) post we are interested in, as @polyluxus has earlier said.

So, at such a high cost, I am not sure if this a feature even worth having, unless we figure out a better way to do it. Thoughts?

shu8 commented 6 years ago

@GaurangTandon urgh. I see what you mean now :/

I'm struggling to think of a better solution, and I think you're right in saying more than a few fetches is too much for just this feature.

I'm going to leave this open for a while in case we manage to come up with something, but if we don't do you have any thoughts on whether we should just change this back to the feature where your own posts and deleted posts were grayed out, or just not do anything on posts that have had an edit at all, but still work for non-edited posts?

GaurangTandon commented 6 years ago

@shu8 I would advise against this because:

  1. it would be an inconsistent behavior. If I find my votes locked on some posts, and not locked on others (when it should be), that'd be a weird user experience for me. Better make it correctly on all posts, or not work on any.
  2. I don't come across deleted posts/own posts as often as I come across edited posts by other people. So, the feature wouldn't work in the main place it's supposed to work :(

@polyluxus thoughts?

shu8 commented 6 years ago

@GaurangTandon you're right, that would end up being confusing :/

I think we'll just remove this feature then?

GaurangTandon commented 6 years ago

"I think we'll just remove this feature then?" yep, pretty much what I'm thinking as well.

polyluxus commented 6 years ago

@shu8 I would appreciate if it would grey out voting buttons on my own posts. I do come across them quite frequently, and it helps me realise that. But maybe it would be better to add that as a new feature, while deleting the old one to avoid confusion.

shu8 commented 6 years ago

@polyluxus No problem, I'll remove this feature and rename it so it'll work like it used to (own/deleted posts) :)

shu8 commented 6 years ago

@polyluxus done in dev v2.2.5 :) You shouldn't need to change any settings, I've just updated the description.

Sorry we couldn't get the other part to work, but it would have been a lot of work to do for such a small feature! :/