「GNU social」の版間の差分
Gnusocialjp (トーク | 投稿記録) (Basic) |
Gnusocialjp (トーク | 投稿記録) (Test) |
||
| 258行目: | 258行目: | ||
まず、GNU socialのサイトにアクセスすると、以下のpublic/.htaccessの内容に従って、URLがindex.php?p=<path>の形式に書き換えられる。 | まず、GNU socialのサイトにアクセスすると、以下のpublic/.htaccessの内容に従って、URLがindex.php?p=<path>の形式に書き換えられる。 | ||
< | <RewriteCond %{REQUEST_FILENAME} !-f | ||
RewriteCond %{REQUEST_FILENAME} !-d | RewriteCond %{REQUEST_FILENAME} !-d | ||
RewriteRule (.*) index.php?p=$1 [L,QSA]</code> | RewriteRule (.*) index.php?p=$1 [L,QSA]</code> | ||
| 279行目: | 279行目: | ||
* main/register | * main/register | ||
* api/account/register.:format | * api/account/register.:format | ||
=== Test === | |||
PHPUnitのテスト記述時のコツがある。<syntaxhighlight lang="php" line="1"> | |||
<?php declare(strict_types=1); | |||
use PHPUnit\Framework\TestCase; | |||
define('GNUSOCIAL', true); | |||
define('INSTALLDIR', '.'); | |||
set_include_path('../../'); | |||
require_once(INSTALLDIR . '/lib/util/framework.php'); | |||
final class ApiAccountRegisterActionTest extends TestCase | |||
{ | |||
private $instance; | |||
protected function setUp(): void | |||
{ | |||
$GLOBALS['config'] = []; | |||
$this->instance = new ApiAccountRegisterAction; | |||
} | |||
public function testHandle(): void | |||
{ | |||
$GLOBALS['config']['site'] = ['closed' => true]; | |||
$this->expectExceptionMessage('Registration not allowed.'); | |||
$mock = $this->createMock('ApiAccountRegisterAction'); | |||
$mock->expects($this->any()) | |||
->method('clientError') | |||
->willReturnCallback(function( | |||
string $msg, int $code = 400, ?string $format = null) { | |||
throw new \Exception($msg, $code, $format); | |||
}); | |||
$this->doMethod($mock, 'handle', []); | |||
} | |||
/** | |||
* privateメソッドを実行する. | |||
* @param object $object 対象オブジェクト。 | |||
* @param string $methodName privateメソッドの名前。 | |||
* @param array $param privateメソッドに渡す引数。 | |||
* @return mixed 実行結果。 | |||
* @throws \ReflectionException 引数のクラスがない場合に発生。 | |||
*/ | |||
private function doMethod(object $object, string $methodName, array $param): object | |||
{ | |||
// テスト対象のクラスをnewする. | |||
$controller = $object; | |||
// ReflectionClassをテスト対象のクラスをもとに作る. | |||
$reflection = new \ReflectionClass($controller); | |||
// メソッドを取得する. | |||
$method = $reflection->getMethod($methodName); | |||
// アクセス許可をする. | |||
$method->setAccessible(true); | |||
// メソッドを実行して返却値をそのまま返す. | |||
return $method->invokeArgs($controller, $param); | |||
} | |||
} | |||
</syntaxhighlight>ポイントは以下3点。 | |||
# 冒頭のdefineからrequire_onceの部分でライブラリーのオートロード類を設定する。GNUSOCIALのマクロがないと冒頭のexitで終わるので定義必要。 | |||
# テスト対象インスタンス生成時に$configを参照していることが多いので、setUpでダミーで定義しておく。 | |||
# clientErrorでexitで中断することが多いので、試験できるようにmockでclientErrorを例外を出す別の関数に置換しておく。これで試験できる。 | |||
2024年1月21日 (日) 14:50時点における版
API
GNU socialのWeb APIについては,フッターの [Help](doc/help), [About](doc/about), [TOS](doc/tos) などを選択して遷移できるDOCS画面の [API](doc/api) (Api - GNU social JP) を選択することで表示できる。
ソースコード上はdoc-src (v2.0.2 - NotABug.org: Free code hosting) 配下にAPI文書が格納されている。
AtomPubとTwitter互換APIの2種類のAPIを利用可能。Twitter APIのほうがシンプルだが、AtomPubだとリッチテキストが使用可能など、機能に若干の違いがある。
以下の記事も参考になる。
API末尾の拡張子atom/xml/jsonに応じた形式の応答をサーバーが返す模様。
Twitter API
- Twitter-compatible API - I ask questions: QvitterのREADMEに記載のあったTwitter互換APIの説明文書。
- Standard v1.1 | Docs | Twitter Developer Platform: X/Twitterの公式文書。
GNU socialのTwitter APIは完全互換ではないので注意が必要。
Post
wget -O - \
--http-user=vaginaplant \
--http-passwd=XXXXXX \
--post-data='status=LGBTPZN' \
https://freezepeach.xyz/api/statuses/update.json
curl -u username:password -d "status=status" https://domain/api/statuses/update.json
添付ファイルの例。
curl -u username:password \ -F "media=@image.jpg" \ -F "status=post message" \ https://domain.jp/api/statuses/update.json
返信する場合、リクエストボディーにin_reply_to_status_idで返信先投稿IDを指定する。Twitter APIだと、本文に@メンションが必須だが、GSの場合、メンションは任意の模様。ただ、他の実装だと必須のものもあったりする。あるほうが安定するかもしれない。
Get
JSONの配列で応答が返ってくる。
curl $HOST/api/statuses/user_timeline/test.json
[
{
"text": "QSettings",
"truncated": false,
"created_at": "Thu Jan 04 11:51:11 +0900 2024",
"in_reply_to_status_id": null,
"uri": "tag:gnusocial.jp,2024-01-04:noticeId=5026657:objectType=note",
"source": "",
"source_link": null,
"id": 5026657,
"in_reply_to_user_id": null,
"in_reply_to_screen_name": null,
"geo": null,
"user": {
"id": 2,
"name": "test",
"screen_name": "test",
"location": "Japan",
"description": "gnusocial.jpのテストアカウントです。\r\nID/PW=test/666666。\r\n投稿にはメール登録が必要です。基本は閲覧用です。",
"profile_image_url": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-stream.png",
"profile_image_url_https": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-stream.png",
"profile_image_url_profile_size": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-profile.png",
"profile_image_url_original": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-profile.png",
"groups_count": 1,
"linkcolor": false,
"backgroundcolor": false,
"url": "https://example.com",
"protected": false,
"followers_count": 15,
"friends_count": 1,
"created_at": "Mon Jul 18 13:10:39 +0900 2022",
"utc_offset": "32400",
"time_zone": "Asia/Tokyo",
"statuses_count": 35,
"following": false,
"statusnet_blocking": false,
"notifications": false,
"statusnet_profile_url": "https://gnusocial.jp/test",
"cover_photo": false,
"background_image": false,
"profile_link_color": false,
"profile_background_color": false,
"profile_banner_url": false,
"is_local": true,
"is_silenced": false,
"rights": {
"delete_user": false,
"delete_others_notice": false,
"silence": false,
"sandbox": false
},
"is_sandboxed": false,
"ostatus_uri": "https://gnusocial.jp/index.php/user/2",
"favourites_count": 0
},
"statusnet_html": "QSettings",
"statusnet_conversation_id": 2539347,
"statusnet_in_groups": false,
"external_url": "https://gnusocial.jp/notice/5026657",
"in_reply_to_profileurl": null,
"in_reply_to_ostatus_uri": null,
"attentions": [],
"fave_num": 0,
"repeat_num": 0,
"is_post_verb": true,
"is_local": true,
"favorited": false,
"repeated": false
},
{
"text": "test 2",
"truncated": false,
"created_at": "Thu Jan 04 10:41:35 +0900 2024",
"in_reply_to_status_id": null,
"uri": "tag:gnusocial.jp,2024-01-04:noticeId=5026119:objectType=note",
"source": "",
"source_link": null,
"id": 5026119,
"in_reply_to_user_id": null,
"in_reply_to_screen_name": null,
"geo": null,
"user": {
"id": 2,
"name": "test",
"screen_name": "test",
"location": "Japan",
"description": "gnusocial.jpのテストアカウントです。\r\nID/PW=test/666666。\r\n投稿にはメール登録が必要です。基本は閲覧用です。",
"profile_image_url": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-stream.png",
"profile_image_url_https": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-stream.png",
"profile_image_url_profile_size": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-profile.png",
"profile_image_url_original": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-profile.png",
"groups_count": 1,
"linkcolor": false,
"backgroundcolor": false,
"url": "https://example.com",
"protected": false,
"followers_count": 15,
"friends_count": 1,
"created_at": "Mon Jul 18 13:10:39 +0900 2022",
"utc_offset": "32400",
"time_zone": "Asia/Tokyo",
"statuses_count": 35,
"following": false,
"statusnet_blocking": false,
"notifications": false,
"statusnet_profile_url": "https://gnusocial.jp/test",
"cover_photo": false,
"background_image": false,
"profile_link_color": false,
"profile_background_color": false,
"profile_banner_url": false,
"is_local": true,
"is_silenced": false,
"rights": {
"delete_user": false,
"delete_others_notice": false,
"silence": false,
"sandbox": false
},
"is_sandboxed": false,
"ostatus_uri": "https://gnusocial.jp/index.php/user/2",
"favourites_count": 0
},
"statusnet_html": "test 2",
"statusnet_conversation_id": 2539084,
"statusnet_in_groups": false,
"external_url": "https://gnusocial.jp/notice/5026119",
"in_reply_to_profileurl": null,
"in_reply_to_ostatus_uri": null,
"attentions": [],
"fave_num": 0,
"repeat_num": 0,
"is_post_verb": true,
"is_local": true,
"favorited": false,
"repeated": false
},
{
"text": "test (https://gnusocial.jp/test) started following めうるみ (とけた) (https://mewl.me/@mewl).",
"truncated": false,
"created_at": "Thu Sep 08 08:32:22 +0900 2022",
"in_reply_to_status_id": null,
"uri": "tag:gnusocial.jp,2022-09-07:subscription:2:person:9308:2022-09-08T08:32:22+09:00",
"source": "activity",
"source_link": null,
"id": 274143,
"in_reply_to_user_id": null,
"in_reply_to_screen_name": null,
"geo": null,
"user": {
"id": 2,
"name": "test",
"screen_name": "test",
"location": "Japan",
"description": "gnusocial.jpのテストアカウントです。\r\nID/PW=test/666666。\r\n投稿にはメール登録が必要です。基本は閲覧用です。",
"profile_image_url": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-stream.png",
"profile_image_url_https": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-stream.png",
"profile_image_url_profile_size": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-profile.png",
"profile_image_url_original": "https://gnusocial.jp/theme/gnusocialjp/default-avatar-profile.png",
"groups_count": 1,
"linkcolor": false,
"backgroundcolor": false,
"url": "https://example.com",
"protected": false,
"followers_count": 15,
"friends_count": 1,
"created_at": "Mon Jul 18 13:10:39 +0900 2022",
"utc_offset": "32400",
"time_zone": "Asia/Tokyo",
"statuses_count": 35,
"following": false,
"statusnet_blocking": false,
"notifications": false,
"statusnet_profile_url": "https://gnusocial.jp/test",
"cover_photo": false,
"background_image": false,
"profile_link_color": false,
"profile_background_color": false,
"profile_banner_url": false,
"is_local": true,
"is_silenced": false,
"rights": {
"delete_user": false,
"delete_others_notice": false,
"silence": false,
"sandbox": false
},
"is_sandboxed": false,
"ostatus_uri": "https://gnusocial.jp/index.php/user/2",
"favourites_count": 0
},
"statusnet_html": "test started following めうるみ (とけた).",
"statusnet_conversation_id": 154562,
"statusnet_in_groups": false,
"external_url": "https://gnusocial.jp/notice/274143",
"in_reply_to_profileurl": null,
"in_reply_to_ostatus_uri": null,
"attentions": [],
"fave_num": 0,
"repeat_num": 0,
"is_post_verb": false,
"is_local": true,
"favorited": false,
"repeated": false
}
]
Develop
Config
「lib/util/default.php」にデフォルト設定一覧がある。
ここの設定項目は、 common_config(key1, key2)で参照できる。
Basic
バグ修正などで、通信と実コードの対応関係を特定する手順があるので整理する。
まず、GNU socialのサイトにアクセスすると、以下のpublic/.htaccessの内容に従って、URLがindex.php?p=<path>の形式に書き換えられる。
<RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule (.*) index.php?p=$1 [L,QSA]
lib/router.php: ルーティング処理 (URLによる挙動の変更) 。ここで<path>と呼び出される処理 (action) が対応づけられている。
index.php内のmain関数でcall_user_func内で最終的にパスに対応するactionクラスのhandleを呼び出しており、このhandleの連鎖で処理が進む。
actionはactions配下にaction名.phpの形式で格納されている。
こういう風に見ると、ソースコードの該当処理を探しやすい。
例えば、グループを新規作成する画面は、POST group/newの通信を行う。これに対応づくactionはnewgroupとなっている。
実際、actions/newgroup.phpというファイルが存在する。この中にdoPost() があり、これで処理している。
このような流れで、だいたい行っている処理の場所を特定できる。
Register
- main/register
- api/account/register.:format
Test
PHPUnitのテスト記述時のコツがある。
<?php declare(strict_types=1);
use PHPUnit\Framework\TestCase;
define('GNUSOCIAL', true);
define('INSTALLDIR', '.');
set_include_path('../../');
require_once(INSTALLDIR . '/lib/util/framework.php');
final class ApiAccountRegisterActionTest extends TestCase
{
private $instance;
protected function setUp(): void
{
$GLOBALS['config'] = [];
$this->instance = new ApiAccountRegisterAction;
}
public function testHandle(): void
{
$GLOBALS['config']['site'] = ['closed' => true];
$this->expectExceptionMessage('Registration not allowed.');
$mock = $this->createMock('ApiAccountRegisterAction');
$mock->expects($this->any())
->method('clientError')
->willReturnCallback(function(
string $msg, int $code = 400, ?string $format = null) {
throw new \Exception($msg, $code, $format);
});
$this->doMethod($mock, 'handle', []);
}
/**
* privateメソッドを実行する.
* @param object $object 対象オブジェクト。
* @param string $methodName privateメソッドの名前。
* @param array $param privateメソッドに渡す引数。
* @return mixed 実行結果。
* @throws \ReflectionException 引数のクラスがない場合に発生。
*/
private function doMethod(object $object, string $methodName, array $param): object
{
// テスト対象のクラスをnewする.
$controller = $object;
// ReflectionClassをテスト対象のクラスをもとに作る.
$reflection = new \ReflectionClass($controller);
// メソッドを取得する.
$method = $reflection->getMethod($methodName);
// アクセス許可をする.
$method->setAccessible(true);
// メソッドを実行して返却値をそのまま返す.
return $method->invokeArgs($controller, $param);
}
}
ポイントは以下3点。
- 冒頭のdefineからrequire_onceの部分でライブラリーのオートロード類を設定する。GNUSOCIALのマクロがないと冒頭のexitで終わるので定義必要。
- テスト対象インスタンス生成時に$configを参照していることが多いので、setUpでダミーで定義しておく。
- clientErrorでexitで中断することが多いので、試験できるようにmockでclientErrorを例外を出す別の関数に置換しておく。これで試験できる。
