Repository: praw-dev/praw

Issue:
Support for api/store_visits
The reddit api exposes a method for gold users to store their link visits for synchronization purposes.

https://www.reddit.com/dev/api/#POST_api_store_visits

There is no direct support for this in PRAW, but one way to do it is by using the raw Post command. Here's a sample:
`reddit = praw.Reddit(...)`
`reddit.post("api/store_visits", data={'links':'t3_8q3ck5'})`

That would mark the link with the full name of t3_8q3ck5 as viewed.

There are a few paths to take here:
1- Add the steps above to the PRAW documentation and call it a job well done
2- Add minor functionality to PRAW to support this. Basically just wrap the post call above with a method on the Reddit class
3- Support this functionality at a better level by adding this as a method on the Submission class.

I believe this API also supports batching, so to prevent these calls from eating into the small amount of request quota, there could be a timer that fires to make the request every x seconds.

I'm thinking about going with the third route since it is the easier to use option. Batching can come as a secondary feature later.

Any concerns/suggestions/thoughts?



Discussion:
I like the idea of #3. Want to give it a try? I agree that batching can come later. Thanks for looking into it.
Yeah, I'm definitely interested. The change/tests themselves are easy, but validation might be a bit of a challenge since I've never set up a python environment. What's the pull request process? Run pre_push.py and submit?

Edit: If there isn't anything available, I can write something quick and submit it in a second pull request, since I'm going through the pain of discovering pytest and necessary modules as I go. Complete python newbie.
Edit2: Never mind, found contributing.rst.

Resolving patch:
diff --git a/praw/const.py b/praw/const.py
--- a/praw/const.py
+++ b/praw/const.py
@@ -135,6 +135,7 @@
     'site_admin':             'api/site_admin/',
     'spoiler':                'api/spoiler/',
     'sticky_submission':      'api/set_subreddit_sticky/',
+    'store_visits':           'api/store_visits',
     'sub_recommended':        'api/recommend/sr/{subreddits}',
     'submission':             'comments/{id}/',
     'submission_replies':     'message/selfreply/',
diff --git a/praw/models/reddit/submission.py b/praw/models/reddit/submission.py
--- a/praw/models/reddit/submission.py
+++ b/praw/models/reddit/submission.py
@@ -98,6 +98,16 @@ def mod(self):
             self._mod = SubmissionModeration(self)
         return self._mod
 
+    @property
+    def shortlink(self):
+        """Return a shortlink to the submission.
+
+        For example http://redd.it/eorhm is a shortlink for
+        https://www.reddit.com/r/announcements/comments/eorhm/reddit_30_less_typing/.
+
+        """
+        return urljoin(self._reddit.config.short_url, self.id)
+
     def __init__(self, reddit, id=None,  # pylint: disable=redefined-builtin
                  url=None, _data=None):
         """Initialize a Submission instance.
@@ -158,6 +168,20 @@ def _fetch(self):
     def _info_path(self):
         return API_PATH['submission'].format(id=self.id)
 
+    def mark_visited(self):
+        """Mark submission as visited.
+
+        Example usage:
+
+        .. code:: python
+
+           submission = reddit.submission(id='5or86n')
+           submission.mark_visited()
+
+        """
+        data = {'links': self.fullname}
+        self._reddit.post(API_PATH['store_visits'], data=data)
+
     def hide(self, other_submissions=None):
         """Hide Submission.
 
@@ -178,16 +202,6 @@ def hide(self, other_submissions=None):
         for submissions in self._chunk(other_submissions, 50):
             self._reddit.post(API_PATH['hide'], data={'id': submissions})
 
-    @property
-    def shortlink(self):
-        """Return a shortlink to the submission.
-
-        For example http://redd.it/eorhm is a shortlink for
-        https://www.reddit.com/r/announcements/comments/eorhm/reddit_30_less_typing/.
-
-        """
-        return urljoin(self._reddit.config.short_url, self.id)
-
     def unhide(self, other_submissions=None):
         """Unhide Submission.
 


Test patch:
diff --git a/tests/integration/cassettes/TestSubmission.test_mark_visited.json b/tests/integration/cassettes/TestSubmission.test_mark_visited.json
new file mode 100644
--- /dev/null
+++ b/tests/integration/cassettes/TestSubmission.test_mark_visited.json
@@ -0,0 +1,222 @@
+{
+  "http_interactions": [
+    {
+      "recorded_at": "2018-06-19T07:27:30",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "grant_type=password&password=<PASSWORD>&username=<USERNAME>"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "Basic <BASIC_AUTH>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "60"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/6.0.0.dev0 prawcore/1.0.0"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://www.reddit.com/api/v1/access_token"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{\"access_token\": \"<ACCESS_TOKEN>\", \"token_type\": \"bearer\", \"expires_in\": 3600, \"scope\": \"*\"}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "113"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Tue, 19 Jun 2018 07:27:22 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Set-Cookie": [
+            "edgebucket=9yfkOcFKOSotRWeKCE; Domain=reddit.com; Max-Age=63071999; Path=/;  secure"
+          ],
+          "Strict-Transport-Security": [
+            "max-age=15552000; includeSubDomains; preload"
+          ],
+          "Via": [
+            "1.1 varnish"
+          ],
+          "X-Cache": [
+            "MISS"
+          ],
+          "X-Cache-Hits": [
+            "0"
+          ],
+          "X-Moose": [
+            "majestic"
+          ],
+          "X-Served-By": [
+            "cache-sea1028-SEA"
+          ],
+          "X-Timer": [
+            "S1529393242.160049,VS0,VE425"
+          ],
+          "cache-control": [
+            "max-age=0, must-revalidate"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://www.reddit.com/api/v1/access_token"
+      }
+    },
+    {
+      "recorded_at": "2018-06-19T07:27:30",
+      "request": {
+        "body": {
+          "encoding": "utf-8",
+          "string": "api_type=json&links=t3_8s529q"
+        },
+        "headers": {
+          "Accept": [
+            "*/*"
+          ],
+          "Accept-Encoding": [
+            "identity"
+          ],
+          "Authorization": [
+            "bearer <ACCESS_TOKEN>"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "29"
+          ],
+          "Content-Type": [
+            "application/x-www-form-urlencoded"
+          ],
+          "Cookie": [
+            "edgebucket=9yfkOcFKOSotRWeKCE"
+          ],
+          "User-Agent": [
+            "<USER_AGENT> PRAW/6.0.0.dev0 prawcore/1.0.0"
+          ]
+        },
+        "method": "POST",
+        "uri": "https://oauth.reddit.com/api/store_visits?raw_json=1"
+      },
+      "response": {
+        "body": {
+          "encoding": "UTF-8",
+          "string": "{}"
+        },
+        "headers": {
+          "Accept-Ranges": [
+            "bytes"
+          ],
+          "Connection": [
+            "keep-alive"
+          ],
+          "Content-Length": [
+            "2"
+          ],
+          "Content-Type": [
+            "application/json; charset=UTF-8"
+          ],
+          "Date": [
+            "Tue, 19 Jun 2018 07:27:22 GMT"
+          ],
+          "Server": [
+            "snooserv"
+          ],
+          "Strict-Transport-Security": [
+            "max-age=15552000; includeSubDomains; preload"
+          ],
+          "Via": [
+            "1.1 varnish"
+          ],
+          "X-Cache": [
+            "MISS"
+          ],
+          "X-Cache-Hits": [
+            "0"
+          ],
+          "X-Moose": [
+            "majestic"
+          ],
+          "X-Served-By": [
+            "cache-sea1036-SEA"
+          ],
+          "X-Timer": [
+            "S1529393243.619855,VS0,VE107"
+          ],
+          "cache-control": [
+            "private, s-maxage=0, max-age=0, must-revalidate, max-age=0, must-revalidate"
+          ],
+          "expires": [
+            "-1"
+          ],
+          "x-content-type-options": [
+            "nosniff"
+          ],
+          "x-frame-options": [
+            "SAMEORIGIN"
+          ],
+          "x-ratelimit-remaining": [
+            "599.0"
+          ],
+          "x-ratelimit-reset": [
+            "158"
+          ],
+          "x-ratelimit-used": [
+            "1"
+          ],
+          "x-ua-compatible": [
+            "IE=edge"
+          ],
+          "x-xss-protection": [
+            "1; mode=block"
+          ]
+        },
+        "status": {
+          "code": 200,
+          "message": "OK"
+        },
+        "url": "https://oauth.reddit.com/api/store_visits?raw_json=1"
+      }
+    }
+  ],
+  "recorded_with": "betamax/0.8.1"
+}
\ No newline at end of file
diff --git a/tests/integration/models/reddit/test_submission.py b/tests/integration/models/reddit/test_submission.py
--- a/tests/integration/models/reddit/test_submission.py
+++ b/tests/integration/models/reddit/test_submission.py
@@ -133,6 +133,12 @@ def test_save(self):
                 'TestSubmission.test_save'):
             Submission(self.reddit, '4b536p').save()
 
+    def test_mark_visited(self):
+        self.reddit.read_only = False
+        with self.recorder.use_cassette(
+                'TestSubmission.test_mark_visited'):
+            Submission(self.reddit, '8s529q').mark_visited()
+
     def test_unhide(self):
         self.reddit.read_only = False
         with self.recorder.use_cassette(