├── .README.MD_images ├── 03508508.png ├── 0655ae9e.png ├── 31851b79.png ├── 7f1ab67c.png ├── a2d1fdf3.png ├── abd90457.png ├── b69421f5.png ├── c082551f.png ├── e815d712.png └── f71ed087.png ├── README.MD ├── bot.py ├── config.json └── utils.py /.README.MD_images/03508508.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/03508508.png -------------------------------------------------------------------------------- /.README.MD_images/0655ae9e.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/0655ae9e.png -------------------------------------------------------------------------------- /.README.MD_images/31851b79.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/31851b79.png -------------------------------------------------------------------------------- /.README.MD_images/7f1ab67c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/7f1ab67c.png -------------------------------------------------------------------------------- /.README.MD_images/a2d1fdf3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/a2d1fdf3.png -------------------------------------------------------------------------------- /.README.MD_images/abd90457.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/abd90457.png -------------------------------------------------------------------------------- /.README.MD_images/b69421f5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/b69421f5.png -------------------------------------------------------------------------------- /.README.MD_images/c082551f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/c082551f.png -------------------------------------------------------------------------------- /.README.MD_images/e815d712.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/e815d712.png -------------------------------------------------------------------------------- /.README.MD_images/f71ed087.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/realByg/azure-helper-bot/2014901682e43ef0754e1a2ac30031f1b052ccc7/.README.MD_images/f71ed087.png -------------------------------------------------------------------------------- /README.MD: -------------------------------------------------------------------------------- 1 | # 🏺 `azure-helper-bot` 2 | 3 | ![](.README.MD_images/a2d1fdf3.png) 4 | 5 | ### 🍾 用 telegram bot 管理 Azure 虚拟机 6 | 7 | ### 🔖 主要功能 8 | 9 | + 管理 Azure 账号,管理账号订阅 10 | + 创建和管理 Azure 虚拟机,更换虚拟机 IP 11 | 12 | ### 📦 部分截图 13 | 14 | 1. 账号添加和管理 15 | 16 | ![](.README.MD_images/e815d712.png) 17 | 18 | ![](.README.MD_images/7f1ab67c.png) 19 | 20 | ![](.README.MD_images/abd90457.png) 21 | 22 | 2. 实例创建和管理 23 | 24 | ![](.README.MD_images/b69421f5.png) 25 | 26 | ![](.README.MD_images/03508508.png) 27 | 28 | ![](.README.MD_images/f71ed087.png) 29 | 30 | ![](.README.MD_images/c082551f.png) 31 | 32 | ![](.README.MD_images/0655ae9e.png) 33 | 34 | ### 🌌 用法 35 | 36 | 1. 在 [@botfather](https://t.me/botfather) 新建一个 bot,记下 `bot token` 37 | 38 | 2. 选择相应的系统平台,下载 release 中的一键包和 `config.json` 39 | 40 | 3. 按照你的需求编辑 `config.json`,`*` 的部分需要去掉或编辑 41 | 42 | ``` 43 | { 44 | "BOT": { 45 | "NAME": "Azure 小助手", 46 | "TOKEN": "*bot 的 token", 47 | "ADMINS": [*你的 telegram 数字 id] 48 | }, 49 | "VM": { 50 | "DISK_SIZE_DEFAULT": *默认磁盘大小, 51 | "LOCATIONS": { 52 | "*创建虚拟机时显示的地区": "*可根据需求编辑", 53 | "美国中": "centralus", 54 | "美国东": "eastus", 55 | "美国西": "westus", 56 | "东亚": "eastasia", 57 | "东南亚": "southeastasia", 58 | "日本东": "japaneast", 59 | "日本西": "japanwest", 60 | "韩国中": "koreacentral", 61 | "韩国南": "koreasouth" 62 | }, 63 | "SIZES": { 64 | "*创建虚拟机时显示的虚拟机型号": "*可根据需求编辑", 65 | "B1s": "Standard_B1s", 66 | "B2s": "Standard_B2s", 67 | "B4ms": "Standard_B4ms", 68 | "D2s_v3": "Standard_D2s_v3", 69 | "D4s_v3": "Standard_D4s_v3", 70 | "F1s": "Standard_F1s", 71 | "F2": "Standard_F2", 72 | "F2s": "Standard_F2s", 73 | "F4s": "Standard_F4s" 74 | }, 75 | "OS_INFOS": { 76 | "*创建虚拟机时显示的虚拟机镜像": "*可根据需求编辑", 77 | "Ubuntu 16.04": { 78 | "os": "Ubuntu 16.04", 79 | "image_reference": { 80 | "publisher": "Canonical", 81 | "offer": "UbuntuServer", 82 | "sku": "16.04-LTS", 83 | "version": "latest" 84 | } 85 | }, 86 | "Ubuntu 18.04": { 87 | "os": "Ubuntu 18.04", 88 | "image_reference": { 89 | "publisher": "Canonical", 90 | "offer": "UbuntuServer", 91 | "sku": "18.04-LTS", 92 | "version": "latest" 93 | } 94 | }, 95 | "CentOs 7.5": { 96 | "os": "CentOs 7.5", 97 | "image_reference": { 98 | "publisher": "OpenLogic", 99 | "offer": "CentOS", 100 | "sku": "7.5", 101 | "version": "latest" 102 | } 103 | }, 104 | "CentOS 8.2": { 105 | "os": "CentOS 8.2", 106 | "image_reference": { 107 | "publisher": "OpenLogic", 108 | "offer": "CentOS", 109 | "sku": "8_2", 110 | "version": "latest" 111 | } 112 | }, 113 | "Debian 10": { 114 | "os": "Debian 10", 115 | "image_reference": { 116 | "publisher": "debian", 117 | "offer": "debian-10", 118 | "sku": "10", 119 | "version": "latest" 120 | } 121 | }, 122 | "Debian 9": { 123 | "os": "Debian 9", 124 | "image_reference": { 125 | "publisher": "credativ", 126 | "offer": "Debian", 127 | "sku": "9", 128 | "version": "latest" 129 | } 130 | } 131 | } 132 | } 133 | } 134 | ``` 135 | 136 | 4. 给予运行权限并运行 137 | 138 | ``` 139 | chmod +x az* 140 | /az* 141 | ``` 142 | 143 | ![](.README.MD_images/31851b79.png) 144 | 145 | ### 🐛 使用问题 146 | 147 | + 不是通过 bot 创建的虚拟机可能更换 IP 等操作失败 148 | + 不支持多个订阅的账号 149 | + 由于使用设备码 `(device code)` 授权,token 有效期只有 3 个月,之后需要重新添加 150 | 151 | *** 152 | 153 | + bot 年久失修,满足日常简单需求,不再维护 154 | + 新版网页版 Azure 小助手已经发布 155 | 156 | -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | import re 2 | import json 3 | import logging 4 | import traceback 5 | from time import sleep 6 | 7 | import telebot 8 | from telebot import types 9 | 10 | from utils import user_dict, UserDict, RefreshToken 11 | 12 | from az.az_token import Token 13 | from az.az_sub import Subscription 14 | from az.az_rg import ResourceGroup 15 | from az.az_nic import Network 16 | from az.az_vm import VirtualMachine 17 | from az.az_config import VM_LOCATIONS, VM_SIZES, VM_OS_INFOS 18 | 19 | config = json.load(open('config.json', 'r', encoding='utf-8')) 20 | BOT_TOKEN = config['BOT']['TOKEN'] 21 | BOT_ADMINS = config['BOT']['ADMINS'] 22 | BOT_NAME = config['BOT']['NAME'] 23 | 24 | bot = telebot.TeleBot(BOT_TOKEN) 25 | 26 | 27 | @bot.message_handler(content_types=['text']) 28 | def handle_text(m): 29 | try: 30 | logger.info(m) 31 | 32 | if m.from_user.id not in BOT_ADMINS: 33 | return 34 | 35 | start(m) 36 | except Exception as e: 37 | traceback.print_exc() 38 | handle_exception(e, m) 39 | 40 | 41 | @bot.callback_query_handler(func=lambda call: True) 42 | def handle_callback(call): 43 | try: 44 | logger.info(call) 45 | 46 | if call.from_user.id not in BOT_ADMINS or \ 47 | len(call.data.split(':')) != 3: 48 | return 49 | 50 | action = call.data.split(':')[0] 51 | sub_action = call.data.split(':')[1] 52 | value = call.data.split(':')[2] 53 | 54 | if action == 'aa': 55 | add_account(call) 56 | 57 | elif action == 'ma': 58 | if sub_action == '': 59 | list_accounts(call, '管理账号', action, 'ss') 60 | elif sub_action == 'ss': 61 | show_account_sub(call, value) 62 | elif sub_action == 'rm': 63 | remove_account(call, value) 64 | 65 | elif action == 'cvm': 66 | if sub_action == '': 67 | list_accounts(call, '创建实例', action, 'ce') 68 | elif sub_action == 'ce': 69 | set_refresh_token_list_sub(call, value, create_vm_set_subscription_id_list_size) 70 | elif sub_action == 'cs': 71 | create_vm_set_subscription_id_list_size(call, value) 72 | elif sub_action == 'csize': 73 | create_vm_set_size_list_os(call, value) 74 | elif sub_action == 'cos': 75 | create_vm_set_os_list_location(call, value) 76 | elif sub_action == 'cl': 77 | create_vm_set_location_confirm_create(call, value) 78 | elif sub_action == 'ok': 79 | create_vm(call) 80 | elif sub_action == 'cancel': 81 | edit_cancel(call) 82 | 83 | elif action == 'mvm': 84 | if sub_action == '': 85 | list_accounts(call, '管理实例', action, 'ce') 86 | elif sub_action == 'ce': 87 | set_refresh_token_list_sub(call, value, manage_vm_set_subscription_id_list_vm) 88 | elif sub_action == 'cs': 89 | manage_vm_set_subscription_id_list_vm(call, value) 90 | elif sub_action == 'gvm': 91 | get_vm(call, value) 92 | elif sub_action == 'ip': 93 | change_vm_ip(call, value) 94 | elif sub_action == 'del': 95 | delete_vm(call, value) 96 | 97 | except Exception as e: 98 | traceback.print_exc() 99 | handle_exception(e, call) 100 | 101 | 102 | def handle_exception(e, d): 103 | bot.send_message( 104 | text=f'出错啦\n{e}', 105 | chat_id=d.from_user.id, 106 | parse_mode='HTML' 107 | ) 108 | start(d) 109 | 110 | 111 | def start(m): 112 | user_dict[m.from_user.id] = UserDict() 113 | 114 | markup = types.InlineKeyboardMarkup(row_width=2) 115 | markup.add( 116 | types.InlineKeyboardButton(text='添加账号', callback_data='aa::'), 117 | types.InlineKeyboardButton(text='管理账号', callback_data='ma::'), 118 | types.InlineKeyboardButton(text='创建实例', callback_data='cvm::'), 119 | types.InlineKeyboardButton(text='管理实例', callback_data='mvm::') 120 | ) 121 | bot.send_message( 122 | text=f'欢迎使用 {BOT_NAME}\n' 123 | f'你可以管理 Azure 账号,创建实例,更换 IP 等\n\n' 124 | f'请选择你要进行的操作:', 125 | chat_id=m.from_user.id, 126 | parse_mode='HTML', 127 | reply_markup=markup 128 | ) 129 | 130 | 131 | def edit_cancel(call): 132 | bot.edit_message_text( 133 | text='已取消', 134 | chat_id=call.from_user.id, 135 | message_id=call.message.message_id 136 | ) 137 | 138 | 139 | def add_account(call): 140 | msg = bot.edit_message_text( 141 | text='正在进行 添加账号\n\n' 142 | '请打开 ' 143 | '此链接' 144 | ' ,登录你要添加的 Azure 账号,找到页面上显示的 租户 (Tenant) ID 并回复' 145 | '', 146 | chat_id=call.from_user.id, 147 | message_id=call.message.message_id, 148 | parse_mode='HTML' 149 | ) 150 | bot.register_next_step_handler(msg, add_account_get_tenant_id) 151 | 152 | 153 | def add_account_get_tenant_id(m): 154 | try: 155 | tenant_id = re.search(r'[0-9a-f]{8}(-[0-9a-f]{4}){3}-[0-9a-f]{12}', m.text).group() 156 | 157 | az_token = Token(tenant_id) 158 | user_code_info = az_token.get_user_code_info() 159 | 160 | msg = bot.send_message( 161 | text='租户 (Tenant) ID 获取成功\n\n' 162 | '请打开 ' 163 | f'此链接' 164 | f' ,输入授权码 {user_code_info["user_code"]} ' 165 | f'并登录刚才的 Azure 账号,授权码将在 {user_code_info["expires_in"]} 秒后过期,如已登录请等待刷新...', 166 | chat_id=m.from_user.id, 167 | parse_mode='HTML' 168 | ) 169 | 170 | sleep(5) 171 | 172 | token = az_token.get(user_code_info) 173 | 174 | if token['tenantId'] == tenant_id: 175 | t = f'{token["userId"]} 添加成功' 176 | RefreshToken().save(token["userId"], token['refreshToken']) 177 | start(m) 178 | 179 | else: 180 | t = '账号添加失败,租户 (Tenant) ID 不一致' 181 | 182 | bot.edit_message_text( 183 | text=t, 184 | chat_id=m.from_user.id, 185 | message_id=msg.message_id, 186 | parse_mode='HTML' 187 | ) 188 | 189 | except AttributeError: 190 | bot.send_message( 191 | text='租户 (Tenant) ID 无效', 192 | chat_id=m.from_user.id 193 | ) 194 | except Exception as e: 195 | handle_exception(e, m) 196 | 197 | 198 | def list_accounts(call, action_text, action, sub_cation): 199 | emails = RefreshToken().list() 200 | 201 | markup = types.InlineKeyboardMarkup(row_width=1) 202 | if emails: 203 | for email in emails: 204 | markup.add( 205 | types.InlineKeyboardButton(text=email, callback_data=f'{action}:{sub_cation}:{email}') 206 | ) 207 | 208 | t = f'正在进行 {action_text}\n\n' \ 209 | '请选择 Azure 账号:' 210 | else: 211 | t = '你还没有添加 Azure 账号' 212 | 213 | bot.edit_message_text( 214 | text=t, 215 | chat_id=call.from_user.id, 216 | message_id=call.message.message_id, 217 | reply_markup=markup, 218 | parse_mode='HTML' 219 | ) 220 | 221 | 222 | def show_account_sub(call, email): 223 | t = f'已选择 {email}\n\n' 224 | bot.edit_message_text( 225 | text=t + '获取账号订阅中...', 226 | chat_id=call.from_user.id, 227 | message_id=call.message.message_id, 228 | parse_mode='HTML' 229 | ) 230 | 231 | refresh_token = RefreshToken().get(email) 232 | az_sub = Subscription(refresh_token) 233 | subs = az_sub.list() 234 | for sub in subs: 235 | t += f'订阅名称: {sub["display_name"]}\n' \ 236 | f'订阅状态: {sub["state"]}\n\n' 237 | 238 | markup = types.InlineKeyboardMarkup() 239 | markup.add(types.InlineKeyboardButton(text='移除账号', callback_data='ma:rm:' + email)) 240 | bot.edit_message_text( 241 | text=t, 242 | chat_id=call.from_user.id, 243 | message_id=call.message.message_id, 244 | reply_markup=markup, 245 | parse_mode='HTML' 246 | ) 247 | 248 | 249 | def remove_account(call, email): 250 | RefreshToken().remove(email) 251 | bot.edit_message_text( 252 | text=f'{email} 已移除', 253 | chat_id=call.from_user.id, 254 | message_id=call.message.message_id 255 | ) 256 | 257 | 258 | def set_refresh_token_list_sub(call, email, next_step): 259 | bot.edit_message_text( 260 | text=f'已选择 {email}\n\n' 261 | '获取账号订阅中...', 262 | chat_id=call.from_user.id, 263 | message_id=call.message.message_id, 264 | parse_mode='HTML' 265 | ) 266 | 267 | refresh_token = RefreshToken().get(email) 268 | 269 | user_dict[call.from_user.id].email = email 270 | user_dict[call.from_user.id].refresh_token = refresh_token 271 | 272 | az_sub = Subscription(refresh_token) 273 | subs = az_sub.list() 274 | 275 | if subs[0]['state'] == 'Enabled': 276 | next_step(call, subs[0]['subscription_id']) 277 | return 278 | 279 | t = f'{email} 下没有可用订阅' 280 | markup = types.InlineKeyboardMarkup(row_width=1) 281 | 282 | bot.edit_message_text( 283 | text=t, 284 | chat_id=call.from_user.id, 285 | message_id=call.message.message_id, 286 | reply_markup=markup, 287 | parse_mode='HTML' 288 | ) 289 | 290 | 291 | def create_vm_set_subscription_id_list_size(call, subscription_id): 292 | user_dict[call.from_user.id].subscription_id = subscription_id 293 | 294 | markup = types.InlineKeyboardMarkup(row_width=2) 295 | buttons = [] 296 | for size in VM_SIZES: 297 | buttons.append( 298 | types.InlineKeyboardButton( 299 | text=size, 300 | callback_data='cvm:csize:' + size 301 | ) 302 | ) 303 | markup.add(*buttons) 304 | 305 | bot.edit_message_text( 306 | text='正在进行 创建实例\n\n' 307 | '请选择实例规格:\n', 308 | chat_id=call.from_user.id, 309 | message_id=call.message.message_id, 310 | reply_markup=markup, 311 | parse_mode='HTML' 312 | ) 313 | 314 | 315 | def create_vm_set_size_list_os(call, size): 316 | user_dict[call.from_user.id].size = VM_SIZES[size] 317 | 318 | markup = types.InlineKeyboardMarkup(row_width=2) 319 | buttons = [] 320 | for os in VM_OS_INFOS: 321 | buttons.append( 322 | types.InlineKeyboardButton( 323 | text=os, 324 | callback_data='cvm:cos:' + os 325 | ) 326 | ) 327 | 328 | markup.add(*buttons) 329 | 330 | bot.edit_message_text( 331 | text='正在进行 创建实例\n\n' 332 | f'已选择规格: {size}\n' 333 | '请选择镜像:', 334 | chat_id=call.from_user.id, 335 | message_id=call.message.message_id, 336 | reply_markup=markup, 337 | parse_mode='HTML' 338 | ) 339 | 340 | 341 | def create_vm_set_os_list_location(call, os): 342 | user_dict[call.from_user.id].os_info = VM_OS_INFOS[os] 343 | 344 | markup = types.InlineKeyboardMarkup(row_width=2) 345 | buttons = [] 346 | for location in VM_LOCATIONS: 347 | buttons.append( 348 | types.InlineKeyboardButton( 349 | text=location, 350 | callback_data='cvm:cl:' + location 351 | ) 352 | ) 353 | 354 | markup.add(*buttons) 355 | 356 | bot.edit_message_text( 357 | text='正在进行 创建实例\n\n' 358 | f'已选择规格: {user_dict[call.from_user.id].size}\n' 359 | f'已选择镜像: {os}\n' 360 | '请选择地区:', 361 | chat_id=call.from_user.id, 362 | message_id=call.message.message_id, 363 | reply_markup=markup, 364 | parse_mode='HTML' 365 | ) 366 | 367 | 368 | def create_vm_set_location_confirm_create(call, location): 369 | user_dict[call.from_user.id].location = VM_LOCATIONS[location] 370 | 371 | markup = types.InlineKeyboardMarkup(row_width=2) 372 | markup.add( 373 | types.InlineKeyboardButton(text='确认', callback_data='cvm:ok:'), 374 | types.InlineKeyboardButton(text='取消', callback_data='cvm:cancel:') 375 | ) 376 | bot.edit_message_text( 377 | text='正在进行 创建实例\n\n' 378 | f'已选择规格: {user_dict[call.from_user.id].size}\n' 379 | f'已选择镜像: {user_dict[call.from_user.id].os_info["os"]}\n' 380 | f'已选择地区: {location}\n' 381 | '确认创建实例 ?', 382 | chat_id=call.from_user.id, 383 | message_id=call.message.message_id, 384 | reply_markup=markup, 385 | parse_mode='HTML' 386 | ) 387 | 388 | 389 | def create_vm(call): 390 | bot.edit_message_text( 391 | text='正在进行 创建实例\n\n' 392 | '创建资源组...', 393 | chat_id=call.from_user.id, 394 | message_id=call.message.message_id, 395 | parse_mode='HTML' 396 | ) 397 | 398 | refresh_token = user_dict[call.from_user.id].refresh_token 399 | subscription_id = user_dict[call.from_user.id].subscription_id 400 | size = user_dict[call.from_user.id].size 401 | os_info = user_dict[call.from_user.id].os_info 402 | location = user_dict[call.from_user.id].location 403 | 404 | az_rg = ResourceGroup(refresh_token, subscription_id) 405 | rgn = az_rg.create(location=location) 406 | 407 | bot.edit_message_text( 408 | text='正在进行 创建实例\n\n' 409 | '创建虚拟网络..', 410 | chat_id=call.from_user.id, 411 | message_id=call.message.message_id, 412 | parse_mode='HTML' 413 | ) 414 | az_nic = Network(refresh_token, subscription_id, rgn) 415 | az_nic.create_virtual_network(location) 416 | subnet_id = az_nic.create_subnet() 417 | 418 | bot.edit_message_text( 419 | text='正在进行 创建实例\n\n' 420 | '分配动态 IP...', 421 | chat_id=call.from_user.id, 422 | message_id=call.message.message_id, 423 | parse_mode='HTML' 424 | ) 425 | public_ip_id = az_nic.create_public_ip(location) 426 | 427 | bot.edit_message_text( 428 | text='正在进行 创建实例\n\n' 429 | '创建安全组...', 430 | chat_id=call.from_user.id, 431 | message_id=call.message.message_id, 432 | parse_mode='HTML' 433 | ) 434 | nsg_id = az_nic.create_network_security_group(location) 435 | az_nic.security_rules_allow_all() 436 | 437 | bot.edit_message_text( 438 | text='正在进行 创建实例\n\n' 439 | '更新网络接口...', 440 | chat_id=call.from_user.id, 441 | message_id=call.message.message_id, 442 | parse_mode='HTML' 443 | ) 444 | nic_id = az_nic.create_or_update_network_interface_client(subnet_id, nsg_id, location, public_ip_id) 445 | 446 | bot.edit_message_text( 447 | text='正在进行 创建实例\n\n' 448 | '部署实例中........\n', 449 | chat_id=call.from_user.id, 450 | message_id=call.message.message_id, 451 | parse_mode='HTML' 452 | ) 453 | az_vm = VirtualMachine(refresh_token, subscription_id, rgn) 454 | vm_info = az_vm.create(size, os_info, location, nic_id) 455 | public_ip = az_nic.get_public_ip() 456 | vm_info.update({ 457 | 'ip': public_ip 458 | }) 459 | 460 | bot.edit_message_text( 461 | text='正在进行 创建实例\n\n' 462 | '创建实例完成\n' 463 | f'名称: {vm_info["vm_name"]}\n' 464 | f'IP: {vm_info["ip"]}\n' 465 | f'用户名: {vm_info["username"]}\n' 466 | f'密码: {vm_info["password"]}\n' 467 | f'镜像: {vm_info["os"]}\n' 468 | f'规格: {vm_info["size"]}\n' 469 | f'地区: {vm_info["location"]}\n' 470 | f'创建时间: {vm_info["time"]}\n', 471 | chat_id=call.from_user.id, 472 | message_id=call.message.message_id, 473 | parse_mode='HTML' 474 | ) 475 | 476 | 477 | def manage_vm_set_subscription_id_list_vm(call, subscription_id): 478 | user_dict[call.from_user.id].subscription_id = subscription_id 479 | refresh_token = user_dict[call.from_user.id].refresh_token 480 | email = user_dict[call.from_user.id].email 481 | 482 | bot.edit_message_text( 483 | text=f'已选择 {email}\n\n' 484 | '获取实例中...', 485 | chat_id=call.from_user.id, 486 | message_id=call.message.message_id, 487 | parse_mode='HTML' 488 | ) 489 | 490 | az_vm = VirtualMachine(refresh_token, subscription_id) 491 | vms = az_vm.list() 492 | 493 | t = f'已选择 {email}\n\n' \ 494 | '没有实例' 495 | markup = types.InlineKeyboardMarkup(row_width=1) 496 | if vms: 497 | t = '正在进行 管理实例\n' \ 498 | '请选择实例:' 499 | for vm in vms: 500 | markup.add( 501 | types.InlineKeyboardButton(text=vm['name'], callback_data='mvm:gvm:' + vm['name']) 502 | ) 503 | 504 | bot.edit_message_text( 505 | text=t, 506 | chat_id=call.from_user.id, 507 | message_id=call.message.message_id, 508 | reply_markup=markup, 509 | parse_mode='HTML' 510 | ) 511 | 512 | 513 | def get_vm(call, vm_name): 514 | refresh_token = user_dict[call.from_user.id].refresh_token 515 | subscription_id = user_dict[call.from_user.id].subscription_id 516 | 517 | bot.edit_message_text( 518 | text='正在进行 管理实例\n\n' 519 | f'获取实例 {vm_name} 中...', 520 | chat_id=call.from_user.id, 521 | message_id=call.message.message_id, 522 | parse_mode='HTML' 523 | ) 524 | 525 | vm_info = {} 526 | 527 | az_vm = VirtualMachine(refresh_token, subscription_id, vm_name.replace('-vm', '-rg')) 528 | vms = az_vm.list() 529 | for vm in vms: 530 | if vm['name'] == vm_name: 531 | vm_info = vm 532 | break 533 | 534 | vm_instance_view = az_vm.instance_view() 535 | 536 | az_nic = Network(refresh_token, subscription_id, vm_name.replace('-vm', '-rg')) 537 | public_ip = az_nic.get_public_ip() 538 | 539 | try: 540 | vm_info.update({ 541 | 'os': vm_instance_view.os_name + ' ' + vm_instance_view.os_version, 542 | 'status': vm_instance_view.statuses[1].display_status, 543 | 'ip': public_ip 544 | }) 545 | except TypeError: 546 | bot.edit_message_text( 547 | text='获取实例信息失败, 可能在创建或开机或删除过程中,请稍后再试', 548 | chat_id=call.from_user.id, 549 | message_id=call.message.message_id, 550 | parse_mode='HTML' 551 | ) 552 | return 553 | 554 | markup = types.InlineKeyboardMarkup(row_width=2) 555 | markup.add( 556 | types.InlineKeyboardButton(text='更换 IP', callback_data='mvm:ip:' + vm_name), 557 | types.InlineKeyboardButton(text='删除实例', callback_data='mvm:del:' + vm_name), 558 | ) 559 | bot.edit_message_text( 560 | text='正在进行 管理实例\n\n' 561 | f'实例信息:\n' 562 | f'名称: {vm_info["name"]}\n' 563 | f'IP: {vm_info["ip"]}\n' 564 | f'用户名: {vm_info["os_profile"]["admin_username"]}\n' 565 | f'镜像: {vm_info["os"]}\n' 566 | f'规格: {vm_info["hardware_profile"]["vm_size"]}\n' 567 | f'地区: {vm_info["location"]}\n' 568 | f'状态: {vm_info["status"]}\n\n' 569 | f'请选择操作:', 570 | chat_id=call.from_user.id, 571 | message_id=call.message.message_id, 572 | reply_markup=markup, 573 | parse_mode='HTML' 574 | ) 575 | 576 | 577 | def change_vm_ip(call, vm_name): 578 | refresh_token = user_dict[call.from_user.id].refresh_token 579 | subscription_id = user_dict[call.from_user.id].subscription_id 580 | 581 | bot.edit_message_text( 582 | text='正在进行 管理实例\n\n' 583 | f'更换实例 {vm_name} IP 中...', 584 | chat_id=call.from_user.id, 585 | message_id=call.message.message_id, 586 | parse_mode='HTML' 587 | ) 588 | 589 | az_nic = Network(refresh_token, subscription_id, vm_name.replace('-vm', '-rg')) 590 | nic = az_nic.get_network_interface_client() 591 | public_ip_id = nic.ip_configurations[0].public_ip_address.id 592 | 593 | az_nic.create_or_update_network_interface_client( 594 | subnet_id=nic.ip_configurations[0].subnet.id, 595 | nsg_id=nic.network_security_group.id, 596 | location=nic.location, 597 | public_ip_id=None 598 | ) 599 | 600 | az_nic.create_or_update_network_interface_client( 601 | subnet_id=nic.ip_configurations[0].subnet.id, 602 | nsg_id=nic.network_security_group.id, 603 | location=nic.location, 604 | public_ip_id=public_ip_id 605 | ) 606 | 607 | get_vm(call, vm_name) 608 | 609 | 610 | def delete_vm(call, vm_name): 611 | refresh_token = user_dict[call.from_user.id].refresh_token 612 | subscription_id = user_dict[call.from_user.id].subscription_id 613 | 614 | bot.edit_message_text( 615 | text='正在进行 管理 VM\n\n' 616 | f'删除实例 {vm_name} 及其资源组中...', 617 | chat_id=call.from_user.id, 618 | message_id=call.message.message_id, 619 | parse_mode='HTML' 620 | ) 621 | 622 | az_rg = ResourceGroup(refresh_token, subscription_id) 623 | az_rg.delete(vm_name.replace('-vm', '-rg')) 624 | 625 | bot.edit_message_text( 626 | text='正在进行 管理 VM\n\n' 627 | f'删除实例 {vm_name} 完成', 628 | chat_id=call.from_user.id, 629 | message_id=call.message.message_id, 630 | parse_mode='HTML' 631 | ) 632 | 633 | 634 | logger = telebot.logger 635 | telebot.logger.setLevel(logging.INFO) 636 | 637 | bot.polling(none_stop=True) 638 | -------------------------------------------------------------------------------- /config.json: -------------------------------------------------------------------------------- 1 | { 2 | "BOT": { 3 | "NAME": "Azure 小助手", 4 | "TOKEN": "", 5 | "ADMINS": [] 6 | }, 7 | "VM": { 8 | "DISK_SIZE_DEFAULT": 64, 9 | "LOCATIONS": { 10 | "美国中": "centralus", 11 | "美国东": "eastus", 12 | "美国西": "westus", 13 | "东亚": "eastasia", 14 | "东南亚": "southeastasia", 15 | "日本东": "japaneast", 16 | "日本西": "japanwest", 17 | "韩国中": "koreacentral", 18 | "韩国南": "koreasouth" 19 | }, 20 | "SIZES": { 21 | "B1s": "Standard_B1s", 22 | "B2s": "Standard_B2s", 23 | "B4ms": "Standard_B4ms", 24 | "D2s_v3": "Standard_D2s_v3", 25 | "D4s_v3": "Standard_D4s_v3", 26 | "F1s": "Standard_F1s", 27 | "F2": "Standard_F2", 28 | "F2s": "Standard_F2s", 29 | "F4s": "Standard_F4s" 30 | }, 31 | "OS_INFOS": { 32 | "Ubuntu 16.04": { 33 | "os": "Ubuntu 16.04", 34 | "image_reference": { 35 | "publisher": "Canonical", 36 | "offer": "UbuntuServer", 37 | "sku": "16.04-LTS", 38 | "version": "latest" 39 | } 40 | }, 41 | "Ubuntu 18.04": { 42 | "os": "Ubuntu 18.04", 43 | "image_reference": { 44 | "publisher": "Canonical", 45 | "offer": "UbuntuServer", 46 | "sku": "18.04-LTS", 47 | "version": "latest" 48 | } 49 | }, 50 | "CentOs 7.5": { 51 | "os": "CentOs 7.5", 52 | "image_reference": { 53 | "publisher": "OpenLogic", 54 | "offer": "CentOS", 55 | "sku": "7.5", 56 | "version": "latest" 57 | } 58 | }, 59 | "CentOS 8.2": { 60 | "os": "CentOS 8.2", 61 | "image_reference": { 62 | "publisher": "OpenLogic", 63 | "offer": "CentOS", 64 | "sku": "8_2", 65 | "version": "latest" 66 | } 67 | }, 68 | "Debian 10": { 69 | "os": "Debian 10", 70 | "image_reference": { 71 | "publisher": "debian", 72 | "offer": "debian-10", 73 | "sku": "10", 74 | "version": "latest" 75 | } 76 | }, 77 | "Debian 9": { 78 | "os": "Debian 9", 79 | "image_reference": { 80 | "publisher": "credativ", 81 | "offer": "Debian", 82 | "sku": "9", 83 | "version": "latest" 84 | } 85 | } 86 | } 87 | } 88 | } -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | user_dict = {} 4 | 5 | 6 | class UserDict: 7 | 8 | def __init__(self): 9 | self.email = None 10 | self.refresh_token = None 11 | self.subscription_id = None 12 | 13 | 14 | class RefreshToken: 15 | 16 | def __init__(self): 17 | self.base_path = os.path.join(os.getcwd(), 'rtokens') 18 | if not os.path.exists(self.base_path): 19 | os.mkdir(self.base_path) 20 | 21 | def save(self, email: str, refresh_token: str): 22 | open(os.path.join(self.base_path, email), 'w').write(refresh_token) 23 | 24 | def list(self): 25 | return os.listdir(self.base_path) 26 | 27 | def get(self, email: str): 28 | return open(os.path.join(self.base_path, email), 'r').read() 29 | 30 | def remove(self, email: str): 31 | os.remove(os.path.join(self.base_path, email)) 32 | --------------------------------------------------------------------------------