diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index f9acb49..21b3ded 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -14,8 +14,7 @@
"forwardPorts": [8000],
"portsAttributes": {
"8000": {
- "label": "Sphinx",
- "onAutoForward": "openPreview"
+ "label": "Sphinx"
}
},
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index b4a07f4..08a848d 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -4,6 +4,7 @@ on:
push:
paths:
- '**.py'
+ - '**.rst'
- '.github/workflows/ci.yml'
jobs:
@@ -26,17 +27,21 @@ jobs:
- name: Install dependencies
run: |
- python -m pip install --upgrade pip pylint black mypy
+ python -m pip install --upgrade pip pylint black mypy sphinx-lint
pip install -r requirements.txt
- name: Run Pylint
run: |
- pylint $(git ls-files '*.py')
+ pylint $(git ls-files 'src/**/*.py')
- name: Run Black
run: |
- black $(git ls-files '*.py') --check
+ black $(git ls-files 'src/**/*.py') --check
- name: Run Mypy
run: |
- mypy $(git ls-files '*.py')
+ mypy $(git ls-files 'src/**/*.py')
+
+ - name: Run Sphinx Lint
+ run: |
+ sphinx-lint $(git ls-files '*.rst')
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
index 2773ad3..1bf4ac5 100644
--- a/.vscode/tasks.json
+++ b/.vscode/tasks.json
@@ -27,6 +27,12 @@
"-e"
],
"problemMatcher": []
+ },
+ {
+ "label": "Build documentation",
+ "type": "shell",
+ "command": "sphinx-autobuild docs/source docs/_build/html",
+ "problemMatcher": []
}
]
}
diff --git a/docs/source/_static/.gitkeep b/docs/source/_static/.gitkeep
deleted file mode 100644
index 8b13789..0000000
--- a/docs/source/_static/.gitkeep
+++ /dev/null
@@ -1 +0,0 @@
-
diff --git a/docs/source/_static/integrations-tab.png b/docs/source/_static/integrations-tab.png
new file mode 100644
index 0000000..298e136
Binary files /dev/null and b/docs/source/_static/integrations-tab.png differ
diff --git a/docs/source/_static/webhook-message.png b/docs/source/_static/webhook-message.png
new file mode 100644
index 0000000..d22fc88
Binary files /dev/null and b/docs/source/_static/webhook-message.png differ
diff --git a/docs/source/_static/webhook-settings.png b/docs/source/_static/webhook-settings.png
new file mode 100644
index 0000000..83475f5
Binary files /dev/null and b/docs/source/_static/webhook-settings.png differ
diff --git a/docs/source/conf.py b/docs/source/conf.py
index 207f6d1..ccb8040 100644
--- a/docs/source/conf.py
+++ b/docs/source/conf.py
@@ -3,20 +3,32 @@
# For the full list of built-in configuration values, see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
+# -- Path setup --------------------------------------------------------------
+
+# If extensions (or modules to document with autodoc) are in another directory,
+# add these directories to sys.path here. If the directory is relative to the
+# documentation root, use os.path.abspath to make it absolute, like shown here.
+#
+import os
+import sys
+sys.path.insert(0, os.path.abspath('../..'))
+
+
# -- Project information -----------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#project-information
project = 'InstaWebhooks'
copyright = '2024, Ryan Luu'
author = 'Ryan Luu'
-release = '0.1'
-version = '0.1.3'
+release = '1.0'
+version = '1.0.0'
# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration
extensions = [
- 'sphinx_copybutton'
+ 'sphinx_copybutton',
+ 'sphinxarg.ext',
]
templates_path = ['_templates']
@@ -31,6 +43,28 @@
html_static_path = ['_static']
html_theme_options = {
+ "footer_icons": [
+ {
+ "name": "GitHub",
+ "url": "https://github.com/RyanLua/InstaWebhooks",
+ "html": """
+
+ """,
+ "class": "",
+ },
+ {
+ "name": "Read the Docs",
+ "url": "https://readthedocs.org/projects/instawebhooks",
+ "html": """
+
+ """,
+ "class": "",
+ },
+ ],
"announcement": "This is a early version of the documentation and not final.",
"source_repository": "https://github.com/RyanLua/InstaWebhooks",
"source_branch": "main",
diff --git a/docs/source/getting-started.rst b/docs/source/getting-started.rst
new file mode 100644
index 0000000..9434871
--- /dev/null
+++ b/docs/source/getting-started.rst
@@ -0,0 +1,78 @@
+Getting Started
+===============
+
+Learn about getting started with InstaWebhooks to send new Instagram posts any Discord channel from scratch.
+
+Installing InstaWebhooks
+------------------------
+
+With `Python `_ installed, install InstaWebhooks with `pip `_:
+
+.. code:: console
+
+ $ pip install instawebhooks
+
+Check that InstaWebhooks was installed correctly by seeing if it reports its version:
+
+.. code:: console
+
+ $ instawebhooks --version
+
+Make sure that are on the `latest version of InstaWebhooks `_.
+
+For more ways to install InstaWebhooks, see the `installation guide `_.
+
+Setting up Discord webhooks
+---------------------------
+
+To get your Discord webhook URL, you need the **Manage Webhooks** permission in the channel you want to send new Instagram posts to.
+
+You can learn more about webhooks through the article, `Intro to Webhooks `_.
+
+Creating your webhook
+^^^^^^^^^^^^^^^^^^^^^
+
+If your already have a webhook, you can skip this step.
+
+#. Open **Server Settings**, then **Integrations**
+#. Click the "**Create Webhook**" button
+
+.. image:: _static/integrations-tab.png
+
+Now you can set the name, channel, and avatar for the webhook.
+
+Getting the webhook URL
+^^^^^^^^^^^^^^^^^^^^^^^
+
+When you have your webhook made, click the "**Copy Webhook URL**" button to copy the URL to your clipboard.
+
+.. image:: _static/webhook-settings.png
+
+The copied URL should look similar this:
+
+.. code:: none
+
+ https://discordapp.com/api/webhooks/0123456789/abcdefghijklmnopqrstuvwxyz
+
+Setting up InstaWebhooks
+------------------------
+
+Now with the webhook URL and a Instagram account in mind, you can set up InstaWebhooks to send new Instagram posts to your Discord channel.
+
+Replace ```` with the username of the Instagram account you want to monitor and ```` with the Discord webhook URL you copied earlier.
+
+.. code:: console
+
+ $ instawebhooks
+
+It should look something like this:
+
+.. code:: console
+
+ $ instawebhooks raenlua https://discord.com/api/webhooks/0123456789/abcdefghijklmnopqrstuvwxyz
+
+Now, whenever the Instagram account `@raenlua` posts a new photo, it will be sent to the Discord webhook.
+
+.. image:: _static/webhook-message.png
+
+For more information about using InstaWebhooks, see the `usage guide `_.
diff --git a/docs/source/index.rst b/docs/source/index.rst
index 08d0250..a196444 100644
--- a/docs/source/index.rst
+++ b/docs/source/index.rst
@@ -8,6 +8,10 @@ InstaWebhooks documentation
InstaWebhooks is a `Python `_ command-line interface which allows you to monitor any Instagram account for new posts and then send them to a `Discord webhook `_.
+* Works with **any Instagram account**, including private accounts if you are a follower
+* Customizable **Discord embeds** for new posts and message contents including **mentions/pings**
+* **User-definable refresh interval** for checking for new posts the second they are posted
+
Quickstart
----------
@@ -30,13 +34,14 @@ Contents
.. toctree::
+ getting-started
installation
usage
.. toctree::
:caption: Project
- Contributing
- Code of Conduct
+ Contributing
+ Code of Conduct
GitHub
- PyPI
\ No newline at end of file
+ PyPI
diff --git a/docs/source/installation.rst b/docs/source/installation.rst
index c022cbc..d6e0c30 100644
--- a/docs/source/installation.rst
+++ b/docs/source/installation.rst
@@ -90,4 +90,4 @@ Installing from source code is another option to contribute or use the latest de
.. code:: console
- $ pip install --editable .
\ No newline at end of file
+ $ pip install --editable .
diff --git a/docs/source/usage.rst b/docs/source/usage.rst
index 9470763..73d7370 100644
--- a/docs/source/usage.rst
+++ b/docs/source/usage.rst
@@ -6,75 +6,35 @@ To see the available options and arguments, run the following command:
.. code:: console
$ instawebhooks --help
- usage: instawebhooks [-h] [-q | -v] [-i REFRESH_INTERVAL] [-c MESSAGE_CONTENT] [-e] [--version]
- instagram_username discord_webhook_url
-
- Monitor Instagram accounts for new posts and send them to a Discord webhook
-
- positional arguments:
- instagram_username the Instagram username to monitor for new posts
- discord_webhook_url the Discord webhook URL to send new posts to
-
- options:
- -h, --help show this help message and exit
- -q, --quiet hide all logging
- -v, --verbose show debug logging
- -i REFRESH_INTERVAL, --refresh-interval REFRESH_INTERVAL
- time in seconds to wait before checking for new posts again
- -c MESSAGE_CONTENT, --message-content MESSAGE_CONTENT
- the message content to send with the webhook
- -e, --no-embed don't show the post embed and only send message content
- --version show program's version number and exit
-
- https://github.com/RaenLua/InstaWebhooks
-
-Below, learn how to use InstaWebhooks and what you can do with it.
Examples
--------
-In the below templates, replace ```` with the Instagram username you want to monitor and ```` with the Discord webhook URL you want to send new posts to.
-
-Your command should look similar to this:
-
-.. code:: console
-
- $ instawebhooks raenlua https://discord.com/api/webhooks/0123456789/abcdefghijklmnopqrstuvwxyz
-
-Templates
----------
-
-Example templates for using InstaWebhooks are provided below. Note to change the Instagram username and Discord webhook URL to your own.
-
-.. note::
-
- The default refresh interval is 1 hour (3600 seconds), and the default message content is an empty string.
-
-Send new posts every hour:
+* Send new posts:
.. code:: console
$ instawebhooks
-Send new posts every hour with verbose logging:
+* Send new posts with verbose logging:
.. code:: console
$ instawebhooks -v
-Send new posts every 30 minutes:
+* Send new posts every 30 minutes:
.. code:: console
$ instawebhooks -i 1800
-Send new posts every hour with a custom message:
+* Send new posts with a custom message:
.. code:: console
$ instawebhooks -c "New post from {owner_name}: {post_url}"
-Send new posts every hour with no embed and a custom message:
+* Send new posts with no embed and a custom message:
.. code:: console
@@ -83,77 +43,22 @@ Send new posts every hour with no embed and a custom message:
Reference
---------
-Positional Arguments
-~~~~~~~~~~~~~~~~~~~~
-
-``INSTAGRAM_USERNAME``
-
- The Instagram username to monitor for new posts.
-
- Usernames must follow the Instagram username format:
-
- * Starts with a letter or underscore.
- * Does not contain consecutive periods.
- * Is between 2 and 30 characters long.
- * Ends with an alphanumeric character or underscore.
-
-``DISCORD_WEBHOOK_URL``
-
- The Discord webhook URL to send new posts to.
-
- URLs must follow the Discord webhook URL format:
-
- * ``https://discord.com/api/webhooks/{webhook_id}/{webhook_token}``
- * ``https://discordapp.com/api/webhooks/{webhook_id}/{webhook_token}``
-
-Optional Arguments
-~~~~~~~~~~~~~~~~~~
-
-``-h, --help``
-
- Show this help message and exit.
-
-``-v, --verbose``
-
- Enable verbose logging.
-
- Changes the logging level to debug, showing the following logs in addition to the default info logs:
-
- * When a check for new posts is started.
- * If a new post is found or not.
- * When a post is sent to Discord.
-
-``-i REFRESH_INTERVAL, --refresh-interval REFRESH_INTERVAL``
-
- .. caution::
-
- Do not set the refresh interval too low or you may be `rate limited by Instagram `_.
-
- The refresh interval to check for new posts in seconds (default: 3600).
-
-``-c MESSAGE_CONTENT, --message-content MESSAGE_CONTENT``
-
- The message content to send to Discord (default: "").
-
- Accepts placeholders for the post information:
+.. argparse::
+ :module: src.instawebhooks.parser
+ :func: parser
+ :prog: instawebhooks
+ :noepilog:
- * ``{post_url}`` - The URL to the post on Instagram
- * ``https://www.instagram.com/C8wRGmyR-6N``
- * ``{owner_url}`` - The URL to the owner's profile on Instagram
- * ``https://www.instagram.com/raenlua``
- * ``{owner_name}`` - The owner's full name
- * ``Ryan Luu``
- * ``{owner_username}`` - The owner's username
- * ``raenlua``
- * ``{post_caption}`` - The post's caption
- * ``This is a post caption.``
- * ``{post_shortcode}`` - The post's shortcode
- * ``C8wRGmyR-6N``
- * ``{post_image_url}`` - The post's image URL
- * ``https://www.instagram.com/p/C8wRGmyR-6N/media``
+ instagram_username : @after
+ Usernames must follow the Instagram username format:
-``-e, --no-embed``
+ * Starts with a letter or underscore.
+ * Does not contain consecutive periods.
+ * Is between 2 and 30 characters long.
+ * Ends with an alphanumeric character or underscore.
- Don't show the post embed and only send message content
+ discord_webhook_url : @after
+ URLs must follow the Discord webhook URL format:
- A message content must be provided when using this option. Empty messages cannot be sent.
\ No newline at end of file
+ * ``https://discord.com/api/webhooks/{webhook_id}/{webhook_token}``
+ * ``https://discordapp.com/api/webhooks/{webhook_id}/{webhook_token}``
diff --git a/requirements.txt b/requirements.txt
index e52a6b2..e3d9716 100644
Binary files a/requirements.txt and b/requirements.txt differ
diff --git a/src/instawebhooks/__main__.py b/src/instawebhooks/__main__.py
index f424ec2..baaf657 100644
--- a/src/instawebhooks/__main__.py
+++ b/src/instawebhooks/__main__.py
@@ -1,16 +1,16 @@
"""Module for sending new Instagram posts to Discord."""
import asyncio
-import importlib.metadata
import io
import logging
import re
import sys
-from argparse import ArgumentParser
-from typing import Dict
from datetime import datetime, timedelta
from itertools import dropwhile, takewhile
from time import sleep
+from typing import Dict
+
+from .parser import parser
try:
from aiohttp import ClientSession
@@ -24,19 +24,6 @@
) from exc
-def regex(pattern: str):
- """Argument type for matching a regex pattern"""
-
- def closure_check_regex(arg_value: str):
- if not re.match(pattern, arg_value):
- raise ValueError(f"invalid value: '{arg_value}'")
- return arg_value
-
- return closure_check_regex
-
-
-version = importlib.metadata.version("instawebhooks")
-
# Set up logging
logger = logging.getLogger(__name__)
logging.basicConfig(
@@ -45,50 +32,6 @@ def closure_check_regex(arg_value: str):
level=logging.INFO,
)
-# Parse command line arguments
-parser = ArgumentParser(
- prog="instawebhooks",
- description=(
- "Monitor Instagram accounts for new posts and send them to a Discord webhook"
- ),
- epilog="https://github.com/RaenLua/InstaWebhooks",
-)
-group = parser.add_mutually_exclusive_group()
-parser.add_argument(
- "instagram_username",
- help="the Instagram username to monitor for new posts",
- type=regex(r"^[a-zA-Z_](?!.*?\.{2})[\w.]{1,28}[\w]$"),
-)
-parser.add_argument(
- "discord_webhook_url",
- help="the Discord webhook URL to send new posts to",
- type=regex(
- r"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-zA-Z0-9_.-]*)$"
- ),
-)
-group.add_argument("-q", "--quiet", help="hide all logging", action="store_true")
-group.add_argument("-v", "--verbose", help="show debug logging", action="store_true")
-parser.add_argument(
- "-i",
- "--refresh-interval",
- help="time in seconds to wait before checking for new posts again",
- type=int,
- default=3600,
-)
-parser.add_argument(
- "-c",
- "--message-content",
- help="the message content to send with the webhook",
- type=str,
- default="",
-)
-parser.add_argument(
- "-e",
- "--no-embed",
- help="don't show the post embed and only send message content",
- action="store_true",
-)
-parser.add_argument("--version", action="version", version="%(prog)s " + version)
args = parser.parse_args()
# Set the logger to debug if verbose is enabled
diff --git a/src/instawebhooks/parser.py b/src/instawebhooks/parser.py
new file mode 100644
index 0000000..8753c1a
--- /dev/null
+++ b/src/instawebhooks/parser.py
@@ -0,0 +1,67 @@
+"""Command line argument parser for InstaWebhooks"""
+
+import importlib.metadata
+import re
+from argparse import ArgumentParser
+
+
+def regex(pattern: str):
+ """Argument type for matching a regex pattern"""
+
+ def closure_check_regex(arg_value: str):
+ if not re.match(pattern, arg_value):
+ raise ValueError(f"invalid value: '{arg_value}'")
+ return arg_value
+
+ return closure_check_regex
+
+
+try:
+ VERSION = importlib.metadata.version("instawebhooks")
+except importlib.metadata.PackageNotFoundError:
+ VERSION = "unknown"
+
+# Parse command line arguments
+parser = ArgumentParser(
+ prog="instawebhooks",
+ description=(
+ "Monitor Instagram accounts for new posts and send them to a Discord webhook"
+ ),
+ epilog="https://github.com/RaenLua/InstaWebhooks",
+)
+group = parser.add_mutually_exclusive_group()
+parser.add_argument(
+ "instagram_username",
+ help="the Instagram username to monitor for new posts",
+ type=regex(r"^[a-zA-Z_](?!.*?\.{2})[\w.]{1,28}[\w]$"),
+)
+parser.add_argument(
+ "discord_webhook_url",
+ help="the Discord webhook URL to send new posts to",
+ type=regex(
+ r"^.*(discord|discordapp)\.com\/api\/webhooks\/([\d]+)\/([a-zA-Z0-9_.-]*)$"
+ ),
+)
+group.add_argument("-q", "--quiet", help="hide all logging", action="store_true")
+group.add_argument("-v", "--verbose", help="show debug logging", action="store_true")
+parser.add_argument(
+ "-i",
+ "--refresh-interval",
+ help="time in seconds to wait before checking for new posts again",
+ type=int,
+ default=3600,
+)
+parser.add_argument(
+ "-c",
+ "--message-content",
+ help="the message content to send with the webhook",
+ type=str,
+ default="",
+)
+parser.add_argument(
+ "-e",
+ "--no-embed",
+ help="don't show the post embed and only send message content",
+ action="store_true",
+)
+parser.add_argument("--version", action="version", version="%(prog)s " + VERSION)