Compare commits

..

1 commit
main ... v0.3

Author SHA1 Message Date
horhik 885dd2f4e7 update requipments.txt 2021-11-23 10:32:18 +03:00
14 changed files with 48 additions and 336 deletions

View file

@ -1,94 +0,0 @@
name: Docker
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.
on:
push:
branches: [ "main" ]
# Publish semver tags as releases.
tags: [ 'v*.*.*' ]
pull_request:
branches: [ "main" ]
env:
# Use docker.io for Docker Hub if empty
REGISTRY: ghcr.io
# github.repository as <account>/<repo>
IMAGE_NAME: ${{ github.repository }}
jobs:
build:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
# This is used to complete the identity challenge
# with sigstore/fulcio when running outside of PRs.
id-token: write
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Install the cosign tool except on PR
# https://github.com/sigstore/cosign-installer
- name: Install cosign
if: github.event_name != 'pull_request'
uses: sigstore/cosign-installer@f3c664df7af409cb4873aa5068053ba9d61a57b6 #v2.6.0
with:
cosign-release: 'v1.13.1'
# Workaround: https://github.com/docker/build-push-action/issues/461
- name: Setup Docker buildx
uses: docker/setup-buildx-action@79abd3f86f79a9d68a23c75a09a9a85889262adf
# Login against a Docker registry except on PR
# https://github.com/docker/login-action
- name: Log into registry ${{ env.REGISTRY }}
if: github.event_name != 'pull_request'
uses: docker/login-action@28218f9b04b4f3f62068d7b6ce6ca5b26e35336c
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
# Extract metadata (tags, labels) for Docker
# https://github.com/docker/metadata-action
- name: Extract Docker metadata
id: meta
uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
# Build and push Docker image with Buildx (don't push on PR)
# https://github.com/docker/build-push-action
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@ac9327eae2b366085ac7f6a2d02df8aa8ead720a
with:
context: .
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max
# Sign the resulting Docker image digest except on PRs.
# This will only write to the public Rekor transparency log when the Docker
# repository is public to avoid leaking data. If you would like to publish
# transparency data even for private images, pass --force to cosign below.
# https://github.com/sigstore/cosign
- name: Sign the published Docker image
if: ${{ github.event_name != 'pull_request' }}
env:
COSIGN_EXPERIMENTAL: "true"
# This step uses the identity token to provision an ephemeral certificate
# against the sigstore community Fulcio instance.
run: echo "${{ steps.meta.outputs.tags }}" | xargs -I {} cosign sign {}@${{ steps.build-and-push.outputs.digest }}

3
.gitignore vendored
View file

@ -2,7 +2,4 @@
already_posted.txt
src/__pycache__/
.venv
.venv/
.env.sh
docker-compose.yml
docker-compose.yaml

View file

@ -1,25 +1,12 @@
FROM python:3.9
RUN pip install instaloader
RUN pip install Mastodon.py
RUN pip install colorama
COPY . /app
WORKDIR /app
RUN pip install -r requirements.txt
ENV USE_DOCKER=1
ENV YOUR_CONTAINER_NAME="$YOUR_CONTAINER_NAME"
ENV I2M_INSTAGRAM_USER="$I2M_INSTAGRAM_USER"
ENV I2M_INSTANCE="$I2M_INSTANCE"
ENV I2M_TOKEN="$I2M_TOKEN"
ENV I2M_CHECK_INTERVAL "$I2M_CHECK_INTERVAL"
ENV I2M_POST_INTERVAL "$I2M_POST_INTERVAL"
ENV I2M_USE_MASTODON "$I2M_USE_MASTODON"
ENV I2M_FETCH_COUNT "$I2M_FETCH_COUNT"
#ENTRYPOINT ["python", "/app/src/main.py", "--instagram-user", I2M_INSTAGRAM_USER, "--instance", I2M_INSTANCE, "--token", I2M_TOKEN, "--check-interval", I2M_CHECK_INTERVAL, "--post-interval", I2M_POST_INTERVAL, "--fetch-count", I2M_FETCH_COUNT, "--use-mastodon", I2M_USE_MASTODON]
#ENTRYPOINT ["echo", "--instagram-user", I2M_INSTAGRAM_USER, "--instance", I2M_INSTANCE, "--token", I2M_TOKEN, "--check-interval", I2M_CHECK_INTERVAL, "--post-interval", I2M_POST_INTERVAL, "--fetch-count", I2M_FETCH_COUNT, "--use-mastodon", I2M_USE_MASTODON]
ENTRYPOINT ["python", "/app/src/main.py"]

21
Docs.md
View file

@ -3,7 +3,6 @@
## How to use
You can use Instagram2Fedi via docker or just like a python script
** Note: ** _Credentials can be complicated. Running without Instagram credentials (`user-name` and `user-password`) appears to work for a short period of time but will, eventually, fail. Providing credentials will work unless Instagram issues a challenge. Recommend leaving `user-name` blank if running as a scheduled job (`--scheduled`) and providing them otherwise._
### With Docker 🐋
Specify your variables in `./env.sh` and then run `./run.sh`
@ -18,7 +17,7 @@ Specify your arguments. You should use `--use-docker 0`.
For example:
``` bash
./insta2fedi --use-docker false --instagram-user <instagram username> --instance <instance domain> --token <OAuth token> --check-interval 10 --post-interval 10 --use-mastodon 4 --user-name <admin> --user-password <admin>
./insta2fedi --use-docker false --instagram-user <instagram username> --instance <instance domain> --token <OAuth token> --check-interval 10 --post-interval 10 --use-mastodon 4
# will check for new post each 10 seconds
```
@ -34,15 +33,7 @@ For example, default maximum photo count in mastodon is `4`
---
`--instagram-user` - Your fetched instagram account user name.
---
`--user-name` - Your instagram user name.
---
`--user-password` - Your instagram password.
`--instagram-user` - Your instagram user name.
---
@ -66,14 +57,6 @@ If theres more than one new post, sets with which time interval should it post t
`--use-docker` - If you're running it via docker container, set to `1` or `True`
---
`--scheduled` - If set, Instagram2Fedi runs once instead of sleeping for `check-interval` and running forever. This is intended for use as a `cron` job. No additional parameter is needed, just add `--scheduled`.
---
`--verbose` - If set, output all logs including secrets. No additional parameter is needed, just add `--scheduled`.
## Default values ⚙
Default values are:

View file

@ -1,5 +1,3 @@
_Guys... Instagram is sh*t. Even [bibliogram](https://www.reddit.com/r/privacy/comments/wrczxc/bibliogram_is_being_discontinued/) is being discontinued. If you're able to migrate you proile to any fediverse instance or contact to person, whose instagram you'd like to crosspost and ask him to post to fediverse to, it wil be the best desicion_
# Instagram2Fedi <span><img width="50px" src="https://upload.wikimedia.org/wikipedia/commons/9/93/Fediverse_logo_proposal.svg"></span>
Simple tool for crossposting posts from instagram to Mastodon/Pixelfed.
@ -7,38 +5,7 @@ Simple tool for crossposting posts from instagram to Mastodon/Pixelfed.
## Using without docker
See [Docs.md](./Docs.md)
## Using docker-compose
1. create `docker-compose.yaml` with following content
_You can use default.docker-compose.yaml from repo_
``` yaml
version: '3'
services:
bot:
build:
context: .
image: "horhik/instagram2fedi:latest"
environment:
- YOUR_CONTAINER_NAME=<whatever>
- I2M_INSTAGRAM_USER=<instgram username>
- I2M_INSTANCE=<mastodon or pixelfed instance>
- I2M_TOKEN=<your token here>
- I2M_CHECK_INTERVAL=3600 #1 hour
- I2M_POST_INTERVAL=3600 #1 hour
- I2M_USE_MASTODON=4 #max carouse - is 4, if there's no limit set to -1
- I2M_FETCH_COUNT=5 # how many instagram posts to fetch per check_interval -
- I2M_USER_NAME=admin # Your instagram login name
- I2M_USER_PASSWORD=admin # Your instagram password
```
** Note: ** _Since somewhen it's seems not possible to fetch any data from instagram anonymously (maybe i'm wrong and there's a solution, I'll be very happy to know about it). Due that you unfortunately have to had an instagram accound and provide login and password to this script_
2. And edit environment variables
3. Run `docker-compose up -d`
## Using with Dockerfile
## Using with Docker
Just clone repo, specify variables and run it.
You can write all needed variables in `./env.sh` and then do `source ./run.sh`

View file

@ -1,17 +0,0 @@
version: '3'
services:
bot:
build:
context: .
#image: "horhik/instagram2fedi:latest"
environment:
- YOUR_CONTAINER_NAME=instagram2fedi
- I2M_INSTAGRAM_USER= #<fetched instagram user name>
- I2M_INSTANCE= #<Mastodon or pixelfed instance>
- I2M_TOKEN= # SECRET TOKEN
- I2M_CHECK_INTERVAL=3600 #1 hour
- I2M_POST_INTERVAL=3600 #1 hour
- I2M_USE_MASTODON=4 #max carouse - is 4, if there's no limit set to -1
- I2M_FETCH_COUNT=5 # how many instagram posts to fetch per check_interval -
- I2M_USER_NAME= # Your instagram login name
- I2M_USER_PASSWORD= # Your instagram password

8
env.sh
View file

@ -1,10 +1,10 @@
#!b/in/sh
# Required
export YOUR_CONTAINER_NAME=kek
export I2M_INSTAGRAM_USER=kek
export I2M_INSTANCE=kek
export I2M_TOKEN=kek
export YOUR_CONTAINER_NAME=
export I2M_INSTAGRAM_USER=
export I2M_INSTANCE=
export I2M_TOKEN=
export I2M_CHECK_INTERVAL=3600 #1 hour
export I2M_POST_INTERVAL=3600 #1 hour

18
requirements.txt Normal file → Executable file
View file

@ -1,14 +1,14 @@
blurhash==1.1.4
certifi==2022.6.15
charset-normalizer==2.1.0
colorama==0.4.5
decorator==5.1.1
certifi==2021.10.8
charset-normalizer==2.0.7
colorama==0.4.4
decorator==5.1.0
idna==3.3
instaloader==4.9.3
instaloader==4.8.1
Mastodon.py==1.5.1
python-dateutil==2.8.2
python-magic==0.4.27
pytz==2022.1
requests==2.28.1
python-magic==0.4.24
pytz==2021.3
requests==2.26.0
six==1.16.0
urllib3==1.26.11
urllib3==1.26.7

2
run.sh
View file

@ -1,3 +1,3 @@
#!/bin/sh
source ./env.sh
docker build -t $YOUR_CONTAINER_NAME .; docker container run -it -v $(pwd):/app
docker build -t $YOUR_CONTAINER_NAME .; docker container run -it -v $(pwd):/app $YOUR_CONTAINER_NAME --use-docker 1 --instagram-user $I2M_INSTAGRAM_USER --instance $I2M_INSTANCE --token $I2M_TOKEN --check-interval $I2M_CHECK_INTERVAL --post-interval $I2M_POST_INTERVAL --fetch-count $I2M_FETCH_COUNT --use-mastodon $I2M_USE_MASTODON

View file

@ -1,43 +1,7 @@
# -*- coding: utf-8 -*-
import os
import datetime
from colorama import Fore, Back, Style
instagram_user = os.environ.get("I2M_INSTAGRAM_USER")
user_name = os.environ.get("I2M_USER_NAME")
user_password = os.environ.get("I2M_USER_PASSWORD")
instance = os.environ.get("I2M_INSTANCE")
token = os.environ.get("I2M_TOKEN")
check_interval = os.environ.get("I2M_CHECK_INTERVAL") #1 hour
post_interval = os.environ.get("I2M_POST_INTERVAL") #1 hour
use_mastodon = os.environ.get("I2M_USE_MASTODON") #max carousel is 4, if there's no limit set to -1
fetch_count = os.environ.get("I2M_FETCH_COUNT") # how many instagram posts to fetch per check_interval
if os.environ.get("I2M_SCHEDULED") == "True":
scheduled_run = True # run continuously (if False) or a single time (if True)
else:
scheduled_run = False
if os.environ.get("I2M_VERBOSE") == "True": # verbose output
verbose_output = True
else:
verbose_output = False
if verbose_output:
print('instagram', instagram_user)
print('instagram', instance)
print(token)
print(check_interval)
print(post_interval)
print(use_mastodon)
print(fetch_count)
print(user_name)
print(user_password)
print(scheduled_run)
print(verbose_output)
def flags(args, defaults):
def process_arguments(args, defaults):
count = 1
while (len(args) > count):
if(args[count] == "--instance"):
@ -61,16 +25,6 @@ def flags(args, defaults):
defaults["carousel-limit"] = int(args[count + 1])
elif (args[count] == "--use-docker"):
defaults["use-docker"] = args[count + 1]
elif (args[count] == "--user-name"):
defaults["user-name"] = args[count + 1]
elif (args[count] == "--user-password"):
defaults["user-password"] = args[count + 1]
elif (args[count] == "--scheduled"):
defaults["scheduled"] = True
count -= 1
elif (args[count] == "--verbose"):
defaults["verbose"] = True
count -= 1
else:
print(Fore.RED + '❗ -> Wrong Argument Name!...')
@ -80,28 +34,3 @@ def flags(args, defaults):
count +=2
return defaults
def check_defaults(arg):
return arg if arg != '' and arg else None
def process_arguments(args, defaults):
defaults["instance"] = instance if instance !='' and instance else None
defaults["instagram-user"] = instagram_user if instagram_user != '' and instagram_user else None
# Users login and password
defaults["user-name"] = check_defaults(user_name)
defaults["user-password"] = check_defaults(user_password)
defaults["token"] = token if token != '' and token else None
defaults["check-interval"] = int(check_interval) if check_interval != '' and check_interval else None
defaults["post-interval"] = int(post_interval) if post_interval != '' and post_interval else None
defaults["fetch-count"] = int(fetch_count) if fetch_count != '' and fetch_count else None
defaults["carousel-limit"] = int(use_mastodon) if use_mastodon != '' and use_mastodon else None
defaults["scheduled"] = bool(scheduled_run) if scheduled_run else False
defaults["verbose"] = bool(verbose_output) if verbose_output else False
#print(Fore.RED + '❗ -> Missing Argument ')
#print(Style.RESET_ALL)
#print(datetime.datetime.now())
# Command line arguments more prioritized, if smth has been written in .env and in cmd args, then Instagram2Fedi will take values from `cmd args`
new_defaults = flags(args, defaults)
return new_defaults

View file

@ -12,41 +12,14 @@ def split_array(arr, size):
def try_to_get_carousel(array, post):
try:
node = vars(post)['_node']
if 'edge_sidecar_to_children' in node:
try:
urls = list(map(lambda arr: arr['node']['display_url'], node['edge_sidecar_to_children']['edges']))
print(Fore.GREEN + "🎠 > Found carousel!")
print(Style.RESET_ALL)
print(datetime.datetime.now())
return urls
except Exception as e:
print(Fore.RED + "🎠💥 > No carousel :( \n", e)
print(Style.RESET_ALL)
print(datetime.datetime.now())
return array
else:
print(Fore.YELLOW + "🎠💥 > No carousel\n")
# We can also have video in a separate key
if 'is_video' in node and node ['is_video']:
try:
urls = [node['video_url']]
print(Fore.GREEN + "🎞 > Found video!")
print(Style.RESET_ALL)
print(datetime.datetime.now())
return urls
except Exception as e:
print(Fore.RED + "🎞💥 > No video :( \n", e)
print(Style.RESET_ALL)
print(datetime.datetime.now())
return array
else:
print(Fore.YELLOW + "🎠💥 > No video\n")
urls = list(map(lambda arr: arr['node']['display_url'], vars(post)['_node']['edge_sidecar_to_children']['edges']))
return urls
print(Fore.GREEN + "🎠 > Found carousel!")
print(Style.RESET_ALL)
print(datetime.datetime.now())
except Exception as e:
print(Fore.RED + "😱💥 > No node :( \n", e)
print(Fore.RED + "🎠💥 > No carousel :( \n", e)
print(Style.RESET_ALL)
print(datetime.datetime.now())
return array
return array

View file

@ -13,34 +13,27 @@ from arguments import process_arguments
from network import get_new_posts
print(sys.argv)
print("ARGUMENTS")
default_settings = {
"instance": None,
"instagram-user": None,
"user-name": "",
"user-password": None,
"instagram-user": None,
"token": None,
"check-interval": 3600,
"post-interval": 60,
"post-interval": 3600,
"fetch-count" : 10,
"carousel-limit": 4,
"scheduled": False,
"verbose": False
"use-docker": True
}
settings = process_arguments(sys.argv, default_settings)
verbose = settings["verbose"]
if verbose:
print("ARGUMENTS")
print(sys.argv)
print('SETTINGS' , settings)
print(settings)
agree = [1, True, "true", "True", "yes", "Yes"]
if (os.environ.get("USE_DOCKER")):
if (agree.count(settings["use-docker"])):
id_filename = "/app/already_posted.txt"
elif (os.environ.get("USE_KUBERNETES")):
id_filename = "/data/already_posted.txt"
else:
id_filename = "./already_posted.txt"
@ -58,13 +51,8 @@ post_interval = settings["post-interval"]#1m
using_mastodon = settings["carousel-limit"] > 0;
mastodon_carousel_size = settings["carousel-limit"]
scheduled = settings["scheduled"]
user = {
"name": settings["user-name"],
"password": settings["user-password"]
}
print(Fore.GREEN + '🚀 > Connecting to Mastodon/Pixelfed...')
print(Style.RESET_ALL)
@ -75,7 +63,5 @@ mastodon = Mastodon(
# api_base_url = 'https://pixelfed.tokyo/'
)
while True:
get_new_posts(mastodon, mastodon_carousel_size, post_limit, id_filename, using_mastodon, mastodon_carousel_size, post_interval, fetched_user, user)
if scheduled:
break
get_new_posts(mastodon, mastodon_carousel_size, post_limit, id_filename, using_mastodon, mastodon_carousel_size, post_interval, fetched_user)
time.sleep(time_interval_sec)

View file

@ -8,17 +8,14 @@ from converters import split_array, try_to_get_carousel
import hashlib
from instaloader import Profile, Instaloader, LatestStamps
def get_instagram_user(user, fetched_user):
def get_instagram_user(user):
L = Instaloader()
print(Fore.GREEN + 'TEST 🚀 > Connecting to Instagram...')
print(Fore.GREEN + '🚀 > Connecting to Instagram...')
print(Style.RESET_ALL)
print(datetime.datetime.now())
if user["name"] != None:
print("USER USER USER!!!!!!!!!!!!!", user["name"])
L.login(user["name"], user["password"])
return Profile.from_username(L.context, fetched_user)
return Profile.from_username(L.context, user)
def get_image(url):
try:
@ -64,7 +61,7 @@ def toot(urls, title, mastodon, fetched_user ):
ids = []
for url in urls:
ids.append(upload_image_to_mastodon(url, mastodon))
post_text = str(title) + "\n" # creating post text
post_text = str(title) + "\n" + "crosposted from https://instagram.com/"+fetched_user # creating post text
post_text = post_text[0:1000]
if(ids):
print(ids)
@ -75,9 +72,9 @@ def toot(urls, title, mastodon, fetched_user ):
print(Style.RESET_ALL)
print(datetime.datetime.now())
def get_new_posts(mastodon, mastodon_carousel_size, post_limit, already_posted_path, using_mastodon, carousel_size, post_interval, fetched_user, user):
def get_new_posts(mastodon, mastodon_carousel_size, post_limit, already_posted_path, using_mastodon, carousel_size, post_interval, fetched_user):
# fetching user profile to get new posts
profile = get_instagram_user(user, fetched_user)
profile = get_instagram_user(fetched_user)
# get list of all posts
posts = profile.get_posts()
stupidcounter = 0

4
src/requipments.txt Normal file
View file

@ -0,0 +1,4 @@
pyparsing==2.4.7
tqdm==4.62.2
urllib3==1.26.6
colorama==