mtschirs / quizduellapi

Inofficial interface to the Quizduell web API written in Python and distributed under GPLv3
GNU General Public License v3.0
22 stars 6 forks source link

Image Questions #4

Open weltmeyer opened 9 years ago

weltmeyer commented 9 years ago

Hi, how to answer to image questions?

old method upload_round_answers throws error about updating to new Version.

weltmeyer commented 9 years ago

Okay, after removing the https pinning from the android version and checking the traffic, it have noticed that some commands have more validators on it now.

Example for upload_answers where first of the six answer was an image question:

POST https://qkgermany.feomedia.se/games/upload_round_answers HTTP/1.1
dt: apl
Content-Type: application/x-www-form-urlencoded; charset=utf-8
authorization: f+3MWBt/YTgHBj4UnfuZ0onsrwXgTPqDHV6CLLJqdTE=
clientdate: 2015-02-10 23:33:54
device-info: samsung|GT-P5200|17
ld: 0|772441258|se.feomedia.quizkampen.de.lite|78|ANlOHQPqCXIkE668FxYZEfUCgW1dRmDToQ==|1423606587335:VT=9223372036854775807
ls: I6y+kbEAiOo8+KIImnX0CeWxDd5WqJMFAhnRXsqBy+LPVB/XkRGNlvNn66ep/VMCbX2p9qDE0YwMOU3HfdyWyUtGxBo3gGfVHRQ6hqvB+sq1BXqPeo4b6m70ZNL+SkUfcf5TbWkP0URmAiXhfTqVwB034fV+KdN7gkevNR2DGTrMSyBwR/Xpz7BB9SCiL01zsHKQ9JevVus46BabvmluKC3FP+7xaclOpUwZsZ/TKZyvcS+vxVG9xSIc1IAeGDWseYMj20uQzSIe4aBD1zh0gptYXHVE6Q5Izehi81ob347le+cLXzlwjQpXbx5Kr2kIcdnzK6cOCReLrmHR0JAiFw==
android-id: 129ef109-2e97-4948-b62e-b601711ff972
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 73
Host: qkgermany.feomedia.se
Connection: Keep-Alive
User-Agent: Quizduell A gzip  1.7.8
Cookie: auth="eyJfdXNlciI6WzUyMjIxMTAzMTMwNTQyMDgsMSwiUWlXdmNTRktUbDZibHJ0WDhIVlgzbCIsMTQyMzYwNjU3OSwxNDIzNjA2NTc5LCJhc2Rhc2Q5OTk5Il19|1423607579|9b37c2ee96a296c1bc2ed1179bfb8de3644297b4"
Cookie2: $Version=1
Accept-Encoding: gzip

game_id=5035430096404480&answers=%5B0%2C0%2C1%2C0%2C2%2C2%5D&cat_choice=2

At the moment, i have not idea how Id,auth,ls are generated. Maybe you can lighten up this stuff a little bit.?

mtschirs commented 9 years ago

Hi weltmeyer, thank you for looking into this!

I just tried

game =  api.start_game("<user ID>")
result = api.upload_round_answers(game['game_id'], [0,0,0], 1)

and I got a normal result

{
    "game": {
        "cat_choices": [
            1
        ], 
        "elapsed_min": 0, 
        "game_id": XXX, 
        "messages": [], 
        "opponent": {
            "avatar_code": "0008085107", 
            "name": "XXX", 
            "show_gift": false, 
            "user_id": "XXX"
        }, 
        "opponent_answers": [], 
        "state": 1, 
        "your_answers": [
            0, 
            0, 
            0
        ], 
        "your_turn": false
    }, 
    "google_play_verification": true
}

Could you describe what I have to do to reproduce the problem?

weltmeyer commented 9 years ago

Hi you have to play against an opponent who gets image questions. then your game_detail will return the image questions like this:

 public class ImageQuestion
    {
        public int index { get; set; }
        public Question2 question { get; set; }
    }

    public class Question2
    {
        public bool image_logotype { get; set; }
        public string cat_name { get; set; }
        public int q_id { get; set; }
        public bool image_tempdeleted { get; set; }
        public string timestamp { get; set; }
        public int cat_id { get; set; }
        public string question { get; set; }
        public string wrong3 { get; set; }
        public string wrong2 { get; set; }
        public string wrong1 { get; set; }
        public string image_url { get; set; }
        public bool image_center { get; set; }
        public string image_copyright { get; set; }
        public bool image_profilepic { get; set; }
        public string correct { get; set; }
    }

 public class Game
    {
        public List<Question> questions { get; set; }
        public List<int> your_answers { get; set; }
        public GameState state { get; set; }
        public int elapsed_min { get; set; }
        public bool your_turn { get; set; }
        public long game_id { get; set; }
        public List<int> cat_choices { get; set; }
        public List<int> opponent_answers { get; set; }
        public List<Message> messages { get; set; }
        public OtherUser opponent { get; set; }
        public int? rating_bonus { get; set; }
        public List<ImageQuestion> image_questions { get; set; }
    }

For this games, it is not working when u set client version to 1.7.8.

it still works when settings client version 1.4.x as it then dont gives you the image questions.

mtschirs commented 9 years ago

Hm, I did the following:

  1. Create two new players via the api
  2. Start a game between these players via the api
  3. Upload round answers for both players via the api
  4. Login to the Quizduell app and play for one player
  5. Upload round answers for the other player via the api

I didn't encounter the error you describe. The game had image questions, I successfully answered these image questions via the api and in the app. Perhaps the problem is elsewhere? E.g. the code you posted seems to be from https://github.com/joshimoo/QuizDuell, which is an out-of-date C# version of this python api.

mtschirs commented 9 years ago

I am pretty sure you are using the outdated C# client API by now. However, updating that code seems easy, you would only have to change the authorization key and modify the method that generates the authorization code. Perhaps you could write to @joshimoo and ask if he would update his repository.

Just out of curiosity: What do you use the Quizduell API for?

weltmeyer commented 9 years ago

i wrote my own c# api based on you code :) maybe i gonna check more about the problem.. but still there are these 3 new form variables that get send sometimes. ... edit: not new form variables but new HEADER-Variables, as you can see in my next post.

weltmeyer commented 9 years ago

Also there are some new command. for example: "/users/current_user_games_m" Fiddler Request:

POST /users/current_user_games_m HTTP/1.1
dt: apl
Content-Type: application/x-www-form-urlencoded; charset=utf-8
authorization: ccklF0gYnHoVTR7t2c4Wp14Z5KTVLVSO11JwW4L+xWg=
clientdate: 2015-02-13 15:30:35
device-info: samsung|GT-P5200|17
ld: 0|772441258|se.feomedia.quizkampen.de.lite|78|ANlOHQPqCXIkE668FxYZEfUCgW1dRmDToQ==|1423606587335:VT=9223372036854775807
ls: I6y+kbEAiOo8+KIImnX0CeWxDd5WqJMFAhnRXsqBy+LPVB/XkRGNlvNn66ep/VMCbX2p9qDE0YwMOU3HfdyWyUtGxBo3gGfVHRQ6hqvB+sq1BXqPeo4b6m70ZNL+SkUfcf5TbWkP0URmAiXhfTqVwB034fV+KdN7gkevNR2DGTrMSyBwR/Xpz7BB9SCiL01zsHKQ9JevVus46BabvmluKC3FP+7xaclOpUwZsZ/TKZyvcS+vxVG9xSIc1IAeGDWseYMj20uQzSIe4aBD1zh0gptYXHVE6Q5Izehi81ob347le+cLXzlwjQpXbx5Kr2kIcdnzK6cOCReLrmHR0JAiFw==
android-id: 129ef109-2e97-4948-b62e-b601711ff972
Content-Type: application/x-www-form-urlencoded; charset=utf-8
Content-Length: 0
Host: qkgermany.feomedia.se
Connection: Keep-Alive
User-Agent: Quizduell A gzip  1.7.8
Cookie: auth="eyJfdXNlciI6WzUyMjIxMTAzMTMwNTQyMDgsMSwiUWlXdmNTRktUbDZibHJ0WDhIVlgzbCIsMTQyMzYwNjU3OSwxNDIzODM3ODE0LCJhc2Rhc2Q5OTk5Il19|1423837815|35104277e35438e2e90e71752578b20006afa6a1"
Cookie2: $Version=1
Accept-Encoding: gzip

Fiddler Response:

HTTP/1.1 200 OK
Cache-Control: no-cache
Content-Type: text/json; charset=utf-8
Set-Cookie: auth=eyJfdXNlciI6WzUyMjIxMTAzMTMwNTQyMDgsMSwiUWlXdmNTRktUbDZibHJ0WDhIVlgzbCIsMTQyMzYwNjU3OSwxNDIzODM3ODE0LCJhc2Rhc2Q5OTk5Il19|1423837837|7f1fc8374810fb5169d712ffad4d8d56d6c46f0e; Max-Age=315360000; Path=/; expires=Mon, 10-Feb-2025 14:30:37 GMT
Vary: Accept-Encoding
Date: Fri, 13 Feb 2015 14:30:37 GMT
Server: Google Frontend
Expires: Fri, 13 Feb 2015 14:30:37 GMT
Content-Length: 2551

{"logged_in": true, "user": {"show_gift": true, "user_id": "5222110313054208", "name": "asdasd9999", "board_game_player": false, "qc": false, "games": [{"opponent_answers": [], "your_answers": [], "game_id": 5536120203902976, "give_up_player_id": 6617667553198080, "elapsed_min": 2880, "rating_bonus": 0, "cat_choices": [], "messages": [], "state": 5, "your_turn": false, "you_gave_up": false, "opponent": {"show_gift": true, "user_id": "6617667553198080", "name": "pati01081979", "avatar_code": null}}, {"opponent_answers": [], "your_answers": [], "game_id": 5427476992884736, "give_up_player_id": 6715314608799744, "elapsed_min": 2880, "rating_bonus": 0, "cat_choices": [], "messages": [], "state": 5, "your_turn": false, "you_gave_up": false, "opponent": {"show_gift": true, "user_id": "6715314608799744", "name": "Zauberstab :-)", "avatar_code": null}}, {"opponent_answers": [1, 3, 2], "your_answers": [], "game_id": 6398725193728000, "give_up_player_id": 5376408086904832, "elapsed_min": 2880, "rating_bonus": 0, "cat_choices": [1], "messages": [], "state": 5, "your_turn": true, "you_gave_up": false, "opponent": {"show_gift": true, "user_id": "5376408086904832", "name": "Ramona Catwomen", "avatar_code": null}}, {"your_answers": [0, 0, 1, 0, 2, 2], "state": 6, "elapsed_min": 946, "rating_bonus": -3, "your_turn": true, "game_id": 5035430096404480, "cat_choices": [1, 2, 2], "opponent_answers": [0, 0, 0, 0, 0, 1, 0, 2, 2], "messages": [], "opponent": {"show_gift": true, "user_id": "4713705000402944", "name": "Stridy", "avatar_code": null}}, {"your_answers": [2, 2, 2, 0, 3, 2], "state": 6, "elapsed_min": 927, "rating_bonus": 0, "your_turn": true, "game_id": 4701746839420928, "cat_choices": [1, 2, 1], "opponent_answers": [0, 0, 1, 0, 3, 0, 0, 0, 0], "messages": [], "opponent": {"show_gift": false, "user_id": "6708525613973504", "name": "KaroffelToffel", "avatar_code": "0008309205"}}], "q_reviewer": 0, "friends": [{"show_gift": false, "user_id": "6708525613973504", "name": "KaroffelToffel", "avatar_code": "0107299104"}], "email": null, "avatar_code": null, "blocked": []}, "settings": {"ppf": 0.0, "feos": 1.0, "admob_med_id": "ae2c09377aa24d06", "splash_freq": 1.0, "refresh_table_freq": 300000, "max_free_games": 8, "check_limbo_games": false, "ad_provider": "admob_mediation", "fulmium": false, "feo": false, "give_up_point_loss": 24, "override": {"GAME_GIVEUP_MESS": "Aufgeben bedeutet -%d im Rating. Sicher?", "INVITE_VIA_WHATSAPP": "Via WhatsApp teilen"}, "admob_med_splash_id": "7aa3a3d2b21a4eef", "refresh_frequency": 10000}}
mtschirs commented 9 years ago

These "ls" and "ld" headers have always been send and, as far a I know, they have something to do with Quizduell Premium. They are not used for authentication.

You are right about the new methods ending in "_m", "_t", "_f" etc. and I assume their main objective is to reduce the amount of transmitted data between server and app. They do not introduce new features, thus I ignored them.

Hm, since you wrote your own C# client I assume the fault lies somewhere in how you create the authorization code. Do you sort the post parameters, apply the regex and 'scramble' the code correctly?

weltmeyer commented 9 years ago

mhmm okay.. but the strange thing is: for most of upload_answers it works. except for some times... it thought it was about the image questions.. i will check this.. btw here is my code for generating the auth codes seems like i do the same as @joshimo and you. ( I also stole some code from @joshimo about the sorting part...) :


        private string _Get_Auth_Code(string url, string client_date, Dictionary<string, string> data)
        {
            var msg = "https://" + this.host_name + url + client_date;
            if (data != null && data.Count > 0)
            {
                var dataValues = data.Values.ToList();
                dataValues.Sort((x, y) =>
                {
                    if (!Char.IsLetterOrDigit(x[0]))
                    {
                        if (Char.IsDigit(y[0]))
                            return 1;
                        if (Char.IsLetter(y[0]))
                            return -1;
                    }
                    return System.String.Compare(x, y, System.StringComparison.Ordinal);
                });

                msg += string.Join("", dataValues);
            }
            msg = Regex.Replace(msg, "[^-abefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPRSTUXYZ012346789 ,.()]", "");

            (new[] { 2, 3, 5 }).ToList().ForEach(x =>
            {
                msg = _Scramble_Auth_Code(msg, x, 0);
            });
            var encoding = new UTF8Encoding();
            var dig = new System.Security.Cryptography.HMACSHA256(encoding.GetBytes(authorization_key));
            var hash = dig.ComputeHash(encoding.GetBytes(msg));
            var b64Hash = Convert.ToBase64String(hash);
            return b64Hash;

        }

        private string _Scramble_Auth_Code(string data, int key, int rev)
        {

            rev += 1;
            var l = data.Length / key;
            if (l > 0)
            {
                var result = "";
                if (rev % 2 == 0)
                {
                    result += _Scramble_Auth_Code((data.Substring(l)), key, rev);
                    result += _Scramble_Auth_Code((data.Substring(0, l)), key, rev);
                }
                else
                {
                    result += _Scramble_Auth_Code((data.Substring(0, l)), key, rev);
                    result += _Scramble_Auth_Code((data.Substring(l)), key, rev);
                }
                return result;
            }
            return data;

        }
mtschirs commented 9 years ago

If you send me the parameters and the produced auth code of a request that fails, I can send you the auth code my python code produces and compare it to yours.

weltmeyer commented 9 years ago

gonna do that, as soon as i get this problem again. i am not playing too often with my win8desktop client :)

i also changed one part that my have produced the error: the sorting from @joshimo only sorted by the first char of the string. i am now sorting by all chars. maybe this helps

weltmeyer commented 9 years ago

okay.. here i have the error. i checked back for authorization and its the same you pythons script creates.

my request minus the cookie:

POST https://qkgermany.appspot.com/games/upload_round_answers HTTP/1.1
Accept-Encoding: identity
Clientdate: 2015-02-13 18:09:23
Device-Info: google|X5
Dt: apl
Android-Id: 347d21e1-de6c-4bcd-b567-1eff1279b331
Authorization: KnCFjafkfh4QB/lCHsbH8MRwtA7Wg7wge0dRDiy8Vjg=
User-Agent: Quizduell A gzip 1.7.8
Content-Type: application/x-www-form-urlencoded
Host: qkgermany.appspot.com
Cookie: auth="REMOVED"
Content-Length: 108
Connection: Close

game_id=6247437386645504&cat_choice=2&answers=%5B0%2C+0%2C+0%2C+0%2C+0%2C+0%2C+0%2C+0%2C+0%2C+0%2C+0%2C+0%5D

the response:

{"link_title": "Updaten!", "google_play_verification": true, "skip": true, "popup_title": "Neue Version verf\u00fcgbar!", "link": "market://details?id=se.feomedia.quizkampen.de.lite", "popup_mess": "Eine neue und bessere Version ist verf\u00fcgbar. Du musst sie updaten, um spielen zu k\u00f6nnen."}{"google_play_verification": true}

testing with you python:

api = quizduell.QuizduellApi()
data = {
            'game_id': '6247437386645504',
            'cat_choice': '2',
            'answers': '[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]',
        }

testcode=api._get_authorization_code('games/upload_round_answers','2015-02-13 18:09:23',data)
print testcode

returns KnCFjafkfh4QB/lCHsbH8MRwtA7Wg7wge0dRDiy8Vjg= which is the same as i get.

Also i tested this exactly game and answer combination with my account in your python api:

import quizduell

api = quizduell.QuizduellApi()

user = api.login_user('myusername', 'mypassword')

if 'popup_mess' in user: 
    print 'Error:', user['popup_mess']

firstgame='6247437386645504'
test=api.upload_round_answers(firstgame,[0,0,0,0,0,0,0,0,0,0,0,0],0);
print test;

This is returning the exakt same json error. Console output:

xxx\quizduellapi-master>c:\Python27\python.exe list.py
Traceback (most recent call last):
  File "list.py", line 12, in <module>
    test=api.upload_round_answers(firstgame,[0,0,0,0,0,0,0,0,0,0,0,0],0);
  File "xxx\quizduellapi-master\quizduell\quizduellapi.py", line 598, in upload_round_answers
    return self._request('/games/upload_round_answers', data)
  File "xxx\quizduellapi-master\quizduell\quizduellapi.py", line 651, in _request
    return json.loads(response.read())
  File "c:\Python27\lib\json\__init__.py", line 338, in loads
    return _default_decoder.decode(s)
  File "c:\Python27\lib\json\decoder.py", line 369, in decode
    raise ValueError(errmsg("Extra data", s, end, len(s)))
ValueError: Extra data: line 1 column 300 - line 1 column 334 (char 299 - 333)
weltmeyer commented 9 years ago

btw.: the Extra data error comes from the additional {"google_play_verification": true} after the first json part

mtschirs commented 9 years ago

I tried to reproduce the update error by uploading round answers to your game id, but it failed with "popup_title": "Hoppla", "popup_mess": "Ein Fehler ist aufgetreten". So it seems the auth code was correct and it went through that check until the server realized that my user_id doesn't match the game_id.

In a second step, I tried reproducing the update error by randomly letting two newly created players play against each other via the API. However, after more than 50 games and hundreds of calls to upload_round_answers, no error had occured.

At this point I am a bit perplexed. I don't manage to reproduce the error message. Could you send the full request including the cookie that causes this error message? Only if the player account is not linked to private information of course.

weltmeyer commented 9 years ago

hmm it seems to work after setting Accept-Encoding to "gzip" instead of "identity" gonna try this now for some time

mtschirs commented 9 years ago

Strange behavior of the server, but as long as it works... I will close this issue then. Good luck with your project.

weltmeyer commented 9 years ago

hmmm didnt fix.. try to play against userId "5638108868509696" i think i got always problems when playing with him.

weltmeyer commented 9 years ago

Also it does not seem to have anything to do with authorization. I answered with android client an compared the auth code to what your and my code would have generated for the combination of data,clientdate und url and it is the same.