init
This commit is contained in:
commit
a93a8d736c
14
.env.example
Normal file
14
.env.example
Normal file
@ -0,0 +1,14 @@
|
||||
OSHI_USERNAME=
|
||||
|
||||
INSTAGRAM_USER=
|
||||
INSTAGRAM_PASS=
|
||||
|
||||
REDIS_HOST=127.0.0.1
|
||||
REDIS_PORT=6379
|
||||
REDIS_PASSWORD=
|
||||
REDIS_DB=0
|
||||
|
||||
BOT_TOKEN=
|
||||
CHAT_ID=
|
||||
|
||||
TELEGRAM_URL=https://api.telegram.org/bot
|
18
.gitignore
vendored
Normal file
18
.gitignore
vendored
Normal file
@ -0,0 +1,18 @@
|
||||
|
||||
*.json
|
||||
|
||||
__pycache__/
|
||||
|
||||
stories/
|
||||
|
||||
*.pyc
|
||||
|
||||
users\.txt
|
||||
|
||||
\.idea/
|
||||
|
||||
\.vscode/\.ropeproject/
|
||||
|
||||
.env
|
||||
Env/
|
||||
venv/
|
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@ -0,0 +1,15 @@
|
||||
FROM python:3-alpine
|
||||
|
||||
|
||||
RUN apk update && apk upgrade && pip install -U pip
|
||||
|
||||
COPY . /app
|
||||
WORKDIR /app
|
||||
RUN pip --no-cache-dir install -r requirements.txt
|
||||
|
||||
RUN crontab /app/crontab.txt
|
||||
RUN touch /var/log/cron.log
|
||||
|
||||
RUN ln -snf /usr/share/zoneinfo/Asia/Jakarta /etc/localtime && echo "Asia/Jakarta" > /etc/timezone
|
||||
|
||||
CMD crond && tail -f /var/log/cron.log
|
32
README.md
Normal file
32
README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# OshiStories
|
||||
Telegram bot to download your idol's latest media stories and posts
|
||||
|
||||
### Requirements
|
||||
* [Python 3](https://www.python.org/)
|
||||
* [Redis](https://redis.io)
|
||||
* [Docker](https://docker.com)
|
||||
|
||||
### Settings before run
|
||||
1. rename .env.example to .env
|
||||
2. Add instagram username to OSHI_USERNAME with comma separated
|
||||
|
||||
### How To Create & Get BOT Token
|
||||
[BotFather](https://www.siteguarding.com/en/how-to-get-telegram-bot-api-token)
|
||||
|
||||
### How To Get CHAT ID
|
||||
1. Add bot as your friend & start chat
|
||||
2. Open
|
||||
```
|
||||
https://api.telegram.org/bot<YOUR_BOT_TOKEN>/getUpdates
|
||||
```
|
||||

|
||||
|
||||
### Build Docker Images
|
||||
```bash
|
||||
$ docker build -t oshi-stories .
|
||||
```
|
||||
|
||||
### Run Docker Container
|
||||
```bash
|
||||
$ docker run -it --rm --name oshi-stories-container -d oshi-stories
|
||||
```
|
1
crontab.txt
Normal file
1
crontab.txt
Normal file
@ -0,0 +1 @@
|
||||
* * * * * python3 /app/run.py story feed > /dev/null
|
10
requirements.txt
Normal file
10
requirements.txt
Normal file
@ -0,0 +1,10 @@
|
||||
certifi==2019.6.16
|
||||
chardet==3.0.4
|
||||
idna==2.8
|
||||
instagram-private-api==1.6.0
|
||||
python-dateutil==2.8.0
|
||||
python-dotenv==0.10.3
|
||||
redis==3.3.8
|
||||
requests==2.22.0
|
||||
six==1.12.0
|
||||
urllib3==1.25.3
|
268
run.py
Normal file
268
run.py
Normal file
@ -0,0 +1,268 @@
|
||||
import argparse
|
||||
import codecs
|
||||
import datetime
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
from dotenv import load_dotenv
|
||||
from xml.dom.minidom import parseString
|
||||
import urllib as urllib
|
||||
import send_telegram
|
||||
import time
|
||||
# try:
|
||||
# import urllib.request as urllib
|
||||
# except ImportError:
|
||||
|
||||
try:
|
||||
from instagram_private_api import (
|
||||
Client, ClientError, ClientLoginError,
|
||||
ClientCookieExpiredError, ClientLoginRequiredError,
|
||||
__version__ as client_version)
|
||||
except ImportError:
|
||||
import sys
|
||||
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), '..'))
|
||||
from instagram_private_api import (
|
||||
Client, ClientError, ClientLoginError,
|
||||
ClientCookieExpiredError, ClientLoginRequiredError,
|
||||
__version__ as client_version)
|
||||
|
||||
from instagram_private_api import ClientError
|
||||
from instagram_private_api import Client
|
||||
import redis
|
||||
|
||||
|
||||
def to_json(python_object):
|
||||
if isinstance(python_object, bytes):
|
||||
return {'__class__': 'bytes',
|
||||
'__value__': codecs.encode(python_object, 'base64').decode()}
|
||||
raise TypeError(repr(python_object) + ' is not JSON serializable')
|
||||
|
||||
|
||||
def from_json(json_object):
|
||||
if '__class__' in json_object and json_object.get('__class__') == 'bytes':
|
||||
return codecs.decode(json_object.get('__value__').encode(), 'base64')
|
||||
return json_object
|
||||
|
||||
|
||||
def onlogin_callback(api, setting_name):
|
||||
cache_settings = api.settings
|
||||
redis_client.setex(setting_name, 7776000000, json.dumps(cache_settings, default=to_json))
|
||||
print('[+] New auth cookie file was made: {0!s}'.format(setting_name))
|
||||
|
||||
def login(username, password):
|
||||
device_id = None
|
||||
try:
|
||||
settings_file = "credentials.json"
|
||||
credential = redis_client.get('credentials')
|
||||
if credential is None:
|
||||
api = Client(
|
||||
username, password,
|
||||
on_login=lambda x: onlogin_callback(x, 'credentials'))
|
||||
else:
|
||||
cached_settings = json.loads(credential, object_hook=from_json)
|
||||
device_id = cached_settings.get('device_id')
|
||||
# reuse auth settings
|
||||
api = Client(
|
||||
username, password,
|
||||
settings=cached_settings)
|
||||
print('[+] Using cached login cookie for "' + api.authenticated_user_name + '".')
|
||||
except (ClientCookieExpiredError, ClientLoginRequiredError) as e:
|
||||
print('ClientCookieExpiredError/ClientLoginRequiredError: {0!s}'.format(e))
|
||||
|
||||
# Login expired
|
||||
# Do relogin but use default ua, keys and such
|
||||
api = Client(
|
||||
username, password,
|
||||
device_id=device_id,
|
||||
on_login=lambda x: onlogin_callback(x, settings_file))
|
||||
|
||||
except ClientLoginError as e:
|
||||
print('[!] Could not login: {:s}.\n[!] {:s}\n\n{:s}'.format(
|
||||
json.loads(e.error_response).get("error_title", "Error title not available."),
|
||||
json.loads(e.error_response).get("message", "Not available"), e.error_response))
|
||||
print('-' * 70)
|
||||
sys.exit(9)
|
||||
except ClientError as e:
|
||||
print('[!] Client Error: {:s}'.format(e.error_response))
|
||||
print('-' * 70)
|
||||
sys.exit(9)
|
||||
except Exception as e:
|
||||
if str(e).startswith("unsupported pickle protocol"):
|
||||
print("[W] This cookie file is not compatible with Python {}.".format(sys.version.split(' ')[0][0]))
|
||||
print("[W] Please delete your cookie file 'credentials.json' and try again.")
|
||||
else:
|
||||
print('[!] Unexpected Exception: {0!s}'.format(e))
|
||||
print('-' * 70)
|
||||
sys.exit(99)
|
||||
|
||||
print('[+] Login to "' + api.authenticated_user_name + '" OK!')
|
||||
cookie_expiry = api.cookie_jar.auth_expires
|
||||
print('[+] Login cookie expiry date: {0!s}'.format(
|
||||
datetime.datetime.fromtimestamp(cookie_expiry).strftime('%Y-%m-%d at %I:%M:%S %p')))
|
||||
|
||||
return api
|
||||
|
||||
def like_post(post_id):
|
||||
like = ig_client.post_like(post_id)
|
||||
return like
|
||||
|
||||
def latest_post(username, user_id):
|
||||
today = datetime.date.today()
|
||||
# today = "2019-09-13"
|
||||
t = time.mktime(datetime.datetime.strptime(str(today), "%Y-%m-%d").timetuple())
|
||||
t = int(t)
|
||||
feed = ig_client.user_feed(user_id, min_timestamp=str(t))
|
||||
f = open("feeds.json", "w")
|
||||
f.write(json.dumps(feed))
|
||||
f.close()
|
||||
|
||||
for post in feed['items']:
|
||||
list_media = []
|
||||
# print()
|
||||
post_id = post['id']
|
||||
pk = post['code']
|
||||
k = '{}:{}:{}'.format("post", username, pk)
|
||||
exist = redis_client.exists(k)
|
||||
if exist == 1:
|
||||
print(pk + " Exist")
|
||||
continue
|
||||
media_type = "photo" if post['media_type'] == 1 else "video"
|
||||
if "carousel_media" in post:
|
||||
carousel = True
|
||||
for i, cs in enumerate(post['carousel_media'], start=1):
|
||||
carousel_media_type = "photo" if cs['media_type'] == 1 else "video"
|
||||
if carousel_media_type == 'photo':
|
||||
list_media.append({
|
||||
"caption":"Photo #{}".format(i),
|
||||
"type":"photo",
|
||||
"media":cs['image_versions2']['candidates'][0]['url']
|
||||
})
|
||||
else:
|
||||
list_media.append({
|
||||
"caption":"Video #{}".format(i),
|
||||
"type":"video",
|
||||
"media":cs['video_versions'][0]['url']
|
||||
})
|
||||
else:
|
||||
carousel = False
|
||||
if media_type == 'photo':
|
||||
url_media = post['image_versions2']['candidates'][0]['url']
|
||||
else:
|
||||
url_media = post['video_versions'][0]['url']
|
||||
post_url = "https://instagram.com/p/{}".format(pk)
|
||||
caption = "[POST] from {}\n\n{}\n\nLink: {}".format(username, post['caption']['text'], post_url)
|
||||
if len(caption) > 1000:
|
||||
caption = caption[:1000] + '...'
|
||||
|
||||
like = like_post(post_id)
|
||||
|
||||
print(pk + " Sending...")
|
||||
if carousel is True:
|
||||
send_telegram.send_media_group(caption=caption, media=list_media)
|
||||
else:
|
||||
send_telegram.telegram_bot_send_media(fileType=media_type, url=url_media, caption=caption)
|
||||
print(pk + " OK")
|
||||
redis_client.setex(k, 86400, "True")
|
||||
return
|
||||
|
||||
def latest_stories(username, user_id):
|
||||
feed = ig_client.user_story_feed(user_id)
|
||||
if feed['reel'] is None:
|
||||
print('No Update')
|
||||
# exit(9)
|
||||
return False
|
||||
taken_at = True
|
||||
feed_json = feed['reel']['items']
|
||||
|
||||
list_video = []
|
||||
list_image = []
|
||||
|
||||
for media in feed_json:
|
||||
if not taken_at:
|
||||
taken_ts = None
|
||||
else:
|
||||
if media.get('imported_taken_at'):
|
||||
taken_ts = datetime.datetime.utcfromtimestamp(media.get('imported_taken_at', "")).strftime(
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
else:
|
||||
taken_ts = datetime.datetime.utcfromtimestamp(media.get('taken_at', "")).strftime(
|
||||
'%Y-%m-%d %H:%M:%S')
|
||||
|
||||
is_video = 'video_versions' in media and 'image_versions2' in media
|
||||
pk = media['code']
|
||||
k = '{}:{}:{}:{}'.format("story", username, "video" if is_video else "photo", pk)
|
||||
exist = redis_client.exists(k)
|
||||
|
||||
if exist == 0:
|
||||
print('{} Sending...'.format(pk))
|
||||
caption = "[STORY] from {}"
|
||||
if is_video:
|
||||
data_media = {
|
||||
'url': media['video_versions'][0]['url'],
|
||||
'taken': taken_ts
|
||||
}
|
||||
# caption = "[VIDEO] from {}"
|
||||
list_video.append(data_media)
|
||||
send_telegram.telegram_bot_send_media(fileType='video', url=data_media['url'], caption=caption.format(username))
|
||||
else:
|
||||
data_media = {
|
||||
'url': media['image_versions2']['candidates'][0]['url'],
|
||||
'taken': taken_ts
|
||||
}
|
||||
# caption = "[PHOTO] from {}"
|
||||
list_image.append(data_media)
|
||||
send_telegram.telegram_bot_send_media(fileType='photo', url=data_media['url'], caption=caption.format(username))
|
||||
print('{} OK'.format(pk))
|
||||
redis_client.setex(k, 86400, json.dumps(data_media))
|
||||
else:
|
||||
print(pk + ' Exists')
|
||||
return feed
|
||||
|
||||
def download_user(user, attempt=0):
|
||||
try:
|
||||
if not user.isdigit():
|
||||
user_res = ig_client.username_info(user)
|
||||
user_id = user_res['user']['pk']
|
||||
else:
|
||||
user_id = user
|
||||
user_info = ig_client.user_info(user_id)
|
||||
if not user_info.get("user", None):
|
||||
raise Exception("No user is associated with the given user id.")
|
||||
else:
|
||||
user = user_info.get("user").get("username")
|
||||
if "feed" in sys.argv:
|
||||
print("[=] Fetch POST [=]")
|
||||
latest_post(user, user_id)
|
||||
if "story" in sys.argv:
|
||||
print("[=] Fetch STORY [=]")
|
||||
latest_stories(user, user_id)
|
||||
|
||||
# print(user)
|
||||
return user
|
||||
except Exception as e:
|
||||
print(e)
|
||||
|
||||
def start():
|
||||
oshi = os.getenv('OSHI_USERNAME')
|
||||
oshi = oshi.split(',')
|
||||
print('------------------------------')
|
||||
for o in oshi:
|
||||
print("Oshi: "+ o)
|
||||
download_user(o)
|
||||
|
||||
if __name__ == '__main__':
|
||||
load_dotenv()
|
||||
try:
|
||||
redis_client = redis.Redis(host=os.getenv('REDIS_HOST'), port=os.getenv('REDIS_PORT'), db=os.getenv('REDIS_DB'), password=os.getenv('REDIS_PASSWORD'))
|
||||
print("Connection to redis has been established...")
|
||||
except Exception as e:
|
||||
print("Cannot connect to redis...")
|
||||
print(e)
|
||||
username = os.getenv('INSTAGRAM_USER')
|
||||
password = os.getenv('INSTAGRAM_PASS')
|
||||
ig_client = login(username, password)
|
||||
start()
|
BIN
screenshot-get-chat_id.png
Normal file
BIN
screenshot-get-chat_id.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 38 KiB |
77
send_telegram.py
Normal file
77
send_telegram.py
Normal file
@ -0,0 +1,77 @@
|
||||
import os
|
||||
import time
|
||||
import requests
|
||||
from dotenv import load_dotenv
|
||||
import json
|
||||
|
||||
def telegram_bot_sendtext(bot_message):
|
||||
send_text = TELEGRAM_URL + BOT_TOKEN + '/sendMessage?chat_id={}&text={}'.format(CHAT_ID,bot_message)
|
||||
response = requests.get(send_text)
|
||||
response_json = response.json()
|
||||
return response.json()
|
||||
|
||||
def send_media_group(**kwargs):
|
||||
caption = kwargs.get('caption')
|
||||
media = kwargs.get('media')
|
||||
carousel = kwargs.get('carousel', None)
|
||||
|
||||
send_text = telegram_bot_sendtext(caption);
|
||||
if send_text['ok'] is not True:
|
||||
print('Stop')
|
||||
return
|
||||
else:
|
||||
message_id = send_text.get('result')['message_id']
|
||||
|
||||
data = {
|
||||
'chat_id' : CHAT_ID,
|
||||
"reply_to_message_id":message_id,
|
||||
"media": json.dumps(media)
|
||||
}
|
||||
send_url = TELEGRAM_URL + '{}{}'.format(BOT_TOKEN, '/sendMediaGroup')
|
||||
response = requests.post(send_url, data=data)
|
||||
response_json = response.json()
|
||||
if response_json.get('ok') is True:
|
||||
print('DONE')
|
||||
return response_json
|
||||
|
||||
def telegram_bot_send_media(**kwargs):
|
||||
fileType = kwargs.get('fileType')
|
||||
url = kwargs.get('url', None)
|
||||
caption = kwargs.get('caption')
|
||||
reply_first = kwargs.get('reply_first', False)
|
||||
reply_id = kwargs.get('reply_id', None)
|
||||
data = {
|
||||
'chat_id' : CHAT_ID,
|
||||
"caption": caption,
|
||||
}
|
||||
if reply_first is True:
|
||||
send_text = telegram_bot_sendtext(caption);
|
||||
if send_text['ok'] is not True:
|
||||
print('Stop')
|
||||
return
|
||||
data.update({"reply_to_message_id": send_text.get('result')['message_id']})
|
||||
if reply_id is not None: data.update({"reply_to_message_id": reply_id})
|
||||
if fileType == 'video':
|
||||
data.update({
|
||||
'video': url
|
||||
})
|
||||
endpoint = "/sendVideo"
|
||||
elif fileType == 'photo':
|
||||
data.update({
|
||||
'photo': url
|
||||
})
|
||||
endpoint = "/sendPhoto"
|
||||
else:
|
||||
print("Failed")
|
||||
return False
|
||||
|
||||
send_url = TELEGRAM_URL + '{}{}'.format(BOT_TOKEN, endpoint)
|
||||
response = requests.post(send_url,data=data)
|
||||
return response.json()
|
||||
|
||||
|
||||
if __name__ == 'send_telegram':
|
||||
load_dotenv()
|
||||
BOT_TOKEN = os.getenv('BOT_TOKEN')
|
||||
CHAT_ID = os.getenv('CHAT_ID')
|
||||
TELEGRAM_URL = os.getenv('TELEGRAM_URL')
|
Loading…
x
Reference in New Issue
Block a user