Compare commits
No commits in common. "eaf320aa39dd4c2b9899918d17ec5233b22d59c5" and "ab258c30dde0be4a97c55dd753fb41f92dcd8df5" have entirely different histories.
eaf320aa39
...
ab258c30dd
57
README.md
57
README.md
@ -1,23 +1,20 @@
|
|||||||
# Readme
|
# Readme
|
||||||
Create Pleroma App is a simple python3 script to generate OAuth tokens for your Pleroma account.
|
Create Pleroma App is a simple python3 script to generate OAuth tokens for your Pleroma account.
|
||||||
|
|
||||||
This will output the following credentials as shell variable:
|
By default it will output your credentials as valid python code, but you can disable this with the --plain switch.
|
||||||
```
|
|
||||||
API_BASE_URL=string
|
|
||||||
CLIENT_ID=string
|
|
||||||
CLIENT_SECRET=string
|
|
||||||
API_BASE_URL=string
|
|
||||||
```
|
|
||||||
|
|
||||||
## Installing
|
## Installing
|
||||||
To setup the python3 environment run the following commands:
|
To setup the python3 environment (for Arch Linux) run the following commands:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
sudo pacman -Syu
|
||||||
|
sudo pacman -S --needed python git
|
||||||
git clone 'https://git.yandere.cc/Anon/CreatePleromaApp.git'
|
git clone 'https://git.yandere.cc/Anon/CreatePleromaApp.git'
|
||||||
cd CreatePleromaApp/
|
cd CreatePleromaApp/
|
||||||
python -m venv venv
|
python -m venv venv
|
||||||
|
source venv/bin/activate
|
||||||
pip install --upgrade pip
|
pip install --upgrade pip
|
||||||
pip install Mastodon.py
|
pip install -r requirements.txt
|
||||||
deactivate
|
deactivate
|
||||||
./run.sh -h
|
./run.sh -h
|
||||||
```
|
```
|
||||||
@ -27,43 +24,17 @@ If everything worked correctly you should see usage information on Create Plerom
|
|||||||
## Default Settings
|
## Default Settings
|
||||||
If you want to change the default values for the input prompts, you can edit the source code directly in `src/create_app.py`
|
If you want to change the default values for the input prompts, you can edit the source code directly in `src/create_app.py`
|
||||||
|
|
||||||
## Generating Your OAuth Tokens
|
## Encryption
|
||||||
To generate your tokens, run the following commands and follow the interactive prompts:
|
You can also encrypt your credentials with the encryption.py module included in the repo. If you choose to use this with your custom bot, you must copy the encryption.py module to your project's root source directory.
|
||||||
```
|
|
||||||
touch cred.sh
|
|
||||||
chmod 600 cred.sh
|
|
||||||
cd ..
|
|
||||||
git clone 'https://git.yandere.cc/Anon/CreatePleromaApp.git'
|
|
||||||
cd CreatePleromaApp/
|
|
||||||
./run.sh > ../FediStatusPoster/cred.sh
|
|
||||||
# Follow the interactive prompts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Encrypting Your OAuth Tokens
|
See below for an example implementation:
|
||||||
If you are going to be running a bot over a long period of time, consider encrypting your OAuth tokens with GPG encryption and a keyfile.
|
|
||||||
|
|
||||||
REMINDER: Your keyfile should typically be stored on a seperate removable device that can be quickly disconnected. If your account is compromised, you should also revoke permissions in your Pleroma settings panel.
|
|
||||||
```
|
```
|
||||||
touch cred.sh.gpg
|
def decrypt_settings(self):
|
||||||
touch cred.sh.key
|
if self.settings_encrypt["encrypt"] and not self.decrypted:
|
||||||
chmod 600 cred.sh.key cred.sh.gpg
|
import encryption
|
||||||
</dev/random tr -dc 'a-zA-Z0-9' | head -c4096 > cred.sh.key
|
self.settings_server = encryption.settings_server_decrypt(self.settings_server, self.settings_encrypt, keyfile_path)
|
||||||
cd ..
|
self.decrypted = True
|
||||||
git clone 'https://git.yandere.cc/Anon/CreatePleromaApp.git'
|
|
||||||
cd CreatePleromaApp/
|
|
||||||
./run.sh | gpg -q -c --pinentry-mode loopback --passphrase-file ../FediStatusPoster/cred.sh.gpg -o ../FediStatusPoster/cred.sh.gpg -
|
|
||||||
# Follow the interactive prompts
|
|
||||||
```
|
|
||||||
|
|
||||||
If you do not want to keep a keyfile, but still want encryption, modify the encryption step with the following:
|
|
||||||
```
|
|
||||||
./run.sh | gpg -q -c --pinentry-mode loopback -o ../FediStatusPoster/cred.sh.gpg -
|
|
||||||
```
|
|
||||||
|
|
||||||
Be sure to update `./run.sh` to point to your new paths:
|
|
||||||
```
|
|
||||||
DEFAULT_CREDENTIALS="cred.sh.gpg"
|
|
||||||
DEFAULT_KEYFILE="cred.sh.key" or DEFAULT_KEYFILE="-" (if not using a keyfile)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Donate
|
## Donate
|
||||||
|
154
create_app.py
154
create_app.py
@ -1,154 +0,0 @@
|
|||||||
#! /usr/bin/env python3
|
|
||||||
|
|
||||||
# CreatePleromaApp, a python script to generate OAuth tokens for fedi
|
|
||||||
# Copyright (C) 2024 Anon <@Anon@yandere.cc>
|
|
||||||
#
|
|
||||||
# This program 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.
|
|
||||||
#
|
|
||||||
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
|
|
||||||
import getpass
|
|
||||||
import os
|
|
||||||
import re
|
|
||||||
import sys
|
|
||||||
import argparse
|
|
||||||
from mastodon import Mastodon, MastodonIllegalArgumentError, MastodonAPIError, MastodonIOError
|
|
||||||
|
|
||||||
|
|
||||||
class CreateAppError(Exception):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
# Modify this section to suit your default preferances
|
|
||||||
def default(key):
|
|
||||||
defaults={
|
|
||||||
"app": "app",
|
|
||||||
"domain": "https://yandere.cc",
|
|
||||||
"scopes": "write",
|
|
||||||
}
|
|
||||||
return defaults[key]
|
|
||||||
|
|
||||||
|
|
||||||
def printerr(s, end=None):
|
|
||||||
print(s, file=sys.stderr, end=end if end != None else os.linesep)
|
|
||||||
|
|
||||||
def inputerr(q):
|
|
||||||
sys.stdout.flush()
|
|
||||||
sys.stderr.flush()
|
|
||||||
printerr(q, end="")
|
|
||||||
return input()
|
|
||||||
|
|
||||||
# Function to format user input consistently with lables
|
|
||||||
def _input(lable, ans=""):
|
|
||||||
if ans:
|
|
||||||
q = "{} (Default: {}): ".format(
|
|
||||||
lable, ans)
|
|
||||||
return inputerr(q) or ans
|
|
||||||
else:
|
|
||||||
q = "{}: ".format(lable)
|
|
||||||
return inputerr(q)
|
|
||||||
|
|
||||||
|
|
||||||
def get_client_id_and_secret(app, permissions, domain):
|
|
||||||
try:
|
|
||||||
return Mastodon.create_app(app, scopes=permissions, api_base_url=domain)
|
|
||||||
except MastodonIOError:
|
|
||||||
raise CreateAppError("An error occurred. Make sure the domain name is correct.")
|
|
||||||
|
|
||||||
|
|
||||||
def get_api(client_id, client_secret, domain):
|
|
||||||
api = Mastodon(
|
|
||||||
client_id,
|
|
||||||
client_secret,
|
|
||||||
api_base_url=domain,
|
|
||||||
feature_set="pleroma"
|
|
||||||
)
|
|
||||||
return api
|
|
||||||
|
|
||||||
|
|
||||||
def get_token(api, email, password, permissions):
|
|
||||||
try:
|
|
||||||
token = api.log_in(email, password, scopes=permissions)
|
|
||||||
return token
|
|
||||||
except MastodonIllegalArgumentError:
|
|
||||||
raise CreateAppError("Username or Password is incorrect.")
|
|
||||||
except MastodonAPIError:
|
|
||||||
raise CreateAppError("Could not grant scopes:", ", ".join(permissions))
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
# Parser
|
|
||||||
parser = argparse.ArgumentParser(
|
|
||||||
description="A script to generate OAuth tokens for fedi",
|
|
||||||
epilog="",
|
|
||||||
add_help=True)
|
|
||||||
_ = parser.parse_args()
|
|
||||||
|
|
||||||
try:
|
|
||||||
# Settings Dictionary
|
|
||||||
settings = {}
|
|
||||||
|
|
||||||
# Create App
|
|
||||||
printerr("Generate and register Mastodon App")
|
|
||||||
printerr("You can just hit enter to accept the default for prompts that have them")
|
|
||||||
printerr("Ctrl+C to Quit\n")
|
|
||||||
|
|
||||||
# Get instance information
|
|
||||||
app_name = _input("Enter your app name", default("app"))
|
|
||||||
settings["API_BASE_URL"] = _input("URL of Instance", default("domain"))
|
|
||||||
email = _input("Enter your email")
|
|
||||||
password = getpass.getpass("Enter password: ")
|
|
||||||
printerr("Scopes: read, write, follow, push")
|
|
||||||
printerr("Separate each scope with a comma (example above).")
|
|
||||||
printerr("!!! Accept the default unless you intend to modify Yandere Lewd Bot !!!")
|
|
||||||
ans = _input("Scopes", default("scopes"))
|
|
||||||
ans = re.sub(r"\s+", "", ans, flags=re.UNICODE)
|
|
||||||
permissions = ans.split(",")
|
|
||||||
printerr("Granting: {}".format(str(permissions)))
|
|
||||||
|
|
||||||
# Begin logging in
|
|
||||||
settings["CLIENT_ID"], settings["CLIENT_SECRET"] =\
|
|
||||||
get_client_id_and_secret(
|
|
||||||
app_name,
|
|
||||||
permissions,
|
|
||||||
settings["API_BASE_URL"]
|
|
||||||
)
|
|
||||||
api = get_api(
|
|
||||||
settings["CLIENT_ID"],
|
|
||||||
settings["CLIENT_SECRET"],
|
|
||||||
settings["API_BASE_URL"]
|
|
||||||
)
|
|
||||||
|
|
||||||
settings["ACCESS_TOKEN"] = get_token(api, email, password, permissions)
|
|
||||||
|
|
||||||
# Output the user's new credentials
|
|
||||||
for k, v in settings.items():
|
|
||||||
v = settings[k]
|
|
||||||
print("{}={}".format(k, v))
|
|
||||||
|
|
||||||
printerr("Success :)")
|
|
||||||
return 0
|
|
||||||
except (KeyboardInterrupt, EOFError):
|
|
||||||
printerr("\nUser Quit :|")
|
|
||||||
return 1
|
|
||||||
except CreateAppError as e:
|
|
||||||
printerr(e)
|
|
||||||
printerr("Error :(")
|
|
||||||
return 2
|
|
||||||
except Exception as e:
|
|
||||||
printerr(e)
|
|
||||||
printerr("Unhandled Exception ¯\\_(ツ)_/¯")
|
|
||||||
return 3
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sys.exit(main())
|
|
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
Mastodon.py
|
||||||
|
cryptography
|
8
run.sh
8
run.sh
@ -1,7 +1,7 @@
|
|||||||
#! /usr/bin/env bash
|
#! /usr/bin/env bash
|
||||||
|
|
||||||
# CreatePleromaApp, a python script to generate OAuth tokens for fedi
|
# Create Pleroma App, automate the creation of pleroma apps
|
||||||
# Copyright (C) 2024 Anon <@Anon@yandere.cc>
|
# Copyright (C) 2022 Anon <@Anon@yandere.cc>
|
||||||
#
|
#
|
||||||
# This program is free software: you can redistribute it and/or modify
|
# This program is free software: you can redistribute it and/or modify
|
||||||
# it under the terms of the GNU General Public License as published by
|
# it under the terms of the GNU General Public License as published by
|
||||||
@ -21,8 +21,8 @@ ABS_PATH="$(readlink -f "$0")"
|
|||||||
RUN_DIR="$(dirname "$ABS_PATH")"
|
RUN_DIR="$(dirname "$ABS_PATH")"
|
||||||
|
|
||||||
# Relative paths to the virtual environment and main.py
|
# Relative paths to the virtual environment and main.py
|
||||||
VENV='${RUN_DIR}/venv/bin/activate'
|
VENV='./venv/bin/activate'
|
||||||
ENTRY='${RUN_DIR}/create_app.py'
|
ENTRY='./src/create_app.py'
|
||||||
|
|
||||||
# cd into the bot's root path, set up the virtual environment, and run
|
# cd into the bot's root path, set up the virtual environment, and run
|
||||||
cd "$RUN_DIR"
|
cd "$RUN_DIR"
|
||||||
|
216
src/create_app.py
Executable file
216
src/create_app.py
Executable file
@ -0,0 +1,216 @@
|
|||||||
|
#! /usr/bin/env python3
|
||||||
|
|
||||||
|
# Create Pleroma App, a python script to generate OAuth tokens for fedi
|
||||||
|
# Copyright (C) 2022 Anon <@Anon@yandere.cc>
|
||||||
|
#
|
||||||
|
# This program 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.
|
||||||
|
#
|
||||||
|
# This program 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 this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
import getpass
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import datetime
|
||||||
|
import argparse
|
||||||
|
import contextlib
|
||||||
|
import os
|
||||||
|
from pprint import pformat
|
||||||
|
from mastodon import Mastodon, MastodonIllegalArgumentError, MastodonAPIError, MastodonIOError
|
||||||
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
|
||||||
|
class CreateAppError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
# Modify this section to suit your default preferances
|
||||||
|
def default(key):
|
||||||
|
defaults={
|
||||||
|
"app": "app",
|
||||||
|
"domain": "https://yandere.cc",
|
||||||
|
"scopes": "write",
|
||||||
|
"encrypt": "y",
|
||||||
|
"cfg": None,
|
||||||
|
"long_date_format": "%m/%d/%Y %I:%M%p"
|
||||||
|
}
|
||||||
|
return defaults[key]
|
||||||
|
|
||||||
|
|
||||||
|
# Function to format user input consistently with lables
|
||||||
|
def _input(lable, ans=None):
|
||||||
|
if ans:
|
||||||
|
q = "{} (Default: {}): ".format(
|
||||||
|
lable, ans)
|
||||||
|
return input(q) or ans
|
||||||
|
else:
|
||||||
|
q = "{}: ".format(lable)
|
||||||
|
return input(q)
|
||||||
|
|
||||||
|
|
||||||
|
def get_client_id_and_secret(app, permissions, domain):
|
||||||
|
try:
|
||||||
|
return Mastodon.create_app(app, scopes=permissions, api_base_url=domain)
|
||||||
|
except MastodonIOError:
|
||||||
|
raise CreateAppError("An error occurred. Make sure the domain name is correct.")
|
||||||
|
|
||||||
|
|
||||||
|
def get_api(client_id, client_secret, domain):
|
||||||
|
api = Mastodon(client_id, client_secret, api_base_url=domain)
|
||||||
|
return api
|
||||||
|
|
||||||
|
|
||||||
|
def get_token(api, email, password, permissions):
|
||||||
|
try:
|
||||||
|
token = api.log_in(email, password, scopes=permissions)
|
||||||
|
return token
|
||||||
|
except MastodonIllegalArgumentError:
|
||||||
|
raise CreateAppError("Username or Password is incorrect.")
|
||||||
|
except MastodonAPIError:
|
||||||
|
raise CreateAppError("Could not grant scopes:", ", ".join(permissions))
|
||||||
|
|
||||||
|
|
||||||
|
def get_cfg(cfg_name):
|
||||||
|
try:
|
||||||
|
import importlib
|
||||||
|
cfg = importlib.import_module(cfg_name)
|
||||||
|
return cfg
|
||||||
|
except ImportError:
|
||||||
|
raise CreateAppError("Cannot import module:", cfg_name, "Make sure you omitted the .py extension and try again")
|
||||||
|
|
||||||
|
|
||||||
|
def package_settings(app, domain, client_id, client_secret, token):
|
||||||
|
settings_server = OrderedDict([
|
||||||
|
("app_name", app),
|
||||||
|
("api_base_url", domain),
|
||||||
|
("client_id", client_id),
|
||||||
|
("client_secret", client_secret),
|
||||||
|
("access_token", token)
|
||||||
|
])
|
||||||
|
return settings_server
|
||||||
|
|
||||||
|
|
||||||
|
def get_setting_reminder(fmt):
|
||||||
|
dt_now = datetime.datetime.now()
|
||||||
|
dt_now = dt_now.replace(year=dt_now.year + 1)
|
||||||
|
settings_reminder = dt_now.strftime(fmt)
|
||||||
|
return settings_reminder
|
||||||
|
|
||||||
|
|
||||||
|
def credentials_py(label, setting):
|
||||||
|
return "{} = {}".format(label, pformat(setting))
|
||||||
|
|
||||||
|
|
||||||
|
def credentials_ini(label, setting):
|
||||||
|
r = "[{}]".format(label)
|
||||||
|
for k, v in setting.items():
|
||||||
|
r += "\n{}={}".format(k, v)
|
||||||
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# Default time localization
|
||||||
|
long_date_format = default("long_date_format")
|
||||||
|
|
||||||
|
# Parser
|
||||||
|
parser = argparse.ArgumentParser(
|
||||||
|
description="A script to generate OAuth tokens for fedi",
|
||||||
|
epilog="",
|
||||||
|
add_help=True)
|
||||||
|
parser.add_argument("-c", "--config", help="Use time localization settings from a pyhton config file (omit the .py extension)", default=None)
|
||||||
|
parser.add_argument("-k", "--keyfile", help="Keyfile used for decryption", default=None)
|
||||||
|
parser.add_argument("--plain", help="Output credentials in plain ini format", action="store_true")
|
||||||
|
parser.add_argument("--minimal", help="Only print OAuth credentials, ignoring encryption settings", action="store_true")
|
||||||
|
arguments = parser.parse_args()
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Custom time localization
|
||||||
|
# This is mainly intended to be used with bots configured with python files
|
||||||
|
# This python file should contain the following dictionary with a key value pair of:
|
||||||
|
# settings_time = {"long_date_format": "%m/%d/%Y %I:%M%p"}
|
||||||
|
load_config = arguments.config or default("cfg")
|
||||||
|
if load_config:
|
||||||
|
cfg = get_cfg(load_config)
|
||||||
|
long_date_format = cfg.settings_time["long_date_format"]
|
||||||
|
|
||||||
|
# Create App
|
||||||
|
print("Generate and register Mastodon App")
|
||||||
|
print("You can just hit enter to accept the default for prompts that have them")
|
||||||
|
print("Ctrl+C to Quit\n")
|
||||||
|
|
||||||
|
# Get instance information
|
||||||
|
app = _input("Enter your app name", default("app"))
|
||||||
|
domain = _input("URL of Instance", default("domain"))
|
||||||
|
email = _input("Enter your email")
|
||||||
|
password = getpass.getpass("Enter password: ")
|
||||||
|
print("Scopes: read, write, follow, push")
|
||||||
|
print("Separate each scope with a comma (example above).")
|
||||||
|
print("!!! Accept the default unless you intend to modify Yandere Lewd Bot !!!")
|
||||||
|
ans = _input("Scopes", default("scopes"))
|
||||||
|
ans = re.sub(r"\s+", "", ans, flags=re.UNICODE)
|
||||||
|
permissions = ans.split(",")
|
||||||
|
print("Granting:", permissions)
|
||||||
|
|
||||||
|
# Begin logging in
|
||||||
|
client_id, client_secret = get_client_id_and_secret(app, permissions, domain)
|
||||||
|
api = get_api(client_id, client_secret, domain)
|
||||||
|
token = get_token(api, email, password, permissions)
|
||||||
|
|
||||||
|
# Credentials (unencrypted)
|
||||||
|
encrypt, salt = False, ""
|
||||||
|
settings_server = package_settings(app, domain, client_id, client_secret, token)
|
||||||
|
reminder = get_setting_reminder(long_date_format)
|
||||||
|
|
||||||
|
# Encrypt
|
||||||
|
if not arguments.minimal:
|
||||||
|
with contextlib.suppress(ImportError):
|
||||||
|
import FediBotEncryption
|
||||||
|
ans = _input("Do you want to encrypt your credentials? (y/n)", default("encrypt"))
|
||||||
|
if ans.upper() in ("Y", "YES"):
|
||||||
|
encrypt = True
|
||||||
|
salt, settings_server = FediBotEncryption.settings_server_encrypt(settings_server, arguments.keyfile)
|
||||||
|
|
||||||
|
settings_encrypt = OrderedDict([
|
||||||
|
("encrypt", encrypt),
|
||||||
|
("salt", salt),
|
||||||
|
("keyfile", arguments.keyfile)
|
||||||
|
])
|
||||||
|
settings_reminder = OrderedDict([
|
||||||
|
("settings_reminder", reminder)
|
||||||
|
])
|
||||||
|
|
||||||
|
# Credential formatting functions
|
||||||
|
formatted_credentials = credentials_ini if arguments.plain else credentials_py
|
||||||
|
|
||||||
|
# Output the user's new credentials
|
||||||
|
print("Copy to your config file!!!")
|
||||||
|
print(formatted_credentials("settings_server", settings_server))
|
||||||
|
if not arguments.minimal:
|
||||||
|
print("\n{}\n".format(formatted_credentials("settings_reminder", settings_reminder)))
|
||||||
|
print(formatted_credentials("settings_encrypt", settings_encrypt))
|
||||||
|
|
||||||
|
print("Success :)")
|
||||||
|
return 0
|
||||||
|
except (KeyboardInterrupt, EOFError):
|
||||||
|
print("User Quit :|")
|
||||||
|
return 1
|
||||||
|
except CreateAppError as e:
|
||||||
|
print(e)
|
||||||
|
print("Error :(")
|
||||||
|
return 2
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
print("Unhandled Exception ¯\\_(ツ)_/¯")
|
||||||
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
Loading…
Reference in New Issue
Block a user