Repository: tweepy/tweepy
Issue: Bug with list_direct_messages

Hi,

I'm trying to use the list_direct_messages api but any time I try to iterate through the items it returns I get this error

Traceback (most recent call last):
  File "reply_dm.py", line 41, in <module>
    main()
  File "reply_dm.py", line 36, in main
    check_messages(api)
  File "reply_dm.py", line 29, in check_messages
    for message in tweepy.Cursor(api.list_direct_messages).items():
  File "C:\\Python27\\bot\\twitter\\lib\\site-packages\\tweepy\\cursor.py", line 195, in next
    self.current_page = self.page_iterator.next()
  File "C:\\Python27\\bot\\twitter\\lib\\site-packages\\tweepy\\cursor.py", line 73, in next
    **self.kargs)
  File "C:\\Python27\\bot\\twitter\\lib\\site-packages\\tweepy\\binder.py", line 250, in _call
    return method.execute()
  File "C:\\Python27\\bot\\twitter\\lib\\site-packages\\tweepy\\binder.py", line 233, in execute
    raise TweepError(error_msg, resp, api_code=api_error_code)
tweepy.error.TweepError: [{u'message': u'invalid cursor', u'code': 214}]

Now, I may be doing something wrong, but I tested a few other API methods such as followers and others and those worked (so I think I'm using cursors properly).

Can anyone confirm this is working in their version of Python?

Changed files:
tweepy/binder.py
tweepy/parsers.py
tweepy/cursor.py

diff --git a/tweepy/binder.py b/tweepy/binder.py
index 7d352a6..846cfbf 100644
--- a/tweepy/binder.py
+++ b/tweepy/binder.py
@@ -55,6 +55,7 @@ def bind_api(**config):
                                                  api.wait_on_rate_limit)
             self.wait_on_rate_limit_notify = kwargs.pop('wait_on_rate_limit_notify',
                                                         api.wait_on_rate_limit_notify)
+            self.return_cursors = kwargs.pop('return_cursors', False)
             self.parser = kwargs.pop('parser', api.parser)
             self.session.headers = kwargs.pop('headers', {})
             self.build_parameters(args, kwargs)
@@ -233,7 +234,8 @@ def bind_api(**config):
                     raise TweepError(error_msg, resp, api_code=api_error_code)
 
             # Parse the response payload
-            result = self.parser.parse(self, resp.text)
+            self.return_cursors = self.return_cursors or 'cursor' in self.session.params
+            result = self.parser.parse(self, resp.text, return_cursors=self.return_cursors)
 
             # Store result into cache if one is available.
             if self.use_cache and self.api.cache and self.method == 'GET' and result:
@@ -253,7 +255,10 @@ def bind_api(**config):
 
     # Set pagination mode
     if 'cursor' in APIMethod.allowed_param:
-        _call.pagination_mode = 'cursor'
+        if APIMethod.payload_type == 'direct_message':
+            _call.pagination_mode = 'dm_cursor'
+        else:
+            _call.pagination_mode = 'cursor'
     elif 'max_id' in APIMethod.allowed_param:
         if 'since_id' in APIMethod.allowed_param:
             _call.pagination_mode = 'id'
diff --git a/tweepy/cursor.py b/tweepy/cursor.py
index 1803c37..2a3d950 100644
--- a/tweepy/cursor.py
+++ b/tweepy/cursor.py
@@ -13,6 +13,8 @@ class Cursor(object):
         if hasattr(method, 'pagination_mode'):
             if method.pagination_mode == 'cursor':
                 self.iterator = CursorIterator(method, *args, **kwargs)
+            elif method.pagination_mode == 'dm_cursor':
+                self.iterator = DMCursorIterator(method, *args, **kwargs)
             elif method.pagination_mode == 'id':
                 self.iterator = IdIterator(method, *args, **kwargs)
             elif method.pagination_mode == 'page':
@@ -87,6 +89,28 @@ class CursorIterator(BaseIterator):
         return data
 
 
+class DMCursorIterator(BaseIterator):
+
+    def __init__(self, method, *args, **kwargs):
+        BaseIterator.__init__(self, method, *args, **kwargs)
+        self.next_cursor = self.kwargs.pop('cursor', None)
+        self.page_count = 0
+
+    def next(self):
+        if self.next_cursor == -1 or (self.limit and self.page_count == self.limit):
+            raise StopIteration
+        data = self.method(cursor=self.next_cursor, return_cursors=True, *self.args, **self.kwargs)
+        self.page_count += 1
+        if isinstance(data, tuple):
+            data, self.next_cursor = data
+        else:
+            self.next_cursor = -1
+        return data
+
+    def prev(self):
+        raise TweepError('This method does not allow backwards pagination')
+
+
 class IdIterator(BaseIterator):
 
     def __init__(self, method, *args, **kwargs):
@@ -193,6 +217,8 @@ class ItemIterator(BaseIterator):
         if self.current_page is None or self.page_index == len(self.current_page) - 1:
             # Reached end of current page, get the next page...
             self.current_page = self.page_iterator.next()
+            while len(self.current_page) == 0:
+                self.current_page = self.page_iterator.next()
             self.page_index = -1
         self.page_index += 1
         self.num_tweets += 1
diff --git a/tweepy/parsers.py b/tweepy/parsers.py
index 70fd978..7d09f63 100644
--- a/tweepy/parsers.py
+++ b/tweepy/parsers.py
@@ -10,7 +10,7 @@ from tweepy.models import ModelFactory
 
 class Parser(object):
 
-    def parse(self, method, payload):
+    def parse(self, method, payload, *args, **kwargs):
         """
         Parse the response payload and return the result.
         Returns a tuple that contains the result data and the cursors
@@ -32,7 +32,7 @@ class RawParser(Parser):
     def __init__(self):
         pass
 
-    def parse(self, method, payload):
+    def parse(self, method, payload, *args, **kwargs):
         return payload
 
     def parse_error(self, payload):
@@ -43,20 +43,20 @@ class JSONParser(Parser):
 
     payload_format = 'json'
 
-    def parse(self, method, payload):
+    def parse(self, method, payload, return_cursors=False):
         try:
             json = json_lib.loads(payload)
         except Exception as e:
             raise TweepError('Failed to parse JSON payload: %s' % e)
 
-        needs_cursors = 'cursor' in method.session.params
-        if needs_cursors and isinstance(json, dict) \\
-                and 'previous_cursor' in json \\
-                and 'next_cursor' in json:
-            cursors = json['previous_cursor'], json['next_cursor']
-            return json, cursors
-        else:
-            return json
+        if return_cursors and isinstance(json, dict):
+            if 'next_cursor' in json:
+                if 'previous_cursor' in json:
+                    cursors = json['previous_cursor'], json['next_cursor']
+                    return json, cursors
+                else:
+                    return json, json['next_cursor']
+        return json
 
     def parse_error(self, payload):
         error_object = json_lib.loads(payload)
@@ -79,7 +79,7 @@ class ModelParser(JSONParser):
         JSONParser.__init__(self)
         self.model_factory = model_factory or ModelFactory
 
-    def parse(self, method, payload):
+    def parse(self, method, payload, return_cursors=False):
         try:
             if method.payload_type is None:
                 return
@@ -88,7 +88,7 @@ class ModelParser(JSONParser):
             raise TweepError('No model for this payload type: '
                              '%s' % method.payload_type)
 
-        json = JSONParser.parse(self, method, payload)
+        json = JSONParser.parse(self, method, payload, return_cursors=return_cursors)
         if isinstance(json, tuple):
             json, cursors = json
         else: