Skip to content

Commit

Permalink
Added url option
Browse files Browse the repository at this point in the history
  • Loading branch information
linsvensson committed Sep 16, 2023
1 parent 6a7a3cb commit faf69ba
Show file tree
Hide file tree
Showing 4 changed files with 629 additions and 98 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ dist/
*.code-workspace
catalog-scanner.json
constants.py
logs.txt
cache/
40 changes: 40 additions & 0 deletions RequestDetails.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
{
"features": {
"responsive_web_graphql_exclude_directive_enabled": true,
"verified_phone_label_enabled": false,
"responsive_web_graphql_timeline_navigation_enabled": true,
"responsive_web_graphql_skip_user_profile_image_extensions_enabled": false,
"tweetypie_unmention_optimization_enabled": true,
"vibe_api_enabled": false,
"responsive_web_edit_tweet_api_enabled": false,
"graphql_is_translatable_rweb_tweet_is_translatable_enabled": false,
"view_counts_everywhere_api_enabled": true,
"longform_notetweets_consumption_enabled": true,
"tweet_awards_web_tipping_enabled": false,
"freedom_of_speech_not_reach_fetch_enabled": false,
"standardized_nudges_misinfo": false,
"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled": false,
"interactive_text_enabled": false,
"responsive_web_twitter_blue_verified_badge_is_enabled": true,
"responsive_web_text_conversations_enabled": false,
"longform_notetweets_richtext_consumption_enabled": false,
"responsive_web_enhance_cards_enabled": false,
"creator_subscriptions_tweet_preview_api_enabled": true,
"longform_notetweets_rich_text_read_enabled": true,
"longform_notetweets_inline_media_enabled": true,
"responsive_web_media_download_video_enabled": true,
"responsive_web_twitter_article_tweet_consumption_enabled": true
},
"variables": {
"with_rux_injections": false,
"includePromotedContent": true,
"withCommunity": true,
"withQuickPromoteEligibilityTweetFields": true,
"withBirdwatchNotes": true,
"withDownvotePerspective": false,
"withReactionsMetadata": false,
"withReactionsPerspective": false,
"withVoice": true,
"withV2Timeline": true
}
}
236 changes: 138 additions & 98 deletions discord_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
import contextlib
import datetime
import pathlib
import requests

from absl import app, logging
import discord
Expand All @@ -16,11 +15,12 @@

import constants
import scanner
import twitter_video_dl as tvdl

ERROR_EMOJI = ':exclamation:'
SUCCESS_EMOJI = ':tada:'
SCANNING_EMOJI = ':mag:'
WAIT_EMOJI = ':hourglass:'
ERROR_EMOJI = ":exclamation:"
SUCCESS_EMOJI = ":tada:"
SCANNING_EMOJI = ":mag:"
WAIT_EMOJI = ":hourglass:"

WAIT_LOCK = asyncio.Lock()

Expand All @@ -32,79 +32,89 @@

def upload_to_datastore(result, discord_user_id=None) -> datastore.Entity:
datastore_client = datastore.Client.from_service_account_json(
'catalog-scanner.json')
"catalog-scanner.json"
)

temp_key = datastore_client.key('Catalog')
temp_key = datastore_client.key("Catalog")
key = datastore_client.allocate_ids(temp_key, 1)[0]
catalog = datastore.Entity(key, exclude_from_indexes=['data', 'unmatched'])
catalog = datastore.Entity(key, exclude_from_indexes=["data", "unmatched"])

key_hash = hashid_client.encode(key.id)
catalog_data = '\n'.join(result.items).encode('utf-8')
catalog.update({
'hash': key_hash,
'data': catalog_data,
'locale': result.locale,
'type': result.mode.name.lower(),
'created': datetime.datetime.utcnow(),
'discord_user': discord_user_id,
})
catalog_data = "\n".join(result.items).encode("utf-8")
catalog.update(
{
"hash": key_hash,
"data": catalog_data,
"locale": result.locale,
"type": result.mode.name.lower(),
"created": datetime.datetime.utcnow(),
"discord_user": discord_user_id,
}
)
if result.unmatched:
unmatched_data = '\n'.join(result.unmatched).encode('utf-8')
catalog['unmatched'] = unmatched_data
unmatched_data = "\n".join(result.unmatched).encode("utf-8")
catalog["unmatched"] = unmatched_data

datastore_client.put(catalog)
return catalog

async def handle_scan(ctx: discord.ApplicationContext, attachment: discord.Attachment, filetype: str, url: discord.Option(str)) -> None:

async def handle_scan(
ctx: discord.ApplicationContext,
attachment: discord.Attachment,
filetype: str,
url: str,
) -> None:
"""Downloads the file, runs the scans and uploads the results, while updating the user along the way."""
await reply(ctx, f'{SCANNING_EMOJI} Scan started, your results will be ready soon!')
await reply(ctx, f"{SCANNING_EMOJI} Scan started, your results will be ready soon!")

tmp_dir = pathlib.Path('cache')
tmp_dir = pathlib.Path("cache")

if url:
try:
video = requests.get(url)
if 200 == video.status_code:
tmp_file = tmp_dir / f'{ctx.user.id}_video.mp4'
tmp_file.parent.mkdir(parents=True, exist_ok=True)
with open(tmp_file, 'wb') as f:
f.write(video.content)
else:
logging.exception('Unexpected scan error.')
await reply(ctx, f'{ERROR_EMOJI} Failed to scan media. Make sure you have a valid {filetype}.')
return
tmp_file = tmp_dir / f"{ctx.user.id}_video.mp4"
tvdl.download_video(url, tmp_file)
except Exception:
logging.exception('Unexpected scan error.')
await reply(ctx, f'{ERROR_EMOJI} Failed to scan media. Make sure you have a valid {filetype}.')
logging.exception("Unexpected scan error.")
await reply(
ctx,
f"{ERROR_EMOJI} Failed to scan media. Make sure you have a valid {filetype}.",
)
return
else:
file = await attachment.to_file()
tmp_file = tmp_dir / f'{attachment.id}_{file.filename}'
tmp_file = tmp_dir / f"{attachment.id}_{file.filename}"
tmp_file.parent.mkdir(parents=True, exist_ok=True)
await attachment.save(tmp_file)

try:
result = await async_scan(tmp_file)
except AssertionError as e:
error_message = improve_error_message(str(e))
await reply(ctx, f'{ERROR_EMOJI} Failed to scan: {error_message}')
await reply(ctx, f"{ERROR_EMOJI} Failed to scan: {error_message}")
return
except Exception:
logging.exception('Unexpected scan error.')
await reply(ctx, f'{ERROR_EMOJI} Failed to scan media. Make sure you have a valid {filetype}.')
logging.exception("Unexpected scan error.")
await reply(
ctx,
f"{ERROR_EMOJI} Failed to scan media. Make sure you have a valid {filetype}.",
)
return

if not result.items:
await reply(ctx, f'{ERROR_EMOJI} Did not find any items.')
await reply(ctx, f"{ERROR_EMOJI} Did not find any items.")
return

with contextlib.suppress(FileNotFoundError):
os.remove(tmp_file)

catalog = upload_to_datastore(result, ctx.user.id)
url = 'https://nook.lol/{}'.format(catalog['hash'])
logging.info('Found %s items with %s: %s', len(result.items), result.mode, url)
await reply(ctx, f"{SUCCESS_EMOJI} Found {len(result.items)} items in your {filetype}.\nResults: {url}")
url = "https://nook.lol/{}".format(catalog["hash"])
logging.info("Found %s items with %s: %s", len(result.items), result.mode, url)
await reply(
ctx,
f"{SUCCESS_EMOJI} Found {len(result.items)} items in your {filetype}.\nResults: {url}",
)


async def async_scan(filename: os.PathLike) -> scanner.ScanResult:
Expand All @@ -124,90 +134,120 @@ async def reply(ctx: discord.ApplicationContext, message: str) -> None:

def improve_error_message(message: str) -> str:
"""Adds some more details to the error message."""
if 'is too long' in message:
message += ' Make sure you scroll with the correct analog stick (see instructions),'
message += ' and trim the video around the start and end of the scrolling.'
if 'scrolling too slowly' in message:
message += ' Make sure you hold down the *right* analog stick.'
message += ' See https://twitter.com/CatalogScanner/status/1261737975244865539'
if 'scrolling inconsistently' in message:
message += ' Please scroll once, from start to finish, in one direction only.'
if 'Invalid video' in message:
message += ' Make sure the video is exported directly from your Nintendo Switch '
message += 'and that you\'re scrolling through a supported page. See nook.lol'
if 'not showing catalog or recipes' in message:
message += ' Make sure to record the video with your Switch using the capture button.'
if 'x224' in message:
message += '\n(It seems like you\'re downloading the video from your Facebook and '
message += 're-posting it; try downloading it directly from your Switch instead)'
if '640x360' in message:
message += '\nIt seems like Discord might have compressed your video; '
message += 'go to *Settings -> Text & Media* and set *Video Uploads* to **Best Quality**.'
elif 'Invalid resolution' in message:
message += '\n(Make sure you are recording and sending directly from the Switch)'
if 'Pictures Mode' in message:
message += ' Press X to switch to list mode and try again!'
if 'blocking a reaction' in message:
message += ' Make sure to move the cursor to an empty slot or the top right corner, '
message += 'otherwise your results may not be accurate.'
if 'Workbench scanning' in message:
message += ' Please use the DIY Recipes phone app instead (beige background).'
if 'catalog is not supported' in message:
message += ' Please use the Catalog phone app instead (yellow background).'
if 'Incomplete critter scan' in message:
message += ' Make sure to fully capture the leftmost and rightmost sides of the page.'
if 'not uploaded directly' in message:
message += ' Make sure to record and download the video using the Switch\'s media gallery.'
if "is too long" in message:
message += (
" Make sure you scroll with the correct analog stick (see instructions),"
)
message += " and trim the video around the start and end of the scrolling."
if "scrolling too slowly" in message:
message += " Make sure you hold down the *right* analog stick."
message += " See https://twitter.com/CatalogScanner/status/1261737975244865539"
if "scrolling inconsistently" in message:
message += " Please scroll once, from start to finish, in one direction only."
if "Invalid video" in message:
message += (
" Make sure the video is exported directly from your Nintendo Switch "
)
message += "and that you're scrolling through a supported page. See nook.lol"
if "not showing catalog or recipes" in message:
message += (
" Make sure to record the video with your Switch using the capture button."
)
if "x224" in message:
message += (
"\n(It seems like you're downloading the video from your Facebook and "
)
message += (
"re-posting it; try downloading it directly from your Switch instead)"
)
if "640x360" in message:
message += "\nIt seems like Discord might have compressed your video; "
message += "go to *Settings -> Text & Media* and set *Video Uploads* to **Best Quality**."
elif "Invalid resolution" in message:
message += (
"\n(Make sure you are recording and sending directly from the Switch)"
)
if "Pictures Mode" in message:
message += " Press X to switch to list mode and try again!"
if "blocking a reaction" in message:
message += (
" Make sure to move the cursor to an empty slot or the top right corner, "
)
message += "otherwise your results may not be accurate."
if "Workbench scanning" in message:
message += " Please use the DIY Recipes phone app instead (beige background)."
if "catalog is not supported" in message:
message += " Please use the Catalog phone app instead (yellow background)."
if "Incomplete critter scan" in message:
message += (
" Make sure to fully capture the leftmost and rightmost sides of the page."
)
if "not uploaded directly" in message:
message += " Make sure to record and download the video using the Switch's media gallery."
return message


@bot.slash_command(
name='scan',
description='Extracts your Animal Crossing items (catalog, recipes, critters, reactions, music).',
name="scan",
description="Extracts your Animal Crossing items (catalog, recipes, critters, reactions, music).",
)
@option("url", str, description="The url of a video to scan", required=False)
@option(
"attachment",
discord.Attachment,
description="The video or image to scan",
required=False,
)
@option('url', discord.Option(str), description='The url of a video to scan', required=False)
@option('attachment', discord.Attachment, description='The video or image to scan', required=False)
async def scan(ctx: discord.ApplicationContext, url: discord.Option(str), attachment: discord.Attachment):
logging.info('Got request from %s', ctx.user)

# Verify that there is an attachment and it's the correct type.
if not attachment or not attachment.content_type or not url:
await reply(ctx, f'{ERROR_EMOJI} No attachment or url found.')
async def scan(
ctx: discord.ApplicationContext, url: str, attachment: discord.Attachment
):
logging.info("Got request from %s", ctx.user)

# Verify that there is an attachment and it's the correct type.
if not attachment and not url:
await reply(ctx, f"{ERROR_EMOJI} No attachment or url found.")
return

if attachment:
filetype, _, _ = attachment.content_type.partition('/') # {type}/{format}
if filetype not in ('video', 'image'):
await reply(ctx, f'{ERROR_EMOJI} The attachment needs to be a valid video or image file.')
filetype, _, _ = attachment.content_type.partition("/") # {type}/{format}
if filetype not in ("video", "image"):
await reply(
ctx,
f"{ERROR_EMOJI} The attachment needs to be a valid video or image file.",
)
return
if url:
filetype = 'url'
filetype = "url"

# Have a queue system that handles requests one at a time.
if WAIT_LOCK.locked:
position = 1 if not WAIT_LOCK._waiters else len(WAIT_LOCK._waiters) + 1 # type: ignore
logging.info('%s (%s) is in queue position %s', ctx.user, attachment.id, position)
await reply(ctx, f'{WAIT_EMOJI} You are #{position} in the queue, your scan will start soon.')
logging.info("%s (%s) is in queue position %s", ctx.user, ctx.user.id, position)
await reply(
ctx,
f"{WAIT_EMOJI} You are #{position} in the queue, your scan will start soon.",
)
async with WAIT_LOCK:
await handle_scan(ctx, attachment, filetype, url)


@bot.event
async def on_ready():
assert bot.user, 'Failed to login'
logging.info('Bot logged in as %s', bot.user)
assert bot.user, "Failed to login"
logging.info("Bot logged in as %s", bot.user)


def main(argv):
del argv # unused
bot.run(constants.DISCORD_TOKEN)


if __name__ == '__main__':
if __name__ == "__main__":
# Write logs to file.
file_handler = stdlib_logging.FileHandler('logs.txt')
file_handler = stdlib_logging.FileHandler("logs.txt")
logging.get_absl_logger().addHandler(file_handler) # type: ignore
# Disable noise discord logs.
stdlib_logging.getLogger('discord.client').setLevel(stdlib_logging.WARNING)
stdlib_logging.getLogger('discord.gateway').setLevel(stdlib_logging.WARNING)
stdlib_logging.getLogger("discord.client").setLevel(stdlib_logging.WARNING)
stdlib_logging.getLogger("discord.gateway").setLevel(stdlib_logging.WARNING)

app.run(main)
Loading

0 comments on commit faf69ba

Please sign in to comment.