From 5f259ad2758d1711716a5e7975a84b41c9a9111b Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Fri, 10 Jun 2022 21:45:24 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D1=8F?= =?UTF-8?q?=D0=B5=D1=82=20jupyter=20notebook?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Phone_number_migration.ipynb | 253 +++++++++++++++++++++++++++++++++++ main.py | 2 +- 2 files changed, 254 insertions(+), 1 deletion(-) create mode 100644 Phone_number_migration.ipynb diff --git a/Phone_number_migration.ipynb b/Phone_number_migration.ipynb new file mode 100644 index 0000000..71348e6 --- /dev/null +++ b/Phone_number_migration.ipynb @@ -0,0 +1,253 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "yLL1WjbsNJdP" + }, + "source": [ + "# Phone number migration\n", + "\n", + "**Перед запуском необходимо добавить файл с Oauth2 Сredentials (по умолчанию credentials.json)**\n", + "\n" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WmCKpRemMRba" + }, + "source": [ + "Импортируем необходимые библиотеки." + ] + }, + { + "cell_type": "code", + "execution_count": 13, + "metadata": { + "id": "pzHzXS_eFteU" + }, + "outputs": [], + "source": [ + "import os.path\n", + "\n", + "from google.auth.transport.requests import Request\n", + "from google.oauth2.credentials import Credentials\n", + "from google_auth_oauthlib.flow import InstalledAppFlow\n", + "from googleapiclient.discovery import build\n", + "from googleapiclient.errors import HttpError" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qA-EnsZIMOeZ" + }, + "source": [ + "Основные параметры приложения." + ] + }, + { + "cell_type": "code", + "execution_count": 14, + "metadata": { + "id": "rRgABs8rF1d8" + }, + "outputs": [], + "source": [ + "# Параметры приложения\n", + "OLD_NUMBER_PREFIXES = ['+38071', '071']\n", + "NEW_NUMBER_PREFIXES = ['+7949']\n", + "\n", + "# Функция для получения нового номера из основы старого\n", + "# Вернет номер в формате +7 949 123-45-67\n", + "def new_phone_number(base: str):\n", + " num = '7949' + base\n", + " return '+{} {} {}-{}-{}'.format(\n", + " num[0],\n", + " num[1:4],\n", + " num[4:7],\n", + " num[7:9],\n", + " num[9:11]\n", + " )\n", + "# Тип телефона.\n", + "# Доступные значения: https://developers.google.com/people/api/rest/v1/people#phonenumber\n", + "PHONE_TYPE = 'mobile'" + ] + }, + { + "cell_type": "code", + "execution_count": 15, + "metadata": { + "id": "YMHU4Hs_F5-Y" + }, + "outputs": [], + "source": [ + "# Параметры Google API\n", + "CREDENTIALS_FILE = 'credentials.json'\n", + "TOKEN_FILE = 'token.json'\n", + "SCOPES = ['https://www.googleapis.com/auth/contacts']" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-Kdstr4TOs1H" + }, + "source": [ + "Основной код приложения" + ] + }, + { + "cell_type": "code", + "execution_count": 16, + "metadata": { + "id": "TimQdg09F2GV" + }, + "outputs": [], + "source": [ + "def main():\n", + " creds = None\n", + " if os.path.exists(TOKEN_FILE):\n", + " creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)\n", + " if not creds or not creds.valid:\n", + " if creds and creds.expired and creds.refresh_token:\n", + " creds.refresh(Request())\n", + " else:\n", + " flow = InstalledAppFlow.from_client_secrets_file(\n", + " CREDENTIALS_FILE, SCOPES)\n", + " creds = flow.run_console()\n", + " with open(TOKEN_FILE, 'w') as token:\n", + " token.write(creds.to_json())\n", + " try:\n", + " service = build('people', 'v1', credentials=creds)\n", + " print('Получаем список контактов...')\n", + " results = service.people().connections().list(resourceName='people/me',\n", + " personFields='names,phoneNumbers').execute()\n", + " connections = results.get('connections', [])\n", + " print('Количество контактов: {}'.format(len(connections)))\n", + " batch_request = {\n", + " \"contacts\": {},\n", + " \"updateMask\": \"phoneNumbers\"\n", + " }\n", + " for person in connections:\n", + " phoneNumbers = person.get('phoneNumbers', [])\n", + " has_old = False\n", + " old_numbers = set()\n", + " new_numbers = set()\n", + " # Получаем номера нового и старого формата\n", + " for phoneNumber in phoneNumbers:\n", + " num = phoneNumber.get('canonicalForm')\n", + " if not num:\n", + " continue\n", + " base = get_number_without_prefix(num, OLD_NUMBER_PREFIXES)\n", + " if (base):\n", + " has_old = True\n", + " old_numbers.add(base)\n", + " base = get_number_without_prefix(num, NEW_NUMBER_PREFIXES)\n", + " if (base):\n", + " new_numbers.add(base)\n", + " # Если нет старого - пропускаем\n", + " if not has_old:\n", + " continue\n", + " # Получаем старые номера, у которых нету парного нового\n", + " numbers_not_updated = list(\n", + " old_numbers.difference(new_numbers)\n", + " )\n", + " \n", + " # Если нет старых номеров, которые нужно обновить - пропускаем\n", + " if len(numbers_not_updated) == 0:\n", + " continue\n", + " # Получаем новые номера\n", + " new_phone_numbers = [\n", + " new_phone_number(x) for x in numbers_not_updated\n", + " ]\n", + " # batchUpdateContacts: https://developers.google.com/people/api/rest/v1/people/batchUpdateContacts\n", + " # PhoneNumber: https://developers.google.com/people/api/rest/v1/people#phonenumber\n", + " batch_request['contacts'][person['resourceName']] = {\n", + " \"phoneNumbers\": [\n", + " *person['phoneNumbers'],\n", + " [\n", + " { \n", + " \"value\": number, \n", + " \"type\": PHONE_TYPE\n", + " } for number in new_phone_numbers\n", + " ]\n", + " ],\n", + " \"etag\": person['etag']\n", + " }\n", + " contacts_len = len(batch_request['contacts'])\n", + " if (contacts_len > 0):\n", + " print('Количество контактов для обновления: {}'.format(contacts_len))\n", + " print('Обновляем контакты...')\n", + " service.people().batchUpdateContacts(body = batch_request).execute()\n", + " print('Обновление завершено!')\n", + " else:\n", + " print('Нечего обновлять!')\n", + " except HttpError as err:\n", + " print(err)\n", + "\n", + "def get_number_without_prefix(number: str, prefixes: list):\n", + " for prefix in prefixes:\n", + " if number.startswith(prefix):\n", + " return number[len(prefix):]\n", + " return False" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "WnbwQVZPNDpP" + }, + "source": [ + "Запуск приложения" + ] + }, + { + "cell_type": "code", + "execution_count": 17, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 332 + }, + "id": "0v8m0ZYmGHnC", + "outputId": "092f3a86-80d3-45c7-d4d6-f72878d96017" + }, + "outputs": [ + { + "ename": "FileNotFoundError", + "evalue": "ignored", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'__main__'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", + "\u001b[0;32m\u001b[0m in \u001b[0;36mmain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m flow = InstalledAppFlow.from_client_secrets_file(\n\u001b[0;32m---> 10\u001b[0;31m CREDENTIALS_FILE, SCOPES)\n\u001b[0m\u001b[1;32m 11\u001b[0m \u001b[0mcreds\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mflow\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_console\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTOKEN_FILE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'w'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mtoken\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/google_auth_oauthlib/flow.py\u001b[0m in \u001b[0;36mfrom_client_secrets_file\u001b[0;34m(cls, client_secrets_file, scopes, **kwargs)\u001b[0m\n\u001b[1;32m 199\u001b[0m \u001b[0mFlow\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mThe\u001b[0m \u001b[0mconstructed\u001b[0m \u001b[0mFlow\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 200\u001b[0m \"\"\"\n\u001b[0;32m--> 201\u001b[0;31m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mclient_secrets_file\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"r\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mjson_file\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 202\u001b[0m \u001b[0mclient_config\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mjson\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mjson_file\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'credentials.json'" + ] + } + ], + "source": [ + "if __name__ == '__main__':\n", + " main()\n" + ] + } + ], + "metadata": { + "colab": { + "name": "Phone number migration.ipynb", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3", + "name": "python3" + }, + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 0 +} diff --git a/main.py b/main.py index ad7ad4b..a3f8a53 100644 --- a/main.py +++ b/main.py @@ -50,7 +50,7 @@ def main(): else: flow = InstalledAppFlow.from_client_secrets_file( CREDENTIALS_FILE, SCOPES) - creds = flow.run_local_server(port=42047) + creds = flow.run_console() with open(TOKEN_FILE, 'w') as token: token.write(creds.to_json()) From 363530f4f6a6376d045840b5b47ee5ad9a65e0f6 Mon Sep 17 00:00:00 2001 From: Maxim Slipenko Date: Fri, 10 Jun 2022 21:49:38 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=B4=D0=B5=D0=BB=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D1=80=D0=B5=D1=84=D0=B0=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD?= =?UTF-8?q?=D0=B3=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Phone_number_migration.ipynb | 85 ++++++++++++++++++++++++++---------- main.py | 7 ++- 2 files changed, 69 insertions(+), 23 deletions(-) diff --git a/Phone_number_migration.ipynb b/Phone_number_migration.ipynb index 71348e6..09a93d4 100644 --- a/Phone_number_migration.ipynb +++ b/Phone_number_migration.ipynb @@ -23,7 +23,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 1, "metadata": { "id": "pzHzXS_eFteU" }, @@ -49,7 +49,7 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 2, "metadata": { "id": "rRgABs8rF1d8" }, @@ -77,7 +77,7 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": 3, "metadata": { "id": "YMHU4Hs_F5-Y" }, @@ -91,16 +91,48 @@ }, { "cell_type": "markdown", - "metadata": { - "id": "-Kdstr4TOs1H" - }, + "metadata": {}, "source": [ - "Основной код приложения" + "Функция для получения доступа к Google аккаунту:" ] }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 4, + "metadata": {}, + "outputs": [], + "source": [ + "def google_creds():\n", + " creds = None\n", + "\n", + " if os.path.exists(TOKEN_FILE):\n", + " creds = Credentials.from_authorized_user_file(TOKEN_FILE, SCOPES)\n", + "\n", + " if not creds or not creds.valid:\n", + " if creds and creds.expired and creds.refresh_token:\n", + " creds.refresh(Request())\n", + " else:\n", + " flow = InstalledAppFlow.from_client_secrets_file(\n", + " CREDENTIALS_FILE, SCOPES)\n", + " creds = flow.run_console()\n", + " with open(TOKEN_FILE, 'w') as token:\n", + " token.write(creds.to_json())\n", + "\n", + " return creds" + ] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "-Kdstr4TOs1H" + }, + "source": [ + "Основной код приложения:" + ] + }, + { + "cell_type": "code", + "execution_count": 5, "metadata": { "id": "TimQdg09F2GV" }, @@ -200,12 +232,12 @@ "id": "WnbwQVZPNDpP" }, "source": [ - "Запуск приложения" + "Запуск приложения:" ] }, { "cell_type": "code", - "execution_count": 17, + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -216,16 +248,12 @@ }, "outputs": [ { - "ename": "FileNotFoundError", - "evalue": "ignored", - "output_type": "error", - "traceback": [ - "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", - "\u001b[0;31mFileNotFoundError\u001b[0m Traceback (most recent call last)", - "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 1\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0m__name__\u001b[0m \u001b[0;34m==\u001b[0m \u001b[0;34m'__main__'\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m----> 2\u001b[0;31m \u001b[0mmain\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", - "\u001b[0;32m\u001b[0m in \u001b[0;36mmain\u001b[0;34m()\u001b[0m\n\u001b[1;32m 8\u001b[0m \u001b[0;32melse\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 9\u001b[0m flow = InstalledAppFlow.from_client_secrets_file(\n\u001b[0;32m---> 10\u001b[0;31m CREDENTIALS_FILE, SCOPES)\n\u001b[0m\u001b[1;32m 11\u001b[0m \u001b[0mcreds\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mflow\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mrun_console\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 12\u001b[0m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mTOKEN_FILE\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m'w'\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mtoken\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;32m/usr/local/lib/python3.7/dist-packages/google_auth_oauthlib/flow.py\u001b[0m in \u001b[0;36mfrom_client_secrets_file\u001b[0;34m(cls, client_secrets_file, scopes, **kwargs)\u001b[0m\n\u001b[1;32m 199\u001b[0m \u001b[0mFlow\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0mThe\u001b[0m \u001b[0mconstructed\u001b[0m \u001b[0mFlow\u001b[0m \u001b[0minstance\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 200\u001b[0m \"\"\"\n\u001b[0;32m--> 201\u001b[0;31m \u001b[0;32mwith\u001b[0m \u001b[0mopen\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mclient_secrets_file\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0;34m\"r\"\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mjson_file\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 202\u001b[0m \u001b[0mclient_config\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mjson\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mload\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mjson_file\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 203\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mFileNotFoundError\u001b[0m: [Errno 2] No such file or directory: 'credentials.json'" + "name": "stdout", + "output_type": "stream", + "text": [ + "Получаем список контактов...\n", + "Количество контактов: 48\n", + "Нечего обновлять!\n" ] } ], @@ -240,12 +268,25 @@ "name": "Phone number migration.ipynb", "provenance": [] }, + "interpreter": { + "hash": "e7370f93d1d0cde622a1f8e1c04877d8463912d04d973331ad4851f04de6915a" + }, "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3.10.4 64-bit", + "language": "python", "name": "python3" }, "language_info": { - "name": "python" + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.4" } }, "nbformat": 4, diff --git a/main.py b/main.py index a3f8a53..5600da3 100644 --- a/main.py +++ b/main.py @@ -38,7 +38,7 @@ SCOPES = ['https://www.googleapis.com/auth/contacts'] ######## -def main(): +def google_creds(): creds = None if os.path.exists(TOKEN_FILE): @@ -54,7 +54,12 @@ def main(): with open(TOKEN_FILE, 'w') as token: token.write(creds.to_json()) + return creds + +def main(): try: + creds = google_creds() + service = build('people', 'v1', credentials=creds) print('Получаем список контактов...') results = service.people().connections().list(resourceName='people/me',