Compare commits

..

2 Commits

5 changed files with 203 additions and 238 deletions

View File

@ -1,20 +1,23 @@
# Readme
Create Pleroma App is a simple python3 script to generate OAuth tokens for your Pleroma account.
By default it will output your credentials as valid python code, but you can disable this with the --plain switch.
This will output the following credentials as shell variable:
```
API_BASE_URL=string
CLIENT_ID=string
CLIENT_SECRET=string
API_BASE_URL=string
```
## Installing
To setup the python3 environment (for Arch Linux) run the following commands:
To setup the python3 environment run the following commands:
```
sudo pacman -Syu
sudo pacman -S --needed python git
git clone 'https://git.yandere.cc/Anon/CreatePleromaApp.git'
cd CreatePleromaApp/
python -m venv venv
source venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
pip install Mastodon.py
deactivate
./run.sh -h
```
@ -24,17 +27,43 @@ If everything worked correctly you should see usage information on Create Plerom
## 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`
## Encryption
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.
See below for an example implementation:
## Generating Your OAuth Tokens
To generate your tokens, run the following commands and follow the interactive prompts:
```
def decrypt_settings(self):
if self.settings_encrypt["encrypt"] and not self.decrypted:
import encryption
self.settings_server = encryption.settings_server_decrypt(self.settings_server, self.settings_encrypt, keyfile_path)
self.decrypted = True
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
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
touch cred.sh.key
chmod 600 cred.sh.key cred.sh.gpg
</dev/random tr -dc 'a-zA-Z0-9' | head -c4096 > cred.sh.key
cd ..
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

154
create_app.py Executable file
View File

@ -0,0 +1,154 @@
#! /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())

View File

@ -1,2 +0,0 @@
Mastodon.py
cryptography

8
run.sh
View File

@ -1,7 +1,7 @@
#! /usr/bin/env bash
# Create Pleroma App, automate the creation of pleroma apps
# Copyright (C) 2022 Anon <@Anon@yandere.cc>
# 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
@ -21,8 +21,8 @@ ABS_PATH="$(readlink -f "$0")"
RUN_DIR="$(dirname "$ABS_PATH")"
# Relative paths to the virtual environment and main.py
VENV='./venv/bin/activate'
ENTRY='./src/create_app.py'
VENV='${RUN_DIR}/venv/bin/activate'
ENTRY='${RUN_DIR}/create_app.py'
# cd into the bot's root path, set up the virtual environment, and run
cd "$RUN_DIR"

View File

@ -1,216 +0,0 @@
#! /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())