diff --git a/.gitignore b/.gitignore
index d1b22020..9e7cd6ed 100644
--- a/.gitignore
+++ b/.gitignore
@@ -65,6 +65,7 @@ openssl/bin
openssl/certs
openssl/include
openssl/lib
+openssl/lib64
openssl/misc
openssl/private
openssl/share
diff --git a/app/app.pro b/app/app.pro
index 13b96c5a..41319d55 100644
--- a/app/app.pro
+++ b/app/app.pro
@@ -203,7 +203,8 @@ win32:{
translations.path = $$install_dir/translations
mac:translations.path = \
$$install_dir/$${TARGET}.app/Contents/MacOS/translations
-translations.files = $$PWD/i18n/*.qm
+translations.files = $$PWD/i18n/*.qm \
+ $$PWD/../lib/i18n/*.qm
#qmファイルが存在しないとmakefileに追加されないので注意
INSTALLS += translations
diff --git a/app/i18n/app_ja.qm b/app/i18n/app_ja.qm
index 76495fb4..0ea89b69 100644
Binary files a/app/i18n/app_ja.qm and b/app/i18n/app_ja.qm differ
diff --git a/app/i18n/app_ja.ts b/app/i18n/app_ja.ts
index b7618585..86e44293 100644
--- a/app/i18n/app_ja.ts
+++ b/app/i18n/app_ja.ts
@@ -9,7 +9,7 @@
アカウント管理
-
+
Set as main
メインに設定
@@ -18,7 +18,7 @@
ポストの統計とログ
-
+
Content filter
コンテンツフィルター
@@ -27,17 +27,17 @@
ミュート中
-
+
Muted words and tags
ミュートワードの編集
-
+
Muted accounts
ミュート中のアカウント
-
+
Muted lists
ミュート中のリスト
@@ -46,32 +46,32 @@
ブロック中
-
+
Statistics and logs
統計とログ
-
+
Mute
ミュート
-
+
Block
ブロック
-
+
Blocked accounts
ブロック中のアカウント
-
+
Blocked lists
ブロック中のリスト
-
+
Post interaction settings
投稿への反応の設定
@@ -80,12 +80,12 @@
リプライできるユーザー
-
+
Remove account
アカウントを削除
-
+
Close
閉じる
@@ -273,14 +273,14 @@
AtpAbstractListModel
-
-
+
+
Blocked
ブロック中
-
-
+
+
Detached by author
投稿者によって切り離し済み
@@ -685,137 +685,6 @@ Please recreate AppPassword in the official application.
設定
-
- ConfigurableLabels
-
- Content hidden
- 閲覧不可
-
-
- Moderator overrides for special cases.
- 特別な場合のためのモデレーターによるオーバーライド。
-
-
- Content warning
- 閲覧警告
-
-
- Legal
- 法的
-
-
- Content removed for legal reasons.
- 法的な理由で削除されたコンテンツ。
-
-
- Explicit Sexual Images
- 露骨な性的な画像
-
-
- i.e. pornography
- 例えばポルノ
-
-
- Sexually Explicit
- 露骨な性的表現
-
-
- Other Nudity
- その他ヌード
-
-
- Including non-sexual and artistic
- ノンセクシャルや芸術的なものも含む
-
-
- Nudity
- ヌード
-
-
- Basic Moderation
- 基本モデレーション
-
-
- Basic configuration independent of moderation services.
- モデレーションサービスに依存しない基本設定。
-
-
- Pornography
- ポルノ
-
-
- Explicit sexual images.
- 露骨な性的な画像。
-
-
- Sexually Suggestive
- 性的な示唆
-
-
- Does not include nudity
- ヌードは含まない
-
-
- Graphic Media
- グラフィックメディア
-
-
- Explicit or potentially disturbing media.
- 露骨な、あるいは不穏なメディア。
-
-
- Non-sexual Nudity
- 性的でないヌード
-
-
- E.g. artistic nudes.
- 例:芸術的なヌード
-
-
- Violent / Bloody
- 暴力 / 流血
-
-
- Gore, self-harm, torture
- 血糊、自傷行為、拷問
-
-
- Violence
- 暴力
-
-
- Hate Group Iconography
- ヘイトグループの象徴
-
-
- Images of terror groups, articles covering events, etc.
- テロ集団の画像、事件を取り上げた記事など
-
-
- Hate Groups
- ヘイトグループ
-
-
- Spam
- スパム
-
-
- Excessive unwanted interactions
- 過剰な不要な干渉
-
-
- Impersonation / Scam
- なりすまし / 詐欺
-
-
- Impersonation
- なりすまし
-
-
- Accounts falsely claiming to be people or orgs
- 個人や団体を偽ったアカウント
-
-
ContentFilterSettingDialog
@@ -1007,17 +876,22 @@ Please recreate AppPassword in the official application.
HashTagMenu
-
+
Search %s posts
%s を検索
-
+
Search %s posts by this user
このユーザーによる %s を検索
-
+
+ Copy %s
+ %sをコピー
+
+
+
Mute %s posts
%s をミュートする
@@ -2082,93 +1956,6 @@ Please recreate AppPassword in the official application.
ブロック中
-
- LogAccess
-
- Post
- ポスト
-
-
- Total number of posts
- 総ポスト数
-
-
- Number of days posts
- ポストした日数
-
-
- Average number of daily posts
- 1日あたりの平均ポスト数
-
-
- Like
- いいね
-
-
- Total number of likes
- 総いいね数
-
-
- Number of days likes
- いいねした日数
-
-
- Average number of daily likes
- 1日あたりの平均いいね数
-
-
- Repost
- リポスト
-
-
- Total number of reposts
- 総リポスト数
-
-
- Number of days reposts
- リポストした日数
-
-
- Average number of daily reposts
- 1日あたりの平均リポスト数
-
-
- List
- リスト
-
-
- Total number of lists
- リストの数
-
-
- Total number of list items
- リストに登録している総アカウント数
-
-
- Other
- その他
-
-
- Total number of follows
- フォロー数
-
-
- Total number of who can reply
- リプライ制限をした数
-
-
- Total number of blocks
- 総ブロック数
-
-
- Number of registrations for list
- リストに登録しているアカウント数
-
-
- Number of registrations for '%1'
- '%1'に登録しているアカウント数
-
-
LogViewDialog
@@ -2293,12 +2080,12 @@ Please recreate AppPassword in the official application.
NotificationDelegate
-
+
Post from an account you muted.
ミュートしているアカウントのポスト
-
+
signed up with your starter pack
あなたのスターターパックで登録しました
@@ -2306,7 +2093,7 @@ Please recreate AppPassword in the official application.
NotificationListModel
-
+
Post hidden by muted word
ミュートワードを含む
@@ -2322,83 +2109,83 @@ Please recreate AppPassword in the official application.
PostControls
-
+
Repost
リポスト
-
+
Quote
引用
-
-
+
+
Translate
翻訳
-
-
+
+
Copy post text
ポストをコピー
-
-
+
+
Copy url
URLをコピー
-
-
+
+
Open in Official
公式で開く
-
-
+
+
Reposted by
リポストしたアカウント
-
-
+
+
Liked by
いいねしたアカウント
-
-
+
+
Quotes
引用したポスト
-
-
+
+
Unpin this post
固定ポストを解除
-
-
+
+
Pin this post
固定ポストにする
-
-
+
+
Unmute thread
スレッドミュートの解除
-
-
+
+
Mute thread
スレッドをミュート
-
+
Edit interaction settings
投稿への反応の設定
@@ -2407,22 +2194,22 @@ Please recreate AppPassword in the official application.
リプライできるユーザー
-
+
Delete post
ポストを削除
-
+
Re-attach quote
引用を再接続
-
+
Detach quote
引用を切断
-
+
Report post
ポストを通報
@@ -2430,7 +2217,7 @@ Please recreate AppPassword in the official application.
PostDelegate
-
+
Post from an account you muted.
ミュートしているアカウントのポスト
@@ -2506,7 +2293,7 @@ Please recreate AppPassword in the official application.
ポストスレッド
-
+
Quoted content warning
閲覧注意な引用
@@ -2592,87 +2379,86 @@ Please recreate AppPassword in the official application.
登録日 :
-
+
Send mention
メンションを送る
-
+
Send message
メッセージを送る
-
+
Copy handle
ハンドルをコピー
-
+
Copy DID
DIDをコピー
-
+
Copy Official Url
公式のURLをコピー
-
+
Open in new col
新しいカラムで開く
-
+
Open in Official
公式で開く
-
+
Add/Remove from lists
リストへ追加/削除
-
+
Unmute account
ミュート解除
-
+
Mute account
ミュート
-
+
Unblock account
ブロック解除
-
+
Block account
ブロック
-
+
Report account
通報
-
+
Account blocked
ブロックしたアカウント
-
+
Account muted
ミュートしたアカウント
-
This account has been flagged :
- このアカウントに設定されたラベル :
+ このアカウントに設定されたラベル :
-
+
This account has blocked you
あなたをブロックしているアカウント
@@ -2810,28 +2596,28 @@ Please recreate AppPassword in the official application.
-
+
Update list ... (%1)
-
+
Update who can reply ...
-
-
+
+
Update quote status ...
-
+
Uploading images ... (%1/%2)
-
+
Delete list item ... (%1)
@@ -3606,7 +3392,7 @@ Why should this message be reviewed?
TimelineListModel
-
+
Post hidden by muted word
ミュートワードを含む
@@ -3614,7 +3400,7 @@ Why should this message be reviewed?
TimelineView
-
+
Quoted content warning
閲覧注意な引用
@@ -3627,7 +3413,7 @@ Why should this message be reviewed?
-
+
Search posts
検索(ポスト)
@@ -3641,23 +3427,33 @@ Why should this message be reviewed?
リプライできるユーザーの更新中 ...
-
-
+
+ Authentication error
+ 認証エラー
+
+
+
+ Some accounts require you to log in again.
+ いくつかのアカウントでログインが必要です。
+
+
+
+
Updating 'Edit interaction settings' ...
投稿への反応の設定を更新中 ...
-
+
Loading lists
リストの読み込み中
-
+
Chat
チャット
-
+
Loading account(s) ...
アカウント情報の読み込み中 ...
diff --git a/app/main.cpp b/app/main.cpp
index e29cd2b6..070d4e58 100644
--- a/app/main.cpp
+++ b/app/main.cpp
@@ -91,7 +91,7 @@ int main(int argc, char *argv[])
app.setOrganizationName(QStringLiteral("relog"));
app.setOrganizationDomain(QStringLiteral("hagoromo.relog.tech"));
app.setApplicationName(QStringLiteral("Hagoromo"));
- app.setApplicationVersion(QStringLiteral("0.38.0"));
+ app.setApplicationVersion(QStringLiteral("0.39.0"));
#ifndef HAGOROMO_RELEASE_BUILD
app.setApplicationVersion(app.applicationVersion() + "d");
#endif
diff --git a/app/qml/parts/ExternalLinkCard.qml b/app/qml/parts/ExternalLinkCard.qml
index e8466cad..fb301b27 100644
--- a/app/qml/parts/ExternalLinkCard.qml
+++ b/app/qml/parts/ExternalLinkCard.qml
@@ -32,7 +32,7 @@ ClickableFrame {
ImageWithIndicator {
id: thumbImage
Layout.preferredWidth: externalLinkFrame.width
- Layout.preferredHeight: thumbImage.source !== "" ? externalLinkFrame.width * 0.5 : 5
+ Layout.preferredHeight: (thumbImage.source + "") !== "" ? externalLinkFrame.width * 0.5 : 5
fillMode: Image.PreserveAspectCrop
}
Label {
diff --git a/app/qml/parts/HashTagMenu.qml b/app/qml/parts/HashTagMenu.qml
index 276ab98a..0c09d27e 100644
--- a/app/qml/parts/HashTagMenu.qml
+++ b/app/qml/parts/HashTagMenu.qml
@@ -11,6 +11,7 @@ MenuEx {
signal requestViewSearchPosts(string text)
signal requestAddMutedWord(string text)
+ signal requestCopyTagToClipboard(string text)
Action {
enabled: !logMode
@@ -25,6 +26,12 @@ MenuEx {
text: qsTr("Search %s posts by this user").replace("%s", tagMenu.tagText)
onTriggered: requestViewSearchPosts(tagMenu.tagText + " from:" + postAuthor.handle)
}
+ Action {
+ enabled: !logMode
+ icon.source: "../images/copy.png"
+ text: qsTr("Copy %s").replace("%s", tagMenu.tagText)
+ onTriggered: requestCopyTagToClipboard(tagMenu.tagText)
+ }
MenuSeparator {}
Action {
icon.source: "../images/mute.png"
diff --git a/app/qml/parts/NotificationDelegate.qml b/app/qml/parts/NotificationDelegate.qml
index 5cd3bded..150e52d7 100644
--- a/app/qml/parts/NotificationDelegate.qml
+++ b/app/qml/parts/NotificationDelegate.qml
@@ -56,13 +56,14 @@ ClickableFrame {
signal requestViewProfile(string did)
signal requestViewSearchPosts(string text)
signal requestAddMutedWord(string text)
+ signal requestCopyTagToClipboard(string text)
- function openLink(url){
+ function openLink(url, x, y){
if(url.indexOf("did:") === 0){
requestViewProfile(url)
}else if(url.indexOf("search://") === 0){
- tagMenu.x = recrdTextMouseArea.mouseX
- tagMenu.y = recrdTextMouseArea.mouseY
+ tagMenu.x = x
+ tagMenu.y = y
tagMenu.tagText = url.substring(9)
if(tagMenu.tagText.charAt(0) !== "#"){
tagMenu.tagText = "#" + tagMenu.tagText
@@ -275,13 +276,14 @@ ClickableFrame {
wrapMode: Text.WrapAnywhere
font.pointSize: AdjustedValues.f10
lineHeight: 1.3
- onLinkActivated: (url) => openLink(url)
+ onLinkActivated: (url) => openLink(url, recrdTextMouseArea.mouseX, recrdTextMouseArea.mouseY)
onHoveredLinkChanged: displayLink(hoveredLink)
HashTagMenu {
id: tagMenu
onRequestViewSearchPosts: (text) => notificationFrame.requestViewSearchPosts(text)
onRequestAddMutedWord: (text) => notificationFrame.requestAddMutedWord(text)
+ onRequestCopyTagToClipboard: (text) => notificationFrame.requestCopyTagToClipboard(text)
}
}
}
@@ -359,7 +361,9 @@ ClickableFrame {
wrapMode: Text.WrapAnywhere
font.pointSize: AdjustedValues.f10
lineHeight: 1.3
- onLinkActivated: (url) => openLink(url)
+ onLinkActivated: (url) => openLink(url,
+ quoteRecordRecordText.x + quoteRecordRecordText.width / 4,
+ quoteRecordRecordText.y + quoteRecordRecordText.height / 2)
onHoveredLinkChanged: displayLink(hoveredLink)
}
ImagePreview {
diff --git a/app/qml/parts/PostDelegate.qml b/app/qml/parts/PostDelegate.qml
index bbb79032..3dedea2b 100644
--- a/app/qml/parts/PostDelegate.qml
+++ b/app/qml/parts/PostDelegate.qml
@@ -29,6 +29,7 @@ ClickableFrame {
property alias pinnedIndicatorLabel: pinnedIndicatorLabel
property alias postAvatarImage: postAvatarImage
property alias postAuthor: postAuthor
+ property alias authorLabels: authorLabels
property alias recordText: recordText
property alias contentFilterFrame: contentFilterFrame
property alias contentMediaFilterFrame: contentMediaFilterFrame
@@ -53,13 +54,14 @@ ClickableFrame {
signal requestViewProfile(string did)
signal requestViewSearchPosts(string text)
signal requestAddMutedWord(string text)
+ signal requestCopyTagToClipboard(string text)
- function openLink(url){
+ function openLink(url, x, y){
if(url.indexOf("did:") === 0){
requestViewProfile(url)
}else if(url.indexOf("search://") === 0){
- tagMenu.x = recrdTextMouseArea.mouseX
- tagMenu.y = recrdTextMouseArea.mouseY
+ tagMenu.x = x
+ tagMenu.y = y
tagMenu.tagText = url.substring(9)
if(tagMenu.tagText.charAt(0) !== "#"){
tagMenu.tagText = "#" + tagMenu.tagText
@@ -166,6 +168,16 @@ ClickableFrame {
Layout.preferredWidth: parent.basisWidth
layoutWidth: parent.basisWidth
}
+ TagLabelLayout {
+ id: authorLabels
+ Layout.preferredWidth: parent.width
+ visible: count > 0
+ tagSpacing: 2
+ tagColor: "transparent"
+ tagBorderWidth: 1
+ fontPointSize: AdjustedValues.f8
+ }
+
CoverFrame {
id: contentFilterFrame
@@ -198,7 +210,7 @@ ClickableFrame {
wrapMode: Text.WrapAnywhere
font.pointSize: AdjustedValues.f10
lineHeight: 1.3
- onLinkActivated: (url) => openLink(url)
+ onLinkActivated: (url) => openLink(url, recrdTextMouseArea.mouseX, recrdTextMouseArea.mouseY)
onHoveredLinkChanged: displayLink(hoveredLink)
HashTagMenu {
@@ -206,6 +218,7 @@ ClickableFrame {
logMode: postFrame.logMode
onRequestViewSearchPosts: (text) => postFrame.requestViewSearchPosts(text)
onRequestAddMutedWord: (text) => postFrame.requestAddMutedWord(text)
+ onRequestCopyTagToClipboard: (text) => postFrame.requestCopyTagToClipboard(text)
}
}
}
@@ -267,7 +280,9 @@ ClickableFrame {
visible: postFrame.hasQuote &&
quoteFilterFrame.showContent
basisWidth: bodyLayout.basisWidth
- onOpenLink: (url) => postFrame.openLink(url)
+ onOpenLink: (url) => postFrame.openLink(url,
+ quoteRecordFrame.x + quoteRecordFrame.width / 4,
+ quoteRecordFrame.y + quoteRecordFrame.height / 2)
onDisplayLink: (url) => postFrame.displayLink(url)
}
Frame {
diff --git a/app/qml/parts/TagLabelLayout.qml b/app/qml/parts/TagLabelLayout.qml
index ea712d2e..c7c86472 100644
--- a/app/qml/parts/TagLabelLayout.qml
+++ b/app/qml/parts/TagLabelLayout.qml
@@ -3,7 +3,10 @@ import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import QtQuick.Controls.Material 2.15
+import tech.relog.hagoromo.singleton 1.0
+
Item {
+ id: tagLabelLayout
clip: true
implicitHeight: {
if(repeater.count === 0){
@@ -23,6 +26,7 @@ Item {
property alias model: repeater.model
property alias count: repeater.count
property string iconSource: "../images/label.png"
+ property real fontPointSize: AdjustedValues.f10
Repeater {
id: repeater
@@ -73,6 +77,7 @@ Item {
text: model.modelData
source: repeater.parent.iconSource
spacing: repeater.parent.tagSpacing
+ fontPointSize: tagLabelLayout.fontPointSize
}
}
}
diff --git a/app/qml/parts/VideoFrame.qml b/app/qml/parts/VideoFrame.qml
index 9d5a2c00..9229f08b 100644
--- a/app/qml/parts/VideoFrame.qml
+++ b/app/qml/parts/VideoFrame.qml
@@ -33,7 +33,7 @@ ClickableFrame {
id: thumbImage
width: videoFrame.width
height: {
- if(thumbImage.source !== "") {
+ if((thumbImage.source + "") !== "") {
if(sourceSize.width > sourceSize.height){
return (width / sourceSize.width) * sourceSize.height
}else{
diff --git a/app/qml/view/NotificationListView.qml b/app/qml/view/NotificationListView.qml
index 5d4b29e9..7fb86e92 100644
--- a/app/qml/view/NotificationListView.qml
+++ b/app/qml/view/NotificationListView.qml
@@ -84,6 +84,7 @@ ScrollView {
onRequestViewProfile: (did) => notificationListView.requestViewProfile(did)
onRequestViewSearchPosts: (text) => notificationListView.requestViewSearchPosts(text)
onRequestAddMutedWord: (text) => notificationListView.requestAddMutedWord(text)
+ onRequestCopyTagToClipboard: (text) => systemTool.copyToClipboard(text)
moderationFrame.visible: model.muted
userFilterMatched: model.userFilterMatched
diff --git a/app/qml/view/PostThreadView.qml b/app/qml/view/PostThreadView.qml
index 3153db6d..bd3f4080 100644
--- a/app/qml/view/PostThreadView.qml
+++ b/app/qml/view/PostThreadView.qml
@@ -102,6 +102,8 @@ ColumnLayout {
delegate: PostDelegate {
Layout.preferredWidth: rootListView.width
+ property bool isBasisPost: (postThreadView.postUri === model.uri)
+
//自分から自分へは移動しない
onClicked: (mouse) => {
if(postThreadView.postUri !== model.uri){
@@ -111,6 +113,7 @@ ColumnLayout {
onRequestViewProfile: (did) => postThreadView.requestViewProfile(did)
onRequestViewSearchPosts: (text) => postThreadView.requestViewSearchPosts(text)
onRequestAddMutedWord: (text) => postThreadView.requestAddMutedWord(text)
+ onRequestCopyTagToClipboard: (text) => systemTool.copyToClipboard(text)
moderationFrame.visible: model.muted
userFilterMatched: model.userFilterMatched
@@ -128,7 +131,8 @@ ColumnLayout {
postAvatarImage.onClicked: requestViewProfile(model.did)
postAuthor.displayName: model.displayName
postAuthor.handle: model.handle
- postAuthor.indexedAt: (postThreadView.postUri === model.uri) ? "" : model.indexedAt
+ postAuthor.indexedAt: isBasisPost ? "" : model.indexedAt
+ authorLabels.model: isBasisPost ? model.authorLabels : []
recordText.text: {
var text = model.recordText
if(model.recordTextTranslation.length > 0){
@@ -194,7 +198,7 @@ ColumnLayout {
listLinkCardFrame.creatorHandleLabel.text: model.listLinkCreatorHandle
listLinkCardFrame.descriptionLabel.text: model.listLinkDescription
- postInformation.visible: (postThreadView.postUri === model.uri)
+ postInformation.visible: isBasisPost
postInformation.tagsLayout.model: postInformation.visible ? model.tags : []
postInformation.labelsLayout.model: postInformation.visible ? model.labels : []
postInformation.languagesLayout.model: postInformation.visible ? model.languages : []
diff --git a/app/qml/view/ProfileView.qml b/app/qml/view/ProfileView.qml
index 32af03e8..53515914 100644
--- a/app/qml/view/ProfileView.qml
+++ b/app/qml/view/ProfileView.qml
@@ -362,6 +362,15 @@ ColumnLayout {
tagBorderWidth: 1
model: userProfile.belongingLists
}
+ TagLabelLayout {
+ Layout.preferredWidth: profileView.width - 10
+ Layout.maximumWidth: profileView.width
+ Layout.topMargin: 5
+ Layout.leftMargin: 5
+ Layout.rightMargin: 5
+ visible: count > 0
+ model: userProfile.labels
+ }
Label {
id: descriptionLabel
Layout.topMargin: 5
@@ -508,16 +517,6 @@ ColumnLayout {
}
]
}
- IconLabelFrame {
- id: moderationFrame3
- Layout.topMargin: 2
- Layout.preferredWidth: profileView.width
- visible: userProfile.userFilterMatched
- backgroundColor: Material.color(Material.Red)
- borderWidth: 0
- iconSource: "../images/labeling.png"
- labelText: qsTr("This account has been flagged : ") + userProfile.userFilterTitle
- }
IconLabelFrame {
id: moderationFrame2
Layout.topMargin: 2
diff --git a/app/qml/view/TimelineView.qml b/app/qml/view/TimelineView.qml
index a31bbcaa..f7b21c41 100644
--- a/app/qml/view/TimelineView.qml
+++ b/app/qml/view/TimelineView.qml
@@ -108,6 +108,7 @@ ScrollView {
onRequestViewProfile: (did) => timelineView.requestViewProfile(did)
onRequestViewSearchPosts: (text) => timelineView.requestViewSearchPosts(text)
onRequestAddMutedWord: (text) => timelineView.requestAddMutedWord(text)
+ onRequestCopyTagToClipboard: (text) => systemTool.copyToClipboard(text)
moderationFrame.visible: model.muted
userFilterMatched: model.userFilterMatched
diff --git a/app/qtquick/atpabstractlistmodel.cpp b/app/qtquick/atpabstractlistmodel.cpp
index dd46f95c..7471f862 100644
--- a/app/qtquick/atpabstractlistmodel.cpp
+++ b/app/qtquick/atpabstractlistmodel.cpp
@@ -400,11 +400,14 @@ bool AtpAbstractListModel::getQuoteFilterMatched(
return false;
}
-QStringList AtpAbstractListModel::getLabels(
- const QList &labels) const
+QStringList
+AtpAbstractListModel::getLabels(const QList &labels,
+ bool exclude_no_unauth) const
{
QStringList ret;
for (const auto &label : labels) {
+ if (exclude_no_unauth && label.val == "!no-unauthenticated")
+ continue;
ret.append(label.val);
}
return ret;
diff --git a/app/qtquick/atpabstractlistmodel.h b/app/qtquick/atpabstractlistmodel.h
index 2a499ff6..865d496a 100644
--- a/app/qtquick/atpabstractlistmodel.h
+++ b/app/qtquick/atpabstractlistmodel.h
@@ -202,7 +202,8 @@ public slots:
QString getContentFilterMessage(const QList &labels,
const bool for_media) const;
bool getQuoteFilterMatched(const AtProtocolType::AppBskyFeedDefs::PostView &post) const;
- QStringList getLabels(const QList &labels) const;
+ QStringList getLabels(const QList &labels,
+ bool exclude_no_unauth = false) const;
QStringList getLaunguages(const QVariant &record) const;
QString getVia(const QVariant &record) const;
diff --git a/app/qtquick/profile/userprofile.cpp b/app/qtquick/profile/userprofile.cpp
index 78d707cf..123ac5fe 100644
--- a/app/qtquick/profile/userprofile.cpp
+++ b/app/qtquick/profile/userprofile.cpp
@@ -84,29 +84,16 @@ void UserProfile::getProfile(const QString &did)
setBlocking(detail.viewer.blocking.contains(m_account.did));
setBlockingUri(detail.viewer.blocking);
if (detail.labels.isEmpty()) {
- setUserFilterMatched(false);
- setUserFilterTitle(QString());
+ setLabels(QStringList());
} else {
- QString title;
- QString val;
- for (const auto &label : detail.labels) {
+ QStringList labels;
+ for (const auto &label : qAsConst(detail.labels)) {
if (label.val == QStringLiteral("!no-unauthenticated"))
continue;
- val = label.val;
- title = labelsTitle(label.val, false);
+ labels.append(label.val);
break;
}
- if (val.isEmpty()) {
- setUserFilterMatched(false);
- setUserFilterTitle(QString());
- } else {
- setUserFilterMatched(true);
- if (title.isEmpty()) {
- setUserFilterTitle(val);
- } else {
- setUserFilterTitle(title);
- }
- }
+ setLabels(labels);
}
setBelongingLists(
ListItemsCache::getInstance()->getListNames(m_account.did, detail.did));
@@ -373,32 +360,6 @@ void UserProfile::setBlockingUri(const QString &newBlockingUri)
emit blockingUriChanged();
}
-bool UserProfile::userFilterMatched() const
-{
- return m_userFilterMatched;
-}
-
-void UserProfile::setUserFilterMatched(bool newUserFilterMatched)
-{
- if (m_userFilterMatched == newUserFilterMatched)
- return;
- m_userFilterMatched = newUserFilterMatched;
- emit userFilterMatchedChanged();
-}
-
-QString UserProfile::userFilterTitle() const
-{
- return m_userFilterTitle;
-}
-
-void UserProfile::setUserFilterTitle(const QString &newUserFilterTitle)
-{
- if (m_userFilterTitle == newUserFilterTitle)
- return;
- m_userFilterTitle = newUserFilterTitle;
- emit userFilterTitleChanged();
-}
-
QString UserProfile::formattedDescription() const
{
return m_formattedDescription;
@@ -540,6 +501,19 @@ QStringList UserProfile::labelerDids() const
return LabelerProvider::getInstance()->labelerDids(m_account);
}
+QStringList UserProfile::labels() const
+{
+ return m_labels;
+}
+
+void UserProfile::setLabels(const QStringList &newLabels)
+{
+ if (m_labels == newLabels)
+ return;
+ m_labels = newLabels;
+ emit labelsChanged();
+}
+
QStringList UserProfile::belongingLists() const
{
return m_belongingLists;
diff --git a/app/qtquick/profile/userprofile.h b/app/qtquick/profile/userprofile.h
index b2af02b8..4091364d 100644
--- a/app/qtquick/profile/userprofile.h
+++ b/app/qtquick/profile/userprofile.h
@@ -47,11 +47,7 @@ class UserProfile : public QObject
Q_PROPERTY(bool blocking READ blocking WRITE setBlocking NOTIFY blockingChanged)
Q_PROPERTY(QString blockingUri READ blockingUri WRITE setBlockingUri NOTIFY blockingUriChanged)
- Q_PROPERTY(bool userFilterMatched READ userFilterMatched WRITE setUserFilterMatched NOTIFY
- userFilterMatchedChanged)
- Q_PROPERTY(QString userFilterTitle READ userFilterTitle WRITE setUserFilterTitle NOTIFY
- userFilterTitleChanged)
-
+ Q_PROPERTY(QStringList labels READ labels WRITE setLabels NOTIFY labelsChanged FINAL)
Q_PROPERTY(QStringList belongingLists READ belongingLists WRITE setBelongingLists NOTIFY
belongingListsChanged)
Q_PROPERTY(QString pinnedPost READ pinnedPost WRITE setPinnedPost NOTIFY pinnedPostChanged)
@@ -107,10 +103,8 @@ class UserProfile : public QObject
void setBlocking(bool newBlocking);
QString blockingUri() const;
void setBlockingUri(const QString &newBlockingUri);
- bool userFilterMatched() const;
- void setUserFilterMatched(bool newUserFilterMatched);
- QString userFilterTitle() const;
- void setUserFilterTitle(const QString &newUserFilterTitle);
+ QStringList labels() const;
+ void setLabels(const QStringList &newLabels);
QStringList belongingLists() const;
void setBelongingLists(const QStringList &newBelongingLists);
@@ -149,8 +143,9 @@ class UserProfile : public QObject
void blockingUriChanged();
void userFilterMatchedChanged();
void userFilterTitleChanged();
- void belongingListsChanged();
+ void labelsChanged();
+ void belongingListsChanged();
void formattedDescriptionChanged();
void pinnedPostChanged();
void serviceEndpointChanged();
@@ -206,6 +201,7 @@ public slots:
QString m_registrationDate;
QStringList m_handleHistory;
bool m_associatedChatAllow;
+ QStringList m_labels;
};
#endif // USERPROFILE_H
diff --git a/app/qtquick/timeline/timelinelistmodel.cpp b/app/qtquick/timeline/timelinelistmodel.cpp
index f29d4b13..f4e307b9 100644
--- a/app/qtquick/timeline/timelinelistmodel.cpp
+++ b/app/qtquick/timeline/timelinelistmodel.cpp
@@ -150,6 +150,8 @@ QVariant TimelineListModel::item(int row, TimelineListModelRoles role) const
return current.post.author.handle;
else if (role == AvatarRole)
return current.post.author.avatar;
+ else if (role == AuthorLabelsRole)
+ return getLabels(current.post.author.labels, true);
else if (role == MutedRole)
return current.post.author.viewer.muted;
else if (role == RecordTextRole)
@@ -756,6 +758,7 @@ QHash TimelineListModel::roleNames() const
roles[DisplayNameRole] = "displayName";
roles[HandleRole] = "handle";
roles[AvatarRole] = "avatar";
+ roles[AuthorLabelsRole] = "authorLabels";
roles[MutedRole] = "muted";
roles[RecordTextRole] = "recordText";
roles[RecordTextPlainRole] = "recordTextPlain";
diff --git a/app/qtquick/timeline/timelinelistmodel.h b/app/qtquick/timeline/timelinelistmodel.h
index 78186bc6..21fa01f2 100644
--- a/app/qtquick/timeline/timelinelistmodel.h
+++ b/app/qtquick/timeline/timelinelistmodel.h
@@ -45,6 +45,7 @@ class TimelineListModel : public AtpAbstractListModel
DisplayNameRole,
HandleRole,
AvatarRole,
+ AuthorLabelsRole,
MutedRole,
RecordTextRole,
RecordTextPlainRole,
diff --git a/app/tools/translatorchanger.cpp b/app/tools/translatorchanger.cpp
index 10d687ea..80ab5777 100644
--- a/app/tools/translatorchanger.cpp
+++ b/app/tools/translatorchanger.cpp
@@ -27,13 +27,20 @@ void TranslatorChanger::initialize(QCoreApplication *app, QQmlApplicationEngine
t->deleteLater();
}
t = new QTranslator(this);
+ if (t->load(QString("lib_%1").arg(lang), dir)) {
+ m_translatorLib[lang] = t;
+ } else {
+ t->deleteLater();
+ }
+ t = new QTranslator(this);
if (t->load(QString("qt_%1").arg(lang), dir)) {
m_translatorSys[lang] = t;
} else {
t->deleteLater();
}
}
- qDebug() << "Loaded languages" << m_translatorApp.keys();
+ qDebug() << "Loaded languages" << m_translatorApp.keys() << m_translatorLib.keys()
+ << m_translatorSys.keys();
}
void TranslatorChanger::connect()
@@ -81,6 +88,9 @@ void TranslatorChanger::change(const QString &lang)
if (m_translatorApp.contains(m_currentLang)) {
m_app->removeTranslator(m_translatorApp[m_currentLang]);
}
+ if (m_translatorLib.contains(m_currentLang)) {
+ m_app->removeTranslator(m_translatorLib[m_currentLang]);
+ }
if (m_translatorSys.contains(m_currentLang)) {
m_app->removeTranslator(m_translatorSys[m_currentLang]);
}
@@ -90,6 +100,9 @@ void TranslatorChanger::change(const QString &lang)
if (m_translatorApp.contains(lang)) {
m_app->installTranslator(m_translatorApp[lang]);
}
+ if (m_translatorLib.contains(lang)) {
+ m_app->installTranslator(m_translatorLib[lang]);
+ }
if (m_translatorSys.contains(lang)) {
m_app->installTranslator(m_translatorSys[lang]);
}
diff --git a/app/tools/translatorchanger.h b/app/tools/translatorchanger.h
index 982bf7cc..8a8be886 100644
--- a/app/tools/translatorchanger.h
+++ b/app/tools/translatorchanger.h
@@ -25,6 +25,7 @@ public slots:
QCoreApplication *m_app;
QQmlApplicationEngine *m_engine;
QHash m_translatorApp;
+ QHash m_translatorLib;
QHash m_translatorSys;
QString m_currentLang;
};
diff --git a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp
index b628ca2f..86ce548e 100644
--- a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp
+++ b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.cpp
@@ -15,7 +15,8 @@
namespace AtProtocolInterface {
-ComAtprotoSyncSubscribeReposEx::ComAtprotoSyncSubscribeReposEx(QObject *parent) : QObject { parent }
+ComAtprotoSyncSubscribeReposEx::ComAtprotoSyncSubscribeReposEx(QObject *parent)
+ : QObject { parent }, m_subscribeMode { SubScribeMode::Firehose }
{
qDebug().noquote() << "ComAtprotoSyncSubscribeReposEx";
@@ -31,6 +32,8 @@ ComAtprotoSyncSubscribeReposEx::ComAtprotoSyncSubscribeReposEx(QObject *parent)
&ComAtprotoSyncSubscribeReposEx::onConnected);
connect(&m_webSocket, &QWebSocket::binaryMessageReceived, this,
&ComAtprotoSyncSubscribeReposEx::onBinaryMessageReceived);
+ connect(&m_webSocket, &QWebSocket::textMessageReceived, this,
+ &ComAtprotoSyncSubscribeReposEx::onTextMessageReceived);
connect(&m_webSocket, &QWebSocket::disconnected, this,
&ComAtprotoSyncSubscribeReposEx::onDisconnected);
@@ -56,9 +59,10 @@ ComAtprotoSyncSubscribeReposEx::ComAtprotoSyncSubscribeReposEx(QObject *parent)
});
}
-void ComAtprotoSyncSubscribeReposEx::open(const QUrl &url)
+void ComAtprotoSyncSubscribeReposEx::open(const QUrl &url, SubScribeMode mode)
{
if (m_webSocket.state() == QAbstractSocket::UnconnectedState) {
+ m_subscribeMode = mode;
m_webSocket.open(url);
}
}
@@ -88,6 +92,20 @@ void ComAtprotoSyncSubscribeReposEx::onDisconnected()
void ComAtprotoSyncSubscribeReposEx::onBinaryMessageReceived(const QByteArray &message)
{
// qDebug().noquote() << "onBinaryMessageReceived" << message.length();
+ if (m_subscribeMode == SubScribeMode::Firehose) {
+ messageReceivedFromFirehose(message);
+ }
+}
+
+void ComAtprotoSyncSubscribeReposEx::onTextMessageReceived(const QString &message)
+{
+ if (m_subscribeMode == SubScribeMode::JetStream) {
+ messageReceivedFromJetStream(message.toUtf8());
+ }
+}
+
+void ComAtprotoSyncSubscribeReposEx::messageReceivedFromFirehose(const QByteArray &message)
+{
CarDecoder decoder(true);
int offset = 0;
@@ -135,4 +153,76 @@ void ComAtprotoSyncSubscribeReposEx::onBinaryMessageReceived(const QByteArray &m
m_webSocket.close();
}
}
+
+void ComAtprotoSyncSubscribeReposEx::messageReceivedFromJetStream(const QByteArray &message)
+{
+
+ QString payload_type;
+
+ QJsonDocument doc = QJsonDocument::fromJson(message);
+ QJsonObject json_src = doc.object();
+ QHash commit_type_to;
+ commit_type_to["c"] = "create";
+ commit_type_to["u"] = "update";
+ commit_type_to["d"] = "delete";
+
+ if (doc.isNull() || json_src.isEmpty()) {
+ qDebug().noquote() << "Invalid data";
+ qDebug().noquote() << "message:" << message;
+ emit errorOccured("InvalidData", "Unreadable JSON data.");
+ m_webSocket.close();
+ } else if (!json_src.contains("type") || !json_src.contains("did")
+ || !json_src.contains("commit")) {
+ qDebug().noquote() << "Unsupport data:" << message;
+ // emit errorOccured("InvalidData", "Unanticipated data structure.");
+ // m_webSocket.close();
+ } else {
+ payload_type = "#commit";
+
+ QJsonObject json_src_commit = json_src.value("commit").toObject();
+ QJsonObject json_dest;
+
+ json_dest.insert("repo", json_src.value("did").toString());
+ json_dest.insert("rev", json_src_commit.value("rev").toString());
+ json_dest.insert("time",
+ QDateTime::fromMSecsSinceEpoch(
+ static_cast(json_src.value("time_us").toDouble() / 1000))
+ .toString(Qt::ISODateWithMs));
+
+ QJsonObject json_dest_commit;
+ json_dest_commit.insert("$link", json_src_commit.value("cid").toString());
+ json_dest.insert("commit", json_dest_commit);
+
+ QJsonObject json_dest_op;
+ QString commit_type = commit_type_to.value(json_src_commit.value("type").toString());
+ json_dest_op.insert("action", commit_type);
+ json_dest_op.insert("path",
+ QString("%1/%2").arg(json_src_commit.value("collection").toString(),
+ json_src_commit.value("rkey").toString()));
+ if (commit_type == "delete") {
+ json_dest_op.insert("cid", QJsonValue());
+ } else {
+ json_dest_op.insert("cid", json_dest_commit);
+ }
+ QJsonArray json_dest_ops;
+ json_dest_ops.append(json_dest_op);
+ json_dest.insert("ops", json_dest_ops);
+
+ QJsonArray json_dest_blocks;
+ if (json_src_commit.contains("record")) {
+ QJsonObject json_dest_block;
+ json_dest_block.insert("cid", json_src_commit.value("cid").toString());
+ json_dest_block.insert("uri",
+ QString("at://%1/%2/%3")
+ .arg(json_src.value("did").toString(),
+ json_src_commit.value("collection").toString(),
+ json_src_commit.value("rkey").toString()));
+ json_dest_block.insert("value", json_src_commit.value("record").toObject());
+ json_dest_blocks.append(json_dest_block);
+ }
+ json_dest.insert("blocks", json_dest_blocks);
+
+ emit received(payload_type, json_dest);
+ }
+}
}
diff --git a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.h b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.h
index 20b3109b..22c1c33f 100644
--- a/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.h
+++ b/lib/extension/com/atproto/sync/comatprotosyncsubscribereposex.h
@@ -12,9 +12,14 @@ class ComAtprotoSyncSubscribeReposEx : public QObject
{
Q_OBJECT
public:
+ enum SubScribeMode : int {
+ Firehose,
+ JetStream,
+ };
+
explicit ComAtprotoSyncSubscribeReposEx(QObject *parent = nullptr);
- void open(const QUrl &url);
+ void open(const QUrl &url, SubScribeMode mode = SubScribeMode::Firehose);
void close();
QAbstractSocket::SocketState state() const;
@@ -28,10 +33,15 @@ public slots:
void onConnected();
void onDisconnected();
void onBinaryMessageReceived(const QByteArray &message);
+ void onTextMessageReceived(const QString &message);
private:
+ void messageReceivedFromFirehose(const QByteArray &message);
+ void messageReceivedFromJetStream(const QByteArray &message);
+
QWebSocket m_webSocket;
QStringList m_payloadTypeList;
+ SubScribeMode m_subscribeMode;
};
}
diff --git a/lib/i18n/lib_ja.qm b/lib/i18n/lib_ja.qm
new file mode 100644
index 00000000..830df881
Binary files /dev/null and b/lib/i18n/lib_ja.qm differ
diff --git a/lib/i18n/lib_ja.ts b/lib/i18n/lib_ja.ts
new file mode 100644
index 00000000..b4c5dc46
--- /dev/null
+++ b/lib/i18n/lib_ja.ts
@@ -0,0 +1,286 @@
+
+
+
+
+ ConfigurableLabels
+
+
+
+ Content hidden
+ 閲覧不可
+
+
+
+
+ Moderator overrides for special cases.
+ 特別な場合のためのモデレーターによるオーバーライド。
+
+
+
+
+ Content warning
+ 閲覧警告
+
+
+
+
+ Legal
+ 法的
+
+
+
+ Content removed for legal reasons.
+ 法的な理由で削除されたコンテンツ。
+
+
+ Explicit Sexual Images
+ 露骨な性的な画像
+
+
+ i.e. pornography
+ 例えばポルノ
+
+
+
+ Sexually Explicit
+ 露骨な性的表現
+
+
+ Other Nudity
+ その他ヌード
+
+
+ Including non-sexual and artistic
+ ノンセクシャルや芸術的なものも含む
+
+
+ Nudity
+ ヌード
+
+
+
+ Basic Moderation
+ 基本モデレーション
+
+
+
+ Basic configuration independent of moderation services.
+ モデレーションサービスに依存しない基本設定。
+
+
+
+ Pornography
+ ポルノ
+
+
+
+ Explicit sexual images.
+ 露骨な性的な画像。
+
+
+
+
+ Sexually Suggestive
+ 性的な示唆
+
+
+
+ Does not include nudity
+ ヌードは含まない
+
+
+
+
+ Graphic Media
+ グラフィックメディア
+
+
+
+ Explicit or potentially disturbing media.
+ 露骨な、あるいは不穏なメディア。
+
+
+
+
+ Non-sexual Nudity
+ 性的でないヌード
+
+
+
+ E.g. artistic nudes.
+ 例:芸術的なヌード
+
+
+
+ Violent / Bloody
+ 暴力 / 流血
+
+
+
+ Gore, self-harm, torture
+ 血糊、自傷行為、拷問
+
+
+
+ Violence
+ 暴力
+
+
+
+ Hate Group Iconography
+ ヘイトグループの象徴
+
+
+
+ Images of terror groups, articles covering events, etc.
+ テロ集団の画像、事件を取り上げた記事など
+
+
+
+ Hate Groups
+ ヘイトグループ
+
+
+
+
+ Spam
+ スパム
+
+
+
+ Excessive unwanted interactions
+ 過剰な不要な干渉
+
+
+
+ Impersonation / Scam
+ なりすまし / 詐欺
+
+
+
+ Impersonation
+ なりすまし
+
+
+
+ Accounts falsely claiming to be people or orgs
+ 個人や団体を偽ったアカウント
+
+
+
+ LogAccess
+
+
+
+
+ Post
+ ポスト
+
+
+
+ Total number of posts
+ 総ポスト数
+
+
+
+ Number of days posts
+ ポストした日数
+
+
+
+ Average number of daily posts
+ 1日あたりの平均ポスト数
+
+
+
+
+
+ Like
+ いいね
+
+
+
+ Total number of likes
+ 総いいね数
+
+
+
+ Number of days likes
+ いいねした日数
+
+
+
+ Average number of daily likes
+ 1日あたりの平均いいね数
+
+
+
+
+
+ Repost
+ リポスト
+
+
+
+ Total number of reposts
+ 総リポスト数
+
+
+
+ Number of days reposts
+ リポストした日数
+
+
+
+ Average number of daily reposts
+ 1日あたりの平均リポスト数
+
+
+
+
+ List
+ リスト
+
+
+
+ Total number of lists
+ リストの数
+
+
+
+ Total number of list items
+ リストに登録している総アカウント数
+
+
+
+
+
+ Other
+ その他
+
+
+
+ Total number of follows
+ フォロー数
+
+
+
+ Total number of who can reply
+ リプライ制限をした数
+
+
+
+ Total number of blocks
+ 総ブロック数
+
+
+
+ Number of registrations for list
+ リストに登録しているアカウント数
+
+
+ Number of registrations for '%1'
+ '%1'に登録しているアカウント数
+
+
+
diff --git a/lib/lib.pro b/lib/lib.pro
index 9480d9d8..5b6763c6 100644
--- a/lib/lib.pro
+++ b/lib/lib.pro
@@ -13,6 +13,8 @@ unix:INCLUDEPATH += ../openssl/include
DEFINES += CPPHTTPLIB_ZLIB_SUPPORT # zlib support for cpp-httplib
+TRANSLATIONS += i18n/lib_ja.ts
+
SOURCES += \
$$PWD/atprotocol/accessatprotocol.cpp \
$$PWD/atprotocol/app/bsky/actor/appbskyactorgetpreferences.cpp \
diff --git a/lib/realtime/firehosereceiver.cpp b/lib/realtime/firehosereceiver.cpp
index 158791f7..c474b238 100644
--- a/lib/realtime/firehosereceiver.cpp
+++ b/lib/realtime/firehosereceiver.cpp
@@ -5,6 +5,8 @@
// #include
#include
+#define USE_JETSTREAM
+
using AtProtocolInterface::ComAtprotoSyncSubscribeReposEx;
namespace RealtimeFeed {
@@ -18,7 +20,11 @@ FirehoseReceiver::FirehoseReceiver(QObject *parent)
,
m_status(FirehoseReceiverStatus::Disconnected)
{
+#ifdef USE_JETSTREAM
+ m_serviceEndpoint = "wss://jetstream2.us-west.bsky.network";
+#else
m_serviceEndpoint = "wss://bsky.network";
+#endif
m_wdgTimer.setInterval(60 * 1000);
connect(&m_client, &ComAtprotoSyncSubscribeReposEx::errorOccured,
@@ -97,9 +103,19 @@ void FirehoseReceiver::start()
if (path.endsWith("/")) {
path.resize(path.length() - 1);
}
+#ifdef USE_JETSTREAM
+ ComAtprotoSyncSubscribeReposEx::SubScribeMode mode =
+ ComAtprotoSyncSubscribeReposEx::SubScribeMode::JetStream;
+ QUrl url(path
+ + "/subscribe?wantedCollections=app.bsky.feed.post&wantedCollections=app.bsky.feed."
+ "repost&wantedCollections=app.bsky.graph.follow");
+#else
+ ComAtprotoSyncSubscribeReposEx::SubScribeMode mode =
+ ComAtprotoSyncSubscribeReposEx::SubScribeMode::Firehose;
QUrl url(path + "/xrpc/com.atproto.sync.subscribeRepos");
+#endif
qDebug().noquote() << "Connect to" << url.toString();
- m_client.open(url);
+ m_client.open(url, mode);
m_wdgTimer.start();
}
diff --git a/lib/tools/opengraphprotocol.cpp b/lib/tools/opengraphprotocol.cpp
index e96e5aa5..ca97f36d 100644
--- a/lib/tools/opengraphprotocol.cpp
+++ b/lib/tools/opengraphprotocol.cpp
@@ -28,6 +28,16 @@ void OpenGraphProtocol::getData(const QString &url)
m_thumb.clear();
QNetworkRequest request((QUrl(url)));
+ request.setRawHeader(QByteArray("accept"), QByteArray("*/*"));
+ request.setRawHeader(QByteArray("sec-ch-ua-platform"), QByteArray("Windows"));
+ request.setRawHeader(QByteArray("sec-fetch-dest"), QByteArray("document"));
+ request.setRawHeader(QByteArray("sec-fetch-mode"), QByteArray("navigate"));
+ request.setRawHeader(QByteArray("sec-fetch-site"), QByteArray("none"));
+ request.setRawHeader(QByteArray("sec-fetch-user"), QByteArray("?1"));
+ request.setHeader(QNetworkRequest::UserAgentHeader,
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, "
+ "like Gecko) Chrome/129.0.0.0 Safari/537.36");
+ request.setTransferTimeout(60 * 1000);
QNetworkAccessManager *manager = new QNetworkAccessManager(this);
connect(manager, &QNetworkAccessManager::finished, [=](QNetworkReply *reply) {
@@ -54,6 +64,9 @@ void OpenGraphProtocol::getData(const QString &url)
emit reply->redirectAllowed();
}
});
+ connect(reply, &QNetworkReply::errorOccurred, [=](QNetworkReply::NetworkError code) {
+ qDebug() << "Reply error:" << code << reply->request().url();
+ });
}
void OpenGraphProtocol::downloadThumb(const QString &path)
@@ -172,7 +185,8 @@ bool OpenGraphProtocol::parse(const QByteArray &data, const QString &src_uri)
QDomDocument doc;
rebuildHtml(ts.readAll(), doc);
#else
- QTextCodec *codec = QTextCodec::codecForHtml(data, QTextCodec::codecForName("utf-8"));
+ QString charset = extractCharset(QString::fromUtf8(data));
+ QTextCodec *codec = QTextCodec::codecForHtml(data, QTextCodec::codecForName(charset.toUtf8()));
QDomDocument doc;
rebuildHtml(codec->toUnicode(data), doc);
#endif
diff --git a/scripts/build.sh b/scripts/build.sh
index e1d7e155..11787ce9 100755
--- a/scripts/build.sh
+++ b/scripts/build.sh
@@ -85,6 +85,7 @@ deploy_hagoromo(){
cp "zlib/lib/libz.so.1.3.1" ${work_dir}/lib
cp "zlib/lib/libz.so.1" ${work_dir}/lib
cp "app/i18n/app_ja.qm" ${work_dir}/bin/translations
+ cp "lib/i18n/lib_ja.qm" ${work_dir}/bin/translations
cp ${QT_BIN_FOLDER}/../translations/qt_ja.qm ${work_dir}/bin/translations
cat ${SCRIPT_FOLDER}/deploy/linux_lib.txt | xargs -i{} cp -P ${QT_BIN_FOLDER}/../lib/{} ${work_dir}/lib
@@ -99,6 +100,7 @@ deploy_hagoromo(){
mkdir -p ${work_dir}/Hagoromo.app/Contents/MacOS/translations
cp "app/i18n/app_ja.qm" ${work_dir}/Hagoromo.app/Contents/MacOS/translations
+ cp "lib/i18n/lib_ja.qm" ${work_dir}/Hagoromo.app/Contents/MacOS/translations
cp ${QT_BIN_FOLDER}/../translations/qt_ja.qm ${work_dir}/Hagoromo.app/Contents/MacOS/translations
cp -RL "zlib/lib/libz.1.dylib" ${work_dir}/Hagoromo.app/Contents/Frameworks
fi
diff --git a/tests/atprotocol_test/atprotocol_test.qrc b/tests/atprotocol_test/atprotocol_test.qrc
index c6e8272a..ea69cbb8 100644
--- a/tests/atprotocol_test/atprotocol_test.qrc
+++ b/tests/atprotocol_test/atprotocol_test.qrc
@@ -74,5 +74,6 @@
response/labels/provider/2/xrpc/app.bsky.labeler.getServices
data/postgate/1/xrpc/com.atproto.repo.createRecord
data/postgate/2/xrpc/com.atproto.repo.createRecord
+ response/ogp/file7.html
diff --git a/tests/atprotocol_test/response/ogp/file7.html b/tests/atprotocol_test/response/ogp/file7.html
new file mode 100644
index 00000000..80363aa1
--- /dev/null
+++ b/tests/atprotocol_test/response/ogp/file7.html
@@ -0,0 +1,19 @@
+
+
+
+
+
+
+
+
+^Cg^O
+
+
+
+
+
+
+
+
+file7
+
diff --git a/tests/atprotocol_test/tst_atprotocol_test.cpp b/tests/atprotocol_test/tst_atprotocol_test.cpp
index 154e400f..ea60273d 100644
--- a/tests/atprotocol_test/tst_atprotocol_test.cpp
+++ b/tests/atprotocol_test/tst_atprotocol_test.cpp
@@ -441,6 +441,28 @@ void atprotocol_test::test_OpenGraphProtocol()
.arg(QString::number(m_listenPort)),
ogp.thumb().toLocal8Bit());
}
+
+ {
+ QSignalSpy spy(&ogp, SIGNAL(finished(bool)));
+ ogp.getData(m_service + "/ogp/file7.html");
+ spy.wait();
+ QVERIFY2(spy.count() == 1, QString("spy.count()=%1").arg(spy.count()).toUtf8());
+
+ QList arguments = spy.takeFirst();
+ QVERIFY(arguments.at(0).toBool());
+
+ QVERIFY2(ogp.uri()
+ == QString("http://localhost:%1/response/ogp/file7.html")
+ .arg(QString::number(m_listenPort)),
+ ogp.uri().toLocal8Bit());
+ QVERIFY2(ogp.title() == QString("file7 TITLE"), ogp.title().toLocal8Bit());
+ QVERIFY2(ogp.description() == QString("file7 ").append(QChar(0x8a73)).append(QChar(0x7d30)),
+ ogp.description().toLocal8Bit());
+ QVERIFY2(ogp.thumb()
+ == QString("http://localhost:%1/response/ogp/images/file7.png")
+ .arg(QString::number(m_listenPort)),
+ ogp.thumb().toLocal8Bit());
+ }
}
void atprotocol_test::test_ogpDecodeHtml()
diff --git a/tools/firehosereader/main.cpp b/tools/firehosereader/main.cpp
index 3a5f6e7d..f6ca7b5f 100644
--- a/tools/firehosereader/main.cpp
+++ b/tools/firehosereader/main.cpp
@@ -4,6 +4,7 @@
#include
#include
#include
+#include
#include "comatprotosyncsubscribereposex.h"
@@ -18,11 +19,22 @@ int main(int argc, char *argv[])
qWarning() << " usage: firehosereader URL STOPER_USER_DID";
return 1;
}
+ ComAtprotoSyncSubscribeReposEx::SubScribeMode mode =
+ ComAtprotoSyncSubscribeReposEx::SubScribeMode::Firehose;
QUrl url(a.arguments().at(1));
QString stopper_did = a.arguments().at(2);
qDebug().noquote() << "url" << url.toString();
+ qDebug().noquote() << " " << url.scheme();
+ qDebug().noquote() << " " << url.host();
+ qDebug().noquote() << " " << url.path();
+ qDebug().noquote() << " " << url.query();
qDebug().noquote() << "Stopper" << stopper_did;
// wss://bsky.network/xrpc/com.atproto.sync.subscribeRepos
+ // wss://jetstream2.us-west.bsky.network/subscribe?wantedCollections=app.bsky.feed.post&wantedCollections=app.bsky.feed.repost&wantedCollections=app.bsky.graph.follow
+
+ if (url.host().startsWith("jetstream")) {
+ mode = ComAtprotoSyncSubscribeReposEx::SubScribeMode::JetStream;
+ }
QElapsedTimer timer;
timer.start();
@@ -105,7 +117,10 @@ int main(int argc, char *argv[])
QCoreApplication::quit();
}
});
- client.open(url);
+ client.open(url, mode);
+
+ QNetworkRequest req(url);
+ qDebug().noquote() << req.url().toString() << req.url().query();
return a.exec();
}
diff --git a/web/content/docs/release-note.en.md b/web/content/docs/release-note.en.md
index 1d7248d0..fcf8d593 100644
--- a/web/content/docs/release-note.en.md
+++ b/web/content/docs/release-note.en.md
@@ -8,6 +8,18 @@ description: This is a multi-column Bluesky client.
## 2024
+### v0.39.0 - 2024/10/12
+
+- Add
+ - Add labels set for the account to the post thread
+ - Added a function to copy hashtags
+- Update
+ - Change the display format of the profile label
+ - Reduces the amount of data transmitted in real-time feeds
+- Fix
+ - Fix the layout when there is no thumbnail image on the link card
+ - Fix a case where the information on the link card could not be obtained
+
### v0.38.0 - 2024/9/28
- Update
diff --git a/web/content/docs/release-note.ja.md b/web/content/docs/release-note.ja.md
index f1448318..378f76a9 100644
--- a/web/content/docs/release-note.ja.md
+++ b/web/content/docs/release-note.ja.md
@@ -8,6 +8,18 @@ description: マルチカラム対応Blueskyクライアント
## 2024
+### v0.39.0 - 2024/10/12
+
+- 追加
+ - ポストスレッドにアカウントに設定されたラベルを追加
+ - ハッシュタグをコピーする機能を追加
+- 更新
+ - プロフィールのラベルの表示方法を変更
+ - リアルタイムフィードの通信量を軽減
+- 修正
+ - リンクカードにサムネ画像がないときのレイアウトを修正
+ - リンクカードの情報を取得できないケースを修正
+
### v0.38.0 - 2024/9/28
- 更新
diff --git a/web/layouts/shortcodes/download_link.html b/web/layouts/shortcodes/download_link.html
index 490e5959..61421fac 100644
--- a/web/layouts/shortcodes/download_link.html
+++ b/web/layouts/shortcodes/download_link.html
@@ -1,9 +1,9 @@
-- Version 0.38.0 (2024/09/28)
+
- Version 0.39.0 (2024/10/12)
- Other versions