Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
adbenitez committed Jun 18, 2024
0 parents commit dae23f8
Show file tree
Hide file tree
Showing 9 changed files with 965 additions and 0 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/python-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
name: CI

on:
push:
branches: [ main, master ]
tags:
- 'v*.*.*'
pull_request:
branches: [ main, master ]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8', '3.11']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install '.[dev]'
- name: Check code with black
run: |
black --check .
- name: Lint code
run: |
pylama
- name: Test with pytest
run: |
#pytest
deploy:
needs: test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
with:
python-version: '3.x'
- id: check-tag
run: |
if [[ "${{ github.event.ref }}" =~ ^refs/tags/v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo ::set-output name=match::true
fi
- name: Create PyPI release
uses: casperdcl/deploy-pypi@v2
with:
password: ${{ secrets.PYPI_TOKEN }}
build: true
# only upload if a tag is pushed (otherwise just build & check)
upload: ${{ github.event_name == 'push' && steps.check-tag.outputs.match == 'true' }}
13 changes: 13 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
__pycache__
.cache
.mypy_cache
.pytest_cache
build
dist
.eggs
*.egg-info
*/_version.py

*~
.#*
*#
674 changes: 674 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Web to PDF

[![Latest Release](https://img.shields.io/pypi/v/web2pdf.svg)](https://pypi.org/project/web2pdf)
[![CI](https://github.com/deltachat-bot/web2pdf/actions/workflows/python-ci.yml/badge.svg)](https://github.com/deltachat-bot/web2pdf/actions/workflows/python-ci.yml)
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)

Delta Chat bot that allows to fetch websites as PDF. Just send any URL to the bot
in private or add it to get a PDF in any message containing an URL.

## Install

```sh
pip install web2pdf
```

You also need to install wkhtmltopdf package. Debian/Ubuntu example:

```sh
sudo apt-get install wkhtmltopdf
```

**Warning!** Version in debian/ubuntu repos have reduced functionality (because it
is compiled without the wkhtmltopdf QT patches), such as adding outlines, headers,
footers, TOC etc. To use this options you should install static binary from
wkhtmltopdf site: https://wkhtmltopdf.org/

## Usage

Configure the bot's Delta Chat account:

```sh
web2pdf init [email protected] PASSWORD
```

You can run `web2pdf init` several times to add multiple different accounts to the
bot so it can be reached in more than one email address.

The bot's display name, avatar and status/signature can also be tweaked:

```
web2pdf config selfavatar "/path/to/avatar.png"
web2pdf config displayname "My Bot"
web2pdf config selfstatus "Hi, I am a Delta Chat bot"
```

To run the bot so it starts processing messages:

```sh
web2pdf serve
```

To see all available options run `web2pdf --help`
3 changes: 3 additions & 0 deletions pylama.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[pylama]
linters=mccabe,pyflakes,pylint,isort,mypy
skip=.*,build/*,tests/*,*/_version.py,*/flycheck_*
50 changes: 50 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
[build-system]
requires = ["setuptools>=64", "setuptools_scm>=8"]
build-backend = "setuptools.build_meta"

[project]
name = "web2pdf"
description = "Download websites as PDF in Delta Chat"
dynamic = ["version"]
readme = "README.md"
requires-python = ">=3.8"
keywords = ["deltachat", "bot", "web", "pdf"]
authors = [
{name = "adbenitez", email = "[email protected]"},
]
classifiers = [
"Development Status :: 4 - Beta",
"Programming Language :: Python",
"License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
]
dependencies = [
"deltabot-cli>=6.2.0,<7.0",
"pdfkit",
]

[project.urls]
Homepage = "https://github.com/deltachat-bot/web2pdf"

[project.optional-dependencies]
dev = [
"black",
"mypy",
"isort",
"pylint",
"pylama",
"pytest",
"types-requests",
]

[project.scripts]
web2pdf = "web2pdf:main"

[tool.setuptools_scm]
# can be empty if no extra settings are needed, presence enables setuptools_scm
version_file = "web2pdf/_version.py"

[tool.isort]
profile = "black"

[tool.mypy]
ignore_missing_imports = "True"
11 changes: 11 additions & 0 deletions web2pdf/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Web2PDF bot."""

from .hooks import cli


def main() -> None:
"""Run the application."""
try:
cli.start()
except KeyboardInterrupt:
pass
5 changes: 5 additions & 0 deletions web2pdf/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
"""Support for package execution."""

from . import main

main()
101 changes: 101 additions & 0 deletions web2pdf/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""Event Hooks"""

import re
from argparse import Namespace
from tempfile import NamedTemporaryFile

import pdfkit
from deltabot_cli import BotCli
from deltachat2 import Bot, ChatType, CoreEvent, EventType, MsgData, NewMsgEvent, events
from rich.logging import RichHandler

from ._version import __version__

cli = BotCli("web2pdf")
cli.add_generic_option("-v", "--version", action="version", version=__version__)
cli.add_generic_option(
"--no-time",
help="do not display date timestamp in log messages",
action="store_false",
)


@cli.on_init
def _on_init(bot: Bot, args: Namespace) -> None:
bot.logger.handlers = [
RichHandler(show_path=False, omit_repeated_times=False, show_time=args.no_time)
]
for accid in bot.rpc.get_all_account_ids():
if not bot.rpc.get_config(accid, "displayname"):
bot.rpc.set_config(accid, "displayname", "Web to PDF")
status = "I am a Delta Chat bot, send me /help for more info"
bot.rpc.set_config(accid, "selfstatus", status)
bot.rpc.set_config(accid, "delete_device_after", str(60 * 60 * 24))


@cli.on(events.RawEvent)
def _log_event(bot: Bot, accid: int, event: CoreEvent) -> None:
if event.kind == EventType.INFO:
bot.logger.debug(event.msg)
elif event.kind == EventType.WARNING:
bot.logger.warning(event.msg)
elif event.kind == EventType.ERROR:
bot.logger.error(event.msg)
elif event.kind == EventType.MSG_DELIVERED:
bot.rpc.delete_messages(accid, [event.msg_id])
elif event.kind == EventType.SECUREJOIN_INVITER_PROGRESS:
if event.progress == 1000:
if not bot.rpc.get_contact(accid, event.contact_id).is_bot:
bot.logger.debug("QR scanned by contact id=%s", event.contact_id)
chatid = bot.rpc.create_chat_by_contact_id(accid, event.contact_id)
_send_help(bot, accid, chatid)


@cli.after(events.NewMessage)
def delete_msgs(bot: Bot, accid: int, event: NewMsgEvent) -> None:
"""Delete messages after processing"""
msg = event.msg
bot.rpc.delete_messages(accid, [msg.id])
bot.logger.debug(f"[chat={msg.chat_id}] Deleted message={msg.id}")


@cli.on(events.NewMessage(is_info=False))
def _web2pdf_filter(bot: Bot, accid: int, event: NewMsgEvent) -> None:
if bot.has_command(event.command):
return
msg = event.msg
chat = bot.rpc.get_basic_chat_info(accid, msg.chat_id)
match = re.search(
r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*(),]|"
r"(?:%[0-9a-fA-F][0-9a-fA-F]))+",
msg.text,
)
if match:
url = match.group()
elif msg.text and chat.chat_type == ChatType.SINGLE:
url = msg.text
else:
return
with NamedTemporaryFile(suffix=".pdf") as file:
try:
pdfkit.from_url(url, file.name)
reply = MsgData(file=file.name, quoted_message_id=msg.id)
bot.rpc.send_msg(accid, msg.chat_id, reply)
except Exception as ex: # noqa: W0718
bot.logger.exception(ex)
text = "Failed to retrieve web site, is the URL correct?"
reply = MsgData(text=text, quoted_message_id=msg.id)
bot.rpc.send_msg(accid, msg.chat_id, reply)


@cli.on(events.NewMessage(command="/help"))
def _help(bot: Bot, accid: int, event: NewMsgEvent) -> None:
_send_help(bot, accid, event.msg.chat_id)


def _send_help(bot: Bot, accid: int, chatid: int) -> None:
text = (
"I'm a bot, I allow to retrieve HTML pages as PDF."
" Just send me any link to a website you would like to save as PDF."
)
bot.rpc.send_msg(accid, chatid, MsgData(text=text))

0 comments on commit dae23f8

Please sign in to comment.