├── .gitignore ├── LICENSE ├── README.md ├── music.jpg └── vktotg.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # Jupyter Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # SageMath parsed files 80 | *.sage.py 81 | 82 | # dotenv 83 | .env 84 | 85 | # virtualenv 86 | .venv 87 | venv/ 88 | ENV/ 89 | 90 | # Spyder project settings 91 | .spyderproject 92 | .spyproject 93 | 94 | # Rope project settings 95 | .ropeproject 96 | 97 | # mkdocs documentation 98 | /site 99 | 100 | # mypy 101 | .mypy_cache/ 102 | 103 | # cache 104 | MusicSaver.session 105 | vk_config.v2.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 HaCk3D 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vktotg 2 | Sends all your vk.com music to telegram channel 3 | 4 | # Screenshot 5 | 6 | 7 | # Requirements 8 | 9 | `sudo pip install bs4 vk_api telethon requests` 10 | 11 | # Run 12 | 13 | `python vktotg.py` 14 | 15 | Downloads all your music locally to folder `Music ` 16 | 17 | You can provide specific `user_id` as argument when launching from command line to dowload audio of this user. Just be sure that you have access to them 18 | 19 | Examle: 20 | `python vktotg.py 28452705` 21 | -------------------------------------------------------------------------------- /music.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/HaCk3Dq/vktotg/f989247bd1947966a603f8c52113de4d015b8938/music.jpg -------------------------------------------------------------------------------- /vktotg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import os 4 | import sys 5 | import ssl 6 | import shutil 7 | import requests 8 | import webbrowser 9 | import vk_api 10 | from getpass import getpass 11 | from vk_api.audio import VkAudio 12 | from telethon.sync import TelegramClient 13 | from telethon.errors import SessionPasswordNeededError 14 | from telethon.tl.functions.channels import CreateChannelRequest, EditPhotoRequest 15 | from telethon.tl.types import ( 16 | DocumentAttributeAudio, DocumentAttributeFilename, Channel, InputChannel, InputChatUploadedPhoto 17 | ) 18 | 19 | folderName = 'Music ' 20 | channelName = 'VKMusic' 21 | 22 | 23 | def captcha_handler(captcha): 24 | url = captcha.get_url() 25 | key = input("Enter captcha code {0}: ".format(url)).strip() 26 | webbrowser.open(url, new=2, autoraise=True) 27 | return captcha.try_again(key) 28 | 29 | 30 | def auth_handler(): 31 | key = input("Enter authentication code: ") 32 | remember_device = True 33 | return key, remember_device 34 | 35 | 36 | def reporthook(sent_bytes, total): 37 | sys.stdout.write( 38 | f'\r{round(sent_bytes / total * 100, 1)}% {round(sent_bytes / 1024 / 1024, 1)}/{round(total / 1024 / 1024, 1)} MB' 39 | ) 40 | sys.stdout.flush() 41 | 42 | 43 | def save(url, filename): 44 | response = requests.get(url, stream=True) 45 | with open(filename, 'wb') as out_file: 46 | shutil.copyfileobj(response.raw, out_file) 47 | del response 48 | 49 | 50 | def send_file(client, entity, file, dur, title, artist, caption): 51 | attr_dict = { 52 | DocumentAttributeFilename: 53 | DocumentAttributeFilename(caption), 54 | DocumentAttributeAudio: 55 | DocumentAttributeAudio(int(dur), title=title, performer=artist) 56 | } 57 | client.send_file( 58 | entity, file, 59 | progress_callback=reporthook, 60 | attributes=list(attr_dict.values()) 61 | ) 62 | 63 | 64 | def auth_vk(): 65 | print('First, log in to vk.com') 66 | 67 | vk_session = vk_api.VkApi( 68 | input('Enter login: '), 69 | getpass('Enter password: '), 70 | captcha_handler=captcha_handler, 71 | auth_handler=auth_handler 72 | ) 73 | 74 | try: 75 | vk_session.auth() 76 | except vk_api.AuthError as error_msg: 77 | print(error_msg) 78 | exit() 79 | 80 | user_id = vk_session.get_api().users.get()[0]['id'] 81 | try: 82 | user_id = str(sys.argv[1]) 83 | print(f'Downloading audios from {user_id}') 84 | except: 85 | pass 86 | if not os.path.exists(folderName + str(user_id)): 87 | os.mkdir(folderName + str(user_id)) 88 | return VkAudio(vk_session), user_id 89 | 90 | 91 | def auth_tg(): 92 | print('\nNow, log in to telegram') 93 | client = TelegramClient('MusicSaver', 184825, '7fd2ade01360bdd6cbc1de0f0120092c').start() 94 | client.connect() 95 | if not client.is_user_authorized(): 96 | try: 97 | client.sign_in(phone=input('Enter full phone number: ')) 98 | client.sign_in(code=input('Enter code that you received: ')) 99 | except SessionPasswordNeededError: 100 | client.sign_in(password=input('Two step verification is enabled. Please enter your password: ')) 101 | return client 102 | 103 | def get_last_readable_track_in_channel(telegramMessagesList, vkAudioList): 104 | offset = 0 105 | local_progress = 0 106 | iterator = len(telegramMessagesList) 107 | while iterator > 0: 108 | last_file = telegramMessagesList[offset].document 109 | if last_file: 110 | lastAudioName = last_file.attributes[1].file_name 111 | else: 112 | offset = offset + 1 113 | iterator = iterator - 1 114 | continue 115 | try: 116 | local_progress = vkAudioList.index(lastAudioName) 117 | except ValueError: 118 | print("Vk audio list does not contain value: " + lastAudioName) 119 | if local_progress > 0: 120 | return local_progress+offset 121 | else: 122 | offset = offset + 1 123 | iterator = iterator - 1 124 | return 0 125 | 126 | def main(): 127 | store_local = input('Do you want to leave the local files? [N/y] ') in ['y', 'yes'] 128 | 129 | vkaudio, user_id = auth_vk() 130 | with auth_tg() as client: 131 | 132 | VKMusicChannel = None 133 | last_file = None 134 | progress = 0 135 | 136 | dialogs = client.get_dialogs(limit=None) 137 | for chat in dialogs: 138 | if type(chat.entity) == Channel and chat.title == channelName: 139 | VKMusicChannel = chat 140 | 141 | if VKMusicChannel is None: 142 | VKMusicChannel = client(CreateChannelRequest( 143 | title=channelName, about='made with https://github.com/HaCk3Dq/vktotg')).chats[0] 144 | client(EditPhotoRequest( 145 | InputChannel(VKMusicChannel.id, VKMusicChannel.access_hash), InputChatUploadedPhoto( 146 | client.upload_file('music.jpg')) 147 | )) 148 | client.delete_messages(client.get_entity(VKMusicChannel), 2) 149 | else: 150 | last_file = client.get_messages(VKMusicChannel, limit=None)[0].document 151 | if last_file: 152 | last_file = last_file.attributes[1].file_name 153 | 154 | audios = vkaudio.get(user_id) 155 | total = len(audios) 156 | if last_file: 157 | vkAudioList = [track['artist'] + ' - ' + track['title'] for track in audios[::-1]] 158 | # try get offset by last saved track 159 | try: 160 | progress = vkAudioList.index(last_file) 161 | except ValueError: 162 | print("Vk audio list does not contain value: " + last_file) 163 | if progress: 164 | progress = progress + 1 165 | else: 166 | # try get offset from last readable track 167 | telegramMessagesList = client.get_messages(VKMusicChannel, limit=None) 168 | progress = get_last_readable_track_in_channel(telegramMessagesList, vkAudioList)+1 169 | if progress == total: 170 | print(f'[Done] Found {progress}/{total} tracks') 171 | exit() 172 | else: 173 | print(f'\nFound {progress}/{total} tracks, continue downloading...') 174 | print() 175 | 176 | progress += 1 177 | for i, track in enumerate(audios[::-1]): 178 | if progress and i < progress - 1: 179 | continue 180 | filename = track['artist'] + ' - ' + track['title'] 181 | escaped_filename = filename.replace("/", "_") 182 | escaped_filename = ''.join(e for e in escaped_filename if e.isalnum() or e == '_' or e == ' ') 183 | file_path = folderName + str(user_id) + '/' + escaped_filename + '.mp3' 184 | 185 | print(f'Downloading [{i + 1}/{total}]') 186 | print(filename) 187 | try: 188 | save(track['url'], file_path) 189 | except ssl.SSLError: 190 | print(f'SSL ERROR: {escaped_filename}, launching again...') 191 | try: 192 | save(track['url'], escaped_filename + '.mp3') 193 | except: 194 | print(f'Failed to save track after 2 tries [{i + 1}/{total}]') 195 | exit() 196 | 197 | print('\nUploading...') 198 | sys.stdout.flush() 199 | send_file( 200 | client, client.get_entity(VKMusicChannel), 201 | file_path, 202 | track['duration'], track['title'], 203 | track['artist'], filename 204 | ) 205 | 206 | if not store_local: 207 | os.remove(file_path) 208 | print() 209 | sys.stdout.flush() 210 | 211 | 212 | if __name__ == '__main__': 213 | main() 214 | print('[Done] Finished uploading all the tracks') 215 | --------------------------------------------------------------------------------