Compare commits

..
14 Commits
Author SHA1 Message Date
KKlochko 8400993ad5 Update the README.
continuous-integration/drone/push Build is passing
2023-07-16 15:44:06 +03:00
KKlochko 7222e18811 Fix the command that was "ans", but it must be "asn". 2023-07-16 15:33:27 +03:00
KKlochko 56feecfeff Add the CI/CD configuration.
continuous-integration/drone/push Build is passing
2023-07-16 14:50:59 +03:00
KKlochko 6806a878b6 Remove the README.org. 2023-07-16 14:47:25 +03:00
KKlochko 73ef4003a8 Add commands to execute the package. 2023-07-16 14:31:15 +03:00
KKlochko f5e9da7c83 Update the package information and imports. 2023-07-16 14:30:29 +03:00
KKlochko 27bae3e375 Update the mail information. 2023-07-16 14:26:55 +03:00
KKlochko a7302d8c2e Add the requirements and .gitignore. 2023-07-16 14:25:21 +03:00
KKlochko 1799f5a3c7 (Added) os paths for the data and the config by default.
Added if config is missing, then the hint shows its path.
Updated pyproject.toml information.
2023-02-28 19:33:28 +02:00
KKlochko 1a4405fbbc (Refactor) remove fucntion for creating DB file. 2023-01-15 12:30:33 +02:00
KKlochko 222bf53b25 (Added) TOML configuration file. (Updated) Paths, name, years. 2023-01-14 21:49:58 +02:00
KKlochko 5c16001824 (Updated) 0.6.0 - the cli interface. 2022-08-11 18:32:48 +03:00
KKlochko 7394f88d29 (Added) 0.5.2 - added img/ folder. 2022-07-21 19:08:36 +03:00
KKlochko df496a8592 (Added) 0.5.2 - add pyproject.toml 2022-07-21 19:05:09 +03:00
13 changed files with 517 additions and 64 deletions
+30
View File
@@ -0,0 +1,30 @@
kind: pipeline
type: docker
name: default
steps:
- name: build
image: python:3.10-alpine
commands:
- python3 -m pip install --upgrade pip
- pip3 install -r requirements.txt
- python3 -m pip install --upgrade build
- python3 -m build
when:
branch:
- main
- name: publish
image: python:3.10-alpine
environment:
TWINE_USERNAME:
from_secret: PYPI_USERNAME
TWINE_PASSWORD:
from_secret: PYPI_PASSWORD
commands:
- python3 -m pip install --upgrade twine
- python3 -m twine upload dist/*
when:
branch:
- main
+179
View File
@@ -0,0 +1,179 @@
*~
.\#*
\#*\#
.*.~undo-tree~
# Created by https://www.toptal.com/developers/gitignore/api/python
# Edit at https://www.toptal.com/developers/gitignore?templates=python
### Python ###
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
#pdm.lock
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
# in version control.
# https://pdm.fming.dev/#use-with-ide
.pdm.toml
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
### Python Patch ###
# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml
# ruff
.ruff_cache/
# End of https://www.toptal.com/developers/gitignore/api/python
+31
View File
@@ -20,3 +20,34 @@
** 0.5.1 <2022-07-10> ** 0.5.1 <2022-07-10>
New feature: download posters and use them as icons for notifications. New feature: download posters and use them as icons for notifications.
New small feature: showing information about a current task. New small feature: showing information about a current task.
** 0.5.2 <2022-07-21>
Added pyproject.toml.
Added img/ folder.
** 0.6.0 <2022-08-11>
Updated the cli interface
** 1.0.1 <2023-01-14>
Added TOML configuration file.
Updated paths of files and folders.
Updated my name to my fullname and release years.
** 1.0.2 <2023-01-15>
Refactor the database class and remove function for the creating file.
** 1.1.1 <2023-02-28>
Added os paths for the data and the config by default.
Added if config is missing, then the hint shows its path.
Updated pyproject.toml information.
** 1.1.2 <2023-07-16>
Add the requirements and .gitignore.
** 1.1.3 <2023-07-16>
Update the mail information.
** 1.1.4 <2023-07-16>
Update the package information and imports.
** 1.2.0 <2023-07-16>
Add commands to execute the package.
** 1.2.1 <2023-07-16>
Remove the README.org.
** 1.2.2 <2023-07-16>
Add the CI/CD configuration.
** 1.2.3 <2023-07-16>
Fix the command that was "ans", but it must be "asn".
** 1.2.4 <2023-07-16>
Update the README.
-11
View File
@@ -1,11 +0,0 @@
* Anitube Simple Notification
Anitube Simple Notification is a application made for getting
notification when a content is updated on the web-site
(anitube.in.ua).
*Not ready for using as applications.*
* Author
Kostya Klochko (c) 2022
* License
Under GNU GPL v3 license
+74
View File
@@ -0,0 +1,74 @@
Anitube Simple Notification
===========================
Anitube Simple Notification is a application made for getting
notification when a content is updated on the web-site (anitube.in.ua).
Install
=======
Ensure that python and pip are installed:
.. code:: shell
python --version
python -m pip -V
# or
python3 --version
python3 -m pip -V
Install the package:
.. code:: shell
pip install anitube-simple-notification
# or
pip3 install anitube-simple-notification
Usage
=====
In application folder create file with name ``config.toml``. If a value
is wrong, then a error will be shown. If there no value or a wrong value
then it will be default.
Example of a config file for all options:
.. code:: toml
POSTERS = true
WAITING_PERIOD = 3600
URLS = [
"https://anitube.in.ua/4110-chainsaw-man.html",
"https://anitube.in.ua/4010-overlord-iv.html",
"https://anitube.in.ua/4097-mob-varyat-100-3-sezon.html",
"https://anitube.in.ua/4087-spy-x-family-part-2.html",
]
The last comma of the urls list can be ommited.
Run the program by one of the commands:
.. code:: shell
anitube-simple-notification
asn
Author
======
Kostiantyn Klochko (c) 2022-2023
Donation
========
Monero:
8BCZr3LaciDZUwNUbC8M5gNZTtnPKoT9cMH95YcBoo2k8sg4qaxejYL4Qvp6V21ViqMHj5sHLiuRwhMYxHTVW1HUNAawV6c
|image1|
License
=======
Under GNU GPL v3 license
.. |image1| image:: ./img/monero.png
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

+40
View File
@@ -0,0 +1,40 @@
[build-system]
requires = ["setuptools>=61.0"]
build-backend = "setuptools.build_meta"
[project]
name = "anitube-simple-notification"
version = "1.2.4"
authors = [
{ name="Kostiantyn Klochko", email="kklochko@protonmail.com" },
]
description = "Getting notification from anitube.in.ua."
readme = "README.rst"
license = { file="COPYING" }
requires-python = ">=3.10"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
"Operating System :: OS Independent",
]
dependencies = [
"requests~=2.28.2",
"beautifulsoup4~=4.11.1",
"notify-py~=0.3.39",
"rich~=13.1.0",
"tomli~=2.0.1",
"loguru~=0.5.3",
"platformdirs~=3.0.0"
]
[project.scripts]
asn = "src.main:main"
anitube-simple-notification = "src.main:main"
[project.urls]
"Homepage" = "https://gitlab.com/KKlochko/anitube-simple-notification"
"Bug Tracker" = "https://gitlab.com/KKlochko/anitube-simple-notification/issues"
[tool.setuptools]
packages = ["src"]
+16
View File
@@ -0,0 +1,16 @@
beautifulsoup4==4.11.1
bs4==0.0.1
certifi==2022.12.7
charset-normalizer==3.0.1
commonmark==0.9.1
idna==3.4
jeepney==0.8.0
loguru==0.5.3
notify-py==0.3.39
platformdirs==3.0.0
Pygments==2.14.0
requests==2.28.2
rich==13.1.0
soupsieve==2.3.2.post1
tomli==2.0.1
urllib3==1.26.14
+88
View File
@@ -0,0 +1,88 @@
##########################################################################
# Copyright (C) 2022-2023 Kostiantyn Klochko <kklochko@protonmail.com> #
# #
# This file is part of Anitube Simple Notification. #
# #
# Anitube Simple Notification is free software: you can redistribute #
# it and/or modify it under the terms of the GNU General Public #
# License as published by the Free Software Foundation, either version #
# 3 of the License, or (at your option) any later version. #
# #
# Anitube Simple Notification is distributed in the hope that it will #
# be useful, but WITHOUT ANY WARRANTY; without even the implied #
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See #
# the GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with Anitube Simple Notification. If not, see #
# <https://www.gnu.org/licenses/>. #
##########################################################################
"""
This module has all for simplify work with the toml configuration file.
"""
from rich.console import Console
import tomli
from pathlib import PosixPath
class Config:
@staticmethod
def config_validation(config: dict) -> bool:
"""Return False if config is invalid. Empty values is valid."""
validations = {
"POSTERS": lambda v: isinstance(v, bool),
"WAITING_PERIOD": lambda v: isinstance(v, int) and v >= 0,
"URLS": lambda l: isinstance(l, list) and len(l) == len(list(filter(str,l))),
}
return all(validations.get(k, lambda x: False)(v) for (k, v) in config.items())
@staticmethod
def config_validation_detail(config: dict) -> dict:
"""Return a dict where key is a field and value is valid status."""
validations = {
"POSTERS": lambda v: isinstance(v, bool),
"WAITING_PERIOD": lambda v: isinstance(v, int) and v >= 0,
"URLS": lambda l: isinstance(l, list) and
len(list(filter(lambda v: isinstance(v,str), l))) and
len(list(filter(lambda v: v.startswith("https://anitube.in.ua/"), l)))
#len(l) == len(list(filter(str,l))),
}
return {k:validations.get(k, lambda x: False)(v) for (k, v) in config.items()}
@staticmethod
def config_validation_error(config: dict):
"""Show error and remove invalid fields."""
validation = Config.config_validation_detail(config)
if not validation.pop("POSTERS", True):
Console().print(f"[red][ERROR] Please, POSTER must be false or true.[/]")
del config["POSTERS"]
if not validation.pop("WAITING_PERIOD", True):
Console().print(
''.join(["[red][ERROR] ",
"Please, WAITING_PERIOD must be an integer that greater",
"than zero or equals to.[/]"]))
del config["WAITING_PERIOD"]
if not validation.pop("URLS", True):
Console().print(
"".join(["[red][ERROR] Please, URLS must be a list of strings"
"which ones are page urls of anitube.in.ua.[/]"]))
del config["URLS"]
@staticmethod
def get_config(config_path:PosixPath, console:Console) -> dict:
"""
Read the configuration file and return dict as configuration.
The configuration file must be in the application folder.
"""
config = {}
try:
with open(config_path, "rb") as file:
config = tomli.load(file)
except FileNotFoundError:
console.print(f"[red][ERROR] Please, create configuration file.[/]")
console.print(f"[yellow][ERROR] The configuration file path: {config_path}.[/]")
except tomli.TOMLDecodeError:
console.print(f"[red][ERROR] Please, check configuration file for correctness.[/]")
Config.config_validation_error(config)
return config
+1 -15
View File
@@ -1,5 +1,5 @@
########################################################################## ##########################################################################
# Copyright (C) 2022 Kostya Klochko <kostya_klochko@ukr.net> # # Copyright (C) 2022-2023 Kostiantyn Klochko <kklochko@protonmail.com> #
# # # #
# This file is part of Anitube Simple Notification. # # This file is part of Anitube Simple Notification. #
# # # #
@@ -29,22 +29,8 @@ class DataBase:
def __init__(self, PATH: str = "user.db", TABLE: str = "animes"): def __init__(self, PATH: str = "user.db", TABLE: str = "animes"):
"""Initialising the connection information.""" """Initialising the connection information."""
self.PATH, self.TABLE = PATH, TABLE self.PATH, self.TABLE = PATH, TABLE
self.database_is_exists()
self.create_table(self.TABLE) self.create_table(self.TABLE)
def database_is_exists(self):
"""Creates DB file if not exists."""
try:
f = open(self.PATH, 'r')
f.close()
except FileNotFoundError:
f = open(self.PATH, 'w')
f.close()
except Exception as e:
print(f"{e=}")
finally:
pass
def create_table(self, table): def create_table(self, table):
"""Create a table in the DB.""" """Create a table in the DB."""
connect = sqlite3.connect(self.PATH) connect = sqlite3.connect(self.PATH)
+47 -30
View File
@@ -1,5 +1,5 @@
########################################################################## ##########################################################################
# Copyright (C) 2022 Kostya Klochko <kostya_klochko@ukr.net> # # Copyright (C) 2022-2023 Kostiantyn Klochko <kklochko@protonmail.com> #
# # # #
# This file is part of Anitube Simple Notification. # # This file is part of Anitube Simple Notification. #
# # # #
@@ -17,64 +17,81 @@
# along with Anitube Simple Notification. If not, see # # along with Anitube Simple Notification. If not, see #
# <https://www.gnu.org/licenses/>. # # <https://www.gnu.org/licenses/>. #
########################################################################## ##########################################################################
from db import DataBase from .db import DataBase
from scraper import Scraper from .scraper import Scraper
from notify import Notification from .notify import Notification
from .config import Config
from rich.console import Console
from rich.progress import track
import time import time
import os
def get_urls(file_path): import platformdirs
"""Read file that containes urls."""
urls = []
with open(file_path, 'r') as file:
for line in file:
if '\n' in line:
line = line.replace('\n', '')
urls.append(line)
return urls
def main(): def main():
# Const sections. # Const sections.
# Console initialising
console = Console()
APPNAME = "anitube-simple-notification"
APPAUTHOR = "KKlochko"
CONFIG_NAME = "config.toml"
BASE_DIR = os.path.dirname(__file__)
DATA_DIR = platformdirs.user_data_dir(APPNAME, APPAUTHOR)
DB_PATH = os.path.join(DATA_DIR, 'user.db')
POSTERS_PATH = os.path.join(DATA_DIR, 'posters')
CONFIG_PATH = platformdirs.user_config_path(APPNAME, APPAUTHOR, CONFIG_NAME)
# check for existing of dirs
if not os.path.exists(DATA_DIR):
os.makedirs(DATA_DIR)
if not os.path.exists(CONFIG_PATH):
os.makedirs(CONFIG_PATH)
config = Config.get_config(CONFIG_PATH, console)
# Here you can change testing headers to yours. # Here you can change testing headers to yours.
HEADERS = { HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0' 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0'
} }
# Default if empty.
MESSAGE = "New episode." MESSAGE = "New episode."
URL_FILE = "urls" WAITING_PERIOD = config.pop('WAITING_PERIOD', 60) # seconds
WAITING_PERIOD = 60 # seconds POSTERS = config.pop('POSTERS', True) # for use poster as icons of notifications
POSTERS = True # For use poster as icons of notifications urls = config.pop('URLS', [])
# Initialising objects # Initialising objects
scr = Scraper(HEADERS) scr = Scraper(HEADERS, POSTERS_PATH)
db = DataBase() db = DataBase(DB_PATH)
# Reading the URL_FILE for making list
urls = get_urls(URL_FILE)
#print(f"{urls}")
# Checks for new urls in file and add as current state # Checks for new urls in file and add as current state
# If one of page has updated then notifing. # If one of page has updated then notifing.
# Repeating the checking with the waiting period. # Repeating the checking with the waiting period.
while True: while True:
print(f"[DOING] Checking for animes [0/{len(urls)}]") console.print(f"[yellow][DOING][/] Checking for animes [0/{len(urls)}]")
count = 0 count = 0
for url in urls: for url in urls:
data = scr.get_anime(url, POSTERS) data = scr.get_anime(url, POSTERS)
if data == None: if data == None:
print(f"[ERROR] A conections trouble is occured.") console.print(f"[red][ERROR][/] A conections trouble is occured.")
continue continue
url, title, status, poster_path = data url, title, status, poster_path = data
print(f"[DOING] Checking for \"{title}\" [{count}/{len(urls)}]") console.print(f"[yellow][DOING][/] Checking for \"{title}\" [{count}/{len(urls)}]")
r = db.add_anime_if(url, title, status, poster_path) r = db.add_anime_if(url, title, status, poster_path)
if r == -1: if r == -1:
n = Notification(title, MESSAGE, poster_path) n = Notification(title, MESSAGE, poster_path)
n.send() n.send()
print(f"[NOTIFICATION] \"{title}\"") console.print(f"[blue bold][NOTIFICATION][/] \"{title}\"")
count+=1 count+=1
print(f"[DONE] Checking for \"{title}\" [{count}/{len(urls)}]") console.print(f"[green][DONE][/] Checking for \"{title}\" [{count}/{len(urls)}]")
print(f"[WAITING] The next check is after {WAITING_PERIOD} seconds") console.print(f"[yellow][WAITING][/] The next check is after {WAITING_PERIOD} seconds")
time.sleep(WAITING_PERIOD) # Sleep while waiting
for n in track(range(WAITING_PERIOD), description="Waiting..."):
time.sleep(1)
if __name__ == "__main__": if __name__ == "__main__":
main() main()
+2 -2
View File
@@ -1,5 +1,5 @@
########################################################################## ##########################################################################
# Copyright (C) 2022 Kostya Klochko <kostya_klochko@ukr.net> # # Copyright (C) 2022-2023 Kostiantyn Klochko <kklochko@protonmail.com> #
# # # #
# This file is part of Anitube Simple Notification. # # This file is part of Anitube Simple Notification. #
# # # #
@@ -27,7 +27,7 @@ from notifypy import Notify
class Notification: class Notification:
"""The handler of notification.""" """The handler of notification."""
def __init__(self, title, message, icon_path=""): def __init__(self, title, message, icon_path=""):
"""Initialising the connection information.""" """Initialising the notification information."""
self.title, self.message, self.icon_path = title, message, icon_path self.title, self.message, self.icon_path = title, message, icon_path
def send(self): def send(self):
+9 -6
View File
@@ -1,5 +1,5 @@
########################################################################## ##########################################################################
# Copyright (C) 2022 Kostya Klochko <kostya_klochko@ukr.net> # # Copyright (C) 2022-2023 Kostiantyn Klochko <kklochko@protonmail.com> #
# # # #
# This file is part of Anitube Simple Notification. # # This file is part of Anitube Simple Notification. #
# # # #
@@ -24,6 +24,8 @@ This module has all for simplify work with scraping.
import requests import requests
from bs4 import BeautifulSoup from bs4 import BeautifulSoup
from rich.console import Console
from rich.progress import track
import os import os
class Scraper: class Scraper:
@@ -63,9 +65,10 @@ class Scraper:
poster_url = "https://anitube.in.ua" + soup.find('span', class_="story_post").find('img').get('src') poster_url = "https://anitube.in.ua" + soup.find('span', class_="story_post").find('img').get('src')
poster_path = f"{self.POSTER_PATH}/{poster_url.split('/')[-1]}" poster_path = f"{self.POSTER_PATH}/{poster_url.split('/')[-1]}"
if GETPOSTER and not self.file_exist(poster_path): if GETPOSTER and not self.file_exist(poster_path):
print(f"[DONWLOADING] The poster for {title}") console = Console()
img = requests.get(poster_url) with console.status("[yellow]Downloading...[/]"):
with open(poster_path,'wb') as file: img = requests.get(poster_url)
file.write(img.content) with open(poster_path,'wb') as file:
print(f"[DONWLOADED] The poster for {title}") file.write(img.content)
console.print(f"[green][DONWLOADED][/] The poster for \"{title}\"")
return [url, title, status, poster_path] return [url, title, status, poster_path]