UnMars / Prolific-Joiner

Automatically join www.prolific.co studies
GNU General Public License v3.0
32 stars 12 forks source link

Script no more useful #39

Closed NoeKun001 closed 2 months ago

ShaggyBro commented 2 months ago

yes sir, my workaround currently was getting the token manually from prolific and start the script with it already inserted.

ShaggyBro commented 2 months ago

To get your bearer token on chrome based browers: -Go to prolific and login, -Press F12 -Go to the 'Network' Tab -filter by 'internal-api.prolific.com/api/v1/' -click F5 -click on any result and search on the "Request Headers" for something like

authorization: Bearer xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

-copy it (eg Bearer JABSKJFBGDBGDBSKDBGKBSDGBSDGNB) -parse it (eg paste it into the address bar of your browser and recopy it) -your config.json should look something like this

{ "auto_renew_bearer" : "True", "pause_on_idle" : "False", "Prolific_ID" : "AAAAAAAAAAAAA", "proxy" : "http://username:password@server:port", "wait_time" : 12 }

-run -paste token

Notes: if you dont use a proxy just leave it blank "proxy" : "", pause on idle is not working, i dont actually use it this is a frankenstein version, expect things to be janky and made in a dumb way guessing the re-entry of the token when it expires wont work as well if idle_pause isnt working, so you will have to close the script and re run it.

import platform
import random
from argparse import ArgumentParser
from distutils.util import strtobool
from json import load
from pathlib import Path
from time import sleep, time
from webbrowser import open_new_tab

from playsound import playsound
from requests import Response, get, post
from requests.structures import CaseInsensitiveDict
from requests.exceptions import RequestException
from rich.console import Console
from rich.text import Text

if platform.system() == "Windows":
    import ctypes

config = load(open(f"{Path(__file__).parent}/config/config.json"))

class ProlificUpdater:
    def __init__(self, bearer, proxy=None):
        self.bearer = bearer
        self.proxy = proxy
        self.oldResults = list()
        self.participantId = config["Prolific_ID"]
        self.last_ip = None

    def getRequestFromProlific(self) -> Response:
        url = "https://internal-api.prolific.com/api/v1/participant/studies/"
        headers = CaseInsensitiveDict()
        headers["Accept"] = "application/json, text/plain, */*"
        headers["Authorization"] = self.bearer
        headers["x-legacy-auth"] = "false"
        proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
        try:
            return get(url, headers=headers, proxies=proxies, timeout=10)
        except RequestException as e:
            console.print(f"[bold red]Request error: {e}[/bold red]")
            return Response()

    def reservePlace(self, id) -> Response:
        url = "https://internal-api.prolific.com/api/v1/submissions/reserve/"
        headers = CaseInsensitiveDict()
        headers["Accept"] = "application/json"
        headers["Authorization"] = self.bearer
        headers["x-legacy-auth"] = "false"
        postObj = {"study_id": id, "participant_id": self.participantId}
        proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
        try:
            return post(url, headers=headers, data=postObj, proxies=proxies)
        except RequestException as e:
            console.print(f"[bold red]Request error: {e}[/bold red]")
            return Response()

    def getResultsFromProlific(self) -> list:
        try:
            response = self.getRequestFromProlific()
        except Exception:
            console.print("[bold red][+] Network error[/bold red]")
            return list()
        if response.status_code == 200:
            return response.json()["results"]
        else:
            print(f"Response error {response.status_code}")
            print(f"Response error {response.reason}")
            return ["bearer"]

    def executeCycle(self) -> bool:
        results = self.getResultsFromProlific()
        if results == ["bearer"]:
            self.bearer = input("Bearer token not valid anymore, please enter a new bearer token: ")
            self.bearer = "Bearer " + self.bearer
            return False

        print("results : ", results)
        if results:
            if results != self.oldResults:
                currency_symbol = "£" if str(results[0]["study_reward"]["currency"]) == "GBP" else "$"
                console.print(
                    f"""Trying to join {results[0]["name"]} ([bold green]{currency_symbol}{str(float(results[0]["study_reward"]["amount"])/100)}[/bold green])"""
                )
                reserve_place_res = self.reservePlace(id=results[0]["id"])
                if reserve_place_res.status_code == 400:
                    console.print(f"""[bold red][+] Error code {str(reserve_place_res.json()["error"]["error_code"])}
                                      \nTitle : {str(reserve_place_res.json()["error"]["title"])}
                                      \nDetails : {str(reserve_place_res.json()["error"]["detail"])}""")
                    self.oldResults = results
                    return False
                else:
                    a_website = "https://app.prolific.com/studies"
                    open_new_tab(a_website)
                    playsound(rf"{Path(__file__).parent}\alert.wav", True)

        self.oldResults = results

        if results:
            return True
        else:
            return False

    def check_ip(self) -> str:
        ip_services = [
            'https://api.ipify.org?format=json',
            'https://api.my-ip.io/ip.json',
            'https://checkip.amazonaws.com',
            'https://api.ipstack.com/check?access_key=YOUR_ACCESS_KEY'
        ]
        for service in ip_services:
            try:
                response = get(service, proxies={"http": self.proxy, "https": self.proxy} if self.proxy else None)
                if response.status_code == 200:
                    return response.json().get("ip", response.text.strip())
            except RequestException:
                continue
        return "Unable to get IP"

def parseArgs() -> dict:
    parser = ArgumentParser(description="Keep updated with Prolific")
    parser.add_argument("-b", "--bearer", type=str, help="bearer token")
    args = parser.parse_args()
    bearer = "Bearer " + args.bearer if args.bearer else None
    return {"bearer": bearer}

if platform.system() == "Windows":

    class LASTINPUTINFO(ctypes.Structure):
        _fields_ = [("cbSize", ctypes.c_uint), ("dwTime", ctypes.c_uint)]

    def get_idle_duration():
        lii = LASTINPUTINFO()
        lii.cbSize = ctypes.sizeof(LASTINPUTINFO)
        if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lii)):
            millis = ctypes.windll.kernel32.GetTickCount() - lii.dwTime
            seconds = millis / 1000
            return seconds
        else:
            error_code = (
                ctypes.windll.kernel32.GetLastError()
            )  # Get the last error code
            print(f"Error code: {error_code}")
            return 0  # Handle the error as needed

    def pause_script():
        print("Pausing script...")
        while True:
            idle_duration = get_idle_duration()
            if idle_duration > 600:  # 10 minutes in seconds
                print(
                    f"Idle Time: {idle_duration} seconds [script paused after 10 minutes of inactivity, to avoid temp ban from the API due to overuse]"
                )
                sleep(10)
            else:
                print("Resuming script...")
                break

if __name__ == "__main__":
    myArguments = parseArgs()
    bearer = myArguments.get("bearer") or input("Please enter your bearer token: ")
    bearer = "Bearer " + bearer
    proxy = config.get("proxy")
    p_updater = ProlificUpdater(bearer=bearer, proxy=proxy)

    # Initial IP check
    ip_address = p_updater.check_ip()
    if ip_address == "Unable to get IP":
        console = Console()
        console.print(f"[bold red]Unable to get IP from all services. Stopping script.[/bold red]")
        exit()

    p_updater.last_ip = ip_address
    console = Console()
    console.print(f"Current IP address: {ip_address}")

    status = console.status("[bold blue] Waiting for study...", spinner="arc")
    status.start()

    # Initial sleep
    sleep(5)

    while True:
        updateTime = config["wait_time"]
        if p_updater.executeCycle():
            status.stop()
            text = Text("Study found !")
            text.stylize("bold red")
            console.print(text)
            sleep(5)
            input("Press enter to resume study search")
            status.start()

        if strtobool(config["pause_on_idle"]) and platform.system() == "Windows":
            idle_duration = get_idle_duration()
            if idle_duration >= 600:  # 10 minutes in seconds
                pause_script()
            # else:
            #     print(f"Idle Time: {idle_duration} seconds [script not paused]") #leave commented if not debugging

        # IP check every 2 minutes
        current_ip = p_updater.check_ip()
        if current_ip != p_updater.last_ip:
            console.print(f"[bold red]IP address has changed from {p_updater.last_ip} to {current_ip}. Stopping script.[/bold red]")
            exit()

        # Generate a random integer between -5 and 5
        random_offset = random.randint(-1, 3)

        # Generate a random number which is +/- 5 from the updateTime variable
        random_time = updateTime + random_offset
        sleep(
            random_time if random_time >= 0 else 0
        )  # if statement covers edge case, if the user sets up <5 wait_time (I found <20 results in a temp ban), which with the use of random above, could result in a negative number being passed to the sleep()
ShaggyBro commented 2 months ago

Brother you are my greatest saviour... I pay homage to you! Glad you got it working brother, have fun.

Just a doubt... when will i have to change the bearer token though? (is there any general interval of time?) and suppose if the token expires while the script is running.... does it indicate me to enter other bearer token or just simply stops? it will say in the console "Bearer token not valid anymore, please enter a new bearer token: " at that point just close it and re run it

And can i also occasionally open prolififc studies on my browser (since im using the same bearer token... i think there might be some conflict) There is no conflict, just keep in mind it counts has a request (so the script is requesting every 12seconds for new studies and then you are requesting as well in browser)

Thank yuo bro <3

ShaggyBro commented 2 months ago

yeah i knew about that post, made me LOL hard.

ill keep it up so everyone that wants can use it. they had a captcha system before as well if I'm not mistaken. Currently I'm on 0 bans so I think I'm still "winning"

ShaggyBro commented 2 months ago

just restart the script with the same bearer token, all errors related to connection will say the bearer token is not valid even if it was just a network error

ShaggyBro commented 2 months ago

yes it will just not show Results: [] or give a timeout/response error the biggest tell will be that the prolific webpage on your browser will ask you to re-login since its the same session token. When it expires it will reflect on your normal prolific browser.

ShaggyBro commented 2 months ago

remove this lines or the script will sometimes randomly stop on you cause of checking for ip change and the return being different than your IP/being badly implemented.

            'https://api.my-ip.io/ip.json',
            'https://checkip.amazonaws.com',
            'https://api.ipstack.com/check?access_key=YOUR_ACCESS_KEY'

Happy hunting!

ShaggyBro commented 2 months ago

avg income daily looks like its going to be within the avg many hours (6h++)

did some cleanup and some exceptions for slow connections/timeouts etc etc


import platform
import random
from argparse import ArgumentParser
from distutils.util import strtobool
from json import load
from pathlib import Path
from time import sleep, time
from webbrowser import open_new_tab

from playsound import playsound
from requests import Response, get, post
from requests.structures import CaseInsensitiveDict
from requests.exceptions import RequestException
from rich.console import Console
from rich.text import Text

if platform.system() == "Windows":
    import ctypes

config = load(open(f"{Path(__file__).parent}/config/config.json"))

class ProlificUpdater:
    def __init__(self, bearer, proxy=None):
        self.bearer = bearer
        self.proxy = proxy
        self.oldResults = list()
        self.participantId = config["Prolific_ID"]
        self.last_ip = None

    def getRequestFromProlific(self) -> Response:
        url = "https://internal-api.prolific.com/api/v1/participant/studies/"
        headers = CaseInsensitiveDict()
        headers["Accept"] = "application/json, text/plain, */*"
        headers["Authorization"] = self.bearer
        headers["x-legacy-auth"] = "false"
        proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
        try:
            return get(url, headers=headers, proxies=proxies, timeout=10)
        except RequestException as e:
            console.print(f"[bold red]Request error: {e}[/bold red]")
            return Response()

    def reservePlace(self, id) -> Response:
        url = "https://internal-api.prolific.com/api/v1/submissions/reserve/"
        headers = CaseInsensitiveDict()
        headers["Accept"] = "application/json"
        headers["Authorization"] = self.bearer
        headers["x-legacy-auth"] = "false"
        postObj = {"study_id": id, "participant_id": self.participantId}
        proxies = {"http": self.proxy, "https": self.proxy} if self.proxy else None
        try:
            return post(url, headers=headers, data=postObj, proxies=proxies)
        except RequestException as e:
            console.print(f"[bold red]Request error: {e}[/bold red]")
            return Response()

    def getResultsFromProlific(self) -> list:
        try:
            response = self.getRequestFromProlific()
        except Exception:
            console.print("[bold red][+] Network error[/bold red]")
            return list()
        if response.status_code == 200:
            return response.json()["results"]
        else:
            print(f"Response error {response.status_code}")
            print(f"Response error {response.reason}")
            return ["bearer"]

    def executeCycle(self) -> bool:
        results = self.getResultsFromProlific()
        if results == ["bearer"]:
            self.bearer = input("Bearer token not valid anymore, please enter a new bearer token: ")
            self.bearer = "Bearer " + self.bearer
            return False

        print("results : ", results)
        if results:
            if results != self.oldResults:
                currency_symbol = "£" if str(results[0]["study_reward"]["currency"]) == "GBP" else "$"
                console.print(
                    f"""Trying to join {results[0]["name"]} ([bold green]{currency_symbol}{str(float(results[0]["study_reward"]["amount"])/100)}[/bold green])"""
                )
                reserve_place_res = self.reservePlace(id=results[0]["id"])
                if reserve_place_res.status_code == 400:
                    console.print(f"""[bold red][+] Error code {str(reserve_place_res.json()["error"]["error_code"])}
                                      \nTitle : {str(reserve_place_res.json()["error"]["title"])}
                                      \nDetails : {str(reserve_place_res.json()["error"]["detail"])}""")
                    self.oldResults = results
                    return False
                else:
                    a_website = "https://app.prolific.com/studies"
                    open_new_tab(a_website)
                    playsound(rf"{Path(__file__).parent}\alert.wav", True)

        self.oldResults = results

        if results:
            return True
        else:
            return False

    def check_ip(self) -> str:
        ip_services = [
            'https://api.ipify.org?format=json',
            'https://api.my-ip.io/ip.json',
            'https://checkip.amazonaws.com',
            'https://api.ipstack.com/check?access_key=YOUR_ACCESS_KEY'
        ]
        for service in ip_services:
            try:
                response = get(service, proxies={"http": self.proxy, "https": self.proxy} if self.proxy else None)
                if response.status_code == 200:
                    return response.json().get("ip", response.text.strip())
            except RequestException:
                continue
        return "Unable to get IP"

def parseArgs() -> dict:
    parser = ArgumentParser(description="Keep updated with Prolific")
    parser.add_argument("-b", "--bearer", type=str, help="bearer token")
    args = parser.parse_args()
    bearer = "Bearer " + args.bearer if args.bearer else None
    return {"bearer": bearer}

if platform.system() == "Windows":

    class LASTINPUTINFO(ctypes.Structure):
        _fields_ = [("cbSize", ctypes.c_uint), ("dwTime", ctypes.c_uint)]

    def get_idle_duration():
        lii = LASTINPUTINFO()
        lii.cbSize = ctypes.sizeof(LASTINPUTINFO)
        if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lii)):
            millis = ctypes.windll.kernel32.GetTickCount() - lii.dwTime
            seconds = millis / 1000
            return seconds
        else:
            error_code = (
                ctypes.windll.kernel32.GetLastError()
            )  # Get the last error code
            print(f"Error code: {error_code}")
            return 0  # Handle the error as needed

    def pause_script():
        print("Pausing script...")
        while True:
            idle_duration = get_idle_duration()
            if idle_duration > 600:  # 10 minutes in seconds
                print(
                    f"Idle Time: {idle_duration} seconds [script paused after 10 minutes of inactivity, to avoid temp ban from the API due to overuse]"
                )
                sleep(10)
            else:
                print("Resuming script...")
                break

if __name__ == "__main__":
    myArguments = parseArgs()
    bearer = myArguments.get("bearer") or input("Please enter your bearer token: ")
    bearer = "Bearer " + bearer
    proxy = config.get("proxy")
    p_updater = ProlificUpdater(bearer=bearer, proxy=proxy)

    # Initial IP check
    ip_address = p_updater.check_ip()
    if ip_address == "Unable to get IP":
        console = Console()
        console.print(f"[bold red]Unable to get IP from all services. Stopping script.[/bold red]")
        exit()

    p_updater.last_ip = ip_address
    console = Console()
    console.print(f"Current IP address: {ip_address}")

    status = console.status("[bold blue] Waiting for study...", spinner="arc")
    status.start()

    # Initial sleep
    sleep(5)

    while True:
        updateTime = config["wait_time"]
        if p_updater.executeCycle():
            status.stop()
            text = Text("Study found !")
            text.stylize("bold red")
            console.print(text)
            sleep(5)
            input("Press enter to resume study search")
            status.start()

        if strtobool(config["pause_on_idle"]) and platform.system() == "Windows":
            idle_duration = get_idle_duration()
            if idle_duration >= 600:  # 10 minutes in seconds
                pause_script()
            # else:
            #     print(f"Idle Time: {idle_duration} seconds [script not paused]") #leave commented if not debugging

        # IP check every 2 minutes
        current_ip = p_updater.check_ip()
        if current_ip != p_updater.last_ip:
            console.print(f"[bold red]IP address has changed from {p_updater.last_ip} to {current_ip}. Stopping script.[/bold red]")
            exit()

        # Generate a random integer between -5 and 5
        random_offset = random.randint(-1, 3)

        # Generate a random number which is +/- 5 from the updateTime variable
        random_time = updateTime + random_offset
        sleep(
            random_time if random_time >= 0 else 0
        )  # if statement covers edge case, if the user sets up <5 wait_time (I found <20 results in a temp ban), which with the use of random above, could result in a negative number being passed to the sleep()
ShaggyBro commented 2 months ago

thanks... Just one confirmation.. previously when the script ran it printed "results: []" and now it is "INFO:main:Results: []" right? and also how can i remove these (since i dont use proxy): 'https://api.my-ip.io/ip.json', 'https://checkip.amazonaws.com', 'https://api.ipstack.com/check?access_key=YOUR_ACCESS_KEY'

Thanks

Yes I added more verbose logging to check why my VPNs and Proxies were timming out in some accounts. (was not prolific, was my relay command server shitting itself)

I edited the above post to a version from today and that it has those ip checkers already removed and some more retry logic, either re-copy the above code or it would be remove lines 106 to 108 if you want to just remove that

amitader commented 2 months ago

image After about 2 minutes it returns an error, is there a way to solve it other than giving it a new token every time?

ShaggyBro commented 2 months ago

use this version https://github.com/UnMars/Prolific-Joiner/issues/39#issuecomment-2289679551 use same logic to setup has here https://github.com/UnMars/Prolific-Joiner/issues/39#issuecomment-2289319012

amitader commented 2 months ago

use this version #39 (comment) use same logic to setup has here #39 (comment)

do i keep the website open?

ShaggyBro commented 2 months ago

use this version #39 (comment) use same logic to setup has here #39 (comment)

do i keep the website open?

its indifferent, just dont logout from prolific since that would probably void the token but you can even close your browser if you want to

--

today in the morning i adjusted my wait_time to be bigger than what i usually have... they seem to be doing adjustments on their side with the new login captcha and overall revamp of the timer etc.. more people than usual on reddit complaining about accounts on hold imo. to be safer than sorry i have a monitor with all accs studies page open on it in case something new like a captcha or a human-check appears after sometime being active is triggered etc. i highly doubt that since it would likely break their extention and it wasnt updated recently, but still they could do it on flagged accs only for example.. all my accs are still in good standing so its still not a auto-flag on the script i think

ShaggyBro commented 2 months ago

just wait and be on the lookout is what im doing, im still not doing a bypass on the captcha with 2captcha or something because they will probably continue and try and change things around until they are satisfied.

has i said my maneuver next couple of days is i changed the wait_time to be higher to compensate if they change rate limits/having the studies page open, i have the studies page open in case they insert something new, and added more error handling on the script (latest version i shared) to keep up with what is happening.

ShaggyBro commented 2 months ago

oh.. you are simultaneouly running the script (with longer time) + watching out the stuies page right? Dont they have the same toke? wont there be any conflict? and also could you please recommend the time range for us too (for the next couple days)... Thanks bro

yes exactly, MY guess is not currently, same has running 2 instances of the script.. you can do it but it will acheive nothing. just have in your mind the studies page refreshes every 60s (one request every 60s) and put your script with larger randomness and longer wait_time to compensate.. still thinkering but did something like (-2, 16) on the random variables and wait time at 22

probably will get some studies not reserved because of it but it is what it is.. also friday.

amitader commented 2 months ago

Maybe add that there will be a sound when the script stops because of an error

profbiyi commented 2 months ago

@ShaggyBro is it not possible to just put the token generated in the code? i did that and the script never told me that my token is not valid again...

ShaggyBro commented 2 months ago

@ShaggyBro is it not possible to just put the token generated in the code? i did that and the script never told me that my token is not valid again...

yes it should work untill it is invalid and ask for another/close the script

profbiyi commented 2 months ago

@ShaggyBro is it not possible to just put the token generated in the code? i did that and the script never told me that my token is not valid again...

yes it should work untill it is invalid and ask for another/close the script

so i placed in in those two places and it never tells that its not valid, i will monitor it and get to know if i am missing something. thanks for all your help brotherly

Screenshot 2024-08-18 at 14 22 47 Screenshot 2024-08-18 at 14 22 11
ShaggyBro commented 2 months ago

my easiest suggestion would be to just input it at the begining the one correlating to your browser.

explaining your doubt i would say imagine the bearer token has the login data stored (a sessionid, cookie). same way you can be logged into multiple devices at the same time, the same way multiple tokens can be valid.

NoeKun001 commented 2 months ago

@ShaggyBro Bro even the new script you provided is not working.... It gives an error (Reserve with a browser) Please help

notquitemoe commented 2 months ago

Yeah I wonder if they changed stuff again, or is it a temporary error. Help would be nice.

SurfingTurtles commented 2 months ago

Same, getting this:

Title : The data supplied is either incomplete or not valid.

PEC-SUB-0010: Sorry, we couldn't create your submission at this time. Please ensure that you are using a supported browser and that you have the latest version of Prolific. Try refreshing the page to resolve the issue.

ThisIsNotUdyr commented 2 months ago

Did anyone find a solution for the PEC-SUB-0010 error?

NoeKun001 commented 2 months ago

@profbiyi @notquitemoe @ShaggyBro @tommyka1 @UnMars did anyone know how to make this work?

terefere2033 commented 2 months ago

As Far I can tell they changed captcha to Turnstile (Cloudflare), it can be bypassed by SeleniumBase UC Mode quite easily, however not sure if this solve PEC=SUB-0010.

NoeKun001 commented 2 months ago

As Far I can tell they changed captcha to Turnstile (Cloudflare), it can be bypassed by SeleniumBase UC Mode quite easily, however not sure if this solve PEC=SUB-0010.

mate could you please give me the code?

profbiyi commented 2 months ago

@ShaggyBro @UnMars any update please?

Arslan30 commented 1 month ago

Error returns that the information is incomplete. I guess prolific changed their API so that it requires something more than a prolific ID to join a study. Someone will have to read documentation and figure out what it is. It failed me on multiple studies and was never able to join since this change happened