feat(bskyweb): fetch displayName, add headerField USERNAME, Member Name secondary
This commit is contained in:
@@ -112,12 +112,12 @@ func (srv *Server) WebInvitePassPkpass(c echo.Context) error {
|
|||||||
return c.JSON(http.StatusServiceUnavailable, echo.Map{"error": "SignerDisabled"})
|
return c.JSON(http.StatusServiceUnavailable, echo.Map{"error": "SignerDisabled"})
|
||||||
}
|
}
|
||||||
|
|
||||||
handle, avatarBytes, err := srv.fetchProfile(c.Request().Context(), did)
|
handle, displayName, avatarBytes, err := srv.fetchProfile(c.Request().Context(), did)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadGateway, echo.Map{"error": "ProfileFetchFailed"})
|
return c.JSON(http.StatusBadGateway, echo.Map{"error": "ProfileFetchFailed"})
|
||||||
}
|
}
|
||||||
|
|
||||||
passJSON, err := BuildPassJSON(did, handle, theme, srv.cfg.InvitePass.TeamID)
|
passJSON, err := BuildPassJSON(did, handle, displayName, theme, srv.cfg.InvitePass.TeamID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "PassBuildFailed"})
|
return c.JSON(http.StatusInternalServerError, echo.Map{"error": "PassBuildFailed"})
|
||||||
}
|
}
|
||||||
@@ -165,7 +165,7 @@ func (srv *Server) WebInviteWalletHero(c echo.Context) error {
|
|||||||
if did == "" {
|
if did == "" {
|
||||||
return c.NoContent(http.StatusBadRequest)
|
return c.NoContent(http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
handle, avatarBytes, err := srv.fetchProfile(c.Request().Context(), did)
|
handle, _, avatarBytes, err := srv.fetchProfile(c.Request().Context(), did)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.JSON(http.StatusBadGateway, echo.Map{"error": "ProfileFetchFailed"})
|
return c.JSON(http.StatusBadGateway, echo.Map{"error": "ProfileFetchFailed"})
|
||||||
}
|
}
|
||||||
@@ -261,16 +261,19 @@ func decodeImage(data []byte) (image.Image, error) {
|
|||||||
return img, err
|
return img, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (srv *Server) fetchProfile(ctx context.Context, did string) (handle string, avatarBytes []byte, err error) {
|
func (srv *Server) fetchProfile(ctx context.Context, did string) (handle, displayName string, avatarBytes []byte, err error) {
|
||||||
pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, did)
|
pv, err := appbsky.ActorGetProfile(ctx, srv.xrpcc, did)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", nil, err
|
return "", "", nil, err
|
||||||
}
|
}
|
||||||
handle = pv.Handle
|
handle = pv.Handle
|
||||||
|
if pv.DisplayName != nil {
|
||||||
|
displayName = *pv.DisplayName
|
||||||
|
}
|
||||||
if pv.Avatar != nil {
|
if pv.Avatar != nil {
|
||||||
// SSRF defense: only fetch https:// URLs
|
// SSRF defense: only fetch https:// URLs
|
||||||
if !strings.HasPrefix(*pv.Avatar, "https://") {
|
if !strings.HasPrefix(*pv.Avatar, "https://") {
|
||||||
return handle, nil, nil
|
return handle, displayName, nil, nil
|
||||||
}
|
}
|
||||||
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, *pv.Avatar, nil)
|
req, _ := http.NewRequestWithContext(ctx, http.MethodGet, *pv.Avatar, nil)
|
||||||
client := &http.Client{Timeout: 5 * time.Second}
|
client := &http.Client{Timeout: 5 * time.Second}
|
||||||
@@ -282,5 +285,5 @@ func (srv *Server) fetchProfile(ctx context.Context, did string) (handle string,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return handle, avatarBytes, nil
|
return handle, displayName, avatarBytes, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ type passField struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type storeCardFields struct {
|
type storeCardFields struct {
|
||||||
|
HeaderFields []passField `json:"headerFields"`
|
||||||
PrimaryFields []passField `json:"primaryFields"`
|
PrimaryFields []passField `json:"primaryFields"`
|
||||||
SecondaryFields []passField `json:"secondaryFields"`
|
SecondaryFields []passField `json:"secondaryFields"`
|
||||||
BackFields []passField `json:"backFields"`
|
BackFields []passField `json:"backFields"`
|
||||||
@@ -64,10 +65,14 @@ type pkPass struct {
|
|||||||
|
|
||||||
const passTypeIdentifier = "pass.xyz.blueskyweb.app"
|
const passTypeIdentifier = "pass.xyz.blueskyweb.app"
|
||||||
|
|
||||||
func BuildPassJSON(did, handle, theme, teamID string) ([]byte, error) {
|
func BuildPassJSON(did, handle, displayName, theme, teamID string) ([]byte, error) {
|
||||||
theme = CoerceTheme(theme)
|
theme = CoerceTheme(theme)
|
||||||
profileURL := "https://bsky.app/profile/" + handle
|
profileURL := "https://bsky.app/profile/" + handle
|
||||||
atHandle := "@" + handle
|
atHandle := "@" + handle
|
||||||
|
memberName := displayName
|
||||||
|
if memberName == "" {
|
||||||
|
memberName = atHandle
|
||||||
|
}
|
||||||
p := pkPass{
|
p := pkPass{
|
||||||
FormatVersion: 1,
|
FormatVersion: 1,
|
||||||
PassTypeIdentifier: passTypeIdentifier,
|
PassTypeIdentifier: passTypeIdentifier,
|
||||||
@@ -80,9 +85,12 @@ func BuildPassJSON(did, handle, theme, teamID string) ([]byte, error) {
|
|||||||
LabelColor: "rgb(255, 255, 255)",
|
LabelColor: "rgb(255, 255, 255)",
|
||||||
BackgroundColor: ThemeBackgroundRGB(theme),
|
BackgroundColor: ThemeBackgroundRGB(theme),
|
||||||
StoreCard: storeCardFields{
|
StoreCard: storeCardFields{
|
||||||
|
HeaderFields: []passField{
|
||||||
|
{Key: "username", Label: "USERNAME", Value: atHandle},
|
||||||
|
},
|
||||||
PrimaryFields: []passField{},
|
PrimaryFields: []passField{},
|
||||||
SecondaryFields: []passField{
|
SecondaryFields: []passField{
|
||||||
{Key: "handle", Label: "", Value: atHandle},
|
{Key: "name", Label: "Member Name", Value: memberName},
|
||||||
},
|
},
|
||||||
BackFields: []passField{
|
BackFields: []passField{
|
||||||
{Key: "about", Label: "About", Value: "Scan the QR code to view this Bluesky profile."},
|
{Key: "about", Label: "About", Value: "Scan the QR code to view this Bluesky profile."},
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ func TestThemeBackgroundRGB(t *testing.T) {
|
|||||||
func TestBuildPassJSON_Golden(t *testing.T) {
|
func TestBuildPassJSON_Golden(t *testing.T) {
|
||||||
for _, theme := range []string{"dawn", "day", "dusk", "night"} {
|
for _, theme := range []string{"dawn", "day", "dusk", "night"} {
|
||||||
t.Run(theme, func(t *testing.T) {
|
t.Run(theme, func(t *testing.T) {
|
||||||
got, err := BuildPassJSON("did:plc:abc123", "alice.bsky.social", theme, "TEAMID00")
|
got, err := BuildPassJSON("did:plc:abc123", "alice.bsky.social", "Alice", theme, "TEAMID00")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("build: %v", err)
|
t.Fatalf("build: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
+10
-3
@@ -10,12 +10,19 @@
|
|||||||
"labelColor": "rgb(255, 255, 255)",
|
"labelColor": "rgb(255, 255, 255)",
|
||||||
"backgroundColor": "rgb(255, 109, 190)",
|
"backgroundColor": "rgb(255, 109, 190)",
|
||||||
"storeCard": {
|
"storeCard": {
|
||||||
|
"headerFields": [
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"label": "USERNAME",
|
||||||
|
"value": "@alice.bsky.social"
|
||||||
|
}
|
||||||
|
],
|
||||||
"primaryFields": [],
|
"primaryFields": [],
|
||||||
"secondaryFields": [
|
"secondaryFields": [
|
||||||
{
|
{
|
||||||
"key": "handle",
|
"key": "name",
|
||||||
"label": "",
|
"label": "Member Name",
|
||||||
"value": "@alice.bsky.social"
|
"value": "Alice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"backFields": [
|
"backFields": [
|
||||||
|
|||||||
+10
-3
@@ -10,12 +10,19 @@
|
|||||||
"labelColor": "rgb(255, 255, 255)",
|
"labelColor": "rgb(255, 255, 255)",
|
||||||
"backgroundColor": "rgb(117, 175, 255)",
|
"backgroundColor": "rgb(117, 175, 255)",
|
||||||
"storeCard": {
|
"storeCard": {
|
||||||
|
"headerFields": [
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"label": "USERNAME",
|
||||||
|
"value": "@alice.bsky.social"
|
||||||
|
}
|
||||||
|
],
|
||||||
"primaryFields": [],
|
"primaryFields": [],
|
||||||
"secondaryFields": [
|
"secondaryFields": [
|
||||||
{
|
{
|
||||||
"key": "handle",
|
"key": "name",
|
||||||
"label": "",
|
"label": "Member Name",
|
||||||
"value": "@alice.bsky.social"
|
"value": "Alice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"backFields": [
|
"backFields": [
|
||||||
|
|||||||
+10
-3
@@ -10,12 +10,19 @@
|
|||||||
"labelColor": "rgb(255, 255, 255)",
|
"labelColor": "rgb(255, 255, 255)",
|
||||||
"backgroundColor": "rgb(177, 90, 162)",
|
"backgroundColor": "rgb(177, 90, 162)",
|
||||||
"storeCard": {
|
"storeCard": {
|
||||||
|
"headerFields": [
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"label": "USERNAME",
|
||||||
|
"value": "@alice.bsky.social"
|
||||||
|
}
|
||||||
|
],
|
||||||
"primaryFields": [],
|
"primaryFields": [],
|
||||||
"secondaryFields": [
|
"secondaryFields": [
|
||||||
{
|
{
|
||||||
"key": "handle",
|
"key": "name",
|
||||||
"label": "",
|
"label": "Member Name",
|
||||||
"value": "@alice.bsky.social"
|
"value": "Alice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"backFields": [
|
"backFields": [
|
||||||
|
|||||||
+10
-3
@@ -10,12 +10,19 @@
|
|||||||
"labelColor": "rgb(255, 255, 255)",
|
"labelColor": "rgb(255, 255, 255)",
|
||||||
"backgroundColor": "rgb(0, 21, 51)",
|
"backgroundColor": "rgb(0, 21, 51)",
|
||||||
"storeCard": {
|
"storeCard": {
|
||||||
|
"headerFields": [
|
||||||
|
{
|
||||||
|
"key": "username",
|
||||||
|
"label": "USERNAME",
|
||||||
|
"value": "@alice.bsky.social"
|
||||||
|
}
|
||||||
|
],
|
||||||
"primaryFields": [],
|
"primaryFields": [],
|
||||||
"secondaryFields": [
|
"secondaryFields": [
|
||||||
{
|
{
|
||||||
"key": "handle",
|
"key": "name",
|
||||||
"label": "",
|
"label": "Member Name",
|
||||||
"value": "@alice.bsky.social"
|
"value": "Alice"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"backFields": [
|
"backFields": [
|
||||||
|
|||||||
Reference in New Issue
Block a user