commit
85a3191426
|
@ -1,3 +1,5 @@
|
||||||
*~
|
*~
|
||||||
already_posted.txt
|
already_posted.txt
|
||||||
src/__pycache__/
|
src/__pycache__/
|
||||||
|
.venv
|
||||||
|
.env.sh
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
# Instagram2Fedi Docs 📜
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
You can use Instagram2Fedi via docker or just like a python script
|
||||||
|
|
||||||
|
### With Docker 🐋
|
||||||
|
|
||||||
|
Specify your variables in `./env.sh` and then run `./run.sh`
|
||||||
|
|
||||||
|
You can modify `docker run` arguments in `./run.sh`
|
||||||
|
|
||||||
|
### Just a python script 🐍
|
||||||
|
|
||||||
|
Run `pip3 install -r requirements.txt` and then run `./insta2fedi`.
|
||||||
|
|
||||||
|
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
|
||||||
|
# will check for new post each 10 seconds
|
||||||
|
```
|
||||||
|
|
||||||
|
## Command line arguments 🖥
|
||||||
|
|
||||||
|
`--use-mastodon` - set not positive number (`0`, `-1`...) if your instance don't have max image count limit.
|
||||||
|
|
||||||
|
For example, default maximum photo count in mastodon is `4`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--instance` - Your instance url
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--instagram-user` - Your instagram user name.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--token` - Your OAuth token
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--check-interval` - Interval in seconds how often to check for new posts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--post-interval` - Interval in seconds between posting new fetched posts.
|
||||||
|
|
||||||
|
If theres more than one new post, sets with which time interval should it post them
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--fetch-count` - How many new posts to select
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`--use-docker` - If you're running it via docker container, set to `1` or `True`
|
||||||
|
|
||||||
|
|
||||||
|
## Default values ⚙
|
||||||
|
Default values are:
|
||||||
|
``` bash
|
||||||
|
--instance None
|
||||||
|
--instagram-user None
|
||||||
|
--token None
|
||||||
|
--check-interval 3600
|
||||||
|
--post-interval 3600
|
||||||
|
--fetch-count 10
|
||||||
|
--use-mastodon 4
|
||||||
|
--use-docker True
|
||||||
|
```
|
|
@ -1,22 +1,24 @@
|
||||||
# Instagram2Fedi <span><img width="50px" src="https://upload.wikimedia.org/wikipedia/commons/9/93/Fediverse_logo_proposal.svg"></span>
|
# Instagram2Fedi <span><img width="50px" src="https://upload.wikimedia.org/wikipedia/commons/9/93/Fediverse_logo_proposal.svg"></span>
|
||||||
|
|
||||||
Simple python 🐍 script for crossposting from instagram to Mastodon/Pixelfed
|
Simple tool for crossposting posts from instagram to Mastodon/Pixelfed.
|
||||||
|
|
||||||
## Installing
|
## Using without docker
|
||||||
|
See [Docs.md](./Docs.md)
|
||||||
|
|
||||||
Just clone repo, build a docker container and run it
|
## 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`
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
git clone https://github.com/horhik/instagram2fedi
|
git clone https://github.com/horhik/instagram2fedi
|
||||||
cd instagram2fedi
|
cd instagram2fedi
|
||||||
docker build -t $YOUR_CONTAINER_NAME .
|
nano ./env.sh
|
||||||
docker container run -it -d -v $(pwd):/app $YOUR_CONTAINER_NAME $I2M_INSTAGRAM_USER $I2M_INSTANCE $I2M_TOKEN
|
source ./run.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
You can write all needed variables in `./env.sh` and then do `source ./run.sh`
|
|
||||||
|
|
||||||
|
![screenshot](./img.png)
|
||||||
![image](https://user-images.githubusercontent.com/46262811/131577640-a3103ff2-af37-422d-96f1-60f1acdef939.png)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
7
env.sh
7
env.sh
|
@ -1,8 +1,13 @@
|
||||||
#!/bin/sh
|
#!b/in/sh
|
||||||
|
|
||||||
|
# Required
|
||||||
export YOUR_CONTAINER_NAME=
|
export YOUR_CONTAINER_NAME=
|
||||||
export I2M_INSTAGRAM_USER=
|
export I2M_INSTAGRAM_USER=
|
||||||
export I2M_INSTANCE=
|
export I2M_INSTANCE=
|
||||||
export I2M_TOKEN=
|
export I2M_TOKEN=
|
||||||
|
|
||||||
|
export I2M_CHECK_INTERVAL=3600 #1 hour
|
||||||
|
export I2M_POST_INTERVAL=3600 #1 hour
|
||||||
|
export I2M_USE_MASTODON=4 #max carousel length is 4, if there's no limit set to -1
|
||||||
|
export I2M_FETCH_COUNT=5 # how many instagram posts to fetch per check_interval
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
#!/bin/sh
|
||||||
|
python3 src/main.py $@
|
|
@ -0,0 +1,3 @@
|
||||||
|
Mastodon.py==1.5.1
|
||||||
|
colorama==0.4.4
|
||||||
|
instaloader==4.8.1
|
2
run.sh
2
run.sh
|
@ -1,3 +1,3 @@
|
||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
source ./env.sh
|
source ./env.sh
|
||||||
docker build -t $YOUR_CONTAINER_NAME .; docker container run -it -v $(pwd):/app $YOUR_CONTAINER_NAME --instagram-user $I2M_INSTAGRAM_USER --instance $I2M_INSTANCE --token $I2M_TOKEN
|
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
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import datetime
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
def process_arguments(args, defaults):
|
def process_arguments(args, defaults):
|
||||||
count = 1
|
count = 1
|
||||||
|
@ -11,28 +13,24 @@ def process_arguments(args, defaults):
|
||||||
defaults["token"] = args[count + 1]
|
defaults["token"] = args[count + 1]
|
||||||
|
|
||||||
elif (args[count] == "--check-interval"):
|
elif (args[count] == "--check-interval"):
|
||||||
defaults["check-interval"] = args[count + 1]
|
defaults["check-interval"] = int(args[count + 1])
|
||||||
|
|
||||||
elif (args[count] == "--post-interval"):
|
elif (args[count] == "--post-interval"):
|
||||||
defaults["post-interval"] = args[count + 1]
|
defaults["post-interval"] = int(args[count + 1])
|
||||||
|
|
||||||
elif (args[count] == "--fetch-count"):
|
elif (args[count] == "--fetch-count"):
|
||||||
defaults["fetch-count"] = args[count + 1]
|
defaults["fetch-count"] = int(args[count + 1])
|
||||||
|
|
||||||
elif (args[count] == "--using-mastodon"):
|
elif (args[count] == "--use-mastodon"):
|
||||||
defaults["carousel-limit"] = int(args[count + 1])
|
defaults["carousel-limit"] = int(args[count + 1])
|
||||||
|
elif (args[count] == "--use-docker"):
|
||||||
|
defaults["use-docker"] = args[count + 1]
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print(Fore.RED + '❗ -> Wrong Argument Name!...')
|
print(Fore.RED + '❗ -> Wrong Argument Name!...')
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
count +=2
|
count +=2
|
||||||
return defaults
|
return defaults
|
||||||
|
|
||||||
#fuck this shit im out''
|
|
||||||
#teenagers scare the living shit out of me
|
|
||||||
#deeeespaaaacito quero esperanto de despacito
|
|
||||||
#хорошо всё будет хорошо
|
|
||||||
#и камнем вниииз
|
|
||||||
#u kinda smell *smif* like a BAKA
|
|
||||||
#Yeren Yegaaaaaaa!!!!!!!!!
|
|
||||||
|
|
|
@ -1,4 +1,6 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
|
import datetime
|
||||||
|
|
||||||
def split_array(arr, size):
|
def split_array(arr, size):
|
||||||
count = len(arr) // size + 1
|
count = len(arr) // size + 1
|
||||||
|
@ -14,9 +16,10 @@ def try_to_get_carousel(array, post):
|
||||||
return urls
|
return urls
|
||||||
print(Fore.GREEN + "🎠 > Found carousel!")
|
print(Fore.GREEN + "🎠 > Found carousel!")
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(Fore.RED + "🎠💥 > No carousel :( \n", e)
|
print(Fore.RED + "🎠💥 > No carousel :( \n", e)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
return array
|
return array
|
||||||
|
|
||||||
|
|
||||||
|
|
25
src/main.py
25
src/main.py
|
@ -1,6 +1,8 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
import os
|
import os
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import datetime
|
||||||
import json
|
import json
|
||||||
from mastodon import Mastodon
|
from mastodon import Mastodon
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
|
@ -11,9 +13,6 @@ from arguments import process_arguments
|
||||||
from network import get_new_posts
|
from network import get_new_posts
|
||||||
|
|
||||||
|
|
||||||
id_filename = "/app/already_posted.txt"
|
|
||||||
with open(id_filename, "a") as f:
|
|
||||||
f.write("\n")
|
|
||||||
|
|
||||||
print(sys.argv)
|
print(sys.argv)
|
||||||
print("ARGUMENTS")
|
print("ARGUMENTS")
|
||||||
|
@ -24,13 +23,24 @@ default_settings = {
|
||||||
"check-interval": 3600,
|
"check-interval": 3600,
|
||||||
"post-interval": 3600,
|
"post-interval": 3600,
|
||||||
"fetch-count" : 10,
|
"fetch-count" : 10,
|
||||||
"carousel-limit": 4
|
"carousel-limit": 4,
|
||||||
|
"use-docker": True
|
||||||
}
|
}
|
||||||
|
|
||||||
settings = process_arguments(sys.argv, default_settings)
|
settings = process_arguments(sys.argv, default_settings)
|
||||||
|
|
||||||
print(settings)
|
print(settings)
|
||||||
|
|
||||||
|
agree = [1, True, "true", "True", "yes", "Yes"]
|
||||||
|
if (agree.count(settings["use-docker"])):
|
||||||
|
id_filename = "/app/already_posted.txt"
|
||||||
|
else:
|
||||||
|
id_filename = "./already_posted.txt"
|
||||||
|
|
||||||
|
|
||||||
|
with open(id_filename, "a") as f:
|
||||||
|
f.write("\n")
|
||||||
|
|
||||||
fetched_user = settings["instagram-user"]
|
fetched_user = settings["instagram-user"]
|
||||||
mastodon_instance = settings["instance"]
|
mastodon_instance = settings["instance"]
|
||||||
mastodon_token = settings["token"]
|
mastodon_token = settings["token"]
|
||||||
|
@ -42,19 +52,16 @@ post_interval = settings["post-interval"]#1m
|
||||||
using_mastodon = settings["carousel-limit"] > 0;
|
using_mastodon = settings["carousel-limit"] > 0;
|
||||||
mastodon_carousel_size = settings["carousel-limit"]
|
mastodon_carousel_size = settings["carousel-limit"]
|
||||||
|
|
||||||
print(Fore.GREEN + '🚀 > Connecting to Instagram...')
|
|
||||||
print(Style.RESET_ALL)
|
|
||||||
|
|
||||||
L = Instaloader()
|
|
||||||
profile = Profile.from_username(L.context, fetched_user)
|
|
||||||
|
|
||||||
print(Fore.GREEN + '🚀 > Connecting to Mastodon/Pixelfed...')
|
print(Fore.GREEN + '🚀 > Connecting to Mastodon/Pixelfed...')
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
mastodon = Mastodon(
|
mastodon = Mastodon(
|
||||||
access_token = mastodon_token,
|
access_token = mastodon_token,
|
||||||
api_base_url = mastodon_instance
|
api_base_url = mastodon_instance
|
||||||
# api_base_url = 'https://pixelfed.tokyo/'
|
# api_base_url = 'https://pixelfed.tokyo/'
|
||||||
)
|
)
|
||||||
while True:
|
while True:
|
||||||
get_new_posts(mastodon, profile, mastodon_carousel_size, post_limit, id_filename, using_mastodon, mastodon_carousel_size, post_interval, fetched_user)
|
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)
|
time.sleep(time_interval_sec)
|
||||||
|
|
|
@ -1,44 +1,63 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
from colorama import Fore, Back, Style
|
from colorama import Fore, Back, Style
|
||||||
import requests
|
import requests
|
||||||
import time
|
import time
|
||||||
|
import datetime
|
||||||
from already_posted import already_posted, mark_as_posted
|
from already_posted import already_posted, mark_as_posted
|
||||||
from converters import split_array, try_to_get_carousel
|
from converters import split_array, try_to_get_carousel
|
||||||
import hashlib
|
import hashlib
|
||||||
|
from instaloader import Profile, Instaloader, LatestStamps
|
||||||
|
|
||||||
|
def get_instagram_user(user):
|
||||||
|
L = Instaloader()
|
||||||
|
|
||||||
|
print(Fore.GREEN + '🚀 > Connecting to Instagram...')
|
||||||
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
|
return Profile.from_username(L.context, user)
|
||||||
|
|
||||||
def get_image(url):
|
def get_image(url):
|
||||||
try:
|
try:
|
||||||
print(Fore.YELLOW + "🚀 > Downloading Image...", url)
|
print(Fore.YELLOW + "🚀 > Downloading Image...", url)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
response = requests.get(url)
|
response = requests.get(url)
|
||||||
response.raw.decode_content = True
|
response.raw.decode_content = True
|
||||||
|
|
||||||
print(Fore.GREEN + "✨ > Downloaded!")
|
print(Fore.GREEN + "✨ > Downloaded!")
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
return response.content
|
return response.content
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|
||||||
print(Fore.RED + "💥 > Failed to download image. \n", e)
|
print(Fore.RED + "💥 > Failed to download image. \n", e)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
|
|
||||||
def upload_image_to_mastodon(url, mastodon):
|
def upload_image_to_mastodon(url, mastodon):
|
||||||
try:
|
try:
|
||||||
print(Fore.YELLOW + "🐘 > Uploading Image...")
|
print(Fore.YELLOW + "🐘 > Uploading Image...")
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
media = mastodon.media_post(media_file = get_image(url), mime_type = "image/jpeg") # sending image to mastodon
|
media = mastodon.media_post(media_file = get_image(url), mime_type = "image/jpeg") # sending image to mastodon
|
||||||
print(Fore.GREEN + "✨ > Uploaded!")
|
print(Fore.GREEN + "✨ > Uploaded!")
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
return media["id"]
|
return media["id"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(Fore.RED + "💥 > failed to upload image to mastodon. \n", e)
|
print(Fore.RED + "💥 > failed to upload image to mastodon. \n", e)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
def toot(urls, title, mastodon, fetched_user ):
|
def toot(urls, title, mastodon, fetched_user ):
|
||||||
try:
|
try:
|
||||||
print(Fore.YELLOW + "🐘 > Creating Toot...", title)
|
print(Fore.YELLOW + "🐘 > Creating Toot...", title)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
ids = []
|
ids = []
|
||||||
for url in urls:
|
for url in urls:
|
||||||
ids.append(upload_image_to_mastodon(url, mastodon))
|
ids.append(upload_image_to_mastodon(url, mastodon))
|
||||||
|
@ -51,28 +70,38 @@ def toot(urls, title, mastodon, fetched_user ):
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(Fore.RED + "😿 > Failed to create toot \n", e)
|
print(Fore.RED + "😿 > Failed to create toot \n", e)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
def get_new_posts(mastodon, profile, mastodon_carousel_size, post_limit, already_posted_path, using_mastodon, carousel_size, post_interval, fetched_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(fetched_user)
|
||||||
|
# get list of all posts
|
||||||
posts = profile.get_posts()
|
posts = profile.get_posts()
|
||||||
stupidcounter = 0
|
stupidcounter = 0
|
||||||
for post in posts:
|
for post in posts:
|
||||||
stupidcounter += 1
|
|
||||||
url_arr = try_to_get_carousel([post.url], post)
|
url_arr = try_to_get_carousel([post.url], post)
|
||||||
if stupidcounter <= post_limit:
|
# checking only `post_limit` last posts
|
||||||
|
if stupidcounter < post_limit:
|
||||||
|
stupidcounter += 1
|
||||||
if already_posted(str(post.mediaid), already_posted_path):
|
if already_posted(str(post.mediaid), already_posted_path):
|
||||||
print(Fore.YELLOW + "🐘 > Already Posted ", post.url)
|
print(Fore.YELLOW + "🐘 > Already Posted ", post.url)
|
||||||
print(Style.RESET_ALL)
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
continue
|
continue
|
||||||
print("Posting... ", post.url)
|
print("Posting... ", post.url)
|
||||||
|
print(datetime.datetime.now())
|
||||||
if using_mastodon:
|
if using_mastodon:
|
||||||
urls_arr = split_array(url_arr, carousel_size)
|
urls_arr = split_array(url_arr, carousel_size)
|
||||||
for urls in urls_arr:
|
for urls in urls_arr:
|
||||||
toot(urls, post.caption, mastodon, fetched_user)
|
toot(urls, post.caption, mastodon, fetched_user)
|
||||||
else:
|
else:
|
||||||
toot(url_arr, post.caption, mastodon, fetched_user)
|
toot(url_arr, post.caption, mastodon, fetched_user)
|
||||||
mark_as_posted(str(post.mediaid), already_posted_path)
|
mark_as_posted(str(post.mediaid), already_posted_path)
|
||||||
time.sleep(post_interval)
|
time.sleep(post_interval)
|
||||||
else:
|
else:
|
||||||
return
|
break
|
||||||
|
print(Fore.GREEN + "✨ > Fetched All")
|
||||||
|
print(Style.RESET_ALL)
|
||||||
|
print(datetime.datetime.now())
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue