├── src
├── __init__.py
├── ryanair.py
├── utils.py
├── seats.py
└── RyanairApi.py
├── .gitignore
├── seats.png
├── screenshot.png
├── ryanair-seats.py
└── README.md
/src/__init__.py:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | *.pyc
2 | setup.py
3 |
--------------------------------------------------------------------------------
/seats.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santoru/ryanair-seats/HEAD/seats.png
--------------------------------------------------------------------------------
/screenshot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/santoru/ryanair-seats/HEAD/screenshot.png
--------------------------------------------------------------------------------
/ryanair-seats.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | from src import ryanair
4 |
5 |
6 | if __name__ == '__main__':
7 | ryanair.main()
8 |
--------------------------------------------------------------------------------
/src/ryanair.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import getpass
4 |
5 | from RyanairApi import RyanairApi
6 |
7 |
8 | def main():
9 | try:
10 | print "Welcome!"
11 |
12 | username = raw_input("Please enter username: ")
13 | password = getpass.getpass("Please enter password:")
14 |
15 | ryanairApi = RyanairApi(username, password)
16 | ryanairApi.getAllSeats()
17 | exit(0)
18 | except KeyboardInterrupt:
19 | print "\nQuitting.."
20 | exit(0)
21 |
--------------------------------------------------------------------------------
/src/utils.py:
--------------------------------------------------------------------------------
1 | # Headers
2 | CLIENT_VERSION = '3.27.1'
3 | CLIENT_OS = 'ios'
4 | CLIENT_V_OS = '10.1.1'
5 | USER_AGENT = 'RyanairApp/3.27.1 (iPhone; iOS 10.1.1; Scale/2.00)'
6 |
7 | # Max passengers supported at the moment
8 | MAXPASSENGERS = 1
9 |
10 |
11 | def getHeaders():
12 | """ Basic headers to set-up a request"""
13 | headers = {
14 | 'Accept': '*/*',
15 | 'Connection': 'close',
16 | 'client-version': CLIENT_VERSION,
17 | 'client': CLIENT_OS,
18 | 'Accept-Language': 'en-GB;q=1',
19 | 'User-Agent': USER_AGENT,
20 | 'client-os': CLIENT_V_OS
21 | }
22 | return headers
23 |
24 |
25 | class bclr:
26 | HEADER = '\033[95m'
27 | OKBLUE = '\033[94m'
28 | OKGREEN = '\033[92m'
29 | WARNING = '\033[93m'
30 | FAIL = '\033[91m'
31 | ENDC = '\033[0m'
32 | BOLD = '\033[1m'
33 | UNDERLINE = '\033[4m'
34 |
35 |
36 | def parseDate(string):
37 | """Parse that string."""
38 | # TODO: Give some format based on timezone, please :D
39 | date = string.split('T')[0]
40 | time = string.split('T')[1]
41 | datef = date.split('-')
42 | datep = datef[2] + '/' + datef[1] + '/' + datef[0]
43 | timef = time.split(':')
44 | timep = timef[0] + ':' + timef[1]
45 | return bclr.WARNING + \
46 | datep + bclr.ENDC + ' - ' + bclr.WARNING + timep + bclr.ENDC
47 |
--------------------------------------------------------------------------------
/src/seats.py:
--------------------------------------------------------------------------------
1 | # Seats are allocated with this order..
2 | rows = [
3 | '33',
4 | '20',
5 | '19',
6 | '22',
7 | '15',
8 | '24',
9 | '12',
10 | '26',
11 | '10',
12 | '28',
13 | '08',
14 | '30',
15 | '18',
16 | '21',
17 | '14',
18 | '23',
19 | '11',
20 | '25',
21 | '09',
22 | '27',
23 | '29',
24 | '07',
25 | '06',
26 | '05',
27 | '04',
28 | '03',
29 | '02',
30 | '01',
31 | '17',
32 | '16',
33 | '31',
34 | '32'
35 | ]
36 |
37 | # ...from A to F
38 | order = [
39 | 'A',
40 | 'B',
41 | 'C',
42 | 'D',
43 | 'E',
44 | 'F'
45 | ]
46 |
47 | # First row only has 3 seats
48 | nonexistent = [
49 | '01D',
50 | '01E',
51 | '01F',
52 | ]
53 |
54 | # Larger seats
55 | morespace = [
56 | '01A',
57 | '01B',
58 | '01C',
59 | '02D',
60 | '02E',
61 | '02F',
62 | ]
63 |
64 |
65 | def getFirstFree(unavailable):
66 | """ Return the seat that will be allocated next
67 | according to the seats already allocated (unavailable). """
68 |
69 | # FIXME: The row are separated in two groups (ABC + DEF)
70 | # If the first group is empty but the second is not, the single place
71 | # is allocated on the second group, instead of on the first.
72 |
73 | for row in rows:
74 | for place in order:
75 | seat = str(row) + place
76 | if seat not in unavailable and seat not in nonexistent:
77 | return seat
78 | return 'N/D'
79 |
80 |
81 | def seatInfo(seat):
82 | """Give some informations about a seat."""
83 |
84 | info = ''
85 | if 'A' in seat:
86 | info += 'Close to the left window'
87 | if 'F' in seat:
88 | info += 'Close to the right window'
89 | if 'C' in seat or 'D' in seat:
90 | info += 'Middle seat'
91 | if seat in morespace:
92 | if info == '':
93 | info += 'More space for legs'
94 | else:
95 | info += ' and more space for legs'
96 | if '16' in seat or '17' in seat:
97 | if info == '':
98 | info += 'More space for legs, emergency exit in the middle'
99 | else:
100 | info += ' and more space for legs, emergency exit in the middle'
101 | if info != '':
102 | return '(' + info + ')'
103 | return info
104 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Ryanair seats prediction
2 | ### A tool to predict which will be your free seat
3 | ##### And a trick to get that damn window-close seat! :P
4 |
5 | This tool will try to predict which seat you will get for free if you will do the check-in just after running the tool (If nobody else will do check-in after you run the script but before you do the check-in).
6 |
7 | ### UPDATE 24/05/2017
8 | I just realized that Ryanair seems to have changed the allocation algorithm. The tool is no more reliable then..
9 | If somebody wants to update the algorithm, just send a pull requests or write to me :) We can work together on it.
10 |
11 | ### USAGE
12 | It's not that hard :P
13 | You will just need to install *requests*
14 | ```
15 | pip install requests
16 | ```
17 | ...and then
18 | ```
19 | ./ryanair-seats.py
20 | ```
21 |
22 | And then log-in.
23 |
24 |
25 |
26 |
27 |
28 | ### TO DO
29 | - Currently the tool works only for one person, I need more investigation to understand how seats are allocated for more than one person.
30 | - Even when predicting one seat, the tool can't be 100% reliable: there are "hard" situations that are currently not checked by the tool.
31 |
32 |
33 | ### FAQ
34 |
35 | #### How it works?
36 | Ryanair let customer buy a seat anytime for the flight they booked. Thanks to this, it's possible to get the list of the seats already reserved. With some analysis on the "Allocation Algorithm" it was possible to understand how the allocation works: Seats are allocated from the middle of the plane, to the ends (both top and down together). Seats are allocated (generally) from A to F. If there is enough space to allocate all passengers of a booking, then the current row is filled. If passenger exceeded the current row space, then the system decide to put them in a new row.
37 | Emergency exit are "filled" when the other seats are allocated.
38 |
39 |
40 |
41 |
42 |
43 |
44 | #### What can I do with this tool?
45 | This tool will try to predict which seat you will get for free if you will do the check-in just after running the tool :P (If nobody else will do check-in after you run the script but before you do the check-in).
46 | Or at least it will try, it's pretty accurate but not perfect yet :D
47 |
48 | #### So, can I choose my seat?
49 | Yes, you can! You just have to pay few euro and get your seat :)
50 | This tool doesn't want to provide a way to get free seats, but just to show how Ryanair seats are allocated.
51 |
52 | ---
53 |
--------------------------------------------------------------------------------
/src/RyanairApi.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 |
3 | import requests
4 | import json
5 | import seats
6 | import utils
7 | import logging
8 |
9 |
10 | class RyanairApi(object):
11 | logging.basicConfig(filename='/dev/null', level=logging.NOTSET)
12 |
13 | basicApiUrl = 'https://api.ryanair.com/'
14 | bookingApiUrl = 'https://nativeapps.ryanair.com/'
15 |
16 | customerId = ""
17 | authToken = ""
18 | sessionToken = ""
19 |
20 | def __init__(self, username, password):
21 | """Get the auth token and the customer ID"""
22 | url = 'userprofile/rest/api/v1/login'
23 | headers = utils.getHeaders()
24 | headers.update({
25 | 'Content-Type': 'application/x-www-form-urlencoded',
26 | 'Content-Lenght': '53'
27 | })
28 | params = {
29 | 'password': password,
30 | 'username': username
31 | }
32 | response = requests.post(
33 | self.basicApiUrl + url,
34 | headers=headers,
35 | params=params)
36 |
37 | if response.status_code != 200:
38 | logging.error('Impossible to login')
39 | print('Login error.')
40 | exit(1)
41 | data = response.json()
42 |
43 | self.customerId = data['customerId']
44 | self.authToken = data['token']
45 | logging.debug('User logged in')
46 |
47 | def getProfile(self):
48 | """Return user profile, printing a description"""
49 | url = 'userprofile/rest/api/v1/secure/users/' + \
50 | self.customerId + \
51 | '/profile/full'
52 | headers = utils.getHeaders()
53 | headers.update({
54 | 'X-Auth-Token': self.authToken
55 | })
56 | response = requests.get(
57 | self.basicApiUrl + url,
58 | headers=headers)
59 | if response.status_code != 200:
60 | logging.error('Impossible to fetch profile')
61 | print('Impossible to fetch profile!')
62 | exit(1)
63 | user = response.json()
64 | print 'So....'
65 | print 'You\'re ' + user['firstName'] + ' ' + user['lastName']
66 | print 'Your nationality is ' + user['nationality']
67 | print 'Phone = ' + user['countryCallingCode'] + user['phoneNumber']
68 | print '--------------------------'
69 | return user
70 |
71 | def getUpcomingBookings(self):
72 | """Get the list of all upcoming bookings"""
73 | url = 'userprofile/rest/api/v1/secure/users/' + \
74 | self.customerId + \
75 | '/bookings/upcoming/all'
76 | headers = utils.getHeaders()
77 | headers.update({
78 | 'X-Auth-Token': self.authToken
79 | })
80 | response = requests.get(self.basicApiUrl + url, headers=headers)
81 | if response.status_code != 200:
82 | logging.error('Impossible to fetch upcoming bookings')
83 | print('Impossible to fetch upcoming bookings!')
84 | exit(1)
85 | return response.json()
86 |
87 | def getAllSeats(self):
88 | bookings = self.getUpcomingBookings()
89 | print 'You have %s travel booked!' % bookings['count']
90 | # Display every booking
91 | for travel in bookings['Bookings']:
92 | self.infoBooking(travel['BookingId'])
93 | return
94 |
95 | def infoBooking(self, bookingId):
96 | """ Get and display information about one booking:
97 | - Print informations about the flights on the booking
98 | - Print informations about the passengers of the flights
99 | - Print a guess of the seat it will be allocated to the passengers
100 | """
101 | url = 'v4/Booking'
102 | headers = utils.getHeaders()
103 | headers.update({
104 | 'Content-Type': 'application/json',
105 | 'Content-Length': '52',
106 | 'X-Auth-Token': self.authToken
107 | })
108 | data = {
109 | 'surrogateId': self.customerId,
110 | 'bookingId': bookingId
111 | }
112 | response = requests.post(
113 | self.bookingApiUrl + url,
114 | headers=headers,
115 | data=json.dumps(data))
116 |
117 | if response.status_code != 200:
118 | logging.error('Impossible to fetch bookings')
119 | print('Impossible to fetch bookings!')
120 | exit(1)
121 | result = response.json()
122 |
123 | # Save the received sessionToken
124 | self.sessionToken = response.headers['X-Session-Token']
125 | # Get informations about seats for this booking
126 | seatsList = self.infoSeats()
127 | if seatsList is False:
128 | logging.error('Impossible to fetch seats')
129 | print('Impossible to fetch seats!')
130 | exit(1)
131 |
132 | # Flight info
133 | print 'Prenotation number: %s (Status of the flight: %s)' % (
134 | utils.bclr.OKBLUE + result['info']['pnr'] + utils.bclr.ENDC,
135 | utils.bclr.OKGREEN + result['info']['status'] + utils.bclr.ENDC)
136 | numberofSeats = len(result['passengers'])
137 | print 'Number of passengers: %s' % numberofSeats
138 | c = 0
139 | # Print information about passengers
140 | for passenger in result['passengers']:
141 | c += 1
142 | print ' %i: %s %s' % (
143 | c,
144 | utils.bclr.HEADER + passenger['name']['first'],
145 | passenger['name']['last'] + utils.bclr.ENDC)
146 | c = 0
147 | for journey in result['journeys']:
148 |
149 | print ' [%s] %s -> %s (%s)' % (
150 | utils.bclr.OKBLUE + journey['flt'] + utils.bclr.ENDC,
151 | utils.bclr.WARNING + journey['orig'] + utils.bclr.ENDC,
152 | utils.bclr.WARNING + journey['dest'] + utils.bclr.ENDC,
153 | utils.parseDate(journey['depart']))
154 |
155 | if 'reasonCode' in journey['changeInfo'] and \
156 | journey['changeInfo']['reasonCode'] == 'PassengerCheckedIn':
157 | print ' Already checked-in'
158 | else:
159 | allocation = self.getSeat(
160 | seatsList[c]['unavailableSeats'],
161 | numberofSeats)
162 | if (allocation['status'] == 'error'):
163 | print allocation['message']
164 | else:
165 | print ' If you do check-in now, you will have \
166 | seat %s %s' % (
167 | utils.bclr.OKGREEN +
168 | allocation['seat'] +
169 | utils.bclr.ENDC,
170 | seats.seatInfo(allocation['seat']))
171 | c += 1
172 | return
173 |
174 | def infoSeats(self):
175 | """ Show informations about seats for the booking
176 | - Return also the list of unavailable (already allocated) seats
177 | """
178 | url = 'v4/Seat'
179 | headers = utils.getHeaders()
180 | headers.update({
181 | 'X-Session-Token': self.sessionToken
182 | })
183 | response = requests.get(self.bookingApiUrl + url, headers=headers)
184 | if response.status_code == 200:
185 | return response.json()
186 | return False
187 |
188 | def getSeat(self, unavailable, numberofSeats):
189 | """ Return the seat that will be allocated during check-in"""
190 | response = dict()
191 | if utils.MAXPASSENGERS > numberofSeats:
192 | response['status'] = 'error'
193 | response['message'] = ' We are sorry but we currently \
194 | don\'t support seat prediction for flights \
195 | with %s passengers.' % numberofSeats
196 | else:
197 | response['status'] = 'ok'
198 | response['seat'] = seats.getFirstFree(unavailable)
199 |
200 | return response
201 |
--------------------------------------------------------------------------------