├── pymailinator ├── __init__.py ├── _version.py ├── tests │ ├── __init__.py │ ├── test_wrapper.py │ └── json │ │ ├── mailbox.json │ │ └── message.json └── wrapper.py ├── setup.cfg ├── .coveralls.yml ├── .coveragerc ├── requirements.txt ├── .travis.yml ├── LICENSE ├── setup.py ├── .gitignore ├── README.md └── fabfile.py /pymailinator/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /pymailinator/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = "0.4.6" -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md -------------------------------------------------------------------------------- /pymailinator/tests/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = 'Ryan.McDevitt' 2 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | service_name: travis-ci 2 | repo_token: ojNAW98GdLgUxzmkEvGztqppQO7LU68ai -------------------------------------------------------------------------------- /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = False 3 | 4 | source = pymailinator 5 | 6 | omit = 7 | /*/tests/* -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Fabric==1.10.0 2 | PyYAML==3.11 3 | cov-core==1.15.0 4 | coverage==3.7.1 5 | ecdsa==0.11 6 | paramiko==1.15.1 7 | py==1.4.26 8 | pycrypto==2.6.1 9 | pytest==2.6.4 10 | pytest-cov==1.8.1 11 | python-coveralls==2.4.3 12 | requests==2.5.0 13 | sh==1.09 14 | six==1.8.0 15 | wsgiref==0.1.2 16 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | - "2.7" 4 | # command to install dependencies 5 | install: pip install -r requirements.txt 6 | # command to run tests 7 | script: 8 | - python -m unittest discover pymailinator 9 | - py.test --cov pymailinator/wrapper.py pymailinator/tests 10 | after_success: 11 | - coveralls -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Ryan McDevitt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | import re 4 | VERSIONFILE="pymailinator/_version.py" 5 | verstrline = open(VERSIONFILE, "rt").read() 6 | VSRE = r"^__version__ = ['\"]([^'\"]*)['\"]" 7 | mo = re.search(VSRE, verstrline, re.M) 8 | if mo: 9 | verstr = mo.group(1) 10 | major, minor, patch = verstr.split('.') 11 | release = "%s.%s" %(major, minor) 12 | else: 13 | raise RuntimeError("Unable to find version string in %s." % (VERSIONFILE,)) 14 | # Setup 15 | setup( 16 | name='py-mailinator', 17 | version=verstr, 18 | url='https://github.com/mc706/py-mailinator', 19 | author='Ryan McDevitt', 20 | author_email='mcdevitt.ryan@gmail.com', 21 | license='MIT License', 22 | packages=['pymailinator'], 23 | include_package_data=True, 24 | description='Python API wrapper for mailinator', 25 | download_url = 'https://github.com/mc706/py-mailinator/tarball/' + release, 26 | keywords = ['mailinator', 'api', 'email'], 27 | classifiers = [ 28 | "Programming Language :: Python :: 2.6", 29 | "Programming Language :: Python :: 2.7", 30 | "Programming Language :: Python :: 3", 31 | "Development Status :: 5 - Production/Stable", 32 | "Environment :: Console", 33 | "Intended Audience :: Developers", 34 | "Intended Audience :: Information Technology", 35 | "License :: OSI Approved :: MIT License", 36 | "Operating System :: OS Independent", 37 | "Topic :: Communications :: Email", 38 | "Topic :: Software Development :: Testing", 39 | "Topic :: Utilities", 40 | ], 41 | ) -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .gitignore support plugin (hsz.mobi) 2 | ### Vagrant template 3 | .vagrant/ 4 | 5 | 6 | ### JetBrains template 7 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 8 | 9 | *.iml 10 | 11 | ## Directory-based project format: 12 | .idea/ 13 | # if you remove the above rule, at least ignore the following: 14 | 15 | # User-specific stuff: 16 | # .idea/workspace.xml 17 | # .idea/tasks.xml 18 | # .idea/dictionaries 19 | 20 | # Sensitive or high-churn files: 21 | # .idea/dataSources.ids 22 | # .idea/dataSources.xml 23 | # .idea/sqlDataSources.xml 24 | # .idea/dynamic.xml 25 | # .idea/uiDesigner.xml 26 | 27 | # Gradle: 28 | # .idea/gradle.xml 29 | # .idea/libraries 30 | 31 | # Mongo Explorer plugin: 32 | # .idea/mongoSettings.xml 33 | 34 | ## File-based project format: 35 | *.ipr 36 | *.iws 37 | 38 | ## Plugin-specific files: 39 | 40 | # IntelliJ 41 | out/ 42 | 43 | # mpeltonen/sbt-idea plugin 44 | .idea_modules/ 45 | 46 | # JIRA plugin 47 | atlassian-ide-plugin.xml 48 | 49 | # Crashlytics plugin (for Android Studio and IntelliJ) 50 | com_crashlytics_export_strings.xml 51 | crashlytics.properties 52 | crashlytics-build.properties 53 | 54 | 55 | ### Python template 56 | # Byte-compiled / optimized / DLL files 57 | __pycache__/ 58 | *.py[cod] 59 | 60 | # C extensions 61 | *.so 62 | 63 | # Distribution / packaging 64 | .Python 65 | env/ 66 | build/ 67 | develop-eggs/ 68 | dist/ 69 | downloads/ 70 | eggs/ 71 | lib/ 72 | lib64/ 73 | parts/ 74 | sdist/ 75 | var/ 76 | *.egg-info/ 77 | .installed.cfg 78 | *.egg 79 | 80 | # PyInstaller 81 | # Usually these files are written by a python script from a template 82 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 83 | *.manifest 84 | *.spec 85 | 86 | # Installer logs 87 | pip-log.txt 88 | pip-delete-this-directory.txt 89 | 90 | # Unit test / coverage reports 91 | htmlcov/ 92 | .tox/ 93 | .coverage 94 | .cache 95 | nosetests.xml 96 | coverage.xml 97 | 98 | # Translations 99 | *.mo 100 | *.pot 101 | 102 | # Django stuff: 103 | *.log 104 | 105 | # Sphinx documentation 106 | docs/_build/ 107 | 108 | # PyBuilder 109 | target/ 110 | 111 | .coveralls.yml 112 | 113 | 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | py-mailinator 2 | ============= 3 | 4 | [![Build Status](https://travis-ci.org/mc706/py-mailinator.svg?branch=master)](https://travis-ci.org/mc706/py-mailinator) 5 | [![PyPI version](https://badge.fury.io/py/py-mailinator.svg)](http://badge.fury.io/py/py-mailinator) 6 | [![Code Health](https://landscape.io/github/mc706/py-mailinator/master/landscape.svg)](https://landscape.io/github/mc706/py-mailinator/master) 7 | [![Coverage Status](https://img.shields.io/coveralls/mc706/py-mailinator.svg)](https://coveralls.io/r/mc706/py-mailinator) 8 | 9 | An python wrapper for the mailinator.com api 10 | 11 | ##Installation 12 | 13 | Install using pip 14 | 15 | ``` 16 | pip install py-mailinator 17 | ``` 18 | 19 | ##Commands 20 | 21 | Retrieve Inbox 22 | Get your api key at the mailinator [settings](https://www.mailinator.com/settings.jsp) page. 23 | 24 | ``` 25 | from pymailinator.wrapper import Inbox 26 | 27 | inbox = Inbox(api_key) 28 | inbox.get() 29 | print inbox.messages 30 | ``` 31 | 32 | ##Check Mail 33 | 34 | ``` 35 | mail = inbox.messages[0] 36 | mail.get_message() 37 | print mail.body 38 | ``` 39 | ##Inbox Object 40 | Methods: 41 | * `get()` : retrieves inbox 42 | * `count()`: run after get, gets length of inbox 43 | * `view_subjects()`: run after get. Gets lists of subject lines of inbox 44 | * `view_message_ids()`: run after get. Gets lists of subject lines of inbox 45 | * `get_message_by_subject(subject)`: takes a subject as a string, returns message or list of messages with that subject 46 | * `get_message_by_id(id)`: takes an message id as a string, returns the message if it exists 47 | * `filter(field, value)`: returns list of message objects where message.field == value 48 | 49 | ##Message Object 50 | Methods: 51 | * `get_message()`: retrieves full message body and headers 52 | 53 | Attributes: 54 | * `id` : message id 55 | * `subject` : message subject line 56 | * `time` : message delivery time 57 | * `seconds_ago` : number of seconds between time of delivery and time of request 58 | * `origfrom` : Original from field 59 | * `ip` : ip address the email was sent from 60 | * `been_read` : boolean if messsage has been opened 61 | * `headers` : only available after `get_message()`, shows the message headers 62 | * `body` : only available after `get_message()`, shows message body 63 | 64 | -------------------------------------------------------------------------------- /fabfile.py: -------------------------------------------------------------------------------- 1 | from fabric.api import local 2 | 3 | 4 | def bump_patch(): 5 | with open('pymailinator/_version.py', 'r') as f: 6 | original = f.read() 7 | version = original.split('=')[1].strip('\" \n\'') 8 | major, minor, patch = version.split('.') 9 | patch = int(patch) + 1 10 | with open('pymailinator/_version.py', 'w') as f: 11 | f.write('__version__ = "%s.%s.%s"' % (major, minor, patch)) 12 | local('git add pymailinator/_version.py') 13 | local('git commit -m "updated version to %s.%s.%s"'% (major, minor, patch)) 14 | local('git push') 15 | 16 | 17 | def bump_minor(): 18 | with open('pymailinator/_version.py', 'r') as f: 19 | original = f.read() 20 | version = original.split('=')[1].strip('\" \n\'') 21 | major, minor, patch = version.split('.') 22 | patch = 0 23 | minor = int(minor) + 1 24 | with open('pymailinator/_version.py', 'w') as f: 25 | f.write('__version__ = "%s.%s.%s"' % (major, minor, patch)) 26 | local('git add pymailinator/_version.py') 27 | local('git commit -m "updated version to %s.%s.%s"'% (major, minor, patch)) 28 | local('git tag %s.%s -m "Update for release"' % (major, minor)) 29 | local('git push --tags origin master') 30 | 31 | 32 | def bump_major(): 33 | with open('pymailinator/_version.py', 'r') as f: 34 | original = f.read() 35 | version = original.split('=')[1].strip('\" \n\'') 36 | major, minor, patch = version.split('.') 37 | patch = 0 38 | minor = 0 39 | major = int(major) + 1 40 | with open('pymailinator/_version.py', 'w') as f: 41 | f.write('__version__ = "%s.%s.%s"' % (major, minor, patch)) 42 | local('git add pymailinator/_version.py') 43 | local('git commit -m "updated version to %s.%s.%s"'% (major, minor, patch)) 44 | local('git tag %s.%s -m "Update for release"' % (major, minor)) 45 | local('git push --tags origin master') 46 | 47 | 48 | def deploy_test(release='patch'): 49 | if release == 'patch': 50 | bump_patch() 51 | elif release == 'minor': 52 | bump_minor() 53 | elif release == 'major': 54 | bump_major() 55 | elif release == 'none': 56 | pass 57 | else: 58 | bump_patch() 59 | local('python setup.py register -r pypitest') 60 | local('python setup.py sdist upload -r pypitest') 61 | 62 | 63 | def deploy(release='patch'): 64 | if release == 'patch': 65 | bump_patch() 66 | elif release == 'minor': 67 | bump_minor() 68 | elif release == 'major': 69 | bump_major() 70 | elif release == 'none': 71 | pass 72 | else: 73 | bump_patch() 74 | local('python setup.py register -r pypi') 75 | local('python setup.py sdist upload -r pypi') -------------------------------------------------------------------------------- /pymailinator/wrapper.py: -------------------------------------------------------------------------------- 1 | import json 2 | from time import sleep 3 | from email.utils import parseaddr, formataddr 4 | 5 | try: 6 | from urllib.request import urlopen 7 | from urllib.parse import urlencode 8 | except ImportError: 9 | from urllib import urlopen, urlencode 10 | 11 | 12 | TIME_SLEEP_TOO_MANY_REQUESTS = .7 13 | NUMBER_ATTEMPTS_TOO_MANY_REQUESTS = 12 14 | 15 | 16 | class TooManyRequests(Exception): 17 | pass 18 | 19 | 20 | class InvalidToken(Exception): 21 | pass 22 | 23 | 24 | class MissingToken(Exception): 25 | pass 26 | 27 | 28 | class MessageNotFound(Exception): 29 | pass 30 | 31 | 32 | class RateLimiterReached(Exception): 33 | pass 34 | 35 | 36 | class Message(object): 37 | """Message Object for Mailinator Email API 38 | """ 39 | _baseURL = 'http://api.mailinator.com/api/email' 40 | 41 | def __init__(self, token, data): 42 | self.token = token 43 | self.id = data.get('id', None) 44 | self.subject = data.get('subject', None) 45 | self.time = data.get('time', None) 46 | self.to = data.get('to', None) 47 | self.seconds_ago = data.get('seconds_ago', None) 48 | self.ip = data.get('ip') 49 | 50 | try: 51 | self.origfrom = data['origfrom'] 52 | # Support old Message attributes 53 | self.fromshort, self.fromfull = parseaddr(self.origfrom) 54 | except KeyError: 55 | # Try the old data model 56 | self.fromfull = data.get('fromfull') 57 | self.fromshort = data.get('from') 58 | self.origfrom = formataddr((self.fromshort, self.fromfull)) 59 | 60 | self.headers = {} 61 | self.body = "" 62 | 63 | def get_message(self): 64 | query_string = {'token': self.token, 'msgid': self.id} 65 | url = self._baseURL + "?" + urlencode(query_string) 66 | request = get_request(url) 67 | if request.getcode() == 404: 68 | raise MessageNotFound 69 | response = request.read() 70 | data = json.loads(clean_response(response), strict=False) 71 | if data.get('error', False): 72 | if data.get('error').lower() is "rate limiter reached": 73 | raise RateLimiterReached 74 | self.headers = data.get('data').get('headers') 75 | self.body = "".join([part['body'] for part in data['data']['parts']]) 76 | 77 | 78 | class Inbox(object): 79 | """Inbox Object for retrieving an inbox 80 | 81 | Args: 82 | token: An api token, from http://www.mailinator.com/settings.jsp 83 | Methods: 84 | get: 85 | Args: 86 | mailbox: optional argument for the name of an inbox 87 | Returns: 88 | 89 | Raises: 90 | InvalidToken: When the api token is invalid 91 | MissingToken: When run without a token 92 | 93 | """ 94 | _baseURL = "http://api.mailinator.com/api/inbox" 95 | 96 | def __init__(self, token): 97 | self.token = token 98 | self.messages = [] 99 | 100 | def get(self, mailbox=None, private_domain=False): 101 | """Retrieves email from inbox""" 102 | if not self.token: 103 | raise MissingToken 104 | query_string = {'token': self.token} 105 | if mailbox: 106 | query_string.update({'to': mailbox}) 107 | if private_domain: 108 | query_string.update({'private_domain': json.dumps(private_domain)}) 109 | url = self._baseURL + '?' + urlencode(query_string) 110 | request = get_request(url) 111 | if request.getcode() == 400: 112 | raise InvalidToken 113 | response = request.read() 114 | return self._parse(response) 115 | 116 | def count(self): 117 | """returns the number of emails in inbox""" 118 | return len(self.messages) 119 | 120 | def view_subjects(self): 121 | """returns a list of messages subjects""" 122 | return [message.subject for message in self.messages] 123 | 124 | def view_message_ids(self): 125 | """returns a list of message ids""" 126 | return [message.id for message in self.messages] 127 | 128 | def get_message_by_subject(self, subject): 129 | """returns a message object with a subject line""" 130 | messages = [message for message in self.messages if message.subject.lower() == subject.lower()] 131 | if len(messages) == 0: 132 | return None 133 | elif len(messages) == 1: 134 | return messages[0] 135 | else: 136 | return messages 137 | 138 | def get_message_by_id(self, msgid): 139 | """returns a message object by id""" 140 | messages = [message for message in self.messages if message.id == msgid] 141 | if len(messages) == 0: 142 | return None 143 | elif len(messages) == 1: 144 | return messages[0] 145 | 146 | def filter(self, field, value): 147 | """returns a filtered list of messages by where message.field = value""" 148 | return [message for message in self.messages if getattr(message, field) == value] 149 | 150 | def _parse(self, data): 151 | self.messages = [] 152 | parsed = json.loads(clean_response(data), strict=False) 153 | for message in parsed['messages']: 154 | email = Message(self.token, message) 155 | self.messages.append(email) 156 | return self.messages 157 | 158 | 159 | def clean_response(response): 160 | if not isinstance(response, str): 161 | return response.decode('utf-8', 'ignore') 162 | else: 163 | return response 164 | 165 | 166 | def get_request(url, sleep_time=.7): 167 | for i in range(NUMBER_ATTEMPTS_TOO_MANY_REQUESTS): 168 | request = urlopen(url) 169 | if request.getcode() != 429: 170 | break 171 | sleep(TIME_SLEEP_TOO_MANY_REQUESTS) 172 | else: 173 | raise TooManyRequests 174 | return request 175 | -------------------------------------------------------------------------------- /pymailinator/tests/test_wrapper.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import os 3 | from collections import defaultdict 4 | 5 | from pymailinator import wrapper 6 | 7 | 8 | class MockResponse(object): 9 | def __init__(self, url=None, content=None, status=None, filename=None): 10 | self.content = content 11 | self.status = status 12 | self.url = url 13 | if filename and not content: 14 | with open(filename, 'r') as f: 15 | self.content = f.read() 16 | 17 | def getcode(self): 18 | return self.status 19 | 20 | def read(self): 21 | return self.content 22 | 23 | 24 | def get_empty_mailbox(url): 25 | return MockResponse(status=200, content='{"messages":[]}', url=url) 26 | 27 | 28 | def get_bad_api_token(url): 29 | return MockResponse(status=400, content='', url=url) 30 | 31 | 32 | def get_missing_token(url): 33 | return MockResponse(status=404, url=url) 34 | 35 | 36 | def get_missing_message(url): 37 | return MockResponse(status=404, url=url) 38 | 39 | 40 | def get_mailbox(url): 41 | filename = os.path.join(os.path.dirname(__file__), 'json', 'mailbox.json') 42 | return MockResponse(status=200, filename=filename, url=url) 43 | 44 | 45 | def get_message(url): 46 | filename = os.path.join(os.path.dirname(__file__), 'json', 'message.json') 47 | return MockResponse(status=200, filename=filename, url=url) 48 | 49 | 50 | class MockServerTooManyRequests(object): 51 | def __init__(self, count, response): 52 | self.count = count 53 | self.response = response 54 | 55 | def get(self, url): 56 | if self.count > 0: 57 | self.count -= 1 58 | return MockResponse(status=429, content='', url=url) 59 | return self.response(url) 60 | 61 | 62 | # noinspection PyArgumentList 63 | class TestWrapper(unittest.TestCase): 64 | def test_empty_mailbox(self): 65 | wrapper.urlopen = get_empty_mailbox 66 | inbox = wrapper.Inbox('123') 67 | inbox.get() 68 | self.assertEqual(inbox.count(), 0) 69 | self.assertEqual(inbox.messages, []) 70 | 71 | def test_bad_token(self): 72 | wrapper.urlopen = get_bad_api_token 73 | inbox = wrapper.Inbox('123') 74 | with self.assertRaises(wrapper.InvalidToken): 75 | inbox.get() 76 | 77 | def test_missing_token(self): 78 | with self.assertRaises(TypeError): 79 | wrapper.Inbox() 80 | 81 | def test_invalid_token(self): 82 | wrapper.urlopen = get_missing_token 83 | inbox = wrapper.Inbox(False) 84 | with self.assertRaises(wrapper.MissingToken): 85 | inbox.get() 86 | 87 | def test_successful_mailbox(self, mailbox=get_mailbox): 88 | wrapper.urlopen = mailbox 89 | inbox = wrapper.Inbox('123') 90 | inbox.get() 91 | self.assertGreater(inbox.count(), 0) 92 | 93 | def test_successful_message(self, mailbox=get_mailbox, message=get_message): 94 | wrapper.urlopen = mailbox 95 | inbox = wrapper.Inbox('123') 96 | inbox.get() 97 | wrapper.urlopen = message 98 | message = inbox.messages[0] 99 | message.get_message() 100 | self.assertNotEqual(message.body, '') 101 | 102 | def test_missing_message(self, mailbox=get_mailbox): 103 | wrapper.urlopen = mailbox 104 | inbox = wrapper.Inbox('123') 105 | inbox.get() 106 | wrapper.urlopen = get_missing_message 107 | with self.assertRaises(wrapper.MessageNotFound): 108 | inbox.messages[0].get_message() 109 | 110 | def test_view_subjects(self, mailbox=get_mailbox): 111 | wrapper.urlopen = mailbox 112 | inbox = wrapper.Inbox('123') 113 | inbox.get() 114 | self.assertEqual(type(inbox.view_subjects()), list) 115 | self.assertGreater(len(inbox.view_subjects()), 0) 116 | 117 | def test_view_message_ids(self, mailbox=get_mailbox): 118 | wrapper.urlopen = mailbox 119 | inbox = wrapper.Inbox('123') 120 | inbox.get() 121 | self.assertEqual(type(inbox.view_message_ids()), list) 122 | self.assertGreater(len(inbox.view_message_ids()), 0) 123 | 124 | def test_get_message_by_subject(self, mailbox=get_mailbox): 125 | wrapper.urlopen = mailbox 126 | inbox = wrapper.Inbox('123') 127 | inbox.get() 128 | message = inbox.get_message_by_subject('Want to cheat? ') 129 | self.assertEqual(message.subject, 'Want to cheat? ') 130 | 131 | def test_get_message_by_id(self, mailbox=get_mailbox): 132 | wrapper.urlopen = mailbox 133 | inbox = wrapper.Inbox('123') 134 | inbox.get() 135 | message = inbox.get_message_by_id('1418740612-3134545-m8r-rmtci4') 136 | self.assertEqual(message.id, '1418740612-3134545-m8r-rmtci4') 137 | 138 | def test_filter_inbox(self, mailbox=get_mailbox): 139 | wrapper.urlopen = mailbox 140 | inbox = wrapper.Inbox('123') 141 | inbox.get() 142 | filtered_me = inbox.filter('to', 'me') 143 | self.assertEqual(len(filtered_me), 0) 144 | filtered = inbox.filter('to', 'm8r-rmtci4@mailinator.com') 145 | self.assertEqual(len(filtered), 2) 146 | 147 | def test_other_mailbox(self, mailbox=get_mailbox): 148 | wrapper.urlopen = mailbox 149 | inbox = wrapper.Inbox('123') 150 | inbox.get('other') 151 | self.assertGreater(inbox.count(), 0) 152 | 153 | def tests_too_many_request(self): 154 | wrapper.TIME_SLEEP_TOO_MANY_REQUESTS = .1 155 | for test_func in ( 156 | 'test_successful_mailbox', 157 | 'test_successful_message', 158 | 'test_missing_message', 159 | 'test_view_subjects', 160 | 'test_view_message_ids', 161 | 'test_get_message_by_subject', 162 | 'test_get_message_by_id', 163 | 'test_filter_inbox', 164 | 'test_other_mailbox', 165 | ): 166 | mock_server = MockServerTooManyRequests(2, get_mailbox) 167 | getattr(self, test_func)(mailbox=mock_server.get) 168 | 169 | for test_func in ( 170 | 'test_successful_message', 171 | ): 172 | mock_server = MockServerTooManyRequests(2, get_message) 173 | getattr(self, test_func)(message=mock_server.get) 174 | 175 | with self.assertRaises(wrapper.TooManyRequests): 176 | wrapper.NUMBER_ATTEMPTS_TOO_MANY_REQUESTS = 3 177 | mock_server = MockServerTooManyRequests(4, get_mailbox) 178 | self.test_successful_mailbox(mailbox=mock_server.get) 179 | 180 | def test_origfrom_field(self): 181 | mock_data = defaultdict(str) 182 | mock_data['origfrom'] = 'Mock Name ' 183 | mock_message = wrapper.Message(None, mock_data) 184 | 185 | # Backwards compatible message objects. 186 | self.assertEqual(mock_message.fromshort, 'Mock Name') 187 | self.assertEqual(mock_message.fromfull, 'mock@domain.com') 188 | 189 | 190 | if __name__ == '__main__': 191 | unittest.main() 192 | -------------------------------------------------------------------------------- /pymailinator/tests/json/mailbox.json: -------------------------------------------------------------------------------- 1 | {"messages":[{"seconds_ago":7014,"id":"1418740612-3134545-m8r-rmtci4","to":"m8r-rmtci4@mailinator.com","time":1418740612046,"subject":"Don't forget, your 20% offer is waiting. Powerful gifts everyone needs.","fromfull":"bedbathandbeyond@bedbathandbeyond.com","from":"Bed Bath & Beyond","ip":"208.94.20.197"},{"seconds_ago":2793,"id":"1418744833-3493785-m8r-rmtci4","to":"m8r-rmtci4@mailinator.com","time":1418744833950,"subject":"Last day 50% off buy 1 get 2 free, Christmas sale highlights: partition management tool, migrate system, upgrade hard disk and more.","fromfull":"newsletter@easeus.com","from":"EaseUS","ip":"38.126.54.126"},{"seconds_ago":1493,"id":"1418746133-3617520-bob","to":"bob@mailinator.com","time":1418746133314,"subject":"Seasonal tabletop | Gifts for him | Jewelry for the holidays","fromfull":"editor@members.jossandmain.com","from":"Joss & Main","ip":"67.208.181.204"},{"seconds_ago":1475,"id":"1418746151-3619333-bob","to":"bob@mailinator.com","time":1418746151079,"subject":"Get $15 for telling your friends about Nomad","fromfull":"chloe@hellonomad.com","from":"Chlo\u00EB Ferrari ","ip":"167.89.11.11"},{"seconds_ago":1469,"id":"1418746157-3619992-bob","to":"bob@safetymail.info","time":1418746157895,"subject":"Want to cheat? ","fromfull":"internationalization@posses.info","from":"New Message Alert","ip":"173.232.157.140"},{"seconds_ago":1460,"id":"1418746166-3620847-bob","to":"bob@mailinator.com","time":1418746166538,"subject":"Herr Kranz, 1 Jahr lang Live Fu\u00DFball der 1. und 2. Bundesliga mit SPORT1 wartet auf Sie","fromfull":"info@teilnahme.data-hoster.com","from":"SPORT1 Gewinnspiel","ip":"193.169.180.88"},{"seconds_ago":1452,"id":"1418746174-3621580-bob","to":"bob@safetymail.info","time":1418746174461,"subject":"My Local Crime Alert (12\/16\/2014 11:09am) - by SpotCrime","fromfull":"system@spotcrime.com","from":"spotcrime.com","ip":"174.129.243.60"},{"seconds_ago":1450,"id":"1418746176-3621796-bob","to":"bob@mailinator.com","time":1418746176862,"subject":"Someone you follow is chaturbating","fromfull":"no-reply@chaturbate.com","from":"Chaturbate","ip":"184.106.240.218"},{"seconds_ago":1395,"id":"1418746231-3627097-bob","to":"bob@mailinator.com","time":1418746231796,"subject":"ppv.gaydemon.tv Hunk-of-the-day","fromfull":"newsletters@sureflixmail.com","from":"ppv.gaydemon.tv Hunk-of-the-day","ip":"208.66.207.11"},{"seconds_ago":1354,"id":"1418746272-3632030-bob","to":"bob@mailinator.com","time":1418746272826,"subject":"\u5168\u80FD\u578B\u8F66\u95F4\u4E3B\u4EFBbob","fromfull":"kpq@rfv.com","from":"\u8BF7\u8F6C\u9700\u6C42\u90E8\u95E8","ip":"123.64.199.232"},{"seconds_ago":1352,"id":"1418746274-3632254-bob","to":"bob@mailinator.net","time":1418746274747,"subject":"What Wine Has the Texture of Silk?","fromfull":"dailysip@bottlenotes.com","from":"The Daily Sip by Bottlenotes","ip":"192.64.236.228"},{"seconds_ago":1349,"id":"1418746277-3632640-bob","to":"bob@mailismagic.com","time":1418746277628,"subject":"FW: Streaming the best ex girlfriends porn videos. We have the Best SEXtube on the net giving you the HOTTEST ex girlfriends porn movies & tube videos","fromfull":"tonya_williamson@zeta-web.com","from":"Tonya Williamson","ip":"216.70.105.155"},{"seconds_ago":1321,"id":"1418746305-3635486-bob","to":"bob@monumentmail.com","time":1418746305957,"subject":"Maintaining Productivity When Data Growth Runs Rampant","fromfull":"itnewseditor@raxco.com","from":"Editor, Raxco Blog","ip":"173.193.132.182"},{"seconds_ago":1275,"id":"1418746351-3640006-bob","to":"bob@monumentmail.com","time":1418746351718,"subject":"Hickey Freeman \u2022 Gucci Watches \u2022 John Hardy \u2022 Current\/Elliott \u2022 The Winter-Bedding Sale \u2022 Marc New York Performance \u2022 Cuisinart ","fromfull":"RueLaLa@mail.ruelala.com","from":"Rue La La","ip":"208.94.22.169"},{"seconds_ago":1275,"id":"1418746351-3640017-bob","to":"bob@monumentmail.com","time":1418746351718,"subject":"PetsPage.com, CrispMe.com, HunterTreeStands.com + 5 ecommerce techniques for connecting with the most valuable demographic\u2026","fromfull":"news@flippa.com","from":"Flippa Team","ip":"204.75.142.195"},{"seconds_ago":1242,"id":"1418746384-3642874-bob","to":"bob@chammy.info","time":1418746384027,"subject":"FW: Come visit the world's largest ex girlfriends porn video tube. We have thousands of ex girlfriends porn movies available to watch and download","fromfull":"claudine_guerrero@asktheturk.org","from":"Claudine Guerrero","ip":"85.95.249.17"},{"seconds_ago":1224,"id":"1418746402-3644622-bob","to":"bob@safetymail.info","time":1418746402753,"subject":"Don't leave - Expenses Behind For Your family.","fromfull":"insured@whowho250.com","from":"YourInsurance","ip":"185.37.144.184"},{"seconds_ago":1207,"id":"1418746419-3646032-bob","to":"bob@suremail.info","time":1418746419176,"subject":"Apply for Term Life insurance Coverage.","fromfull":"insured@whowho250.com","from":"InsuranceProtection","ip":"185.37.144.184"},{"seconds_ago":1165,"id":"1418746461-3650312-bob","to":"bob@mailinator.com","time":1418746461684,"subject":"137 Dollar Check for MDLinx online survey available R6411","fromfull":"jessica.mccann@research.mdlinx.com","from":"Jessica McCann","ip":"208.45.141.145"},{"seconds_ago":1161,"id":"1418746465-3650657-bob","to":"bob@sogetthis.com","time":1418746465045,"subject":"How to avoid client failure","fromfull":"ruben@bidsketch.com","from":"Ruben | Bidsketch","ip":"205.201.139.3"},{"seconds_ago":1148,"id":"1418746478-3651765-bob","to":"bob@binkmail.com","time":1418746478489,"subject":"Good day!","fromfull":"relub@bagkafgkabgkjecgf.optinemail.biz","from":"Elliee","ip":"217.167.127.85"},{"seconds_ago":1123,"id":"1418746503-3654209-bob","to":"bob@mailinator.com","time":1418746503936,"subject":"How a BEGINNER Makes $110,450","fromfull":"support@autotrafficscalper.com","from":"Profit Team","ip":"70.47.43.116"},{"seconds_ago":1105,"id":"1418746521-3656155-bob","to":"bob@mailinater.com","time":1418746521247,"subject":"RE: Save 50% or more","fromfull":"HumanaTime@mjb042.riskjeans.us","from":"Humana Time","ip":"23.228.96.42"},{"seconds_ago":1094,"id":"1418746532-3657196-bob","to":"bob@mailinator.net","time":1418746532291,"subject":"RE: Save 50% or more","fromfull":"HumanaTime@mjb042.riskjeans.us","from":"Humana Time","ip":"23.228.96.42"},{"seconds_ago":1063,"id":"1418746563-3660089-bob","to":"bob@bobmail.info","time":1418746563020,"subject":"Mobile experts make 2015 predictions","fromfull":"SearchNetworking@lists.techtarget.com","from":"Mobile Digest","ip":"206.19.49.33"},{"seconds_ago":1048,"id":"1418746578-3661668-bob","to":"bob@suremail.info","time":1418746578899,"subject":"URGENT: Rebate Processors Needed: $200 - $1000+ Per DAY... Start NOW!","fromfull":"fedoraforum.org@googlemail.com","from":"FedoraForum.org","ip":"50.97.98.227"},{"seconds_ago":983,"id":"1418746643-3668398-bob","to":"bob@mailinator.com","time":1418746643976,"subject":"Gifts that won't break the bank...","fromfull":"mail@enews.uniqlo-usa.com","from":"Uniqlo USA","ip":"199.7.206.228"},{"seconds_ago":974,"id":"1418746652-3669194-bob","to":"bob@notmailinator.com","time":1418746652618,"subject":"Are you in search of love, care and intimate relation?","fromfull":"info@NORTHBOUND.nl","from":"Kseniya","ip":"212.241.11.9"},{"seconds_ago":922,"id":"1418746704-3675402-bob","to":"bob@mailinator.com","time":1418746704755,"subject":"Bill Clinton: Chokehold Victim 'Didn't Deserve to Die'","fromfull":"newsmax@reply.newsmax.com","from":"Newsmax.com","ip":"8.28.94.204"},{"seconds_ago":897,"id":"1418746729-3678269-bob","to":"bob@reallymymail.com","time":1418746729722,"subject":"We pay Parts, Labor & Roadside","fromfull":"malin@neverexpirewarranty.net","from":"Auto Repairs","ip":"170.130.93.23"},{"seconds_ago":881,"id":"1418746745-3679668-bob","to":"bob@notmailinator.com","time":1418746745086,"subject":"Are you one?","fromfull":"info@SONAR.com","from":"Victoria","ip":"176.194.236.216"},{"seconds_ago":877,"id":"1418746749-3680122-bob","to":"bob@thisisnotmyrealemail.com","time":1418746749888,"subject":"'Looking for sleep apnea management options?'","fromfull":"SleepApnea@philsubmarket.com","from":"Sleep Apnea","ip":"192.99.155.201"},{"seconds_ago":774,"id":"1418746852-3689762-bob","to":"bob@bobmail.info","time":1418746852638,"subject":"Cemex (CX) Final Update - Auto Trade","fromfull":"subscriber-alerts@optionmonster.com","from":"Open Order Pro Alert","ip":"63.208.77.238"},{"seconds_ago":743,"id":"1418746883-3692757-bob","to":"bob@safetymail.info","time":1418746883048,"subject":"KitchenAid, Cuisinart & More","fromfull":"e@yipit.com","from":"Yipit Salt Lake City","ip":"167.89.33.179"},{"seconds_ago":729,"id":"1418746897-3694154-bob","to":"bob@mailinator.com","time":1418746897453,"subject":"Save 82% on a MacBook Pro","fromfull":"no-reply@alm.com","from":"no-reply@alm.com","ip":"204.14.32.140"},{"seconds_ago":719,"id":"1418746907-3695192-bob","to":"bob@notmailinator.com","time":1418746907797,"subject":"Hi! I am Natalia from Russia.","fromfull":"info@LEAN.com","from":"Natalia","ip":"212.79.121.254"},{"seconds_ago":674,"id":"1418746952-3699051-bob","to":"bob@sogetthis.com","time":1418746952450,"subject":"Entdecken Sie unser Weihnachtsgeschenk f\u00FCr Sie!","fromfull":"newsletter@news.olympus-imaging.eu","from":"Olympus Newsletter","ip":"91.241.73.43"},{"seconds_ago":604,"id":"1418747022-3705058-bob","to":"bob@mailismagic.com","time":1418747022212,"subject":"you find her","fromfull":"CSC_Dates_May14O@pharres.com","from":"Anastasia AdPartner","ip":"173.243.124.221"},{"seconds_ago":569,"id":"1418747057-3708603-bob","to":"bob@mailinator.com","time":1418747057745,"subject":"\u2744 Today: Jazz Sale, Fender Amp Giveaway + New Course & Christmas Song Lesson \u2744","fromfull":"info@truefire.com","from":"TrueFire","ip":"208.38.147.6"},{"seconds_ago":560,"id":"1418747066-3709510-bob","to":"bob@mailinator.com","time":1418747066388,"subject":"\u677E\u4E0B\u667A\u6167\u751F\u4EA7\u7BA1\u7406bob","fromfull":"bqaq@iuoh.com","from":"\u8BF7\u8F6C\u76F8\u5173\u90E8\u95E8","ip":"14.124.227.118"},{"seconds_ago":436,"id":"1418747190-3721598-bob","to":"bob@notmailinator.com","time":1418747190572,"subject":"magical girl!","fromfull":"mepab@gbkcjkazkacjdbe.laderecissa.in","from":"Ashleigh","ip":"72.38.10.138"},{"seconds_ago":365,"id":"1418747261-3727042-bob","to":"bob@mailinator.com","time":1418747261279,"subject":"Latino Vote Split Will Help GOP - Lunch Alert!","fromfull":"subscribers@dickmorris.com","from":"Dick Morris Reports","ip":"69.25.195.62"},{"seconds_ago":326,"id":"1418747300-3729705-bob","to":"bob@bobmail.info","time":1418747300171,"subject":"Are you making money from YouTube? Find out how.","fromfull":"tcnews@tunecore.com","from":"TuneCore","ip":"198.37.146.108"},{"seconds_ago":322,"id":"1418747304-3730054-bob","to":"bob@mailinator.com","time":1418747304492,"subject":"Medicines here","fromfull":"patrick@mattsonfamily.com","from":"bob@mailinator.com","ip":"190.48.237.153"},{"seconds_ago":252,"id":"1418747374-3735570-bob","to":"bob@mailinator.net","time":1418747374605,"subject":"12 Color LED Candles. BuyOne\/GetOne 12-16.14.","fromfull":"12colorledcandles@dazehost-r3.link","from":"12 Color LED Candle Offer","ip":"94.102.48.8"},{"seconds_ago":190,"id":"1418747436-3740488-bob","to":"bob@mailinator.net","time":1418747436264,"subject":"Re: Your Score's May Have Changed on 12\/16\/14 16583530","fromfull":"creditalert@score714reports.rocks","from":"Credit Alert","ip":"107.158.235.115"},{"seconds_ago":168,"id":"1418747458-3742736-bob","to":"bob@mailinator.com","time":1418747458490,"subject":"[Nokia3650Dreamland] Emma Stoned fucked hard HD Porn Video","fromfull":"Nokia3650Dreamland@yahoogroups.com","from":"Haruki Mizuno nokia3650dreamland@nicealert.com [Nokia3650Dreamland]","ip":"98.138.215.22"},{"seconds_ago":129,"id":"1418747497-3747615-bob","to":"bob@safetymail.info","time":1418747497947,"subject":"No insurance? Inexpensive Dentists are out there! ","fromfull":"Caesarizes@one1year.eu","from":"Dentist Snap Partner","ip":"23.239.133.189"},{"seconds_ago":126,"id":"1418747500-3747938-bob","to":"bob@mailinator.com","time":1418747500900,"subject":"Airfare Alert: U.S. flight sale on American from $151 r\/t!","fromfull":"memberservice@tripalertz.com","from":"TripAlertz","ip":"74.63.231.206"},{"seconds_ago":94,"id":"1418747532-3751298-bob","to":"bob@sogetthis.com","time":1418747532409,"subject":"Ercetion med$!Hgih qualiity","fromfull":"fzambrano@aguasantofagasta.cl","from":"bob@sogetthis.com","ip":"222.254.164.175"},{"seconds_ago":15,"id":"1418747611-3758053-bob","to":"bob@mailinator.com","time":1418747611660,"subject":"[Rebeccawildfans] Emma Stoned fucked hard HD Porn Video","fromfull":"Rebeccawildfans@yahoogroups.com","from":"Haruki Mizuno rebeccawildfans@nicealert.com [Rebeccawildfans]","ip":"98.136.219.57"},{"seconds_ago":13,"id":"1418747613-3758163-bob","to":"bob@safetymail.info","time":1418747613580,"subject":"No insurance? Inexpensive Dentists are out there! ","fromfull":"bins@one1year.eu","from":"Dentist Snap Partner","ip":"23.239.133.189"}]} -------------------------------------------------------------------------------- /pymailinator/tests/json/message.json: -------------------------------------------------------------------------------- 1 | {"apiInboxFetchesLeft":638,"apiEmailFetchesLeft":10,"data":{"headers":{"content-type":"text\/html; charset=UTF-8","to":"m8r-rmtci4@mailinator.com","domainkey-signature":"q=dns; a=rsa-sha1; c=nofws;\r\n\ts=ED2007-11; d=bedbathandbeyond.com;\r\n\th=Received:Date:Content-Type:Content-Transfer-Encoding:MIME-Version:From:Reply-To:To:Subject:Message-Id:X-Mail-From:X-Match:X-RCPT-To:X-Mailer:List-Unsubscribe;\r\n\tb=AF\/6p+FwYBJRgqntyRtfphC0zWNHwLG62KQPcu0BvnFuZKqSTCn61KiquCL0rscr\r\n\tqSYoJUqRdCZxjJkbAN4RDqmo41UEeToIyfLf7XrEBEOcACWStmj355eGKJ4f2DJ0","subject":"Don't forget, your 20% offer is waiting.\r\n Powerful gifts everyone needs.","content-transfer-encoding":"7bit","mime-version":"1.0","x-received-time":"1418740612046","received":"from [127.0.0.1] ([127.0.0.1:59772])\r\n\tby bm1-10.bo3.e-dialog.com (envelope-from )\r\n\t(ecelerity 2.2.2.45 r(34222M)) with ECSTREAM\r\n\tid 1A\/B5-24037-38340945; Tue, 16 Dec 2014 09:36:51 -0500","list-unsubscribe":", ","dkim-signature":"v=1; a=rsa-sha1; d=bedbathandbeyond.com; s=ED-DKIM-V3; c=relaxed\/relaxed;\r\n\tq=dns\/txt; i=@bedbathandbeyond.com; t=1418740611;\r\n\th=From;\r\n\tbh=xlTx59B\/4r7Xk2yx3F94sXgb1AY=;\r\n\tb=f7kRncygDIgDaAtRvd0fL6nxcYkGISgW5EMdyVrX4yWuyJBwHTrsy5RPw+Ej3bN6\r\n\tZegMlbdXZHAIF8uE249s1BwMqlbIk5q0Zg6wq43VgDQ7qJvJNo5aSq4G8ZR48XU6\r\n\tFS\/k0XFS0Y7WRe+wab8uozJYoP9vg7\/EkkPhrJHbdlw=;","from":"\"Bed Bath & Beyond\" ","x-match":"bedbathbeyond.bounce.ed10.net","date":"Tue, 16 Dec 2014 09:36:51 -0500","sender":"ITZD75-ULVQ50-LQQWO1-TPH04UV-JIPBEKE-H-M2-20141216-633d23c8dd933@bedbathbeyond.bounce.ed10.net","x-connecting-ip":"208.94.20.197","x-mailer":"EDMAIL R6.00.02","x-rcpt-to":"m8r-rmtci4@mailinator.com","reply-to":"\"Bed Bath & Beyond\" "},"seconds_ago":6965,"id":"1418740612-3134545-m8r-rmtci4","to":"m8r-rmtci4@mailinator.com","time":1418740612046,"subject":"Don't forget, your 20% offer is waiting. Powerful gifts everyone needs.","fromfull":"bedbathandbeyond@bedbathandbeyond.com","parts":[{"headers":{"content-type":"text\/html; charset=UTF-8","content-transfer-encoding":"7bit"},"body":"\r\n\r\n\r\n\r\n\r\n\r\n