gnuboard5-activitypub/extend/activitiypub.extend.php

510 lines
20 KiB
PHP

<?php
if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
// ActivityPub implementation for GNUBOARD 5
// Go Namhyeon <gnh1201@gmail.com>
// References:
// * https://www.w3.org/TR/activitypub/
define("ACTIVITYPUB_INSTANCE_ID", md5_file(G5_DATA_PATH . "/dbconfig.php"));
define("ACTIVITYPUB_URL", (empty(G5_URL) ? "http://" . ACTIVITYPUB_INSTANCE_ID . ".local" : G5_URL));
define("ACTIVITYPUB_DATA_URL", ACTIVITYPUB_URL . '/' . G5_DATA_DIR);
define("ACTIVITYPUB_G5_TABLENAME", G5_TABLE_PREFIX . "apstreams");
define("ACTIVITYPUB_G5_USERNAME", "apstreams");
define("NAMESPACE_ACTIVITYSTREAMS", "https://www.w3.org/ns/activitystreams");
define("NAMESPACE_ACTIVITYSTREAMS_PUBLIC", "https://www.w3.org/ns/activitystreams#Public");
function activitypub_get_url($action, $params) {
return ACTIVITYPUB_URL . "/?route=activitypub." . $action . "&" . http_build_query($params);
}
function activitypub_json_encode($arr) {
return json_encode( $arr );
}
function activitypub_get_icon($mb) {
global $config;
$icon_file_url = "";
if ($config['cf_use_member_icon']) {
$mb_dir = substr($mb['mb_id'], 0, 2);
$icon_file = G5_DATA_PATH . '/member/' . $mb_dir . '/' . get_mb_icon_name($mb['mb_id']).'.gif';
if (file_exists($icon_file)) {
$icon_filemtile = (defined('G5_USE_MEMBER_IMAGE_FILETIME') && G5_USE_MEMBER_IMAGE_FILETIME) ? '?'.filemtime($icon_file) : '';
$icon_file_url = ACTIVITYPUB_DATA_URL . '/member/' . $mb_dir . '/' . get_mb_icon_name($mb['mb_id']) . '.gif' . $icon_filemtile;
}
}
if (empty($icon_file_url)) {
$icon_file_url = "https://www.gravatar.com/avatar/" . md5($mb['mb_email']);
}
return $icon_file_url;
}
function activitypub_get_recent_items() {
global $g5;
$items = array();
$sql = "select * from " . $g5['board_new_table'];
$result = sql_query($sql);
while ($row = sql_fetch_array($result)) {
$sql2 = "select * from " . ($g5['write_prefix'] . $row['bo_table']) . " where wr_id = '" . $row['wr_id'] . "'";
$row2 = sql_fetch($sql2);
if ($row2['mb_id']) {
array_push($items, array("from" => $row['mb_id'], "to" => $row2['mb_id']));
}
}
return $items;
}
function activitypub_get_followers($mb) {
$followers = array();
if ($mb['mb_id']) {
$recent_items = activitypub_get_recent_items();
foreach($recent_items as $item) {
if ($item['to'] == $mb['mb_id'] && $item['from'] != $mb['mb_id']) {
array_push($followers, $item['from']);
}
}
$followers = array_unique($followers);
}
return $followers;
}
function activitypub_get_following($mb) {
$following = array();
if ($mb['mb_id']) {
$recent_items = activitypub_get_recent_items();
foreach($recent_items as $item) {
if ($item['from'] == $mb['mb_id'] && $item['to'] != $mb['mb_id']) {
array_push($following, $item['to']);
}
}
$following = array_unique($following);
}
return $following;
}
function activitypub_parse_url($url) {
$ctx = parse_url($url);
$qstr = $ctx['query'];
parse_str($qstr, $qctx);
$ctx['query'] = $qctx;
return $ctx;
}
function activitypub_add_memo($mb_id, $recv_mb_id, $me_memo) {
global $g5;
$tmp_row = sql_fetch(" select max(me_id) as max_me_id from {$g5['memo_table']} ");
$me_id = $tmp_row['max_me_id'] + 1;
$sql = " insert into {$g5['memo_table']} ( me_recv_mb_id, me_send_mb_id, me_send_datetime, me_memo, me_read_datetime, me_type, me_send_ip ) values ( '$recv_mb_id', '$mb_id', '".G5_TIME_YMDHIS."', '$me_memo', '0000-00-00 00:00:00' , 'recv', '{$_SERVER['REMOTE_ADDR']}' ) ";
sql_query($sql);
return ($me_id == sql_insert_id());
}
function activitypub_set_liked($good, $bo_table, $wr_id) {
global $g5;
// 추천(찬성), 비추천(반대) 카운트 증가
sql_query(" update {$g5['write_prefix']}{$bo_table} set wr_{$good} = wr_{$good} + 1 where wr_id = '{$wr_id}' ");
// 내역 생성
sql_query(" insert {$g5['board_good_table']} set bo_table = '{$bo_table}', wr_id = '{$wr_id}', mb_id = '" . ACTIVITYPUB_G5_USERNAME . "', bg_flag = '{$good}', bg_datetime = '" . G5_TIME_YMDHIS . "' ");
}
class _GNUBOARD_ActivityPub {
public static function open() {
header("Content-Type: application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"");
}
public static function whois() {
$mb = get_member($_GET['mb_id']);
if (!$mb['mb_id']) {
return activitypub_json_encode(array("message" => "Could not find the user"));
}
$context = array(
"@context" => array(ACTIVITYPUB_CONTEXT_URL, array("@language" => "ko")),
"type" => "Person",
"id" => activitypub_get_url("whois", array("mb_id" => $mb['mb_id'])),
"name" => $mb['mb_name'],
"preferredUsername" => $mb['mb_nick'],
"summary" => $mb['mb_profile'],
"inbox" => activitypub_get_url("inbox", array("mb_id" => $mb['mb_id'])),
"outbox" => activitypub_get_url("outbox", array("mb_id" => $mb['mb_id'])),
"followers" => activitypub_get_url("followers", array("mb_id" => $mb['mb_id'])),
"following" => activitypub_get_url("following", array("mb_id" => $mb['mb_id'])),
"liked" => activitypub_get_url("liked", array("mb_id" => $mb['mb_id'])),
"icon" => array(
activitypub_get_icon($mb)
)
);
return activitypub_json_encode($context);
}
public static function streams() {
$params = array(
"bo_table" => $_GET['bo_table'],
"wr_id" => $_GET['wr_id']
);
if (!empty($params['bo_table']) && !empty($params['wr_id'])) {
$qstr = http_build_query(array(
"bo_table" => $params['bo_table'],
"wr_id" => $params['wr_id']
));
header("Location: " . G5_BBS_URL . "/board.php?" . $qstr);
} else {
return activitypub_json_encode(array("message" => "Could not find the stream"));
}
}
public static function inbox() {
global $g5;
// 개인에게 보낸 메시지는 쪽지에 저장
// 공개(Public) 설정한 메시지는 ACTIVITYPUB_G5_TABLENAME에 저장
// 게시물이 특정된 경우 댓글로 저장 (그누 전용)
$data = json_decode(file_get_contents("php://input"), true);
if (empty($data['@context'])) {
return activitypub_json_encode(array("message" => "This is a broken context"));
}
if ($data['@context'] != NAMESPACE_ACTIVITYSTREAMS) {
return activitypub_json_encode(array("message" => "This is not an ActivityStreams request"));
}
if (!empty($data['type'])) {
// 멤버정보 처리
$mb_id = activitypub_parse_url($data['actor'])['query']['mb_id'];
if (empty($mb_id)) {
$mb_id = ACTIVITYPUB_G5_USERNAME; // 그누보드에 호환되는 액터가 아니므로 기본 액터(mb_id=apstreams) 사용
}
$mb = get_member($mb_id);
// 수신자 확인
$to = $data['to'];
// 원글 정보 확인
$object = $data['object'];
// 타입 별 해야될 일 지정
switch ($data['type']) {
case "Create":
// 내용 처리
if (empty($object['content'])) {
return activitypub_json_encode(array("message" => "Content is empty"));
}
$content = $object['content'];
// 답글인지 확인
if (!empty($object['inReplyTo'])) {
// 답글 정보 확인
$query = activitypub_parse_url($object['inReplyTo'])['query'];
// 특정 글이 지목되어 있을 때 -> 댓글로 작성
if (!empty($query['bo_table']) && !empty($query['wr_id'])) {
$wr_id = $query['wr_id'];
$write_table = G5_TABLE_PREFIX . $query['bo_table'];
$wr = get_write($write_table, $wr_id);
// 글이 존재하는 경우
if (!empty($wr['wr_id'])) {
$mb = get_member(ACTIVITYPUB_G5_USERNAME);
$wr_homepage = $data['actor'];
$sql = "
insert into $write_table
set ca_name = '{$wr['ca_name']}',
wr_option = '',
wr_num = '{$wr['wr_num']}',
wr_reply = '',
wr_parent = '{$wr['wr_id']}',
wr_is_comment = 1,
wr_comment = '',
wr_comment_reply = '',
wr_subject = '',
wr_content = '$content',
mb_id = '{$mb['mb_id']}',
wr_password = '',
wr_name = '{$mb['mb_name']}',
wr_email = '',
wr_homepage = '$wr_homepage',
wr_datetime = '" . G5_TIME_YMDHIS . "',
wr_last = '',
wr_ip = '{$_SERVER['REMOTE_ADDR']}',
wr_1 = '',
wr_2 = '',
wr_3 = '',
wr_4 = '',
wr_5 = '',
wr_6 = '',
wr_7 = '',
wr_8 = '',
wr_9 = '',
wr_10 = ''
";
sql_query($sql);
}
// 원글이 삭제된 경우
else {
return activitypub_json_encode(array("message" => "Could not find the original message"));
}
}
}
break;
case "Like":
// '좋아요'는 원글 표시가 문자열로 되어 있음
if (is_string($object)) {
$query = activitypub_parse_url($object);
// 원글을 특정한 경우
if (!empty($query['bo_table']) && !empty($query['wr_id'])) {
$wr_id = $query['wr_id'];
$write_table = G5_TABLE_PREFIX . $query['bo_table'];
$wr = get_write($write_table, $wr_id);
// 원글이 존재하는 경우
if (!empty($wr['wr_id'])) {
activitypub_set_liked("good", $query['bo_table'], $wr['wr_id']);
}
// 원글이 삭제된 경우
else {
return activitypub_json_encode(array("message" => "Could not find the original message"));
}
}
// 특정하지 않은 경우
else {
return activitypub_json_encode(array("message" => "Please specify the original message"));
}
}
// 문자열이 아닌 경우
else {
return activitypub_json_encode(array("message" => "'Object' must be set type to 'String' to request 'Like' type"));
}
break;
case "Dislike":
// '싫어요'는 원글 표시가 문자열로 되어 있음
if (is_string($object)) {
$query = activitypub_parse_url($object);
// 원글을 특정한 경우
if (!empty($query['bo_table']) && !empty($query['wr_id'])) {
$wr_id = $query['wr_id'];
$write_table = G5_TABLE_PREFIX . $query['bo_table'];
$wr = get_write($write_table, $wr_id);
// 원글이 존재하는 경우
if (!empty($wr['wr_id'])) {
activitypub_set_liked("nogood", $query['bo_table'], $wr['wr_id']);
}
// 원글이 삭제된 경우
else {
return activitypub_json_encode(array("message" => "Could not find the original message"));
}
}
// 특정하지 않은 경우
else {
return activitypub_json_encode(array("message" => "Please specify the original message"));
}
}
// 문자열이 아닌 경우
else {
return activitypub_json_encode(array("message" => "'Object' must be set type to 'String' to request 'Like' type"));
}
break;
default:
return activitypub_json_encode(array("message" => "This is not implemented type"));
}
// 받을사람(수신자) 처리
foreach($to as $_to) {
$query = activitypub_parse_url($_to)['query'];
// 공개 게시물일 때
if ($_to == NAMESPACE_ACTIVITYSTREAMS_PUBLIC) {
$mb = get_member(ACTIVITYPUB_G5_USERNAME);
$write_table = ACTIVITYPUB_G5_TABLENAME;
$wr_num = get_next_num($write_table);
$wr_reply = '';
$ca_name = 'ActivityStreams';
$wr_subject = mb_substr($content, 0, 45);
$wr_seo_title = mb_substr($content, 0, 45);
$wr_content = $content;
$wr_link1 = $data['actor'];
$wr_link2 = '';
$wr_homepage = $data['actor'];
$sql = "
insert into $write_table
set wr_num = '$wr_num',
wr_reply = '$wr_reply',
wr_comment = 0,
ca_name = '$ca_name',
wr_option = '',
wr_subject = '$wr_subject',
wr_content = '$wr_content',
wr_seo_title = '$wr_seo_title',
wr_link1 = '$wr_link1',
wr_link2 = '$wr_link2',
wr_link1_hit = 0,
wr_link2_hit = 0,
wr_hit = 0,
wr_good = 0,
wr_nogood = 0,
mb_id = '{$mb['mb_id']}',
wr_password = '',
wr_name = '{$mb['mb_name']}',
wr_email = '',
wr_homepage = '$wr_homepage',
wr_datetime = '" . G5_TIME_YMDHIS . "',
wr_last = '" . G5_TIME_YMDHIS . "',
wr_ip = '{$_SERVER['REMOTE_ADDR']}',
wr_1 = '',
wr_2 = '',
wr_3 = '',
wr_4 = '',
wr_5 = '',
wr_6 = '',
wr_7 = '',
wr_8 = '',
wr_9 = '',
wr_10 = ''
";
sql_query($sql);
return activitypub_json_encode(array("message" => "Success"));
}
// 특정 회원이 지목되어 있을 때 -> 메모로 작성
else if (!empty($query['mb_id'])) {
switch ($query['route']) {
case "activitypub.whois":
activitypub_add_memo($mb['mb_id'], $query['mb_id'], $content);
break;
case "activitypub.followers":
$followers = activitypub_get_followers($mb);
foreach($followers as $_mb_id) {
activitypub_add_memo($mb['mb_id'], $_mb_id, $content);
}
break;
case "activitypub.following":
$following = activitypub_get_following($mb);
foreach($following as $_mb_id) {
activitypub_add_memo($mb['mb_id'], $_mb_id, $content);
}
break;
}
}
}
}
}
public static function outbox() {
// TODO
}
public static function followers() {
$params = array(
"mb_id" => $_GET['mb_id']
);
$mb = get_member($params['mb_id']);
return activitypub_json_encode(array("followers" => activitypub_get_followers($mb)));
}
public static function following() {
$params = array(
"mb_id" => $_GET['mb_id']
);
$mb = get_member($_GET['mb_id']);
return activitypub_json_encode(array("following" => activitypub_get_following($mb)));
}
public static function liked() {
return self::inbox();
}
public static function close() {
exit();
}
}
$route = $_GET['route'];
switch ($route) {
case "activitypub.whois":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::whois();
_GNUBOARD_ActivityPub::close();
break;
case "activitypub.streams":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::streams();
_GNUBOARD_ActivityPub::close();
break;
case "activitypub.inbox":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::inbox();
_GNUBOARD_ActivityPub::close();
break;
case "activitypub.outbox":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::outbox();
_GNUBOARD_ActivityPub::close();
break;
case "activitypub.followers":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::followers();
_GNUBOARD_ActivityPub::close();
break;
case "activitypub.following":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::following();
_GNUBOARD_ActivityPub::close();
break;
case "activitypub.liked":
_GNUBOARD_ActivityPub::open();
echo _GNUBOARD_ActivityPub::liked();
_GNUBOARD_ActivityPub::close();
break;
}