Skip to content

Commit e6213d5

Browse files
committed
classroom htmlからの取得
1 parent 597951a commit e6213d5

File tree

4 files changed

+93
-32
lines changed

4 files changed

+93
-32
lines changed

scapi/sites/classroom.py

Lines changed: 33 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
dt_from_isoformat,
2727
temporary_httpclient,
2828
page_api_iterative,
29+
page_html_iterative,
2930
split
3031
)
3132
from ..utils.client import HTTPClient
@@ -228,45 +229,60 @@ async def create_class_studio(self,title:str,description:str|None=None) -> Studi
228229
studio.title = data.get("gallery_title")
229230
return studio
230231

231-
async def get_class_studios(self,start_page:int|None=None,end_page:int|None=None) -> AsyncGenerator[Studio]:
232+
async def get_class_studios(self,start_page:int|None=None,end_page:int|None=None,*,use_api:bool=False) -> AsyncGenerator[Studio]:
232233
"""
233234
クラスのスタジオを取得する。
234235
235236
Args:
236237
start_page (int|None, optional): 取得するスタジオの開始ページ位置。初期値は1です。
237238
end_page (int|None, optional): 取得するスタジオの終了ページ位置。初期値はstart_pageの値です。
239+
use_api (bool, optional): 教師向けAPIを使用するか。教師の場合のみ使用できます。
238240
239241
Yields:
240242
Studio: 取得したスタジオ
241243
"""
242-
self.require_session()
243-
async for _s in page_api_iterative(
244-
self.client,f"https://scratch.mit.edu/site-api/classrooms/studios/{self.id}/",
245-
start_page,end_page
246-
):
247-
_s:OldAnyObjectPayload[OldStudioPayload]
248-
yield Studio._create_from_data(_s["pk"],_s["fields"],self.client_or_session,Studio._update_from_old_data)
244+
if use_api:
245+
self.require_session()
246+
async for _s in page_api_iterative(
247+
self.client,f"https://scratch.mit.edu/site-api/classrooms/studios/{self.id}/",
248+
start_page,end_page
249+
):
250+
yield Studio._create_from_data(_s["pk"],_s["fields"],self.client_or_session,Studio._update_from_old_data)
251+
else:
252+
async for _t in page_html_iterative(
253+
self.client,f"https://scratch.mit.edu/classes/{self.id}/studios/",
254+
start_page,end_page,list_class="gallery thumb item"
255+
):
256+
yield Studio._create_from_html(_t,self.client_or_session,host=self.educator)
249257

250-
async def get_class_students(self,start_page:int|None=None,end_page:int|None=None) -> AsyncGenerator[User]:
258+
async def get_students(self,start_page:int|None=None,end_page:int|None=None,*,use_api:bool=False) -> AsyncGenerator[User]:
251259
"""
252260
クラスの生徒を取得する。
253261
254262
Args:
255263
start_page (int|None, optional): 取得するユーザーの開始ページ位置。初期値は1です。
256264
end_page (int|None, optional): 取得するユーザーの終了ページ位置。初期値はstart_pageの値です。
265+
use_api (bool, optional): 教師向けAPIを使用するか。教師の場合のみ使用できます。
257266
258267
Yields:
259268
User: 取得したユーザー
260269
"""
261-
self.require_session()
262-
async for _u in page_api_iterative(
263-
self.client,f"https://scratch.mit.edu/site-api/classrooms/students/{self.id}/",
264-
start_page,end_page
265-
):
266-
_u:OldAnyObjectPayload[StudentPayload]
267-
yield User._create_from_data(_u["fields"]["user"]["username"],_u["fields"],self.client_or_session,User._update_from_student_data)
270+
if use_api:
271+
self.require_session()
272+
async for _u in page_api_iterative(
273+
self.client,f"https://scratch.mit.edu/site-api/classrooms/students/{self.id}/",
274+
start_page,end_page
275+
):
276+
_u:OldAnyObjectPayload[StudentPayload]
277+
yield User._create_from_data(_u["fields"]["user"]["username"],_u["fields"],self.client_or_session,User._update_from_student_data)
278+
else:
279+
async for _t in page_html_iterative(
280+
self.client,f"https://scratch.mit.edu/classes/{self.id}/students/",
281+
start_page,end_page,list_class="user thumb item"
282+
):
283+
yield User._create_from_html(_t,self.client_or_session)
268284

269-
async def get_class_activity(
285+
async def get_privete_activity(
270286
self,
271287
start_page:int|None=None,
272288
end_page:int|None=None,

scapi/sites/studio.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from __future__ import annotations
22

33
import datetime
4-
from typing import TYPE_CHECKING, AsyncGenerator, Final, Literal
4+
from typing import TYPE_CHECKING, AsyncGenerator, Final, Literal, Self
55

66
import aiohttp
77
from ..utils.types import (
@@ -20,7 +20,9 @@
2020
UNKNOWN_TYPE,
2121
api_iterative,
2222
dt_from_isoformat,
23-
_AwaitableContextManager
23+
_AwaitableContextManager,
24+
Tag,
25+
split
2426
)
2527
from ..utils.client import HTTPClient
2628
from ..utils.error import (
@@ -141,6 +143,17 @@ def _update_from_old_data(self, data:OldStudioPayload):
141143

142144
description=data.get("description")
143145
)
146+
147+
@classmethod
148+
def _create_from_html(cls,data:Tag,client_or_session:"HTTPClient|Session",*,host:"User|None|UNKNOWN_TYPE"=None) -> Self:
149+
_span:Tag = data.find("span",{"class":"title"})
150+
_a:Tag = _span.find("a")
151+
studio = cls(int(split(str(_a["href"]),"/studios/","/",True)),client_or_session)
152+
studio.title = _a.get_text().strip()
153+
if host:
154+
studio._host = host
155+
studio.host_id = host.id
156+
return studio
144157

145158
@property
146159
def created_at(self) -> datetime.datetime|UNKNOWN_TYPE:

scapi/sites/user.py

Lines changed: 18 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import datetime
44
from enum import Enum
55
import random
6-
from typing import TYPE_CHECKING, AsyncGenerator, Final, NamedTuple, Sequence
6+
from typing import TYPE_CHECKING, AsyncGenerator, Final, NamedTuple, Self, Sequence
77

88
import aiohttp
99
import bs4
@@ -21,6 +21,7 @@
2121
MAYBE_UNKNOWN,
2222
UNKNOWN_TYPE,
2323
api_iterative,
24+
page_html_iterative,
2425
dt_from_isoformat,
2526
_AwaitableContextManager,
2627
Tag,
@@ -124,6 +125,16 @@ def _update_from_student_data(self,data:StudentPayload):
124125
is_banned=data.get("is_banned")
125126
)
126127
self._update_from_old_data(data["user"])
128+
129+
@classmethod
130+
def _create_from_html(cls,data:Tag,client_or_session:"HTTPClient|Session") -> Self:
131+
_a:Tag = data.find("a")
132+
_img:Tag = data.find("img")
133+
user = cls(split(str(_a["href"]),"/users/","/",True),client_or_session)
134+
user_id = split(str(_img["data-original"]),"/user/","_") or ""
135+
if user_id.isdecimal():
136+
user.id = int(user_id)
137+
return user
127138

128139
@property
129140
def joined_at(self) -> datetime.datetime|UNKNOWN_TYPE:
@@ -343,18 +354,12 @@ async def get_loves(self,start_page:int|None=None,end_page:int|None=None) -> Asy
343354
Yields:
344355
Project: 取得したプロジェクト
345356
"""
346-
start_page = start_page or 1
347-
end_page = end_page or start_page
348-
if TYPE_CHECKING: _p:Tag
349-
for i in range(start_page,end_page+1):
350-
try:
351-
response = await self.client.get(f"https://scratch.mit.edu/projects/all/{self.username}/loves/",params={"page":i})
352-
except NotFound:
353-
return
354-
data = bs4.BeautifulSoup(response.text, "html.parser")
355-
content:Tag = data.find("div",{"class":"box-content"})
356-
for _p in content.find_all("li",{"class":"project thumb item"}):
357-
yield Project._create_from_html(_p,self.client_or_session)
357+
358+
async for _t in page_html_iterative(
359+
self.client,f"https://scratch.mit.edu/projects/all/{self.username}/loves/",
360+
start_page=start_page,end_page=end_page,list_class="project thumb item"
361+
):
362+
yield Project._create_from_html(_t,self.client_or_session)
358363

359364

360365
async def get_message_count(self) -> int:

scapi/utils/common.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,33 @@ async def page_api_iterative(
172172
if not data:
173173
return
174174

175+
async def page_html_iterative(
176+
_client:"HTTPClient",
177+
url:str,
178+
start_page:int|None=None,
179+
end_page:int|None=None,
180+
params:dict[str,str|int|float]|None=None,
181+
*,
182+
outside_class:str|None="media-grid",
183+
list_class:str,
184+
list_name:str|None="li"
185+
) -> AsyncGenerator[Tag]:
186+
params = params or {}
187+
start_page = start_page or 1
188+
end_page = end_page or start_page
189+
for i in range(start_page,end_page+1):
190+
try:
191+
response = await _client.get(url,params={"page":i}|params)
192+
except NotFound:
193+
return
194+
data:Tag = bs4.BeautifulSoup(response.text, "html.parser")
195+
if outside_class is not None:
196+
data = data.find("div",{"class":outside_class})
197+
198+
objs:Sequence[Tag] = data.find_all(list_name,{"class":list_class}) # pyright: ignore[reportArgumentType]
199+
for obj in objs:
200+
yield obj
201+
175202
def get_client_and_session(client_or_session:"HTTPClient|Session|None") -> tuple["HTTPClient","Session|None"]:
176203
from .client import HTTPClient
177204
if client_or_session is None:

0 commit comments

Comments
 (0)