init
This commit is contained in:
BIN
.gitea/.DS_Store
vendored
Normal file
BIN
.gitea/.DS_Store
vendored
Normal file
Binary file not shown.
112
.gitea/workflows/auto-release.yml
Normal file
112
.gitea/workflows/auto-release.yml
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
name: Auto Release
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: '0 0 * * *'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update-and-release:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Python
|
||||||
|
uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: '3.x'
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pip install requests
|
||||||
|
|
||||||
|
- name: Configure Git
|
||||||
|
run: |
|
||||||
|
git config user.name "Gitea Actions"
|
||||||
|
git config user.email "actions@gitea.local"
|
||||||
|
|
||||||
|
- name: Get file hash before update
|
||||||
|
id: before
|
||||||
|
run: |
|
||||||
|
if [ -f discord-quest.js ]; then
|
||||||
|
echo "hash=$(sha256sum discord-quest.js | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "hash=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Run update script
|
||||||
|
id: update
|
||||||
|
env:
|
||||||
|
GITEA_ACTIONS: true
|
||||||
|
run: python main.py || true
|
||||||
|
|
||||||
|
- name: Get file hash after update
|
||||||
|
id: after
|
||||||
|
run: |
|
||||||
|
if [ -f discord-quest.js ]; then
|
||||||
|
echo "hash=$(sha256sum discord-quest.js | cut -d' ' -f1)" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "hash=" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Check for changes
|
||||||
|
id: changes
|
||||||
|
run: |
|
||||||
|
if [ "${{ steps.before.outputs.hash }}" != "${{ steps.after.outputs.hash }}" ]; then
|
||||||
|
echo "changed=true" >> $GITHUB_OUTPUT
|
||||||
|
else
|
||||||
|
echo "changed=false" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Commit and push changes
|
||||||
|
if: steps.changes.outputs.changed == 'true'
|
||||||
|
run: |
|
||||||
|
if [ -n "$(git status --porcelain discord-quest.js)" ]; then
|
||||||
|
git add discord-quest.js
|
||||||
|
git commit -m "Update discord-quest.js from upstream gist"
|
||||||
|
git push origin main
|
||||||
|
fi
|
||||||
|
|
||||||
|
- name: Get latest tag
|
||||||
|
if: steps.changes.outputs.changed == 'true'
|
||||||
|
id: tag
|
||||||
|
run: |
|
||||||
|
latest_tag=$(git describe --tags --abbrev=0 2>/dev/null || echo "v0.0.0")
|
||||||
|
echo "latest=$latest_tag" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
version=$(echo $latest_tag | sed 's/v//')
|
||||||
|
IFS='.' read -r major minor patch <<< "$version"
|
||||||
|
patch=$((patch + 1))
|
||||||
|
new_tag="v${major}.${minor}.${patch}"
|
||||||
|
echo "new=$new_tag" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Create and push tag
|
||||||
|
if: steps.changes.outputs.changed == 'true'
|
||||||
|
run: |
|
||||||
|
git tag ${{ steps.tag.outputs.new }}
|
||||||
|
git push origin ${{ steps.tag.outputs.new }}
|
||||||
|
|
||||||
|
- name: Create Gitea release
|
||||||
|
if: steps.changes.outputs.changed == 'true'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
GITEA_URL: ${{ secrets.GITEA_URL || github.server_url }}
|
||||||
|
run: |
|
||||||
|
REPO_OWNER=$(echo ${{ github.repository }} | cut -d'/' -f1)
|
||||||
|
REPO_NAME=$(echo ${{ github.repository }} | cut -d'/' -f2)
|
||||||
|
|
||||||
|
API_URL="${GITEA_URL}/api/v1/repos/${REPO_OWNER}/${REPO_NAME}/releases"
|
||||||
|
|
||||||
|
curl -X POST "${API_URL}" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{
|
||||||
|
\"tag_name\": \"${{ steps.tag.outputs.new }}\",
|
||||||
|
\"name\": \"Release ${{ steps.tag.outputs.new }}\",
|
||||||
|
\"body\": \"Automated release: Updated discord-quest.js from upstream gist\",
|
||||||
|
\"draft\": false,
|
||||||
|
\"prerelease\": false
|
||||||
|
}"
|
||||||
|
|
||||||
166
discord-quest.js
Normal file
166
discord-quest.js
Normal file
@@ -0,0 +1,166 @@
|
|||||||
|
delete window.$;
|
||||||
|
let wpRequire = webpackChunkdiscord_app.push([[Symbol()], {}, r => r]);
|
||||||
|
webpackChunkdiscord_app.pop();
|
||||||
|
|
||||||
|
let ApplicationStreamingStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getStreamerActiveStreamMetadata).exports.Z;
|
||||||
|
let RunningGameStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getRunningGames).exports.ZP;
|
||||||
|
let QuestsStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getQuest).exports.Z;
|
||||||
|
let ChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.getAllThreadsForParent).exports.Z;
|
||||||
|
let GuildChannelStore = Object.values(wpRequire.c).find(x => x?.exports?.ZP?.getSFWDefaultChannel).exports.ZP;
|
||||||
|
let FluxDispatcher = Object.values(wpRequire.c).find(x => x?.exports?.Z?.__proto__?.flushWaitQueue).exports.Z;
|
||||||
|
let api = Object.values(wpRequire.c).find(x => x?.exports?.tn?.get).exports.tn;
|
||||||
|
|
||||||
|
const supportedTasks = ["WATCH_VIDEO", "PLAY_ON_DESKTOP", "STREAM_ON_DESKTOP", "PLAY_ACTIVITY", "WATCH_VIDEO_ON_MOBILE"]
|
||||||
|
let quests = [...QuestsStore.quests.values()].filter(x => x.userStatus?.enrolledAt && !x.userStatus?.completedAt && new Date(x.config.expiresAt).getTime() > Date.now() && supportedTasks.find(y => Object.keys((x.config.taskConfig ?? x.config.taskConfigV2).tasks).includes(y)))
|
||||||
|
let isApp = typeof DiscordNative !== "undefined"
|
||||||
|
if(quests.length === 0) {
|
||||||
|
console.log("You don't have any uncompleted quests!")
|
||||||
|
} else {
|
||||||
|
let doJob = function() {
|
||||||
|
const quest = quests.pop()
|
||||||
|
if(!quest) return
|
||||||
|
|
||||||
|
const pid = Math.floor(Math.random() * 30000) + 1000
|
||||||
|
|
||||||
|
const applicationId = quest.config.application.id
|
||||||
|
const applicationName = quest.config.application.name
|
||||||
|
const questName = quest.config.messages.questName
|
||||||
|
const taskConfig = quest.config.taskConfig ?? quest.config.taskConfigV2
|
||||||
|
const taskName = supportedTasks.find(x => taskConfig.tasks[x] != null)
|
||||||
|
const secondsNeeded = taskConfig.tasks[taskName].target
|
||||||
|
let secondsDone = quest.userStatus?.progress?.[taskName]?.value ?? 0
|
||||||
|
|
||||||
|
if(taskName === "WATCH_VIDEO" || taskName === "WATCH_VIDEO_ON_MOBILE") {
|
||||||
|
const maxFuture = 10, speed = 7, interval = 1
|
||||||
|
const enrolledAt = new Date(quest.userStatus.enrolledAt).getTime()
|
||||||
|
let completed = false
|
||||||
|
let fn = async () => {
|
||||||
|
while(true) {
|
||||||
|
const maxAllowed = Math.floor((Date.now() - enrolledAt)/1000) + maxFuture
|
||||||
|
const diff = maxAllowed - secondsDone
|
||||||
|
const timestamp = secondsDone + speed
|
||||||
|
if(diff >= speed) {
|
||||||
|
const res = await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: Math.min(secondsNeeded, timestamp + Math.random())}})
|
||||||
|
completed = res.body.completed_at != null
|
||||||
|
secondsDone = Math.min(secondsNeeded, timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
if(timestamp >= secondsNeeded) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
await new Promise(resolve => setTimeout(resolve, interval * 1000))
|
||||||
|
}
|
||||||
|
if(!completed) {
|
||||||
|
await api.post({url: `/quests/${quest.id}/video-progress`, body: {timestamp: secondsNeeded}})
|
||||||
|
}
|
||||||
|
console.log("Quest completed!")
|
||||||
|
doJob()
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
console.log(`Spoofing video for ${questName}.`)
|
||||||
|
} else if(taskName === "PLAY_ON_DESKTOP") {
|
||||||
|
if(!isApp) {
|
||||||
|
console.log("This no longer works in browser for non-video quests. Use the discord desktop app to complete the", questName, "quest!")
|
||||||
|
} else {
|
||||||
|
api.get({url: `/applications/public?application_ids=${applicationId}`}).then(res => {
|
||||||
|
const appData = res.body[0]
|
||||||
|
const exeName = appData.executables.find(x => x.os === "win32").name.replace(">","")
|
||||||
|
|
||||||
|
const fakeGame = {
|
||||||
|
cmdLine: `C:\\Program Files\\${appData.name}\\${exeName}`,
|
||||||
|
exeName,
|
||||||
|
exePath: `c:/program files/${appData.name.toLowerCase()}/${exeName}`,
|
||||||
|
hidden: false,
|
||||||
|
isLauncher: false,
|
||||||
|
id: applicationId,
|
||||||
|
name: appData.name,
|
||||||
|
pid: pid,
|
||||||
|
pidPath: [pid],
|
||||||
|
processName: appData.name,
|
||||||
|
start: Date.now(),
|
||||||
|
}
|
||||||
|
const realGames = RunningGameStore.getRunningGames()
|
||||||
|
const fakeGames = [fakeGame]
|
||||||
|
const realGetRunningGames = RunningGameStore.getRunningGames
|
||||||
|
const realGetGameForPID = RunningGameStore.getGameForPID
|
||||||
|
RunningGameStore.getRunningGames = () => fakeGames
|
||||||
|
RunningGameStore.getGameForPID = (pid) => fakeGames.find(x => x.pid === pid)
|
||||||
|
FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: realGames, added: [fakeGame], games: fakeGames})
|
||||||
|
|
||||||
|
let fn = data => {
|
||||||
|
let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.PLAY_ON_DESKTOP.value)
|
||||||
|
console.log(`Quest progress: ${progress}/${secondsNeeded}`)
|
||||||
|
|
||||||
|
if(progress >= secondsNeeded) {
|
||||||
|
console.log("Quest completed!")
|
||||||
|
|
||||||
|
RunningGameStore.getRunningGames = realGetRunningGames
|
||||||
|
RunningGameStore.getGameForPID = realGetGameForPID
|
||||||
|
FluxDispatcher.dispatch({type: "RUNNING_GAMES_CHANGE", removed: [fakeGame], added: [], games: []})
|
||||||
|
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn)
|
||||||
|
|
||||||
|
doJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn)
|
||||||
|
|
||||||
|
console.log(`Spoofed your game to ${applicationName}. Wait for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else if(taskName === "STREAM_ON_DESKTOP") {
|
||||||
|
if(!isApp) {
|
||||||
|
console.log("This no longer works in browser for non-video quests. Use the discord desktop app to complete the", questName, "quest!")
|
||||||
|
} else {
|
||||||
|
let realFunc = ApplicationStreamingStore.getStreamerActiveStreamMetadata
|
||||||
|
ApplicationStreamingStore.getStreamerActiveStreamMetadata = () => ({
|
||||||
|
id: applicationId,
|
||||||
|
pid,
|
||||||
|
sourceName: null
|
||||||
|
})
|
||||||
|
|
||||||
|
let fn = data => {
|
||||||
|
let progress = quest.config.configVersion === 1 ? data.userStatus.streamProgressSeconds : Math.floor(data.userStatus.progress.STREAM_ON_DESKTOP.value)
|
||||||
|
console.log(`Quest progress: ${progress}/${secondsNeeded}`)
|
||||||
|
|
||||||
|
if(progress >= secondsNeeded) {
|
||||||
|
console.log("Quest completed!")
|
||||||
|
|
||||||
|
ApplicationStreamingStore.getStreamerActiveStreamMetadata = realFunc
|
||||||
|
FluxDispatcher.unsubscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn)
|
||||||
|
|
||||||
|
doJob()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FluxDispatcher.subscribe("QUESTS_SEND_HEARTBEAT_SUCCESS", fn)
|
||||||
|
|
||||||
|
console.log(`Spoofed your stream to ${applicationName}. Stream any window in vc for ${Math.ceil((secondsNeeded - secondsDone) / 60)} more minutes.`)
|
||||||
|
console.log("Remember that you need at least 1 other person to be in the vc!")
|
||||||
|
}
|
||||||
|
} else if(taskName === "PLAY_ACTIVITY") {
|
||||||
|
const channelId = ChannelStore.getSortedPrivateChannels()[0]?.id ?? Object.values(GuildChannelStore.getAllGuilds()).find(x => x != null && x.VOCAL.length > 0).VOCAL[0].channel.id
|
||||||
|
const streamKey = `call:${channelId}:1`
|
||||||
|
|
||||||
|
let fn = async () => {
|
||||||
|
console.log("Completing quest", questName, "-", quest.config.messages.questName)
|
||||||
|
|
||||||
|
while(true) {
|
||||||
|
const res = await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: false}})
|
||||||
|
const progress = res.body.progress.PLAY_ACTIVITY.value
|
||||||
|
console.log(`Quest progress: ${progress}/${secondsNeeded}`)
|
||||||
|
|
||||||
|
await new Promise(resolve => setTimeout(resolve, 20 * 1000))
|
||||||
|
|
||||||
|
if(progress >= secondsNeeded) {
|
||||||
|
await api.post({url: `/quests/${quest.id}/heartbeat`, body: {stream_key: streamKey, terminal: true}})
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("Quest completed!")
|
||||||
|
doJob()
|
||||||
|
}
|
||||||
|
fn()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
doJob()
|
||||||
|
}
|
||||||
93
main.py
Normal file
93
main.py
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
import requests
|
||||||
|
import re
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
URL = "https://gist.githubusercontent.com/aamiaa/204cd9d42013ded9faf646fae7f89fbb/raw"
|
||||||
|
OUTPUT_FILENAME = "discord-quest.js"
|
||||||
|
REPO_REMOTE = "origin" # Change if your remote is named differently
|
||||||
|
REPO_BRANCH = "main" # Change if your branch is named differently
|
||||||
|
|
||||||
|
def fetch_and_extract():
|
||||||
|
print(f"Fetching content from {URL}...")
|
||||||
|
try:
|
||||||
|
response = requests.get(URL)
|
||||||
|
response.raise_for_status()
|
||||||
|
content = response.text
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error downloading content: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Regex to find markdown code blocks (``` ... ```)
|
||||||
|
# This looks for blocks optionally specified as javascript/js
|
||||||
|
code_blocks = re.findall(r'```(?:javascript|js)?\s*(.*?)```', content, re.DOTALL)
|
||||||
|
|
||||||
|
if not code_blocks:
|
||||||
|
print("No code blocks found in the fetched text.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Heuristic: Find the block that looks like the actual script.
|
||||||
|
# Based on the known content, the script usually contains 'webpackChunkdiscord_app'
|
||||||
|
# If we can't find that specific keyword, we default to the longest block.
|
||||||
|
target_code = None
|
||||||
|
for block in code_blocks:
|
||||||
|
if "webpackChunkdiscord_app" in block:
|
||||||
|
target_code = block
|
||||||
|
break
|
||||||
|
|
||||||
|
if not target_code:
|
||||||
|
print("Could not identify the specific Discord script block. Using the longest code block found.")
|
||||||
|
target_code = max(code_blocks, key=len)
|
||||||
|
|
||||||
|
return target_code.strip()
|
||||||
|
|
||||||
|
def has_changes(filename):
|
||||||
|
# Check if the file is modified in git
|
||||||
|
result = subprocess.run(["git", "status", "--porcelain", filename], capture_output=True, text=True)
|
||||||
|
return bool(result.stdout.strip())
|
||||||
|
|
||||||
|
def git_commit_and_push(filename):
|
||||||
|
try:
|
||||||
|
print("Changes detected. Committing...")
|
||||||
|
subprocess.run(["git", "add", filename], check=True)
|
||||||
|
subprocess.run(["git", "commit", "-m", "Update discord-quest.js from upstream gist"], check=True)
|
||||||
|
|
||||||
|
print("Pushing to Gitea...")
|
||||||
|
subprocess.run(["git", "push", REPO_REMOTE, REPO_BRANCH], check=True)
|
||||||
|
print("Successfully pushed changes.")
|
||||||
|
except subprocess.CalledProcessError as e:
|
||||||
|
print(f"Git operation failed: {e}")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
def main():
|
||||||
|
# 1. Extract new code
|
||||||
|
new_code = fetch_and_extract()
|
||||||
|
|
||||||
|
# 2. Read existing file to compare (avoids git modifying timestamp if identical)
|
||||||
|
if os.path.exists(OUTPUT_FILENAME):
|
||||||
|
with open(OUTPUT_FILENAME, 'r', encoding='utf-8') as f:
|
||||||
|
current_code = f.read().strip()
|
||||||
|
else:
|
||||||
|
current_code = None
|
||||||
|
|
||||||
|
# 3. Write file only if content is different
|
||||||
|
if new_code != current_code:
|
||||||
|
print(f"Updating {OUTPUT_FILENAME}...")
|
||||||
|
with open(OUTPUT_FILENAME, 'w', encoding='utf-8') as f:
|
||||||
|
f.write(new_code + '\n') # Add newline at end of file
|
||||||
|
else:
|
||||||
|
print("No changes in content detected. Exiting.")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
# 4. Check Git status and Push (skip in CI - workflow handles it)
|
||||||
|
if os.getenv("CI") or os.getenv("GITHUB_ACTIONS") or os.getenv("GITEA_ACTIONS"):
|
||||||
|
print("Running in CI environment. Skipping git operations - workflow will handle commit/push.")
|
||||||
|
elif has_changes(OUTPUT_FILENAME):
|
||||||
|
git_commit_and_push(OUTPUT_FILENAME)
|
||||||
|
else:
|
||||||
|
print("File updated but git status shows no changes (clean).")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
Reference in New Issue
Block a user