topbanner_forum
  *

avatar image

Welcome, Guest. Please login or register.
Did you miss your activation email?

Login with username, password and session length
  • Sunday January 12, 2025, 3:58 pm
  • Proudly celebrating 15+ years online.
  • Donate now to become a lifetime supporting member of the site and get a non-expiring license key for all of our programs.
  • donate

Author Topic: Variable-speed repeated button press  (Read 4801 times)

nkormanik

  • Participant
  • Joined in 2010
  • *
  • Posts: 559
    • View Profile
    • Donate to Member
Variable-speed repeated button press
« on: July 02, 2023, 01:56 AM »
Imagine repeatedly pressing the Page-Down key.  It can get old fast.  Poor index finger.

Suppose we have a Page-Down that automatically repeats.  Once every 5 seconds (the 'default').  For 100 rounds.

How cool would that be??!!

Here's the huge challenge:  Create the above.  With the following addition....

If user presses the Ctrl-Minus key, we multiply the 5 seconds by 1.05, and use that value in between each round.

If user presses the Ctrl-Plus key, we multiply the 5 seconds by 0.95, and use that value in between each round.

The idea is to get a 'variable-speed' repeated button press.

Here is some of the logic, as I see it:

There is a 'default' amount of time between button presses.  Say, 5 seconds.  There is some variable somewhere holding this value.  Let's call the variable "Rest."

Every time the user hits Ctrl-Minus, the little program looks for the variable "Rest," multiplies it by 1.05, and writes that result back to the variable location.  So, "Rest" is now 5.25.

If user hits Ctrl-Minus another time, current value of "Rest" is multiplied by 1.05, with the result then written to the variable.  "Rest" then becomes 5.51.  Etc.  Etc.

Hitting Ctrl-Plus has the opposite effect.  "Rest" value is multiplied by 0.95.

So, the 'loop' for each round looks something like this:

1.  Check value of "Rest."
2.  Wait that long.
3.  Press the Page-Down key.

Any thoughts greatly appreciated.  If you were to write such a program, what programming language would you use?

Nicholas Kormanik




paradisusvic

  • Participant
  • Joined in 2024
  • *
  • Posts: 79
  • Call me Vic!
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #1 on: January 07, 2025, 03:25 PM »
Hi Nick, I'm doing this as a quickie release for N.A.N.Y.  :Thmbsup:

what programming language would you use?

The programming language is C#.
My name's Victor but do feel free to call me Vic!

❤️ Support on Patreon @ www.patreon.com/paradisusio

GitHub: github.com/paradisusio

🌟 Good projects deserve support 🌎🌍🌏

✉ Email / Extend your PayPal support ❤️: paradisusvic@gmail.com
« Last Edit: January 08, 2025, 06:57 AM by paradisusvic »

nkormanik

  • Participant
  • Joined in 2010
  • *
  • Posts: 559
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #2 on: January 07, 2025, 04:36 PM »
Sounds wonderful, Vic!

I hope you can accomplish this.  Hope others here toss in their two cents to make the mission clearer for as many users as possible.

Ctrl-plus = page-down rate a little faster in our reader program
Ctrl-minus = page-down rate a little slower in our reader program


I use Calibre for reading epubs.  I've requested for years for them to incorporate something like this.  Seems that it should be relatively easy.  I don't really understand why it's not.  What am I missing??

Guess you'll have to be the one.

Thanks!!


skwire

  • Global Moderator
  • Joined in 2005
  • *****
  • Posts: 5,290
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #3 on: January 07, 2025, 05:24 PM »
Hope others here toss in their two cents to make the mission clearer for as many users as possible.

Here's a quick'n'dirty AutoHotkey script that does what you want.  Notes:

  • No GUI.
  • When you start the script, you have five seconds to focus the your target window.
  • Instead of your multiplier suggestion, the following script simply adds or subtracts 500 milliseconds from the current delay.  Of course, you could change this to, say, 250, if you want more granularity.
  • You can hover over the tray icon to see the current delay in milliseconds.
  • Control+Escape will shut down the script.

Code: Autohotkey [Select]
  1. FileEncoding, UTF-8-RAW
  2.  
  3. nDelay := 5000 ; Starting delay in milliseconds.
  4. Menu, Tray, Tip, % "Delay: " . nDelay " ms"
  5.  
  6. Sleep, 5000 ; Five second delay to focus target window.
  7.  
  8. {
  9.     SendInput, {PgDn}
  10.     Sleep, % nDelay
  11. }
  12.  
  13. Return ; End of auto-execute section.
  14.  
  15. ^NumpadAdd:: ; Ctrl + Numpad+Plus key.
  16. ^=::         ; Ctrl + equals/plus key.
  17. {
  18.     nDelay -= 500 ; Subtract 500 milliseconds from delay.
  19.     Menu, Tray, Tip, % "Delay: " . nDelay " ms"
  20. }
  21. Return
  22.  
  23. ^NumpadSub:: ; Ctrl + NumpadMinus key.
  24. ^-::         ; Ctrl + dash/minus key.
  25. {
  26.     nDelay += 500 ; Add 500 milliseconds to delay.
  27.     Menu, Tray, Tip, % "Delay: " . nDelay " ms"
  28. }
  29. Return
  30.  
  31.  ^ESC:: ExitApp ; Ctrl + Escape to kill the script.

Shades

  • Member
  • Joined in 2006
  • **
  • Posts: 2,944
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #4 on: January 07, 2025, 10:53 PM »
Imagine repeatedly pressing the Page-Down key.  It can get old fast.  Poor index finger.

Agree with you there about automatic scrolling.

My solution is/was both much more involved, but also simpler.

My take:
Really liking the Calibre software, but I found that keeping up the library between multiple computers is a bit of a hassle. And as browsers keep taking over my PDF viewer preferences (darned Edge!!!!) I thought, let's just see if there is an online version of Calibre that I can self-host. Guess what, there is indeed someone who did do this. As I have a spare desktop, I turned that into a Proxmox node. On that node I created a Linux VM (Ubuntu Server LTS, so no GUI of any kind). And then I followed the instructions from here: calibre-web

Actually, I have 2 spare desktops, the other one has the Proxmox Backup System installed onto it. This makes automated backups really simple. So I don't have to worry too much about messing the Calibre-web VM up or lose the books that are now stored inside it.

Automatic scrolling in a web-browser is already supported for years, so it is simply visiting the calibre-web instance in my LAN with a browser from any computer, enabling automatic scrolling, adjust the rate to one that is preferred at that moment and read at my leisure.

So you can see that my solution is way more involved, but also simpler...in a way.

On a somewhat related side-note:
Might even see if there is a (self-hostable) LLM out there that can "read aloud" the book by simply pointing it to the URL that calibre-web assigned to the book I want it to read to me. As sometimes I only have bandwidth enough (mentally) to listen to a book, while I'm doing something else. If listening to a book or almost any other type of documentation, professionally or otherwise, is more your thing, you could try and make the book(s)/document(s) you want to "hear" available to Google's NotebookLM.

It "ingests" the content and turns it into a podcast between 2 AI persons (male and female), which sound very convincing discussing the document(s) you provided.

skwire

  • Global Moderator
  • Joined in 2005
  • *****
  • Posts: 5,290
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #5 on: January 08, 2025, 11:55 AM »
I turned that into a Proxmox node

FWIW, I've been a big Proxmox user for the last few years.  Love it.

paradisusvic

  • Participant
  • Joined in 2024
  • *
  • Posts: 79
  • Call me Vic!
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #6 on: January 08, 2025, 01:37 PM »
Guess you'll have to be the one.

Whoah! Skwire became the one! :Thmbsup:



Can you please confirm Skwire's script suits your needs, or do you still prefer a GUI? (I'm okay and happy either way :))

(I now proceed to assist another fellow with a N.A.N.Y. release!)
My name's Victor but do feel free to call me Vic!

❤️ Support on Patreon @ www.patreon.com/paradisusio

GitHub: github.com/paradisusio

🌟 Good projects deserve support 🌎🌍🌏

✉ Email / Extend your PayPal support ❤️: paradisusvic@gmail.com

nkormanik

  • Participant
  • Joined in 2010
  • *
  • Posts: 559
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #7 on: January 08, 2025, 04:20 PM »
My preference would be the option that serves the greatest needs out there.

In Windows presently there seems to be no native program for accomplishing what is outlined above, strangely, in my view.

In Android there is a very good reading program with such functionality built in -- Moon+ Reader.  Android programs can be run in Windows -- for at least the present, word is that Microsoft will dump this ability -- but it involves extra overhead and clunkiness.

Skwire's Autohotkey approach works, but is that the best solution for the greatest number of people?

Maybe some others here will have an opinion?

The very best solution would be for Calibre to build this function in.  Calibre does have 'auto scrolling', but it is just the entire page scrolling up one line at a time, which, to me, seems silly.  Far better to have the entire page changing, as works with page up and page down.

nkormanik

  • Participant
  • Joined in 2010
  • *
  • Posts: 559
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #8 on: January 08, 2025, 04:32 PM »
Below is an 'enhanced' Autohotkey version, complements of ChatGPT:

The provided AutoHotkey script (Skwire's) is a simple and effective program to repeatedly send the "Page Down" key (`PgDn`) to a target window at adjustable intervals. It has some solid features, but there are several potential improvements for better functionality, flexibility, and user experience. Here's a breakdown of the current features and suggestions for improvement:

---

### Current Features:
1. **Auto Page Down Loop:**
   - The script repeatedly sends the `PgDn` key at intervals defined by `nDelay`.
   - The delay can be adjusted dynamically using keyboard shortcuts.

2. **Dynamic Delay Adjustment:**
   - Use `Ctrl + NumpadAdd` or `Ctrl + Equals` to decrease delay by 500 ms.
   - Use `Ctrl + NumpadSub` or `Ctrl + Dash` to increase delay by 500 ms.
   - The delay is displayed in the tray tooltip for feedback.

3. **Termination Shortcut:**
   - Use `Ctrl + ESC` to immediately exit the script.

4. **Efficient Performance Settings:**
   - `SetBatchLines, -1` and `SetControlDelay, -1` are used to enhance script performance.

5. **File Encoding:**
   - Ensures UTF-8-RAW encoding for compatibility.

---

### Suggestions for Improvement:

#### 1. **Add Bounds for `nDelay`:**
   Prevent the delay from being too small (e.g., less than 100 ms) or unreasonably large.
   ```ahk
   nDelay := Max(100, Min(nDelay, 10000)) ; Clamp delay between 100 ms and 10,000 ms.
   ```

#### 2. **Display Current Delay on Adjustment:**
   Include a notification or visual feedback to show the updated delay when it changes, beyond just the tooltip.
   ```ahk
   ToolTip, Delay adjusted to: %nDelay% ms
   SetTimer, RemoveToolTip, -1000 ; Automatically removes the tooltip after 1 second.
   ```

#### 3. **Add Start/Stop Toggle:**
   Allow users to pause and resume the loop with a toggle key.
   ```ahk
   toggle := true
   ^Space:: ; Ctrl + Space to toggle start/stop.
   toggle := !toggle
   if (toggle)
       SetTimer, PageDownLoop, % nDelay
   else
       SetTimer, PageDownLoop, Off
   Return
   ```

#### 4. **Enhanced Error Handling:**
   Include safeguards to ensure the script handles unexpected conditions, such as missing permissions or hidden target windows.

#### 5. **Customize Target Window:**
   Allow users to specify the target window for sending the `PgDn` key, instead of assuming the focused window.
   ```ahk
   WinGet, targetWindow, ID, A ; Get the active window.
   ```

#### 6. **Improved Tray Menu Options:**
   Add a tray menu to control the script directly without needing keyboard shortcuts.
   ```ahk
   Menu, Tray, Add, Start/Stop Loop, ToggleLoop
   Menu, Tray, Add, Increase Delay, IncreaseDelay
   Menu, Tray, Add, Decrease Delay, DecreaseDelay
   Menu, Tray, Add, Exit, ExitApp
   ```

#### 7. **Save/Restore Delay Setting:**
   Persist the delay setting between script sessions using an `.ini` file or similar approach.
   ```ahk
   IniRead, nDelay, Settings.ini, General, Delay, 5000
   OnExit, SaveSettings
   SaveSettings:
       IniWrite, %nDelay%, Settings.ini, General, Delay
   Return
   ```

#### 8. **Add Hotkey Feedback:**
   Add audible or visual feedback when using hotkeys for better user awareness.

#### 9. **Option to Adjust Step Size:**
   Allow dynamic adjustment of the increment/decrement step size (currently fixed at 500 ms).
   ```ahk
   stepSize := 500 ; Default step size for delay adjustment.
   ^+NumpadAdd::stepSize += 100 ; Ctrl + Shift + Numpad+ to increase step size.
   ^+NumpadSub::stepSize -= 100 ; Ctrl + Shift + Numpad- to decrease step size.
   ```

#### 10. **Prevent Simultaneous Inputs:**
   Avoid sending multiple `PgDn` inputs if the delay is too short and the target window is unresponsive.

#### 11. **Comments for Clarity:**
   Include detailed comments for better readability and maintainability.

---

### Revised Code with Improvements:

```ahk
#SingleInstance, Force
SetBatchLines, -1
SetControlDelay, -1
DetectHiddenWindows, On
SetWorkingDir, %A_ScriptDir%
FileEncoding, UTF-8-RAW

; Initialize variables
nDelay := 5000 ; Starting delay in milliseconds.
stepSize := 500 ; Step size for delay adjustment.
toggle := true ; Loop is active by default.

Menu, Tray, Tip, % "Delay: " . nDelay " ms"
Sleep, 5000 ; Initial delay to focus the target window.

; Main Loop
SetTimer, PageDownLoop, %nDelay%

PageDownLoop:
    if (toggle) {
        SendInput, {PgDn}
    }
Return

; Hotkeys to adjust delay
^NumpadAdd::
^=:: ; Increase speed
    nDelay := Max(100, nDelay - stepSize)
    UpdateDelay()
Return

^NumpadSub::
^-:: ; Decrease speed
    nDelay := Min(10000, nDelay + stepSize)
    UpdateDelay()
Return

; Pause/Resume toggle
^Space::
    toggle := !toggle
    if (toggle)
        SetTimer, PageDownLoop, % nDelay
    else
        SetTimer, PageDownLoop, Off
    ToolTip, Loop % (toggle ? "Resumed" : "Paused")
    SetTimer, RemoveToolTip, -1000
Return

; Adjust step size
^+NumpadAdd::
    stepSize := Min(1000, stepSize + 100)
    ToolTip, Step size: %stepSize% ms
    SetTimer, RemoveToolTip, -1000
Return

^+NumpadSub::
    stepSize := Max(100, stepSize - 100)
    ToolTip, Step size: %stepSize% ms
    SetTimer, RemoveToolTip, -1000
Return

; Exit script
^ESC::ExitApp

; Update delay and display feedback
UpdateDelay() {
    global nDelay
    Menu, Tray, Tip, % "Delay: " . nDelay " ms"
    ToolTip, Delay adjusted to: %nDelay% ms
    SetTimer, RemoveToolTip, -1000
}

RemoveToolTip:
    ToolTip
Return
```

---

### Key Improvements:
1. Added bounds for `nDelay`.
2. Enhanced visual feedback with `ToolTip`.
3. Introduced a toggle for starting/stopping the loop.
4. Added tray menu for user control.
5. Made delay adjustment step size configurable.
6. Included detailed comments for clarity.

This revised version is more user-friendly, robust, and flexible while maintaining high performance.

nkormanik

  • Participant
  • Joined in 2010
  • *
  • Posts: 559
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #9 on: January 08, 2025, 04:38 PM »
Below is an enhanced Python version, complements of ChatGPT:

Using **PyQt** is an excellent choice for creating a robust, visually appealing, and fully functional GUI application. Below is an enhanced Python script leveraging **PyQt6**, which offers modern GUI features and is well-suited for a program like this.

---

### Python Program with PyQt6

```python
import sys
import pyautogui
import threading
import time
from PyQt6.QtWidgets import (
    QApplication,
    QMainWindow,
    QLabel,
    QPushButton,
    QVBoxLayout,
    QWidget,
    QSpinBox,
)
from PyQt6.QtCore import QTimer


class PageDownApp(QMainWindow):
    def __init__(self):
        super().__init__()

        # Initialize parameters
        self.n_delay = 5  # Delay in seconds
        self.is_running = False
        self.thread = None

        # Set up GUI
        self.setWindowTitle("Page Down Automation")
        self.setGeometry(100, 100, 400, 300)

        # Main layout
        layout = QVBoxLayout()

        # Status Label
        self.status_label = QLabel("Status: Stopped", self)
        self.status_label.setStyleSheet("font-size: 14px; color: black;")
        layout.addWidget(self.status_label)

        # Start Button
        self.start_button = QPushButton("Start", self)
        self.start_button.setStyleSheet("background-color: green; color: white; font-size: 14px;")
        self.start_button.clicked.connect(self.start_loop)
        layout.addWidget(self.start_button)

        # Stop Button
        self.stop_button = QPushButton("Stop", self)
        self.stop_button.setStyleSheet("background-color: red; color: white; font-size: 14px;")
        self.stop_button.clicked.connect(self.stop_loop)
        layout.addWidget(self.stop_button)

        # Delay Adjuster
        self.delay_label = QLabel("Set Delay (seconds):", self)
        self.delay_label.setStyleSheet("font-size: 14px;")
        layout.addWidget(self.delay_label)

        self.delay_spinbox = QSpinBox(self)
        self.delay_spinbox.setValue(self.n_delay)
        self.delay_spinbox.setRange(1, 60)  # Set delay range between 1-60 seconds
        self.delay_spinbox.valueChanged.connect(self.update_delay)
        layout.addWidget(self.delay_spinbox)

        # Exit Button
        self.exit_button = QPushButton("Exit", self)
        self.exit_button.setStyleSheet("background-color: gray; color: white; font-size: 14px;")
        self.exit_button.clicked.connect(self.exit_app)
        layout.addWidget(self.exit_button)

        # Set main layout
        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

    def update_delay(self):
        """Update the delay value."""
        self.n_delay = self.delay_spinbox.value()
        self.update_status()

    def update_status(self):
        """Update the status label."""
        self.status_label.setText(
            f"Status: {'Running' if self.is_running else 'Stopped'} (Delay: {self.n_delay} sec)"
        )

    def start_loop(self):
        """Start the PgDn loop."""
        if self.is_running:
            return
        self.is_running = True
        self.update_status()
        self.thread = threading.Thread(target=self.page_down_loop, daemon=True)
        self.thread.start()

    def stop_loop(self):
        """Stop the PgDn loop."""
        self.is_running = False
        self.update_status()

    def page_down_loop(self):
        """Continuously send PgDn key presses."""
        while self.is_running:
            pyautogui.press("pagedown")
            time.sleep(self.n_delay)

    def exit_app(self):
        """Handle program exit."""
        self.stop_loop()
        QApplication.quit()


# Main program
if __name__ == "__main__":
    app = QApplication(sys.argv)
    window = PageDownApp()
    window.show()
    sys.exit(app.exec())
```

---

### Features and Enhancements:

#### 1. **Modern GUI with PyQt6:**
   - PyQt6 provides a sleek, professional, and user-friendly interface.
   - The GUI includes labels, buttons, and a spin box for intuitive control.

#### 2. **Dynamic Delay Adjustment:**
   - The `QSpinBox` allows users to adjust the delay dynamically in seconds.
   - Delay updates are reflected immediately in the status label.

#### 3. **Threading for Responsiveness:**
   - The `threading.Thread` runs the Page Down loop in the background, keeping the GUI responsive.

#### 4. **Start/Stop Buttons:**
   - Clear start and stop buttons control the automation loop.
   - Buttons are color-coded (green for Start, red for Stop) for easy recognition.

#### 5. **Cross-Platform Compatibility:**
   - Python and PyQt6 work seamlessly on Windows, macOS, and Linux.
   - The program uses `pyautogui` for sending the `PgDn` key, which is also cross-platform.

#### 6. **Safe Exit:**
   - The program stops the loop safely and exits gracefully when the "Exit" button is clicked.

#### 7. **Customizable Range for Delay:**
   - Users can set the delay within a predefined range (1-60 seconds by default).
   - The range can be adjusted as needed.

#### 8. **Clear Visual Feedback:**
   - The status label updates dynamically to show whether the script is running and displays the current delay.

---

### Potential Future Enhancements:

1. **System Tray Integration:**
   - Use `pystray` to minimize the program to the system tray for background operation.

2. **Custom Key Simulation:**
   - Allow the user to specify which key (e.g., `PgDn`, `Arrow Down`) to simulate.

3. **Config Persistence:**
   - Save user preferences (e.g., delay) to a configuration file (`JSON`, `INI`, etc.) and load them on startup.

4. **Target Window Selection:**
   - Integrate with `pygetwindow` to specify a target window for sending key presses.

5. **Advanced Logging:**
   - Add logging with timestamps to track key presses and program activity.

6. **Enhanced GUI Features:**
   - Add a progress bar or visual indicator to show when the next key press will occur.
   - Include light and dark mode options.

---

This PyQt6-based version is user-friendly, powerful, and highly extensible, offering a significant improvement over both the AutoHotkey and earlier Python versions.

Shades

  • Member
  • Joined in 2006
  • **
  • Posts: 2,944
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #10 on: January 08, 2025, 06:35 PM »
Below is an enhanced Python version, complements of ChatGPT

Python is good enough for prototyping. Unfortunately, around 90%  of people is of the mindset that there is nothing more permanent than a temporary solution. So Python gets a lot of "love" from many. Far too many. And the LLMs feed the desire for even more Python like mad.

Ugh. If Python is the solution, I don't even care about the problem it is supposed to solve. Having experienced the dependency hell, which far too many Python-scripters managed to manifest with their creations, makes me very hesitant to use their product at all.

Sure, there are many Python scripters who do know how to write proper Python, but many more don't. And that has grown into a bitter dislike for anything Python. Many programmers (C++/C#) do not appreciate Python either, as it may look simple, it also comes with some deep-rooted (and significant) flaws.

Ah, the times I asked an LLM for scripts in PowerShell or bash, and got python instead, I cannot count on all the digits on all my extremities anymore. Even when indicating I absolutely don't want Python script as an answer to my request...I still get Python from LLMs.

Nah...no Python for me, if I can help it. While realizing the solution in my previous post isn't for everyone, it suits my needs perfectly.

Shades

  • Member
  • Joined in 2006
  • **
  • Posts: 2,944
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #11 on: January 08, 2025, 06:43 PM »
Below is an enhanced Python version, complements of ChatGPT

Which version of ChatGPT were you using? The free one? Good luck running the Python script generated by that one. The difference between the free tiers of online LLMs, not just ChatGPT, and the subscription models is significant. With the free one you are very likely spending quite some time bughunting...that is, if the code is even working at all.

More often than not, you'll end up better writing it yourself. Even something simple as Docker-compose yaml files ChatGPT manages to bungle up. For that and SQL queries, the free tier of Phind has given me much better answers than ChatGPT did. Do with that information what you will, of course.

nkormanik

  • Participant
  • Joined in 2010
  • *
  • Posts: 559
    • View Profile
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #12 on: January 09, 2025, 03:49 PM »
I guess your comments point to the need for expert programming and architecture.

Vic! has done a fine job with other projects.

Hope others on here will chime in with suggestions.


paradisusvic

  • Participant
  • Joined in 2024
  • *
  • Posts: 79
  • Call me Vic!
    • View Profile
    • Read more about this member.
    • Donate to Member
Re: Variable-speed repeated button press
« Reply #13 on: Today at 01:51 PM »
Vic! has done a fine job with other projects.

Thanks for your kind words, I'm doing nKeyPress as we speak to "sneak in" as a NANY 2025 release :Thmbsup:

https://github.com/paradisusio/nkeypress

I'm aiming at several new releases this month of January, on the NANY occasion and our new Paradisus.io site :)
My name's Victor but do feel free to call me Vic!

❤️ Support on Patreon @ www.patreon.com/paradisusio

GitHub: github.com/paradisusio

🌟 Good projects deserve support 🌎🌍🌏

✉ Email / Extend your PayPal support ❤️: paradisusvic@gmail.com