Compare commits
50 Commits
Author | SHA1 | Date |
---|---|---|
George | f5cceda106 | |
Neil Carpenter | 4a0de99672 | |
Neil Carpenter | 16c76af0c0 | |
George | 2db531d2aa | |
neilcar | a5912cee3c | |
neilcar | 04e2d13247 | |
neilcar | 519e6beafa | |
George | 44783e0343 | |
GunChleoc | 79da06cf2e | |
George | e7b8423dc8 | |
Horhik | c388f6096a | |
Horhik | ae9afdbc3e | |
George | 765e398010 | |
GunChleoc | 2721af5aeb | |
George | 509d44f3a4 | |
George | 880bbd7c40 | |
horhik | 95e3f9d220 | |
horhik | ff058ea7d8 | |
horhik | a424783a71 | |
horhik | 43d2ea6c01 | |
horhik | 497d04ad2d | |
horhik | a8354a8b5b | |
George | 85a3191426 | |
George | 684bb8c5bb | |
horhik | 971e74d254 | |
horhik | 68f475f6e5 | |
George | 043739193f | |
George | 918c22a021 | |
Innubis | a31fccae67 | |
horhik | edec38f883 | |
horhik | 2f35753de5 | |
horhik | 2b425b0f5a | |
horhik | 6078359bb6 | |
Innubis | 5c7a8a2861 | |
root | 61fa41d39e | |
horhik | 5c8867a938 | |
horhik | a39365ec1f | |
horhik | 0cea3d42f3 | |
horhik | 66ce481e03 | |
horhik | aa0e8cfdbf | |
horhik | 4847a5e870 | |
horhik | 535ea46f24 | |
horhik | 1c717f9ded | |
horhik | 88e344a9f7 | |
horhik | e35b400819 | |
horhik | 4f27547390 | |
root | 11e33f07a2 | |
root | 6c6a87cbac | |
horhik | df66d02287 | |
horhik | 9ddfab49d4 |
|
@ -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 }}
|
|
@ -1,3 +1,8 @@
|
|||
*~
|
||||
already_posted.txt
|
||||
src/__pycache__/
|
||||
.venv
|
||||
.venv/
|
||||
.env.sh
|
||||
docker-compose.yml
|
||||
docker-compose.yaml
|
||||
|
|
|
@ -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"]
|
||||
|
||||
|
|
|
@ -0,0 +1,89 @@
|
|||
# Instagram2Fedi Docs 📜
|
||||
|
||||
## 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`
|
||||
|
||||
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 --user-name <admin> --user-password <admin>
|
||||
# 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 fetched instagram account user name.
|
||||
|
||||
---
|
||||
|
||||
`--user-name` - Your instagram user name.
|
||||
|
||||
---
|
||||
|
||||
`--user-password` - Your instagram password.
|
||||
|
||||
---
|
||||
|
||||
`--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`
|
||||
|
||||
---
|
||||
|
||||
`--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:
|
||||
``` 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,57 @@
|
|||
_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 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 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`
|
||||
|
||||
``` bash
|
||||
git clone https://github.com/horhik/instagram2fedi
|
||||
cd instagram2fedi
|
||||
docker build -t $YOUR_CONTAINER_NAME .
|
||||
docker container run -it -d -v $(pwd):/app $YOUR_CONTAINER_NAME $I2M_INSTAGRAM_USER $I2M_INSTANCE $I2M_TOKEN
|
||||
nano ./env.sh
|
||||
source ./run.sh
|
||||
```
|
||||
|
||||
You can write all needed variables in `./env.sh` and then do `source ./run.sh`
|
||||
|
||||
|
||||
![image](https://user-images.githubusercontent.com/46262811/131577640-a3103ff2-af37-422d-96f1-60f1acdef939.png)
|
||||
![screenshot](./img.png)
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -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
|
15
env.sh
15
env.sh
|
@ -1,8 +1,13 @@
|
|||
#!/bin/sh
|
||||
#!b/in/sh
|
||||
|
||||
export YOUR_CONTAINER_NAME=
|
||||
export I2M_INSTAGRAM_USER=
|
||||
export I2M_INSTANCE=
|
||||
export I2M_TOKEN=
|
||||
# Required
|
||||
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
|
||||
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,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
|
||||
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
2
run.sh
|
@ -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 $I2M_INSTAGRAM_USER $I2M_INSTANCE $I2M_TOKEN
|
||||
docker build -t $YOUR_CONTAINER_NAME .; docker container run -it -v $(pwd):/app
|
||||
|
|
|
@ -0,0 +1,107 @@
|
|||
# -*- 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):
|
||||
count = 1
|
||||
while (len(args) > count):
|
||||
if(args[count] == "--instance"):
|
||||
defaults["instance"] = args[count + 1]
|
||||
elif (args[count] == "--instagram-user"):
|
||||
defaults["instagram-user"] = args[count + 1]
|
||||
|
||||
elif (args[count] == "--token"):
|
||||
defaults["token"] = args[count + 1]
|
||||
|
||||
elif (args[count] == "--check-interval"):
|
||||
defaults["check-interval"] = int(args[count + 1])
|
||||
|
||||
elif (args[count] == "--post-interval"):
|
||||
defaults["post-interval"] = int(args[count + 1])
|
||||
|
||||
elif (args[count] == "--fetch-count"):
|
||||
defaults["fetch-count"] = int(args[count + 1])
|
||||
|
||||
elif (args[count] == "--use-mastodon"):
|
||||
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!...')
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
|
||||
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
|
||||
|
|
@ -1,4 +1,6 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from colorama import Fore, Back, Style
|
||||
import datetime
|
||||
|
||||
def split_array(arr, size):
|
||||
count = len(arr) // size + 1
|
||||
|
@ -10,13 +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
|
||||
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 node :( \n", e)
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
return array
|
||||
return array
|
||||
|
|
68
src/main.py
68
src/main.py
|
@ -1,41 +1,81 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
from mastodon import Mastodon
|
||||
from colorama import Fore, Back, Style
|
||||
from instaloader import Profile, Instaloader, LatestStamps
|
||||
from arguments import process_arguments
|
||||
|
||||
|
||||
from network import get_new_posts
|
||||
|
||||
id_filename = "/app/already_posted.txt"
|
||||
|
||||
default_settings = {
|
||||
"instance": None,
|
||||
"instagram-user": None,
|
||||
"user-name": "",
|
||||
"user-password": None,
|
||||
"token": None,
|
||||
"check-interval": 3600,
|
||||
"post-interval": 60,
|
||||
"fetch-count" : 10,
|
||||
"carousel-limit": 4,
|
||||
"scheduled": False,
|
||||
"verbose": False
|
||||
}
|
||||
|
||||
settings = process_arguments(sys.argv, default_settings)
|
||||
|
||||
verbose = settings["verbose"]
|
||||
|
||||
if verbose:
|
||||
print("ARGUMENTS")
|
||||
print(sys.argv)
|
||||
print('SETTINGS' , settings)
|
||||
|
||||
agree = [1, True, "true", "True", "yes", "Yes"]
|
||||
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"
|
||||
|
||||
|
||||
with open(id_filename, "a") as f:
|
||||
f.write("\n")
|
||||
|
||||
fetched_user = sys.argv[1]
|
||||
mastodon_instance = sys.argv[2]
|
||||
mastodon_token = sys.argv[3]
|
||||
fetched_user = settings["instagram-user"]
|
||||
mastodon_instance = settings["instance"]
|
||||
mastodon_token = settings["token"]
|
||||
|
||||
post_limit = 1
|
||||
time_interval_sec = 86400
|
||||
post_interval = 10
|
||||
post_limit = settings["fetch-count"]
|
||||
time_interval_sec = settings["check-interval"] #1d
|
||||
post_interval = settings["post-interval"]#1m
|
||||
|
||||
using_mastodon = True;
|
||||
mastodon_carousel_size = 4
|
||||
using_mastodon = settings["carousel-limit"] > 0;
|
||||
mastodon_carousel_size = settings["carousel-limit"]
|
||||
scheduled = settings["scheduled"]
|
||||
|
||||
print(Fore.GREEN + '🚀 > Connecting to Instagram...')
|
||||
print(Style.RESET_ALL)
|
||||
|
||||
L = Instaloader()
|
||||
profile = Profile.from_username(L.context, fetched_user)
|
||||
user = {
|
||||
"name": settings["user-name"],
|
||||
"password": settings["user-password"]
|
||||
}
|
||||
|
||||
print(Fore.GREEN + '🚀 > Connecting to Mastodon/Pixelfed...')
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
mastodon = Mastodon(
|
||||
access_token = mastodon_token,
|
||||
api_base_url = mastodon_instance
|
||||
# api_base_url = 'https://pixelfed.tokyo/'
|
||||
)
|
||||
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, user)
|
||||
if scheduled:
|
||||
break
|
||||
time.sleep(time_interval_sec)
|
||||
|
|
|
@ -1,78 +1,110 @@
|
|||
# -*- coding: utf-8 -*-
|
||||
from colorama import Fore, Back, Style
|
||||
import requests
|
||||
import time
|
||||
import datetime
|
||||
from already_posted import already_posted, mark_as_posted
|
||||
from converters import split_array, try_to_get_carousel
|
||||
import hashlib
|
||||
from instaloader import Profile, Instaloader, LatestStamps
|
||||
|
||||
def get_instagram_user(user, fetched_user):
|
||||
L = Instaloader()
|
||||
|
||||
print(Fore.GREEN + 'TEST 🚀 > 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)
|
||||
|
||||
def get_image(url):
|
||||
try:
|
||||
print(Fore.YELLOW + "🚀 > Downloading Image...", url)
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
|
||||
response = requests.get(url)
|
||||
response.raw.decode_content = True
|
||||
|
||||
print(Fore.GREEN + "✨ > Downloaded!")
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
|
||||
return response.content
|
||||
except Exception as e:
|
||||
|
||||
print(Fore.RED + "💥 > Failed to download image. \n", e)
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
|
||||
|
||||
def upload_image_to_mastodon(url, mastodon):
|
||||
try:
|
||||
print(Fore.YELLOW + "🐘 > Uploading Image...")
|
||||
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
|
||||
print(Fore.GREEN + "✨ > Uploaded!")
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
return media["id"]
|
||||
except Exception as e:
|
||||
print(Fore.RED + "💥 > failed to upload image to mastodon. \n", e)
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
|
||||
def toot(urls, title, mastodon, fetched_user ):
|
||||
try:
|
||||
print(Fore.YELLOW + "🐘 > Creating Toot...", title)
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
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)
|
||||
mastodon.status_post(post_text, media_ids = ids)
|
||||
|
||||
except Exception as e:
|
||||
print(Fore.RED + "😿 > Failed to create toot \n", e)
|
||||
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, user):
|
||||
# fetching user profile to get new posts
|
||||
profile = get_instagram_user(user, fetched_user)
|
||||
# get list of all posts
|
||||
posts = profile.get_posts()
|
||||
stupidcounter = 0
|
||||
for post in posts:
|
||||
stupidcounter += 1
|
||||
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):
|
||||
print(Fore.YELLOW + "🐘 > Already Posted ", post.url)
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
continue
|
||||
print("Posting... ", post.url)
|
||||
print(datetime.datetime.now())
|
||||
if using_mastodon:
|
||||
urls_arr = split_array(url_arr, carousel_size)
|
||||
for urls in urls_arr:
|
||||
toot(urls, post.caption, mastodon, fetched_user)
|
||||
else:
|
||||
toot(url_arr, post.caption, mastodon, fetched_user)
|
||||
|
||||
mark_as_posted(str(post.mediaid), already_posted_path)
|
||||
time.sleep(post_interval)
|
||||
else:
|
||||
return
|
||||
break
|
||||
print(Fore.GREEN + "✨ > Fetched All")
|
||||
print(Style.RESET_ALL)
|
||||
print(datetime.datetime.now())
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue