Skip to content

Commit

Permalink
Add 'freenom-autorenew' hook
Browse files Browse the repository at this point in the history
  • Loading branch information
mondeja committed Jun 29, 2021
1 parent d8d84e3 commit 45a43d1
Show file tree
Hide file tree
Showing 5 changed files with 239 additions and 6 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.4.0
current_version = 1.5.0

[bumpversion:file:setup.cfg]

Expand Down
9 changes: 8 additions & 1 deletion .pre-commit-hooks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
pass_filenames: false
- id: cloudflare-gh-pages-dns
name: cloudflare-gh-pages-dns
entry: cloudflare-gh-pages-dns
entry: cloudflare-gh-pages-dns-hook
description: Check that the DNS records of a domain managed by Cloudflare are properly configured to serve a Github Pages site
language: python
additional_dependencies:
Expand Down Expand Up @@ -38,6 +38,13 @@
language: python
pass_filenames: false
always_run: true
- id: freenom-autorenew
name: freenom-autorenew
entry: freenom-autorenew-hook
description: Renews the free domains of your Freenom account
language: python
always_run: true
pass_filenames: false
- id: root-editorconfig-required
name: root-editorconfig-required
entry: root-editorconfig-required-hook
Expand Down
25 changes: 23 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@

```yaml
- repo: https://github.com/mondeja/pre-commit-hooks
rev: v1.4.0
rev: v1.5.0
hooks:
- id: dev-extras-required
- id: root-editorconfig-required
Expand Down Expand Up @@ -111,6 +111,26 @@ The required DNS records to make it pass are:
- `CF_API_KEY`: [Cloudflare API key][cloudflare-apikey-link] of the user that
is managing the DNS records of the site using [Cloudflare][cloudflare-link].

### **`freenom-autorenew`**

Renews your free [Freenom][freenom-link] domains.

You must set the environment variables `FREENOM_EMAIL` and `FREENOM_PASSWORD`
to give permissions to this hook for entering in your Freenom account.

#### Parameters

- `-domain=DOMAIN`: Domain to renew. This parameter is optional, if you don't
specify it, the hook will renew all of the free domains registered in your
account.
- `-period=DOMAIN`: Period for the new renovation time. This parameter is
optional, if you don't specify it the time will be one year (`12M`).

#### Environment variables

- `FREENOM_EMAIL`: Email of your Freenom account.
- `FREENOM_PASSWORD`: Password of your Freenom account.

### **`root-editorconfig-required`**

Check if your repository has an `.editorconfig` file and if this has a `root`
Expand Down Expand Up @@ -146,8 +166,9 @@ durations...
[tests-image]: https://img.shields.io/github/workflow/status/mondeja/pre-commit-hooks/CI?logo=github&label=tests
[tests-link]: https://github.com/mondeja/pre-commit-hooks/actions?query=workflow%CI

[setup-py-upgrade-link]: https://github.com/asottile/setup-py-upgrade
[cloudflare-link]: https://cloudflare.com
[cloudflare-apikey-link]: https://support.cloudflare.com/hc/en-us/articles/200167836-Managing-API-Tokens-and-Keys
[freenom-link]: https://www.freenom.com
[gh-pages-link]: https://pages.github.com
[pre-commit-po-hooks-link]: https://github.com/mondeja/pre-commit-po-hooks
[setup-py-upgrade-link]: https://github.com/asottile/setup-py-upgrade
203 changes: 203 additions & 0 deletions hooks/freenom_autorenew.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"""Script that auto renews a free Freenom domain."""

import argparse
import functools
import os
import re
import sys

import requests


MOZILLA_USER_AGENT = (
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/79.0.3945.130 Safari/537.36"
)

LOGIN_URL = "https://my.freenom.com/dologin.php"
DOMAIN_STATUS_URL = "https://my.freenom.com/domains.php?a=renewals"
RENEW_DOMAIN_URL = "https://my.freenom.com/domains.php?submitrenewals=true"

TOKEN_PTN = re.compile('name="token" value="(.*?)"', re.I)
DOMAIN_INFO_TPN = re.compile(
r"<tr><td>(.*?)</td><td>[^<]+</td><td>[^<]+"
r'<span class="[^<]+>(\d+?).Days</span>[^&]+&domain=(\d+?)">.*?</tr>',
re.I,
)


class FreeNom:
"""Freenom implementation used to login into Freenom and autorenew free
domains using HTTP requests.
Extracted from https://github.com/SunYufei/freenom
"""

def __init__(self, username: str, password: str):
self._u = username
self._p = password

self._s = requests.Session()
self._s.headers.update(
{
"user-agent": MOZILLA_USER_AGENT,
}
)

def _login(self) -> bool:
self._s.headers.update(
{
"content-type": "application/x-www-form-urlencoded",
"referer": "https://my.freenom.com/clientarea.php",
}
)
r = self._s.post(LOGIN_URL, data={"username": self._u, "password": self._p})
return r.status_code == 200

def renew(self, domain=None, period="12M"):
"""Renew the domains of an account."""
ok = self._login()
if not ok:
sys.stderr.write(
"Failed to login to Freenom.\nPlease, check that you've"
" properly your credentials in 'FREENOM_EMAIL' and"
" 'FREENOM_PASSWORD' environment variables.\n"
)
return

self._s.headers.update({"referer": "https://my.freenom.com/clientarea.php"})
r = self._s.get(DOMAIN_STATUS_URL)

# page token
match = re.search(TOKEN_PTN, r.text)
if not match:
sys.stderr.write("Failed to get token inside Freenom page\n")
return
token = match.group(1)

# renew domains
domains = re.findall(DOMAIN_INFO_TPN, r.text)

for domain_, days, renewal_id in domains:
if domain is not None and domain_ != domain:
continue

days = int(days)
if days < 14:
self._s.headers.update(
{
"referer": (
"https://my.freenom.com/domains.php?a=renewdomain"
"&domain={renewal_id}"
),
"content-type": "application/x-www-form-urlencoded",
}
)
r = self._s.post(
RENEW_DOMAIN_URL,
data={
"token": token,
"renewalid": renewal_id,
f"renewalperiod[{renewal_id}]": period,
"paymentmethod": "credit",
},
)
if r.text.find("Order Confirmation") != -1:
sys.stdout.write(f"{domain_} -> Successful renew\n")
else:
sys.stderr.write(f"{domain_} -> Error renewing!\n")
sys.stdout.write(f"{domain_} -> {days} days for expiration\n")

return True


def check_freenom_auth():
authorization = True

if not os.environ.get("FREENOM_EMAIL"):
sys.stderr.write(
"You must set the environment variable 'FREENOM_EMAIL' with"
" the email used to login into your account.\n"
)
authorization = False

if not os.environ.get("FREENOM_PASSWORD"):
sys.stderr.write(
"You must set the environment variable 'FREENOM_PASSWORD' with"
" the password used to login into your account.\n"
)
authorization = False

return authorization


@functools.lru_cache(maxsize=None)
def freenom_auth_parameters():
return (os.environ["FREENOM_EMAIL"], os.environ["FREENOM_PASSWORD"])


def autorenew_freenom_domain(domain=None, period="12M", quiet=False):
"""Auto renews a free Freenom domain is it's inside the renovation time.
Parameters
----------
domain : str
Domain to renew.
period : str, optional
Period for which to renew. As default, the maximum allowed for free domains.
quiet : bool, optional
If ``True``, don't print messages about what is doing during the process.
"""
if not check_freenom_auth():
return False

freenom = FreeNom(*freenom_auth_parameters())
return freenom.renew(domain=domain, period=period)


def main():
parser = argparse.ArgumentParser()
parser.add_argument("-q", "--quiet", action="store_true", help="Supress output")
parser.add_argument(
"-d",
"-domain",
"--domain",
type=str,
metavar="DOMAIN",
required=False,
default=None,
dest="domain",
help=(
"Freenom domain to renew. By default, all the free domains of your"
" account will be renovated."
),
)
parser.add_argument(
"-p",
"-period",
"--period",
type=str,
metavar="PERIOD",
required=False,
default="12M",
dest="period",
help=(
"Period for the renovation. By default, the maximum allowed by"
" Freenom for free domains, 12 months (12M)."
),
)
args = parser.parse_args()

return (
0
if autorenew_freenom_domain(
domain=args.domain, period=args.period, quiet=args.quiet
)
else 1
)


if __name__ == "__main__":
sys.exit(main())
6 changes: 4 additions & 2 deletions setup.cfg
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
[metadata]
name = mondeja_pre_commit_hooks
version = 1.4.0
version = 1.5.0
description = My own useful pre-commit hooks
long_description = file: README.md
long_description_content_type = text/markdown
url = https://github.com/mondeja/pre-commit-hooks
author = Alvaro Mondejar
author = Alvaro Mondejar Rubio
author_email = mondejar1994@gmail.com
license = BSD-3-Clause
license_file = LICENSE
Expand Down Expand Up @@ -34,6 +34,7 @@ console_scripts =
add-pre-commit-hook = hooks.add_pre_commit_hook:main
cloudflare-gh-pages-dns-hook = hooks.cf_gh_pages_dns_records:main
dev-extras-required-hook = hooks.dev_extras_required:main
freenom-autorenew-hook = hooks.freenom_autorenew:main
nameservers-endswith-hook = hooks.nameservers_endswith:main
root-editorconfig-required-hook = hooks.root_editorconfig_required:main
wavelint-hook = hooks.wavelint:main
Expand Down Expand Up @@ -70,6 +71,7 @@ extend-ignore =
W503,
D103,
D104,
D107,
D205,
D400,
D412,
Expand Down

0 comments on commit 45a43d1

Please sign in to comment.