Merge pull request #75 from dylanpdx/main
Update for recent twitter changes
This commit is contained in:
commit
a987315add
10
Dockerfile
10
Dockerfile
@ -1,19 +1,19 @@
|
|||||||
FROM python:3.6-alpine AS build
|
FROM python:3.10-alpine AS build
|
||||||
RUN apk add build-base python3-dev linux-headers pcre-dev jpeg-dev zlib-dev
|
RUN apk add build-base python3-dev linux-headers pcre-dev jpeg-dev zlib-dev
|
||||||
RUN pip install --upgrade pip
|
RUN pip install --upgrade pip
|
||||||
RUN pip install yt-dlp pillow uwsgi
|
RUN pip install yt-dlp pillow uwsgi
|
||||||
|
|
||||||
FROM python:3.6-alpine AS deps
|
FROM python:3.10-alpine AS deps
|
||||||
WORKDIR /twitfix
|
WORKDIR /twitfix
|
||||||
COPY requirements.txt requirements.txt
|
COPY requirements.txt requirements.txt
|
||||||
COPY --from=build /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages
|
COPY --from=build /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
|
||||||
RUN pip install -r requirements.txt
|
RUN pip install -r requirements.txt
|
||||||
|
|
||||||
FROM python:3.6-alpine AS runner
|
FROM python:3.10-alpine AS runner
|
||||||
EXPOSE 9000
|
EXPOSE 9000
|
||||||
RUN apk add pcre-dev jpeg-dev zlib-dev
|
RUN apk add pcre-dev jpeg-dev zlib-dev
|
||||||
WORKDIR /twitfix
|
WORKDIR /twitfix
|
||||||
CMD ["uwsgi", "twitfix.ini"]
|
CMD ["uwsgi", "twitfix.ini"]
|
||||||
COPY --from=build /usr/local/bin/uwsgi /usr/local/bin/uwsgi
|
COPY --from=build /usr/local/bin/uwsgi /usr/local/bin/uwsgi
|
||||||
COPY --from=deps /usr/local/lib/python3.6/site-packages /usr/local/lib/python3.6/site-packages
|
COPY --from=deps /usr/local/lib/python3.10/site-packages /usr/local/lib/python3.10/site-packages
|
||||||
COPY . .
|
COPY . .
|
||||||
|
@ -30,9 +30,10 @@ def scaleImageIterable(args):
|
|||||||
targetWidth = args[1]
|
targetWidth = args[1]
|
||||||
targetHeight = args[2]
|
targetHeight = args[2]
|
||||||
pad=args[3]
|
pad=args[3]
|
||||||
|
image = image.convert('RGBA')
|
||||||
|
image = ImageOps.expand(image,20)
|
||||||
if pad:
|
if pad:
|
||||||
image = image.convert('RGBA')
|
newImg = ImageOps.contain(image, (targetWidth, targetHeight))
|
||||||
newImg = ImageOps.pad(image, (targetWidth, targetHeight),color=(0, 0, 0, 0))
|
|
||||||
else:
|
else:
|
||||||
newImg = ImageOps.fit(image, (targetWidth, targetHeight)) # scale + crop
|
newImg = ImageOps.fit(image, (targetWidth, targetHeight)) # scale + crop
|
||||||
return newImg
|
return newImg
|
||||||
@ -68,7 +69,8 @@ def combineImages(imageArray, totalWidth, totalHeight,pad=True):
|
|||||||
x += image.size[0]
|
x += image.size[0]
|
||||||
y += imageArray[0].size[1]
|
y += imageArray[0].size[1]
|
||||||
x = 0
|
x = 0
|
||||||
newImage.paste(imageArray[2], (x, y))
|
# paste the final image so that it's centered
|
||||||
|
newImage.paste(imageArray[2], (int((totalWidth - imageArray[2].size[0]) / 2), y))
|
||||||
elif (len(imageArray) == 4): # if there are four images, combine the first two horizontally, then combine the last two vertically
|
elif (len(imageArray) == 4): # if there are four images, combine the first two horizontally, then combine the last two vertically
|
||||||
for image in imageArray[0:2]:
|
for image in imageArray[0:2]:
|
||||||
newImage.paste(image, (x, y))
|
newImage.paste(image, (x, y))
|
||||||
@ -91,11 +93,12 @@ def saveImage(image, name):
|
|||||||
def genImage(imageArray):
|
def genImage(imageArray):
|
||||||
totalSize=getTotalImgSize(imageArray)
|
totalSize=getTotalImgSize(imageArray)
|
||||||
combined = combineImages(imageArray, *totalSize)
|
combined = combineImages(imageArray, *totalSize)
|
||||||
combinedBG = combineImages(imageArray, *totalSize,False)
|
|
||||||
combinedBG = blurImage(combinedBG,50)
|
finalImg = combined.convert('RGB')
|
||||||
finalImg = Image.alpha_composite(combinedBG,combined)
|
|
||||||
#finalImg = ImageOps.pad(finalImg, findImageWithMostPixels(imageArray).size,color=(0, 0, 0, 0))
|
bbox = finalImg.getbbox()
|
||||||
finalImg = finalImg.convert('RGB')
|
finalImg = finalImg.crop(bbox)
|
||||||
|
|
||||||
return finalImg
|
return finalImg
|
||||||
|
|
||||||
def downloadImage(url):
|
def downloadImage(url):
|
||||||
|
2
msgs.py
2
msgs.py
@ -1,6 +1,6 @@
|
|||||||
failedToScan="Failed to scan your link! This may be due to an incorrect link, private/suspended account, deleted tweet, or Twitter itself might be having issues (Check here: https://api.twitterstat.us/)"
|
failedToScan="Failed to scan your link! This may be due to an incorrect link, private/suspended account, deleted tweet, or Twitter itself might be having issues (Check here: https://api.twitterstat.us/)"
|
||||||
failedToScanExtra = "\n\nTwitter gave me this error: "
|
failedToScanExtra = "\n\nTwitter gave me this error: "
|
||||||
tweetNotFound="Tweet not found."
|
tweetNotFound="Tweet not found. Note that this may be a result of Twitter blocking some tweets from being viewed as of April 8 2023."
|
||||||
tweetSuspended="This Tweet is from a suspended account."
|
tweetSuspended="This Tweet is from a suspended account."
|
||||||
|
|
||||||
tweetDescLimit=340
|
tweetDescLimit=340
|
||||||
|
@ -39,7 +39,7 @@ vxTwitter generates a config.json in its root directory the first time you run i
|
|||||||
|
|
||||||
**link_cache** - (Options: **db**, **json**)
|
**link_cache** - (Options: **db**, **json**)
|
||||||
|
|
||||||
- **db**: Caches all links to a mongoDB database. This should be used it you are using uWSGI and are not just running the script on its own as one worker
|
- **db**: Caches all links to a mongoDB database. This should be used if you are using uWSGI and are not just running the script on its own as one worker
|
||||||
- **json**: This saves cached links to a local **links.json** file
|
- **json**: This saves cached links to a local **links.json** file
|
||||||
- **dynamodb**: Saves cached links to a DynamoDB database - set `table` to the table name to cache links to.
|
- **dynamodb**: Saves cached links to a DynamoDB database - set `table` to the table name to cache links to.
|
||||||
- **none**: Does not cache requests. Not reccomended as you can easily use up your Twitter API credits with this. Intended for use with another cache system (i.e NGINX uwsgi_cache)
|
- **none**: Does not cache requests. Not reccomended as you can easily use up your Twitter API credits with this. Intended for use with another cache system (i.e NGINX uwsgi_cache)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
pymongo==4.3.3
|
pymongo==4.3.3
|
||||||
boto3==1.26.69
|
boto3==1.26.104
|
||||||
requests==2.28.2
|
requests==2.28.2
|
||||||
Pillow==9.4.0
|
Pillow==9.4.0
|
||||||
Flask==2.2.2
|
Flask==2.2.3
|
||||||
Flask-Cors==3.0.10
|
Flask-Cors==3.0.10
|
||||||
yt-dlp==2022.7.18
|
yt-dlp==2022.7.18
|
22
test_vx.py
22
test_vx.py
@ -7,6 +7,9 @@ import msgs
|
|||||||
from flask.testing import FlaskClient
|
from flask.testing import FlaskClient
|
||||||
client = FlaskClient(twitfix.app)
|
client = FlaskClient(twitfix.app)
|
||||||
|
|
||||||
|
testUser="https://twitter.com/jack"
|
||||||
|
testUserID = "https://twitter.com/i/user/12"
|
||||||
|
testUserWeirdURLs=["https://twitter.com/jack?lang=en","https://twitter.com/jack/with_replies","https://twitter.com/jack/media","https://twitter.com/jack/likes","https://twitter.com/jack/with_replies?lang=en","https://twitter.com/jack/media?lang=en","https://twitter.com/jack/likes?lang=en","https://twitter.com/jack/"]
|
||||||
testTextTweet="https://twitter.com/jack/status/20"
|
testTextTweet="https://twitter.com/jack/status/20"
|
||||||
testVideoTweet="https://twitter.com/Twitter/status/1263145271946551300"
|
testVideoTweet="https://twitter.com/Twitter/status/1263145271946551300"
|
||||||
testMediaTweet="https://twitter.com/Twitter/status/1118295916874739714"
|
testMediaTweet="https://twitter.com/Twitter/status/1118295916874739714"
|
||||||
@ -40,6 +43,25 @@ def test_textTweetExtract():
|
|||||||
assert 'extended_entities' not in tweet
|
assert 'extended_entities' not in tweet
|
||||||
assert tweet["is_quote_status"]==False
|
assert tweet["is_quote_status"]==False
|
||||||
|
|
||||||
|
def test_UserExtract():
|
||||||
|
user = twExtract.extractUser(testUser)
|
||||||
|
assert user["screen_name"]=="jack"
|
||||||
|
assert user["id"]==12
|
||||||
|
assert user["created_at"] == "Tue Mar 21 20:50:14 +0000 2006"
|
||||||
|
|
||||||
|
def test_UserExtractID():
|
||||||
|
user = twExtract.extractUser(testUserID)
|
||||||
|
assert user["screen_name"]=="jack"
|
||||||
|
assert user["id"]==12
|
||||||
|
assert user["created_at"] == "Tue Mar 21 20:50:14 +0000 2006"
|
||||||
|
|
||||||
|
def test_UserExtractWeirdURLs():
|
||||||
|
for url in testUserWeirdURLs:
|
||||||
|
user = twExtract.extractUser(url)
|
||||||
|
assert user["screen_name"]=="jack"
|
||||||
|
assert user["id"]==12
|
||||||
|
assert user["created_at"] == "Tue Mar 21 20:50:14 +0000 2006"
|
||||||
|
|
||||||
def test_videoTweetExtract():
|
def test_videoTweetExtract():
|
||||||
tweet = twExtract.extractStatus(testVideoTweet)
|
tweet = twExtract.extractStatus(testVideoTweet)
|
||||||
assert tweet["full_text"]==videoVNF_compare['description']
|
assert tweet["full_text"]==videoVNF_compare['description']
|
||||||
|
@ -4,14 +4,15 @@ import json
|
|||||||
import requests
|
import requests
|
||||||
import re
|
import re
|
||||||
from . import twExtractError
|
from . import twExtractError
|
||||||
|
bearer="Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw"
|
||||||
guestToken=None
|
guestToken=None
|
||||||
pathregex = r"\w{1,15}\/(status|statuses)\/(\d{2,20})"
|
pathregex = r"\w{1,15}\/(status|statuses)\/(\d{2,20})"
|
||||||
|
userregex = r"^https?:\/\/(?:www\.)?twitter\.com\/(?:#!\/)?@?([^/?#]*)(?:[?#/].*)?$"
|
||||||
|
userIDregex = r"\/i\/user\/(\d+)"
|
||||||
def getGuestToken():
|
def getGuestToken():
|
||||||
global guestToken
|
global guestToken
|
||||||
if guestToken is None:
|
if guestToken is None:
|
||||||
r = requests.post("https://api.twitter.com/1.1/guest/activate.json", headers={"Authorization":"Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw"})
|
r = requests.post("https://api.twitter.com/1.1/guest/activate.json", headers={"Authorization":bearer})
|
||||||
guestToken = json.loads(r.text)["guest_token"]
|
guestToken = json.loads(r.text)["guest_token"]
|
||||||
return guestToken
|
return guestToken
|
||||||
|
|
||||||
@ -40,7 +41,7 @@ def extractStatus(url):
|
|||||||
# get guest token
|
# get guest token
|
||||||
guestToken = getGuestToken()
|
guestToken = getGuestToken()
|
||||||
# get tweet
|
# get tweet
|
||||||
tweet = requests.get("https://api.twitter.com/1.1/statuses/show/" + twid + ".json?tweet_mode=extended&cards_platform=Web-12&include_cards=1&include_reply_count=1&include_user_entities=0", headers={"Authorization":"Bearer AAAAAAAAAAAAAAAAAAAAAPYXBAAAAAAACLXUNDekMxqa8h%2F40K4moUkGsoc%3DTYfbDKbT3jJPCEVnMYqilB28NHfOPqkca3qaAxGfsyKCs0wRbw", "x-guest-token":guestToken})
|
tweet = requests.get("https://api.twitter.com/1.1/statuses/show/" + twid + ".json?tweet_mode=extended&cards_platform=Web-12&include_cards=1&include_reply_count=1&include_user_entities=0", headers={"Authorization":bearer, "x-guest-token":guestToken})
|
||||||
output = tweet.json()
|
output = tweet.json()
|
||||||
if "errors" in output:
|
if "errors" in output:
|
||||||
# pick the first error and create a twExtractError
|
# pick the first error and create a twExtractError
|
||||||
@ -50,6 +51,32 @@ def extractStatus(url):
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return extractStatus_fallback(url)
|
return extractStatus_fallback(url)
|
||||||
|
|
||||||
|
def extractUser(url):
|
||||||
|
useId=True
|
||||||
|
m = re.search(userIDregex, url)
|
||||||
|
if m is None:
|
||||||
|
m = re.search(userregex, url)
|
||||||
|
if m is None:
|
||||||
|
raise twExtractError.TwExtractError(400, "Invalid URL")
|
||||||
|
else:
|
||||||
|
useId=False
|
||||||
|
screen_name = m.group(1)
|
||||||
|
# get guest token
|
||||||
|
guestToken = getGuestToken()
|
||||||
|
# get user
|
||||||
|
if not useId:
|
||||||
|
user = requests.get(f"https://api.twitter.com/1.1/users/show.json?screen_name={screen_name}",headers={"Authorization":bearer, "x-guest-token":guestToken})
|
||||||
|
else:
|
||||||
|
user = requests.get(f"https://api.twitter.com/1.1/users/show.json?user_id={screen_name}",headers={"Authorization":bearer, "x-guest-token":guestToken})
|
||||||
|
output = user.json()
|
||||||
|
if "errors" in output:
|
||||||
|
# pick the first error and create a twExtractError
|
||||||
|
error = output["errors"][0]
|
||||||
|
raise twExtractError.TwExtractError(error["code"], error["message"])
|
||||||
|
return output
|
||||||
|
|
||||||
|
#def extractUserByID(id):
|
||||||
|
|
||||||
|
|
||||||
def lambda_handler(event, context):
|
def lambda_handler(event, context):
|
||||||
if ("queryStringParameters" not in event):
|
if ("queryStringParameters" not in event):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user