Compare commits

...

23 Commits
v0.3 ... main

Author SHA1 Message Date
George f5cceda106
Merge pull request #17 from neilcar/main
Add --scheduled, --verbose, and no user.
2023-02-04 17:10:37 +03:00
Neil Carpenter 4a0de99672 Add USE_KUBERNETES 2022-12-04 22:06:02 -05:00
Neil Carpenter 16c76af0c0 Add --scheduled, --verbose, and no user. 2022-12-04 21:37:12 -05:00
George 2db531d2aa
Merge pull request #15 from neilcar/main
Add action to build container
2022-11-29 09:40:55 +00:00
neilcar a5912cee3c
Update docker-publish.yml 2022-11-29 00:59:17 -05:00
neilcar 04e2d13247
Updating cosign
Fixing cosign error related to invalid TUF.
2022-11-29 00:54:47 -05:00
neilcar 519e6beafa
Add GH action to build & push 2022-11-29 00:49:24 -05:00
George 44783e0343
Merge pull request #12 from gunchleoc/fix-typo
Fix typo
2022-09-11 17:45:41 +00:00
GunChleoc 79da06cf2e Fix typo 2022-09-04 09:48:01 +01:00
George e7b8423dc8
Update README.md 2022-09-01 09:22:05 +00:00
Horhik c388f6096a Add instagram authorization 2022-08-31 21:02:54 +03:00
Horhik ae9afdbc3e update gitignore 2022-08-31 20:37:28 +03:00
George 765e398010
Merge pull request #7 from gunchleoc/support-video
Add support for video
2022-05-31 10:39:17 +03:00
GunChleoc 2721af5aeb Add support for video 2022-05-29 13:42:15 +01:00
George 509d44f3a4
Just removed emoji 2022-05-13 06:47:30 +03:00
George 880bbd7c40
Update README.md 2022-05-12 23:27:59 +03:00
horhik 95e3f9d220 Add both flags and environment variables support 2022-04-13 23:11:54 +03:00
horhik ff058ea7d8 Update README 2022-04-13 23:10:49 +03:00
horhik a424783a71 finish docker-compose file 2022-04-13 22:42:50 +03:00
horhik 43d2ea6c01 add more environment variables 2022-04-13 21:53:05 +03:00
horhik 497d04ad2d update requipments.txt 2021-11-23 10:38:49 +03:00
horhik a8354a8b5b update requipments.txt 2021-11-23 10:37:07 +03:00
George 85a3191426
Merge pull request #3 from Horhik/v0.3
Merge v0.3 into master
2021-11-22 16:37:48 +03:00
14 changed files with 340 additions and 41 deletions

94
.github/workflows/docker-publish.yml vendored Normal file
View File

@ -0,0 +1,94 @@
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,4 +2,7 @@
already_posted.txt
src/__pycache__/
.venv
.venv/
.env.sh
docker-compose.yml
docker-compose.yaml

View File

@ -1,12 +1,25 @@
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,6 +3,7 @@
## 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`
@ -17,7 +18,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
./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>
# will check for new post each 10 seconds
```
@ -33,7 +34,15 @@ For example, default maximum photo count in mastodon is `4`
---
`--instagram-user` - Your instagram user name.
`--instagram-user` - Your fetched instagram account user name.
---
`--user-name` - Your instagram user name.
---
`--user-password` - Your instagram password.
---
@ -57,6 +66,14 @@ 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,3 +1,5 @@
_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.
@ -5,7 +7,38 @@ Simple tool for crossposting posts from instagram to Mastodon/Pixelfed.
## Using without docker
See [Docs.md](./Docs.md)
## Using with Docker
## 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
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

@ -0,0 +1,17 @@
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=
export I2M_INSTAGRAM_USER=
export I2M_INSTANCE=
export I2M_TOKEN=
export YOUR_CONTAINER_NAME=kek
export I2M_INSTAGRAM_USER=kek
export I2M_INSTANCE=kek
export I2M_TOKEN=kek
export I2M_CHECK_INTERVAL=3600 #1 hour
export I2M_POST_INTERVAL=3600 #1 hour

15
requirements.txt Executable file → Normal file
View File

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

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 $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
docker build -t $YOUR_CONTAINER_NAME .; docker container run -it -v $(pwd):/app

View File

@ -1,7 +1,43 @@
# -*- coding: utf-8 -*-
import os
import datetime
from colorama import Fore, Back, Style
def process_arguments(args, defaults):
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):
count = 1
while (len(args) > count):
if(args[count] == "--instance"):
@ -25,6 +61,16 @@ def process_arguments(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!...')
@ -34,3 +80,28 @@ def process_arguments(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,14 +12,41 @@ def split_array(arr, size):
def try_to_get_carousel(array, post):
try:
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())
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")
except Exception as e:
print(Fore.RED + "🎠💥 > No carousel :( \n", e)
print(Fore.RED + "😱💥 > No node :( \n", e)
print(Style.RESET_ALL)
print(datetime.datetime.now())
return array
return array

View File

@ -13,27 +13,34 @@ from arguments import process_arguments
from network import get_new_posts
print(sys.argv)
print("ARGUMENTS")
default_settings = {
"instance": None,
"instagram-user": None,
"instagram-user": None,
"user-name": "",
"user-password": None,
"token": None,
"check-interval": 3600,
"post-interval": 3600,
"post-interval": 60,
"fetch-count" : 10,
"carousel-limit": 4,
"use-docker": True
"scheduled": False,
"verbose": False
}
settings = process_arguments(sys.argv, default_settings)
print(settings)
verbose = settings["verbose"]
if verbose:
print("ARGUMENTS")
print(sys.argv)
print('SETTINGS' , settings)
agree = [1, True, "true", "True", "yes", "Yes"]
if (agree.count(settings["use-docker"])):
if (os.environ.get("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"
@ -51,8 +58,13 @@ 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)
@ -63,5 +75,7 @@ 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)
get_new_posts(mastodon, mastodon_carousel_size, post_limit, id_filename, using_mastodon, mastodon_carousel_size, post_interval, fetched_user, user)
if scheduled:
break
time.sleep(time_interval_sec)

View File

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

View File

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