├── MANIFEST.in
├── .others
├── bot.png
└── telegram.py.bak
├── .gitignore
├── .env
├── requirements.txt
├── notifly
├── __init__.py
├── gpu_stats.py
├── slack.py
├── discord.py
├── telegram.py
└── tf_notifier.py
├── .github
├── ISSUE_TEMPLATE
│ ├── bug.md
│ ├── proposal.md
│ ├── documentation.md
│ └── feature.md
├── workflows
│ ├── python-publish.yml
│ ├── python-test.yml
│ └── python-app.yml
├── pull_request_template.md
└── config.yml
├── req.txt
├── CHANGELOG.txt
├── setup.py
├── LICENSE
├── test.py
├── temp.py
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── docs
├── index.html
├── slack.html
├── discord.html
├── telegram.html
└── tf_notifier.html
└── README.md
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | global-include *.txt *.py
--------------------------------------------------------------------------------
/.others/bot.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/rexdivakar/Notifly/HEAD/.others/bot.png
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 |
2 | # Created by .ignore support plugin (hsz.mobi)
3 | .idea
4 | !/temp.py
5 | .vscode
6 | test.py
7 | __pycache__/
8 |
--------------------------------------------------------------------------------
/.env:
--------------------------------------------------------------------------------
1 | TOKEN="https://discord.com/api/webhooks/794553880427888641/rUegx2re6-flz7ESZyvIRaBLUMY6e1BhctMXpv2hWA1Zi4I2ulNmuqRSTv9KTI9cPq2Y"
2 |
--------------------------------------------------------------------------------
/requirements.txt:
--------------------------------------------------------------------------------
1 | requests==2.24.0
2 | numpy==1.22.2
3 | matplotlib==3.2.2
4 | slackclient==2.9.3
5 | psutil==5.7.3
6 | python-dotenv==0.15.0
--------------------------------------------------------------------------------
/notifly/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | Notifly Documentation
3 |
4 | Discord: (object): Discord wrapper to send message, images to the channel using Webhooks
5 | Slack (object): Slack wrapper to send message, images to the channel using API
6 | Telegram (object): Telegram Web Client wrapper to send images, files over the bot using API
7 | """
8 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🐛 Bug Report
3 | about: Submit a bug report to help us improve
4 | labels: "bug"
5 | ---
6 |
7 | ## 🐛 Bug Report
8 |
9 | (A clear and concise description of what the bug is.)
10 |
11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)?
12 |
13 | (Write your answer here.)
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/proposal.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 💥 Proposal
3 | about: Propose a non-trivial change to Notifly
4 | labels: "proposal"
5 | ---
6 |
7 | ## 💥 Proposal
8 |
9 | (A clear and concise description of what the proposal is.)
10 |
11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)?
12 |
13 | (Write your answer here.)
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/documentation.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 📚 Documentation
3 | about: Report an issue related to documentation
4 | labels: "documentation"
5 | ---
6 |
7 | ## 📚 Documentation
8 |
9 | (A clear and concise description of what the issue is.)
10 |
11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)?
12 |
13 | (Write your answer here.)
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 🚀 Feature
3 | about: Submit a proposal for a new feature
4 | labels: "feature"
5 | ---
6 |
7 | ## 🚀 Feature
8 |
9 | (A clear and concise description of what the feature is.)
10 |
11 | ### Have you read the [Contributing Guidelines on Pull Requests](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)?
12 |
13 | (Write your answer here.)
14 |
15 | ## Motivation
16 |
17 | (Please outline the motivation for the proposal.)
18 |
19 | ## Pitch
20 |
21 | (Please explain why this feature should be implemented and how it would be used.)
22 |
--------------------------------------------------------------------------------
/req.txt:
--------------------------------------------------------------------------------
1 | tensorflow==2.15.0
2 | matplotlib
3 | slackclient
4 | python-dotenv
5 | psutil
6 | aiohttp>=3.9.2 # not directly required, pinned by Snyk to avoid a vulnerability
7 | fonttools>=4.43.0 # not directly required, pinned by Snyk to avoid a vulnerability
8 | numpy>=1.22.2 # not directly required, pinned by Snyk to avoid a vulnerability
9 | pillow>=10.2.0 # not directly required, pinned by Snyk to avoid a vulnerability
10 | setuptools>=65.5.1 # not directly required, pinned by Snyk to avoid a vulnerability
11 | werkzeug>=2.3.8 # not directly required, pinned by Snyk to avoid a vulnerability
12 | wheel>=0.38.0 # not directly required, pinned by Snyk to avoid a vulnerability
--------------------------------------------------------------------------------
/CHANGELOG.txt:
--------------------------------------------------------------------------------
1 | Change Log
2 | ==========
3 |
4 | 1.0 (17/10/2020)
5 | ------------------
6 | - First Release
7 |
8 | 1.1.5 (26/10/2020)
9 | ------------------
10 | - Basic Fix and Major changes for Telegram module
11 | - Added Discord Bot feature
12 |
13 | 1.2.0 (19/11/2020)
14 | ------------------
15 | - Added Notification Handler for discord, slack
16 | - Added Wrapper for Tensorflow Callback
17 | - Minor Fixes
18 |
19 | 1.3.0 (17/1/2021)
20 | ------------------
21 | - Added functionalities to monitor and track hardware info during model training
22 | - Minor Fixes
23 | - Updated Documentation
24 |
25 | 1.3.1 (21/2/2021)
26 | ------------------
27 | - Bug fix -- CPU instance error on runtime
28 | - Added Wiki pages
29 |
30 | 1.3.2 (23/5/2022)
31 | ------------------
32 | - Resolved Buffer Overflow in NumPy Package (CVE-2021-33430)
33 | - Updated Wiki pages
34 |
35 | 1.3.3 (17/02/2024)
36 | ------------------
37 | - Upgraded the package version to support latest python 3.11 and above
--------------------------------------------------------------------------------
/.github/workflows/python-publish.yml:
--------------------------------------------------------------------------------
1 | # This workflows will upload a Python Package using Twine when a release is created
2 | # For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries
3 |
4 | name: Upload Python Package
5 |
6 | on:
7 | release:
8 | types: [created]
9 |
10 | jobs:
11 | deploy:
12 |
13 | runs-on: ubuntu-latest
14 |
15 | steps:
16 | - uses: actions/checkout@v2
17 | - name: Set up Python
18 | uses: actions/setup-python@v2
19 | with:
20 | python-version: '3.x'
21 | - name: Install dependencies
22 | run: |
23 | python -m pip install --upgrade pip
24 | pip install setuptools wheel twine
25 | - name: Build and publish
26 | env:
27 | TWINE_USERNAME: __token__
28 | TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }}
29 | run: |
30 | python setup.py sdist bdist_wheel
31 | twine upload --verbose dist/*
32 |
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | from setuptools import setup
2 | from pathlib import Path
3 |
4 | classifiers = [
5 | 'Development Status :: 5 - Production/Stable',
6 | 'Intended Audience :: Education',
7 | 'Operating System :: Microsoft :: Windows :: Windows 10',
8 | 'License :: OSI Approved :: MIT License',
9 | 'Programming Language :: Python :: 3'
10 | ]
11 |
12 | # Get the long description from the README file
13 | long_description = Path("README.md").read_text(encoding="utf-8")
14 |
15 | setup(
16 | name='notifly',
17 | version='1.3.3',
18 | description='Notification on the fly !',
19 | long_description=long_description,
20 | long_description_content_type='text/markdown',
21 | url='https://github.com/rexdivakar/Telegram-Notifly',
22 | author='Divakar R, Sanchit Jain',
23 | author_email='rexdivakar@hotmail.com , sanchitjain1996@gmail.com',
24 | license='MIT',
25 | classifiers=classifiers,
26 | keywords='Bot Notification',
27 | packages=['notifly'],
28 | install_requires=['requests', 'numpy==1.19.3', 'matplotlib', 'slackclient', 'python-dotenv', 'psutil']
29 | )
30 |
--------------------------------------------------------------------------------
/.github/workflows/python-test.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a variety of Python versions
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Pytest
5 |
6 | on:
7 | push:
8 | branches:
9 | - main
10 | - '*'
11 | pull_request:
12 | branches:
13 | - main
14 | - 'releases/**'
15 |
16 | jobs:
17 | build:
18 |
19 | runs-on: ubuntu-latest
20 | strategy:
21 | matrix:
22 | python-version: [3.11]
23 |
24 | steps:
25 | - uses: actions/checkout@v2
26 | - name: Set up Python ${{ matrix.python-version }}
27 | uses: actions/setup-python@v2
28 | with:
29 | python-version: ${{ matrix.python-version }}
30 | - name: Install dependencies
31 | run: |
32 | python -m pip install --upgrade pip
33 | pip install flake8 pytest
34 | if [ -f req.txt ]; then pip install -r req.txt; fi
35 |
36 | - name: Test with pytest
37 | run: |
38 | pytest test.py -v --disable-warnings
39 |
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 rex_divakar
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 |
--------------------------------------------------------------------------------
/.github/workflows/python-app.yml:
--------------------------------------------------------------------------------
1 | # This workflow will install Python dependencies, run tests and lint with a single version of Python
2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
3 |
4 | name: Lint Check
5 |
6 | on:
7 | push:
8 | branches:
9 | - main
10 | - '*'
11 | pull_request:
12 | branches:
13 | - main
14 |
15 | jobs:
16 | build:
17 |
18 | runs-on: ubuntu-latest
19 |
20 | steps:
21 | - uses: actions/checkout@v2
22 | - name: Set up Python 3.11
23 | uses: actions/setup-python@v2
24 | with:
25 | python-version: 3.11
26 | - name: Install dependencies
27 | run: |
28 | python -m pip install --upgrade pip
29 | pip install flake8 pytest
30 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
31 | - name: Lint with flake8
32 | run: |
33 | # stop the build if there are Python syntax errors or undefined names
34 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics
35 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
36 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
37 |
--------------------------------------------------------------------------------
/.github/pull_request_template.md:
--------------------------------------------------------------------------------
1 | #### Issue Number
2 | ISSUE #
3 |
7 |
8 | #### Describe the changes you've made
9 |
12 |
13 | #### Describe if there is any unusual behaviour of your code(Write `NA` if there isn't)
14 |
17 |
18 | #### Additional context (OPTIONAL)
19 |
22 |
23 | #### Test plan (OPTIONAL)
24 |
28 |
29 | #### Checklist
30 |
34 | - [ ] My code follows the code style of this project.
35 | - [ ] I have performed a self-review of my own code.
36 | - [ ] My change requires a change to the documentation.
37 | - [ ] I have read the **[CONTRIBUTING](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md)** document.
38 | - [ ] I have updated the documentation accordingly.
39 | - [ ] I have commented my code, particularly in hard-to-understand areas.
40 | - [ ] My changes generate no new warnings.
41 | - [ ] I have added tests that prove my fix is effective or that my feature works.
42 | - [ ] The title of my pull request is a short description of the requested changes.
43 |
--------------------------------------------------------------------------------
/test.py:
--------------------------------------------------------------------------------
1 | import ssl
2 | from notifly import discord
3 | import tensorflow as tf
4 | import os
5 | from dotenv import load_dotenv
6 |
7 | load_dotenv()
8 |
9 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
10 |
11 |
12 | def test():
13 | ssl._create_default_https_context = ssl._create_unverified_context
14 | token = os.getenv('TOKEN')
15 | notifier = discord.Notifier(token)
16 |
17 | class TestCallback(tf.keras.callbacks.Callback):
18 |
19 | @notifier.notify_on_epoch_begin(epoch_interval=1, graph_interval=1, hardware_stats_interval=1)
20 | def on_epoch_begin(self, epoch, logs=None):
21 | pass
22 |
23 | @notifier.notify_on_epoch_end(epoch_interval=1, graph_interval=1, hardware_stats_interval=1)
24 | def on_epoch_end(self, epoch, logs=None):
25 | pass
26 |
27 | @notifier.notify_on_train_begin()
28 | def on_train_begin(self, logs=None):
29 | pass
30 |
31 | @notifier.notify_on_train_end()
32 | def on_train_end(self, logs=None):
33 | pass
34 |
35 | fashion_mnist = tf.keras.datasets.fashion_mnist
36 | (train_images, train_labels), (_, _) = fashion_mnist.load_data()
37 |
38 | model = tf.keras.Sequential([
39 | tf.keras.layers.Flatten(input_shape=(28, 28)),
40 | tf.keras.layers.Dense(2, activation='relu'),
41 | tf.keras.layers.Dense(10)
42 | ])
43 |
44 | model.compile(optimizer='adam',
45 | loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
46 | metrics=['accuracy'])
47 |
48 | model.fit(train_images, train_labels, epochs=1, callbacks=[TestCallback()])
49 |
50 |
51 | test()
52 |
--------------------------------------------------------------------------------
/.github/config.yml:
--------------------------------------------------------------------------------
1 | # Configuration for welcome - https://github.com/behaviorbot/welcome
2 |
3 | # Configuration for new-issue-welcome - https://github.com/behaviorbot/new-issue-welcome
4 | # Comment to be posted to on first time issues
5 |
6 | newIssueWelcomeComment: >
7 | Hello there!👋 Welcome to the project!🚀⚡
8 |
9 |
10 | Thank you and congrats🎉 for opening your very first issue in this project.
11 | Please adhere to our [Code of Conduct](https://github.com/rexdivakar/Notifly/blob/main/CODE_OF_CONDUCT.md).
12 | Please make sure not to start working on the issue, unless you get assigned to it.😄
13 | Feel free to join us on [OpenSource-Dev-Community](https://discord.gg/tTJQxvSaDP)✨ Hope you have a great time there!😄
14 |
15 |
16 | # Configuration for new-pr-welcome - https://github.com/behaviorbot/new-pr-welcome
17 | # Comment to be posted to on PRs from first time contributors in your repository
18 |
19 | newPRWelcomeComment: >
20 | Hello there!👋 Welcome to the project!💖
21 |
22 |
23 | Thank you and congrats🎉 for opening your first pull request in this project.
24 | Please make sure you have followed our [Contributing Guidelines](https://github.com/rexdivakar/Notifly/blob/main/CONTRIBUTING.md).🙌🙌 We will get back to you as soon as we can 😄.
25 | Feel free to join us on [OpenSource-Dev-Community](https://discord.gg/tTJQxvSaDP)✨ Hope you have a great time there!😄
26 |
27 | # Configuration for first-pr-merge - https://github.com/behaviorbot/first-pr-merge
28 | # Comment to be posted to on pull requests merged by a first time user
29 |
30 | firstPRMergeComment: >
31 | Congrats on merging your first pull request! 🎉 All the best for your amazing open source journey ahead 🚀.
32 |
--------------------------------------------------------------------------------
/notifly/gpu_stats.py:
--------------------------------------------------------------------------------
1 | from subprocess import Popen, PIPE
2 | from distutils import spawn
3 | import os
4 | import sys
5 | import platform
6 |
7 |
8 | def gpu():
9 | """
10 | Monitors the hardware info on the runtime.
11 |
12 | Returns:
13 | The list of parameters containing hardware info
14 | """
15 | if platform.system() == "Windows":
16 | try:
17 | nvidia_smi = spawn.find_executable('nvidia-smi')
18 | except FileNotFoundError as fn:
19 | print(fn)
20 |
21 | if nvidia_smi is None:
22 | nvidia_smi = "%s\\Program Files\\NVIDIA Corporation\\NVSMI\\nvidia-smi.exe" % os.environ['systemdrive']
23 | else:
24 | nvidia_smi = "nvidia-smi"
25 |
26 | # Get ID, processing and memory utilization for all GPUs (deviceIds, uuid, gpuUtil, memTotal, memUsed, memFree,
27 | # driver,gpu_name,serial,display_mode,display_active, temp_gpu)
28 | try:
29 | p = Popen([nvidia_smi,
30 | "--query-gpu=index,uuid,utilization.gpu,memory.total,memory.used,memory.free,driver_version,name,"
31 | "gpu_serial,display_active,display_mode,temperature.gpu",
32 | "--format=csv,noheader,nounits"], stdout=PIPE)
33 | stdout, stderr = p.communicate()
34 | if p is not None:
35 | x = stdout.decode('UTF-8').split(',')
36 | if len(x) > 1:
37 | return x
38 |
39 | print('No Gpu Found, continuing with CPU instance')
40 |
41 | except SystemError as stderr:
42 | print('Unable to establish a communication with GPU', stderr)
43 | sys.exit(1)
44 | except FileNotFoundError as fn:
45 | print('Unable to find GPU connection in your device, Proceeding using CPU instance')
46 |
--------------------------------------------------------------------------------
/temp.py:
--------------------------------------------------------------------------------
1 | import ssl
2 | from notifly import tf_notifier
3 | import tensorflow as tf
4 | from dotenv import load_dotenv
5 | import os
6 |
7 |
8 | load_dotenv()
9 |
10 | ssl._create_default_https_context = ssl._create_unverified_context
11 | token = os.getenv('TOKEN')
12 | notifier = tf_notifier.TfNotifier(token=token, platform='discord')
13 |
14 |
15 | class TestCallback(tf.keras.callbacks.Callback):
16 |
17 | @notifier.notify_on_epoch_begin(epoch_interval=1, graph_interval=10)
18 | def on_epoch_begin(self, epoch, logs=None):
19 | pass
20 |
21 | @notifier.notify_on_epoch_end(epoch_interval=1, graph_interval=10)
22 | def on_epoch_end(self, epoch, logs=None):
23 | pass
24 |
25 | @notifier.notify_on_train_begin()
26 | def on_train_begin(self, logs=None):
27 | pass
28 |
29 | @notifier.notify_on_train_end()
30 | def on_train_end(self, logs=None):
31 | pass
32 |
33 |
34 | fashion_mnist = tf.keras.datasets.fashion_mnist
35 |
36 | (train_images, train_labels), (test_images, test_labels) = fashion_mnist.load_data()
37 |
38 | class_names = ['T-shirt/top', 'Trouser', 'Pullover', 'Dress', 'Coat',
39 | 'Sandal', 'Shirt', 'Sneaker', 'Bag', 'Ankle boot']
40 |
41 | model = tf.keras.Sequential([
42 | tf.keras.layers.Flatten(input_shape=(28, 28)),
43 | tf.keras.layers.Dense(5, activation='relu'),
44 | tf.keras.layers.Dense(10)
45 | ])
46 |
47 | model.compile(optimizer='adam',
48 | loss=tf.keras.losses.SparseCategoricalCrossentropy(from_logits=True),
49 | metrics=['accuracy'])
50 |
51 | model.fit(train_images, train_labels, epochs=5, callbacks=[TestCallback()])
52 |
53 | test_loss, test_acc = model.evaluate(test_images, test_labels, verbose=2)
54 |
55 | print('\nTest accuracy:', test_acc)
56 |
--------------------------------------------------------------------------------
/notifly/slack.py:
--------------------------------------------------------------------------------
1 | """Slack-API call wrapper"""
2 |
3 | import slack
4 | from slack.errors import SlackApiError
5 | from notifly.tf_notifier import TfNotifier
6 |
7 |
8 | class Notifier(TfNotifier):
9 | def __init__(self, token, channel='general'):
10 | """
11 | Initialize the slack webclient instance using API.
12 |
13 | Args:
14 | token (string): API token [Mandatory].
15 | channel (string): Post's the notification to it's corresponding channel [default = #general].
16 | Returns:
17 | The output of slack API_test which verify the Authentication protocol.
18 | Raises:
19 | SlackApiError.
20 | """
21 | self.__client = slack.WebClient(token=token)
22 | self.__channel = channel
23 |
24 | try:
25 | self.__client.api_test()['ok']
26 | except SlackApiError as api_err:
27 | print(f"Got an error: {api_err.response['error']}")
28 | exit(1)
29 |
30 | def send_message(self, msg) -> object:
31 | """
32 | Function to post message to the slack channel.
33 |
34 | Args:
35 | msg (string): Enter your message to post.
36 | Returns:
37 | Outputs the response of message on post operation.
38 | Raises:
39 | SlackApiError
40 | """
41 | try:
42 | return self.__client.chat_postMessage(channel=self.__channel, text=msg)
43 | except SlackApiError as api_err:
44 | print(f"Got an error: {api_err.response['error']}")
45 | exit(1)
46 |
47 | def send_file(self, file_path) -> object:
48 | """
49 | Function to post an file to the slack channel.
50 |
51 | Args:
52 | file_path (string): Enter the path of the file to be sent.
53 | Returns:
54 | Outputs the response of message on post operation.
55 | Raises:
56 | FileNotFoundError
57 | """
58 | try:
59 | return self.__client.files_upload(file=file_path, channels=self.__channel)
60 | except FileNotFoundError as fl_err:
61 | print(fl_err)
62 |
--------------------------------------------------------------------------------
/notifly/discord.py:
--------------------------------------------------------------------------------
1 | """Discord API webhooks wrapper"""
2 |
3 | import requests
4 | from notifly.tf_notifier import TfNotifier
5 | from requests import exceptions
6 |
7 |
8 | class Notifier(TfNotifier):
9 |
10 | class __AuthError(Exception):
11 | """
12 | Authentication Exception
13 | """
14 | pass
15 |
16 | def __init__(self, webhooks):
17 | """
18 | Initialize the Discord Webhook instance
19 |
20 | Args:
21 | webhooks (basestring): Discord webhook token [Mandatory]
22 | Returns:
23 | The response of webhook status
24 | Raises:
25 | Exception, AuthError, MissingSchema
26 | """
27 | self.__webhooks = webhooks
28 | self.payload = None
29 |
30 | try:
31 | if requests.get(self.__webhooks).status_code == 401:
32 | raise Notifier.__AuthError('Invalid Webhook')
33 | except exceptions.ConnectionError as err:
34 | print('HTTPS Connection Error - Unable to reach the destination')
35 | exit(1)
36 | except Notifier.__AuthError as ty_err:
37 | print(ty_err)
38 | exit(1)
39 | except requests.models.MissingSchema as ms_err:
40 | print(ms_err)
41 | exit(1)
42 |
43 | def send_message(self, msg) -> object:
44 | """
45 | Function to post message to the discord channel
46 |
47 | Args:
48 | msg (string): Posts message to the discord channel [String] [UTF-8]
49 | Returns:
50 | The response to the message on post operation.
51 | Raises:
52 | ConnectionError
53 | """
54 | payload = {'content': str(msg)}
55 | try:
56 | return requests.post(url = self.__webhooks, data = payload)
57 | except exceptions.ConnectionError as cer:
58 | print(cer)
59 | exit(1)
60 |
61 | def send_file(self, file_path) -> object:
62 | """
63 | Function to post an image to the discord channel
64 |
65 | Args:
66 | file_path (string): Enter the path of the file to be sent
67 | Returns:
68 | The response to the message on post operation.
69 | Raises:
70 | FileNotFoundError, OverflowError
71 | """
72 | try:
73 | self.payload = {'file': open(file_path, 'rb')}
74 | except FileNotFoundError as fl_er:
75 | print(fl_er)
76 | exit(1)
77 | try:
78 | return requests.post(url = self.__webhooks, files = self.payload)
79 | except OverflowError as err:
80 | print('Size Overflow Error', err)
81 | exit(1)
82 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at tawishisharma1@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 |
3 | When contributing to this repository, please first discuss the change you wish to make via issue with the owners of this repository before making a change.
4 |
5 | ## Maintainers
6 |
7 | #### Divakar -- Discord id (root#5998)
8 | #### Sanchit -- Discord id (sanchit#5988)
9 |
10 | Discord server - https://discord.gg/e47wmTW
11 |
12 | Please note we have a code of conduct, please follow it in all your interactions with the project.
13 |
14 | ## Issues
15 |
16 | ### Issues are very valuable to this project.
17 | * Ideas are a valuable source of contributions others can make
18 | * Problems show where this project is lacking
19 | * With a question you show where contributors can improve the user experience
20 | _Thank you_ for creating them.
21 |
22 | ## Pull Requests
23 | ### Pull requests are, a great way to get your ideas into this repository.
24 | When deciding if I merge in a pull request I look at the following things:
25 | #### Does it state intent
26 | You should be clear which problem you're trying to solve with your contribution.
27 | For example:
28 | > Add link to code of conduct in README.md
29 |
30 | Doesn't tell me anything about why you're doing that
31 |
32 | > Add link to code of conduct in README.md because users don't always look in the CONTRIBUTING.md
33 | Tells me the problem that you have found, and the pull request shows me the action you have taken to solve it.
34 | #### Is it of good quality
35 | * There are no spelling mistakes
36 | * It reads well
37 | * For english language contributions: Has a good score on Grammarly or Hemingway App
38 | #### Does it move this repository closer to my vision for the repository
39 | Objecive for our repository is to build a package that acts as a notification medium which sends us about the model training parameters over telegram/discord/slack.We can also use this package as a notification medium for sending texts, picture and more.
40 | #### Does it follow the contributor covenant
41 | This repository has a [code of conduct][1], remove things that do not respect it.
42 |
43 | ## Our Pledge
44 |
45 | In the interest of fostering an open and welcoming environment, we as
46 | contributors and maintainers pledge to making participation in our project and
47 | our community a harassment-free experience for everyone, regardless of age, body
48 | size, disability, ethnicity, gender identity and expression, level of experience,
49 | nationality, personal appearance, race, religion, or sexual identity and
50 | orientation.
51 |
52 | ### Our Standards
53 |
54 | Examples of behavior that contributes to creating a positive environment
55 | include:
56 |
57 | * Using welcoming and inclusive language
58 | * Being respectful of differing viewpoints and experiences
59 | * Gracefully accepting constructive criticism
60 | * Focusing on what is best for the community
61 | * Showing empathy towards other community members
62 |
63 | Examples of unacceptable behavior by participants include:
64 |
65 | * The use of sexualized language or imagery and unwelcome sexual attention or
66 | advances
67 | * Trolling, insulting/derogatory comments, and personal or political attacks
68 | * Public or private harassment
69 | * Publishing others' private information, such as a physical or electronic
70 | address, without explicit permission
71 | * Other conduct which could reasonably be considered inappropriate in a
72 | professional setting
73 |
74 | ### Our Responsibilities
75 |
76 | Project maintainers are responsible for clarifying the standards of acceptable
77 | behavior and are expected to take appropriate and fair corrective action in
78 | response to any instances of unacceptable behavior.
79 |
80 | Project maintainers have the right and responsibility to remove, edit, or
81 | reject comments, commits, code, wiki edits, issues, and other contributions
82 | that are not aligned to this Code of Conduct, or to ban temporarily or
83 | permanently any contributor for other behaviors that they deem inappropriate,
84 | threatening, offensive, or harmful.
85 |
86 | ### Scope
87 |
88 | This Code of Conduct applies both within project spaces and in public spaces
89 | when an individual is representing the project or its community. Examples of
90 | representing a project or community include using an official project e-mail
91 | address, posting via an official social media account, or acting as an appointed
92 | representative at an online or offline event. Representation of a project may be
93 | further defined and clarified by project maintainers.
94 |
95 | ### Enforcement
96 |
97 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
98 | reported by contacting the project team at rexdivakar@hotmail.com. All
99 | complaints will be reviewed and investigated and will result in a response that
100 | is deemed necessary and appropriate to the circumstances. The project team is
101 | obligated to maintain confidentiality with regard to the reporter of an incident.
102 | Further details of specific enforcement policies may be posted separately.
103 |
104 | Project maintainers who do not follow or enforce the Code of Conduct in good
105 | faith may face temporary or permanent repercussions as determined by other
106 | members of the project's leadership.
107 |
108 | ### Attribution
109 |
110 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
111 | available at [http://contributor-covenant.org/version/1/4][version]
112 |
113 | [homepage]: http://contributor-covenant.org
114 | [version]: http://contributor-covenant.org/version/1/4/
115 | [1]:https://github.com/rexdivakar/Notifly/blob/main/CODE_OF_CONDUCT.md
116 |
--------------------------------------------------------------------------------
/.others/telegram.py.bak:
--------------------------------------------------------------------------------
1 | """ Telegram Wrapper"""""
2 |
3 | import json
4 | import os
5 | import requests
6 |
7 |
8 | class BotHandler:
9 | def __init__(self, token, dir_download= './downloads'):
10 | self.token = token
11 | self.api_url = "https://api.telegram.org/bot{}/".format(token)
12 | self.file_url = "https://api.telegram.org/file/bot{}/".format(token)
13 | self.dirDownloads = dir_download
14 |
15 | # url = "https://api.telegram.org/bot/"
16 |
17 | def get_updates(self, offset=0, timeout=10):
18 | method = 'getUpdates'
19 | params = {'timeout': timeout, 'offset': offset}
20 | resp = requests.get(self.api_url + method, params)
21 | try:
22 | return resp.json()['result']
23 | except TimeoutError as tm_err:
24 | print(tm_err)
25 | exit(1)
26 |
27 | def chat_id_response(self):
28 | try:
29 | fetch_updates = self.get_updates()
30 | return fetch_updates[0]['message']['chat']['id']
31 | except TimeoutError as tm_err:
32 | print(tm_err)
33 | exit(1)
34 |
35 | def send_message(self, msg, notification=False):
36 | try:
37 | method = 'sendMessage'
38 | params = {'chat_id': self.chat_id_response(), 'text': msg,
39 | 'parse_mode': 'HTML', 'disable_notification': notification}
40 | return requests.post(self.api_url + method, params)
41 | except IndexError as err:
42 | print('Time out error')
43 | exit(1)
44 |
45 | def send_image(self, img_path):
46 | method = 'sendPhoto?' + 'chat_id=' + str(self.chat_id_response())
47 | if img_path[-4:] in ['.jpg', '.png']:
48 | pass
49 | else:
50 | print('Invalid File Format, please use .jpg or .png format')
51 | exit(1)
52 | try:
53 | files = {'photo': open(img_path, 'rb')}
54 | return requests.post(self.api_url + method, files = files)
55 | except FileNotFoundError as fl_err:
56 | print(fl_err)
57 | exit(1)
58 |
59 | def send_document(self, file_path):
60 | method = 'sendDocument?' + 'chat_id=' + str(self.chat_id_response())
61 | try:
62 | files = {'document': open(file_path, 'rb')}
63 | return requests.post(self.api_url + method, files = files)
64 | except FileNotFoundError as fn_err:
65 | print(fn_err)
66 | exit(1)
67 | except TimeoutError as tm_err:
68 | print(tm_err)
69 | exit(1)
70 |
71 | def download_files(self):
72 | fetch_updates = self.get_updates()
73 | for update in fetch_updates:
74 | file_name = ''
75 | media = ['document', 'photo', 'video', 'voice']
76 | # Check if update message contains any of the media keys from above
77 | intersection = list(set(update['message'].keys()) & set(media))
78 | if intersection:
79 | media_key = intersection[0]
80 |
81 | # Determine file_id. For photos multiple versions exist. Use the last one.
82 | if media_key == 'photo':
83 | file_id = update['message'][media_key][-1]['file_id']
84 | else:
85 | file_id = update['message'][media_key]['file_id']
86 |
87 | if media_key == 'document':
88 | # In a document, it's possible to use the original name.
89 | file_name = update['message'][media_key]['file_name']
90 | self.get_file(file_id, file_name)
91 |
92 | def get_file(self, file_id, filename=''):
93 | """"Follow the getFile endpoint and download the file by file_id.
94 | A fileName can be given, else a file_unique_id gibberish name will be used.
95 | See also: https://core.telegram.org/bots/api#getfile
96 | """
97 | method = 'getFile?' + 'file_id=' + str(file_id)
98 | res = requests.post(self.api_url + method, file_id)
99 | try:
100 | file_path = res.json()['result']['file_path']
101 | # Determine the fileName. Use modified file_path if none given.
102 | if not filename:
103 | filename = file_path[file_path.rfind('/') + 1:]
104 | except (KeyError, ValueError):
105 | return "500 - Failed parsing the file link from API response."
106 |
107 | if not os.path.exists(self.dirDownloads):
108 | os.mkdir(self.dirDownloads)
109 |
110 | local_path = os.path.join(self.dirDownloads, filename)
111 |
112 | # Download file as stream.
113 | res = requests.get(self.file_url + file_path, stream=True)
114 | if res.status_code == 200:
115 | try:
116 | with open(local_path, 'wb') as f:
117 | for chunk in res:
118 | f.write(chunk)
119 | except IOError:
120 | pass
121 | return '200 - {} written.'.format(local_path)
122 | else:
123 | return '404 - Error accessing {}'.format(file_path)
124 |
125 | def session_dump(self):
126 | resp = self.get_updates()
127 | try:
128 | if not os.path.exists(self.dirDownloads):
129 | os.mkdir(self.dirDownloads)
130 | local_path = os.path.join(self.dirDownloads, 'session_dump.json')
131 |
132 | with open(local_path, 'w+', encoding='utf-8') as outfile:
133 | json.dump(resp, outfile)
134 | except IOError as io_err:
135 | print(io_err)
136 | exit(1)
137 |
--------------------------------------------------------------------------------
/notifly/telegram.py:
--------------------------------------------------------------------------------
1 | """ Telegram Wrapper"""
2 |
3 | import json
4 | import os
5 | import sys
6 | import requests
7 | from notifly.tf_notifier import TfNotifier
8 |
9 |
10 | class Notifier(TfNotifier):
11 | def __init__(self, token, dir_download= './downloads'):
12 | """
13 | Initialize the telegram client using tokens to access the HTTP API
14 |
15 | Args:
16 | token (string): API token [Mandatory].
17 | Args:
18 | dir_download (string): -> Storage location for dumping payload [default = "./downloads"]
19 | """
20 | self.token = token
21 | self.api_url = "https://api.telegram.org/bot{}/".format(token)
22 | self.file_url = "https://api.telegram.org/file/bot{}/".format(token)
23 | self.dirDownloads = dir_download
24 |
25 | def __get_updates(self, offset=0, timeout=10):
26 | """
27 | Get the latest updates as json file
28 |
29 | Args:
30 | offset (int): default value 0
31 | timeout (int): Timeout for the request post (default value 10)
32 | Returns:
33 | The json results
34 | Raises:
35 | TimeoutError
36 | """
37 | method = 'getUpdates'
38 | params = {'timeout': timeout, 'offset': offset}
39 | resp = requests.get(self.api_url + method, params)
40 | try:
41 | return resp.json()['result']
42 | except KeyError:
43 | print('TimeoutError')
44 | sys.exit(1)
45 | except requests.exceptions.ConnectionError as err:
46 | print('HTTPS Connection Error - Unable to reach the destination')
47 |
48 | def __chat_id_response(self) -> int:
49 | """
50 | Fetches the latest chat id from the client
51 |
52 | Returns:
53 | The latest chat-id of the client
54 | Raises:
55 | TimeoutError
56 | """
57 | try:
58 | fetch_updates = self.__get_updates()
59 | return fetch_updates[0]['message']['chat']['id']
60 | except TimeoutError as tm_err:
61 | print(tm_err)
62 | sys.exit(1)
63 |
64 | def send_message(self, msg, notification=False) -> object:
65 | """
66 | Function to send message
67 |
68 | Args:
69 | msg (string): Enter the message to post
70 | notification (bool): Disable_Notification (default=False)
71 | Returns:
72 | The status_code of the post operation (send_message)
73 | Raises:
74 | IndexError
75 | """
76 | try:
77 | method = 'sendMessage'
78 | params = {'chat_id': self.__chat_id_response(), 'text': msg,
79 | 'parse_mode': 'HTML', 'disable_notification': notification}
80 | return requests.post(self.api_url + method, params)
81 | except IndexError:
82 | print('Time out error')
83 | sys.exit(1)
84 | except requests.exceptions.ConnectionError as err:
85 | print('HTTPS Connection Error - Unable to reach the destination')
86 | sys.exit(1)
87 |
88 | def send_image(self, img_path) -> object:
89 | """
90 | Function to send image via telegram
91 |
92 | Args:
93 | img_path (basestring): Enter the file_path to send the image file
94 | Returns:
95 | The status_code of the post operation (send_image)
96 | Raises:
97 | FileNotFoundError, InvalidFormatError
98 | """
99 | method = 'sendPhoto?' + 'chat_id=' + str(self.__chat_id_response())
100 | if img_path[-4:] not in ['.jpg', '.png']:
101 | print('Invalid File Format, please use .jpg or .png format')
102 | sys.exit(1)
103 | try:
104 | files = {'photo': open(img_path, 'rb')}
105 | return requests.post(self.api_url + method, files = files)
106 | except FileNotFoundError as fl_err:
107 | print(fl_err)
108 | sys.exit(1)
109 | except requests.exceptions.ConnectionError as err:
110 | print('HTTPS Connection Error - Unable to reach the destination')
111 | sys.exit(1)
112 |
113 | def send_file(self, file_path) -> object:
114 | """
115 | Function to send documents via telegram
116 |
117 | Args:
118 | file_path (basestring): Enter the file_path to send the image file
119 | Returns:
120 | The status_code of the post operation (send_document)
121 | Raises:
122 | FileNotFoundError, TimeoutError
123 | """
124 | method = 'sendDocument?' + 'chat_id=' + str(self.__chat_id_response())
125 | try:
126 | files = {'document': open(file_path, 'rb')}
127 | return requests.post(self.api_url + method, files = files)
128 | except FileNotFoundError as fn_err:
129 | print(fn_err)
130 | sys.exit(1)
131 | except TimeoutError as tm_err:
132 | print(tm_err)
133 | sys.exit(1)
134 | except requests.exceptions.ConnectionError as err:
135 | print('HTTPS Connection Error - Unable to reach the destination')
136 | sys.exit(1)
137 |
138 | def session_dump(self) -> json:
139 | """
140 | Function to Dump all the data from the telegram client during the current session
141 |
142 | Returns:
143 | Dumps the session details in the form of json file.
144 | Raises:
145 | IOError
146 | """
147 | resp = self.__get_updates()
148 | try:
149 | if not os.path.exists(self.dirDownloads):
150 | os.mkdir(self.dirDownloads)
151 | local_path = os.path.join(self.dirDownloads, 'session_dump.json')
152 |
153 | with open(local_path, 'w+', encoding='utf-8') as outfile:
154 | json.dump(resp, outfile)
155 | except IOError as io_err:
156 | print(io_err)
157 | sys.exit(1)
158 |
--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | notifly API documentation
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
Package notifly
23 |
24 |
25 |
Notifly Documentation
26 |
Discord: (object): Discord wrapper to send message, images to the channel using Webhooks
27 | Slack (object): Slack wrapper to send message, images to the channel using API
28 | Telegram (object): Telegram Web Client wrapper to send images, files over the bot using API
29 |
30 |
31 | Expand source code
32 |
33 |
"""
34 | Notifly Documentation
35 |
36 | Discord: (object): Discord wrapper to send message, images to the channel using Webhooks
37 | Slack (object): Slack wrapper to send message, images to the channel using API
38 | Telegram (object): Telegram Web Client wrapper to send images, files over the bot using API
39 | """
Outputs the response of message on post operation.
192 |
Raises
193 |
FileNotFoundError
194 |
195 |
196 | Expand source code
197 |
198 |
def send_file(self, file_path) -> object:
199 | """
200 | Function to post an file to the slack channel.
201 |
202 | Args:
203 | file_path (string): Enter the path of the file to be sent.
204 | Returns:
205 | Outputs the response of message on post operation.
206 | Raises:
207 | FileNotFoundError
208 | """
209 | try:
210 | return self.__client.files_upload(file = file_path, channels = self.__channel)
211 | except FileNotFoundError as fl_err:
212 | print(fl_err)
213 |
214 |
215 |
216 | def send_message(self, msg) ‑> object
217 |
218 |
219 |
Function to post message to the slack channel.
220 |
Args
221 |
222 |
msg : string
223 |
Enter your message to post.
224 |
225 |
Returns
226 |
Outputs the response of message on post operation.
227 |
Raises
228 |
SlackApiError
229 |
230 |
231 | Expand source code
232 |
233 |
def send_message(self, msg) -> object: #TODO Add unicode check
234 | """
235 | Function to post message to the slack channel.
236 |
237 | Args:
238 | msg (string): Enter your message to post.
239 | Returns:
240 | Outputs the response of message on post operation.
241 | Raises:
242 | SlackApiError
243 | """
244 | try:
245 | return self.__client.chat_postMessage(channel = self.__channel, text = msg)
246 | except SlackApiError as api_err:
247 | print(f"Got an error: {api_err.response['error']}")
248 | exit(1)
The status_code of the post operation (send_message)
447 |
Raises
448 |
IndexError
449 |
450 |
451 | Expand source code
452 |
453 |
def send_message(self, msg, notification=False) -> object:
454 | """
455 | Function to send message
456 |
457 | Args:
458 | msg (string): Enter the message to post
459 | notification (bool): Disable_Notification (default=False)
460 | Returns:
461 | The status_code of the post operation (send_message)
462 | Raises:
463 | IndexError
464 | """
465 | try:
466 | method = 'sendMessage'
467 | params = {'chat_id': self.__chat_id_response(), 'text': msg,
468 | 'parse_mode': 'HTML', 'disable_notification': notification}
469 | return requests.post(self.api_url + method, params)
470 | except IndexError:
471 | print('Time out error')
472 | exit(1)
473 |
474 |
475 |
476 | def session_dump(self) ‑>
477 |
478 |
479 |
Function to Dump all the data from the telegram client during the current session
480 |
Returns
481 |
Dumps the session details in the form of json file.
482 |
Raises
483 |
IOError
484 |
485 |
486 | Expand source code
487 |
488 |
def session_dump(self) -> json:
489 | """
490 | Function to Dump all the data from the telegram client during the current session
491 |
492 | Returns:
493 | Dumps the session details in the form of json file.
494 | Raises:
495 | IOError
496 | """
497 | resp = self.__get_updates()
498 | try:
499 | if not os.path.exists(self.dirDownloads):
500 | os.mkdir(self.dirDownloads)
501 | local_path = os.path.join(self.dirDownloads, 'session_dump.json')
502 |
503 | with open(local_path, 'w+', encoding='utf-8') as outfile:
504 | json.dump(resp, outfile)
505 | except IOError as io_err:
506 | print(io_err)
507 | exit(1)