Skip to content

Commit 54e7c63

Browse files
author
Archie
committed
Merge remote-tracking branch 'origin/master' into dev
2 parents dda1d69 + 0b49807 commit 54e7c63

File tree

15 files changed

+510
-410
lines changed

15 files changed

+510
-410
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,5 @@ venv/
44
__pycache__
55
*.spec
66
build
7-
dist
7+
dist
8+
*.zip

README.md

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -9,24 +9,38 @@ _От автора:_
99
Я не несу ответственности за ваши номера (если они улетят в бан или еще чего по-хуже).
1010
Скрипт не делает ничего запрещенного, а лишь "нажимает кнопочки, которые вы могли
1111
бы нажать в их приложении, потратив в 10 (да-да) раз больше времени".
12+
1213
Скрипт не ворует данные и не каким образом не взаимодействует ни с чем-либо кроме
1314
оффициального (но не публичного) API Tele2.
15+
Простое консольное прилоджение сделано лишь для того, чтобы у пользователя не
16+
возникало сомнений в честности действий, выполняемых скриптом.
17+
Вы можете посмотреть исходники и подредактировать код, если в том есть неоходимость.
18+
Подобные приложения с веб-интерфейсом и т.п вынуждены хранить ваши авторизационные токены на своих серверах,
19+
что может привести с потере доступа к аккаунту.
20+
Т.к существует возможность бесконечного продления токена без необходимости
21+
повторного ввода СМС-кода, ввели смс в типичное мошенническое веб-приложение
22+
1 раз и злоумышленник получит доступ к вашему аккаунту НАВСЕГДА (да).
23+
24+
1425
Если возникли какие-либо проблемы в работе - откройте обсуждение на вкладке Issues и,
15-
если, нашли решение, предложите автору, буду очень признателен. Так как скрипт очень
16-
чувствителен к региону, из которого он запускается, проблемы возникнуть могут, и не факт,
17-
что конкретно ваша проблема вообще решаема (например, старый тариф без поддержки
18-
Маркета и т.п). Спасибо за понимание)
26+
если, нашли решение, приложите свои комментарии. Так как скрипт очень
27+
чувствителен к региону, из которого он запускается, могут возникнуть проблемы в работе, и не факт,
28+
что конкретно ваша проблема решаема (например, старый тариф без поддержки
29+
Маркета и т.п).
30+
31+
Спасибо за понимание)
1932

2033

2134
## Features
2235
* Quick market listing of your Tele2 data
2336
* Bumping up lots that haven't been sold
2437
* Asynchronous queries to _Tele2 API_ allow to perform multiple actions simultaneously
25-
* __[v1.3] Lots auto re-listing after some user-provided interval__
38+
* Lots auto re-listing after some user-provided interval
39+
* **[v2.0+] Token auto-refreshing without extra SMS inputs**
2640

2741

28-
## Demo (v1.0.0 on Windows 10)
29-
![imgur demo gif](https://i.imgur.com/xKTTRDS.gif)
42+
## Demo (v2.0.0 on Windows 10 Terminal)
43+
![imgur demo gif](https://i.imgur.com/Ciy2tp3.gif)
3044

3145

3246
## Installation (basic - *Windows x64 only [x32 work in progress]*)
@@ -62,9 +76,9 @@ and download **zip**-archive (tele2-profit@\<version\>.zip)
6276

6377

6478
## Usage
65-
1. Login with running `python auth.py` (or auth.exe if built version). Access token works 4 hours, then it needs to be updated.
66-
**note: access-token saves on your PC _only_, in `./config.json` file**
67-
2. Run `python main.py` (or main.exe if built version) and select action.
79+
Run `python main.py` (or main.exe if built version), login, and select action.
80+
81+
**note: Access-token saves on your PC _only_, in `./config.json` file**
6882

6983
### FYI: Current Tele2 market lot requirements
7084

@@ -84,12 +98,8 @@ For example: `60 80` - 60 minutes (or gb) will be listed for 80 rub.
8498
For example: `68` - 68 minutes (or gb) will be listed with **minimum** possible price *(in this case - 55 rub if minutes, 1020 rub if gb)*.
8599
When done leave input field empty (just hit enter) and you will jump to the next part.
86100

87-
88-
## TODO
89-
* Use refresh token to support longer auth persistence (currently 4 hours)
90-
91101
## Donations
92-
Special thanks to my donators:
102+
Huge thanks to my donors:
93103
* Кирилл - 100 rub
94104
* Alex - 300 rub
95105
* Никита - 300 rub

_build.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import os
2+
import shutil
3+
import zipfile
4+
5+
from _version import __version__
6+
7+
8+
def remove_dir_if_exists(path):
9+
if os.path.isdir(path):
10+
shutil.rmtree(path)
11+
12+
13+
def remove_file_if_exists(path):
14+
if os.path.isfile(path):
15+
os.remove(path)
16+
17+
18+
def build_zip():
19+
os.system('pyinstaller --onefile --paths venv main.py')
20+
with zipfile.ZipFile(f'tele2-profit@{__version__}.zip', 'w',
21+
zipfile.ZIP_DEFLATED) as zip_file:
22+
zip_file.write('dist/main.exe', 'main.exe')
23+
24+
25+
if __name__ == '__main__':
26+
remove_file_if_exists(f'tele2-profit@{__version__}.zip')
27+
build_zip()
28+
remove_dir_if_exists('dist')
29+
remove_dir_if_exists('build')
30+
remove_dir_if_exists('__pycache__')
31+
remove_file_if_exists('main.spec')

_version.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = '2.0.0'

app/account.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
from colorama import Fore
2+
3+
from app.api import Tele2Api
4+
5+
6+
async def print_balance(api):
7+
balance = await api.get_balance()
8+
print(Fore.YELLOW + 'Balance: ' + Fore.MAGENTA + f'{balance} rub.')
9+
10+
11+
async def print_rests(api: Tele2Api):
12+
print('Checking your rests...')
13+
print(
14+
Fore.CYAN + 'note: only plan (not market-bought ones nor transferred)'
15+
' rests can be sold')
16+
rests = await api.get_rests()
17+
print(Fore.WHITE + 'You have')
18+
print(Fore.YELLOW + f'\t{rests["voice"]} min')
19+
print(Fore.GREEN + f'\t{rests["data"]} gb')
20+
print(Fore.WHITE + '\t\tavailable to sell.')
21+
return rests

api.py renamed to app/api.py

Lines changed: 53 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,81 @@
1-
from aiohttp import ClientSession
2-
from time import sleep
1+
from aiohttp import ClientSession, ContentTypeError, ClientResponse
2+
3+
from app.constants import SECURITY_BYPASS_HEADERS, MAIN_API, SMS_VALIDATION_API, \
4+
TOKEN_API
5+
6+
7+
async def _try_parse_to_json(response: ClientResponse):
8+
try:
9+
response_json = await response.json()
10+
return response_json
11+
except ContentTypeError:
12+
return None
13+
14+
15+
def _is_ok(response: ClientResponse):
16+
return response.status == 200
317

418

519
class Tele2Api:
620
session: ClientSession
721
access_token: str
822

9-
def __init__(self, phone_number: str, access_token: str = ''):
10-
base_api = f'https://my.tele2.ru/api/subscribers/{phone_number}'
23+
def __init__(self, phone_number: str, access_token: str = '',
24+
refresh_token: str = ''):
25+
base_api = MAIN_API + phone_number
1126
self.market_api = f'{base_api}/exchange/lots/created'
1227
self.rests_api = f'{base_api}/rests'
1328
self.profile_api = f'{base_api}/profile'
1429
self.balance_api = f'{base_api}/balance'
15-
self.sms_post_url = f'https://my.tele2.ru/api/validation/number/{phone_number}'
16-
self.auth_post_url = 'https://my.tele2.ru/auth/realms/tele2-b2c/protocol/openid-connect/token'
30+
self.sms_post_url = SMS_VALIDATION_API + phone_number
31+
self.auth_post_url = TOKEN_API
1732
self.access_token = access_token
33+
self.refresh_token = refresh_token
1834

1935
async def __aenter__(self):
2036
self.session = ClientSession(headers={
2137
'Authorization': f'Bearer {self.access_token}',
22-
'Connection': 'keep-alive',
23-
'Tele2-User-Agent': '"mytele2-app/3.17.0"; "unknown"; "Android/9"; "Build/12998710"',
24-
'X-API-Version': '1',
25-
'User-Agent': 'okhttp/4.2.0'
38+
**SECURITY_BYPASS_HEADERS
2639
})
2740
return self
2841

2942
async def __aexit__(self, *args):
3043
await self.session.close()
3144

45+
async def check_if_authorized(self):
46+
response = await self.session.get(self.profile_api)
47+
return _is_ok(response)
48+
3249
async def send_sms_code(self):
3350
await self.session.post(self.sms_post_url, json={'sender': 'Tele2'})
3451

35-
async def get_access_token(self, phone_number: str, sms_code: str):
52+
async def auth_with_code(self, phone_number: str, sms_code: str):
3653
response = await self.session.post(self.auth_post_url, data={
3754
'client_id': 'digital-suite-web-app',
3855
'grant_type': 'password',
3956
'username': phone_number,
4057
'password': sms_code,
4158
'password_type': 'sms_code'
4259
})
43-
return (await response.json())['access_token']
60+
if _is_ok(response):
61+
response_json = await _try_parse_to_json(response)
62+
return response_json['access_token'], response_json['refresh_token']
4463

45-
async def check_auth_code(self):
46-
response = await self.session.get(self.profile_api)
47-
return response.status
64+
async def refresh_tokens(self, refresh_token: str):
65+
response = await self.session.post(self.auth_post_url, data={
66+
'client_id': 'digital-suite-web-app',
67+
'grant_type': 'refresh_token',
68+
'refresh_token': refresh_token,
69+
})
70+
if _is_ok(response):
71+
response_json = await _try_parse_to_json(response)
72+
return response_json['access_token'], response_json['refresh_token']
4873

4974
async def get_balance(self):
5075
response = await self.session.get(self.balance_api)
51-
return (await response.json())['data']['value']
76+
if _is_ok(response):
77+
response_json = await _try_parse_to_json(response)
78+
return response_json['data']['value']
5279

5380
async def sell_lot(self, lot):
5481
response = await self.session.put(self.market_api, json={
@@ -57,27 +84,25 @@ async def sell_lot(self, lot):
5784
'volume': {'value': lot['amount'],
5885
'uom': 'min' if lot['lot_type'] == 'voice' else 'gb'}
5986
})
60-
# TODO: вместо ожидания 0.5 поставить что-то более полезное,
61-
# одновременно не получается выставлять больше (((
62-
sleep(0.5)
63-
return await response.json()
87+
88+
return await _try_parse_to_json(response)
6489

6590
async def return_lot(self, lot_id):
6691
response = await self.session.delete(f'{self.market_api}/{lot_id}')
67-
# TODO: вместо ожидания 0.5 поставить что-то более полезное,
68-
# одновременно не получается выставлять больше (((
69-
sleep(0.5)
70-
return await response.json()
92+
return await _try_parse_to_json(response)
7193

7294
async def get_active_lots(self):
7395
response = await self.session.get(self.market_api)
74-
lots = list((await response.json())['data'])
75-
active_lots = [a for a in lots if a['status'] == 'active']
76-
return active_lots
96+
if _is_ok(response):
97+
response_json = await _try_parse_to_json(response)
98+
lots = list(response_json['data'])
99+
active_lots = [a for a in lots if a['status'] == 'active']
100+
return active_lots
77101

78102
async def get_rests(self):
79103
response = await self.session.get(self.rests_api)
80-
rests = list((await response.json())['data']['rests'])
104+
response_json = await _try_parse_to_json(response)
105+
rests = list(response_json['data']['rests'])
81106
sellable = [a for a in rests if a['type'] == 'tariff']
82107
return {
83108
'data': int(

app/auth.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import json
2+
import re
3+
from colorama import Fore
4+
5+
from app.api import Tele2Api
6+
7+
8+
def input_phone_number():
9+
while True:
10+
user_input = input(
11+
Fore.CYAN +
12+
f'Input your Tele2 phone number (leave empty to cancel): '
13+
)
14+
if re.match(r'^7\d{10}?$', user_input):
15+
return user_input
16+
elif user_input == '':
17+
exit()
18+
else:
19+
print(Fore.RED + 'Incorrect number format. Correct - 7xxxxxxxxxx')
20+
21+
22+
async def get_tokens(api: Tele2Api, phone_number: str):
23+
await api.send_sms_code()
24+
while True:
25+
try:
26+
sms_code = input(Fore.LIGHTCYAN_EX + 'SMS code: ')
27+
return await api.auth_with_code(phone_number, sms_code)
28+
except KeyError:
29+
print(Fore.RED + 'Invalid SMS-сode. Try again')
30+
31+
32+
def write_config_to_file(phone_number: str, access_token: str,
33+
refresh_token: str):
34+
with open('config.json', 'w') as f:
35+
json.dump({
36+
'x-p': phone_number,
37+
'x-at': access_token,
38+
'x-rt': refresh_token,
39+
}, f, indent=2)

app/constants.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
SECURITY_BYPASS_HEADERS = {
2+
'Connection': 'keep-alive',
3+
'Tele2-User-Agent': '"mytele2-app/3.17.0"; "unknown"; "Android/9"; "Build/12998710"',
4+
'X-API-Version': '1',
5+
'User-Agent': 'okhttp/4.2.0'
6+
}
7+
8+
MAIN_API = 'https://my.tele2.ru/api/subscribers/'
9+
SMS_VALIDATION_API = 'https://my.tele2.ru/api/validation/number/'
10+
TOKEN_API = 'https://my.tele2.ru/auth/realms/tele2-b2c/protocol/openid-connect/token/'

0 commit comments

Comments
 (0)