Repository: kmyk/online-judge-tools
Pull request #120 (2018-01-06 09:39:25)

#100: Yukicoder: the official API to get solved


Patch:
diff --git a/onlinejudge/implementation/utils.py b/onlinejudge/implementation/utils.py
index bf519d3..7b6f40b 100644
--- a/onlinejudge/implementation/utils.py
+++ b/onlinejudge/implementation/utils.py
@@ -141,8 +141,7 @@ class SampleZipper(object):
             self.dangling = { 'data': s, 'name': name }
         else:
             if re.search('input', name, re.IGNORECASE) or re.search('入力', name):
-                if not (re.search('output', name, re.IGNORECASE) or re.search('出力', name)):  # to ignore titles like "Output for Sample Input 1"
-                    log.warning('strange name for output string: %s', name)
+                log.warning('strange name for output string: %s', name)
             self.data += [ {
                 'input': self.dangling,
                 'output': { 'data': s, 'name': name },
diff --git a/onlinejudge/yukicoder.py b/onlinejudge/yukicoder.py
index 3cc9cc1..3f0e789 100644
--- a/onlinejudge/yukicoder.py
+++ b/onlinejudge/yukicoder.py
@@ -79,14 +79,14 @@ class YukicoderService(onlinejudge.service.Service):
                 and result.netloc == 'yukicoder.me':
             return cls()
 
-    # example: {"Id":10,"Name":"yuki2006","Solved":280,"Level":34,"Rank":59,"Score":52550,"Points":7105,"Notice":"匿名ユーザーの情報は取れません。ユーザー名が重複している場合は最初に作られたIDが優先されます（その場合は運営にご報告いただければマージします）。このAPIはベータ版です。予告なく変更される場合があります。404を返したら廃止です。"}
-    def get_user(self, id=None, name=None, session=None):
+    def _issue_official_api(self, api, id=None, name=None, session=None):
         assert (id is not None) != (name is not None)
         if id is not None:
             assert isinstance(id, int)
-            url = 'https://yukicoder.me/api/v1/user/%d' % id
+            sometihng = { 'user': '', 'solved': 'id/' }[api]
+            url = 'https://yukicoder.me/api/v1/{}/{}{}'.format(api, sometihng, id)
         else:
-            url = 'https://yukicoder.me/api/v1/user/name/%s' % urllib.parse.quote(name)
+            url = 'https://yukicoder.me/api/v1/{}/name/{}'.format(api, urllib.parse.quote(name))
         session = session or utils.new_default_session()
         try:
             resp = utils.request('GET', url, session=session)
@@ -95,6 +95,15 @@ class YukicoderService(onlinejudge.service.Service):
             return None
         return json.loads(resp.content.decode(resp.encoding))
 
+    # example: {"Id":10,"Name":"yuki2006","Solved":280,"Level":34,"Rank":59,"Score":52550,"Points":7105,"Notice":"匿名ユーザーの情報は取れません。ユーザー名が重複している場合は最初に作られたIDが優先されます（その場合は運営にご報告いただければマージします）。このAPIはベータ版です。予告なく変更される場合があります。404を返したら廃止です。"}
+    def get_user(self, *args, **kwargs):
+        return self._issue_official_api('user', *args, **kwargs)
+
+    # https://twitter.com/yukicoder/status/935943170210258944
+    # example: [{"No":46,"ProblemId":43,"Title":"はじめのn歩","AuthorId":25,"TesterId":0,"Level":1,"ProblemType":0,"Tags":"実装"}]
+    def get_solved(self, *args, **kwargs):
+        return self._issue_official_api('solved', *args, **kwargs)
+
     # example: https://yukicoder.me/users/237/favorite
     def get_user_favorite(self, id, session=None):
         url = 'https://yukicoder.me/users/%d/favorite' % id


Test patch:
diff --git a/tests/yukicoder.py b/tests/yukicoder.py
index 346daab..af8a35d 100644
--- a/tests/yukicoder.py
+++ b/tests/yukicoder.py
@@ -7,6 +7,13 @@ class YukicoderTest(unittest.TestCase):
 
     def test_get_user_10(self):
         data = YukicoderService().get_user(id=10)
+        self.assertIn('Id', data)
+        self.assertIn('Name', data)
+        self.assertIn('Solved', data)
+        self.assertIn('Level', data)
+        self.assertIn('Rank', data)
+        self.assertIn('Score', data)
+        self.assertIn('Points', data)
         self.assertEqual(data['Id'], 10)
         self.assertEqual(data['Name'], 'yuki2006')
     def test_get_user_yuki2006(self):
@@ -17,6 +24,24 @@ class YukicoderTest(unittest.TestCase):
         data = YukicoderService().get_user(id=0)
         self.assertIs(data, None)
 
+    def test_get_solved_10(self):
+        data = YukicoderService().get_solved(id=10)
+        self.assertGreater(len(data), 200)
+        self.assertIn('No', data[0])
+        self.assertIn('ProblemId', data[0])
+        self.assertIn('Title', data[0])
+        self.assertIn('AuthorId', data[0])
+        self.assertIn('TesterId', data[0])
+        self.assertIn('Level', data[0])
+        self.assertIn('ProblemType', data[0])
+        self.assertIn('Tags', data[0])
+    def test_get_solved_yuki2006(self):
+        data = YukicoderService().get_solved(name='yuki2006')
+        self.assertGreater(len(data), 200)
+    def test_get_solved_0(self):
+        data = YukicoderService().get_solved(id=0)
+        self.assertIs(data, None)
+
     def test_get_user_favorite_10(self):
         data = YukicoderService().get_user_favorite(id=10)
         it = list(filter(lambda row: row['#'] == 10000, data))