dmitri.shuralyov.com/service/change/...

githubapi: Add support for bot actors.

Otherwise, they were showing up as ghost users.
dmitshur committed 2 years ago commit caf5deb01491616203ca0ed56937ef94650f29a0
githubapi/githubapi.go
@@ -218,11 +218,11 @@ func (s service) ListCommits(ctx context.Context, rs string, id uint64) ([]chang
 	var commits []changes.Commit
 	for _, c := range cs {
 		commits = append(commits, changes.Commit{
 			SHA:        *c.SHA,
 			Message:    *c.Commit.Message,
-			Author:     ghUser(c.Author),
+			Author:     ghV3User(c.Author),
 			AuthorTime: *c.Commit.Author.Date,
 		})
 	}
 	return commits, nil
 }
@@ -549,40 +549,59 @@ func ghRepoSpec(rs string) (repoSpec, error) {
 
 type githubqlActor struct {
 	User struct {
 		DatabaseID uint64
 	} `graphql:"...on User"`
+	Bot struct {
+		DatabaseID uint64
+	} `graphql:"...on Bot"`
 	Login     string
 	AvatarURL string `graphql:"avatarUrl(size:96)"`
 	URL       string
 }
 
 func ghActor(actor githubqlActor) users.User {
-	if actor.User.DatabaseID == 0 {
-		// Deleted user, replace with https://github.com/ghost.
-		return users.User{
-			UserSpec: users.UserSpec{
-				ID:     10137,
-				Domain: "github.com",
-			},
-			Login:     "ghost",
-			AvatarURL: "https://avatars3.githubusercontent.com/u/10137?v=4",
-			HTMLURL:   "https://github.com/ghost",
-		}
+	if actor.User.DatabaseID == 0 && actor.Bot.DatabaseID == 0 {
+		return ghost // Deleted user, replace with https://github.com/ghost.
 	}
 	return users.User{
 		UserSpec: users.UserSpec{
-			ID:     actor.User.DatabaseID,
+			ID:     actor.User.DatabaseID | actor.Bot.DatabaseID,
 			Domain: "github.com",
 		},
 		Login:     actor.Login,
 		AvatarURL: actor.AvatarURL,
 		HTMLURL:   actor.URL,
 	}
 }
 
-func ghUser(user *github.User) users.User {
+type githubqlUser struct {
+	DatabaseID uint64
+	Login      string
+	AvatarURL  string `graphql:"avatarUrl(size:96)"`
+	URL        string
+}
+
+func ghUser(user githubqlUser) users.User {
+	if user.DatabaseID == 0 {
+		return ghost // Deleted user, replace with https://github.com/ghost.
+	}
+	return users.User{
+		UserSpec: users.UserSpec{
+			ID:     user.DatabaseID,
+			Domain: "github.com",
+		},
+		Login:     user.Login,
+		AvatarURL: user.AvatarURL,
+		HTMLURL:   user.URL,
+	}
+}
+
+func ghV3User(user *github.User) users.User {
+	if *user.ID == 0 {
+		return ghost // Deleted user, replace with https://github.com/ghost.
+	}
 	return users.User{
 		UserSpec: users.UserSpec{
 			ID:     uint64(*user.ID),
 			Domain: "github.com",
 		},
@@ -590,10 +609,21 @@ func ghUser(user *github.User) users.User {
 		AvatarURL: *user.AvatarURL,
 		HTMLURL:   *user.HTMLURL,
 	}
 }
 
+// ghost is https://github.com/ghost, a replacement for deleted users.
+var ghost = users.User{
+	UserSpec: users.UserSpec{
+		ID:     10137,
+		Domain: "github.com",
+	},
+	Login:     "ghost",
+	AvatarURL: "https://avatars3.githubusercontent.com/u/10137?v=4",
+	HTMLURL:   "https://github.com/ghost",
+}
+
 // ghPRState converts a GitHub PullRequestState to changes.State.
 func ghPRState(state githubql.PullRequestState) changes.State {
 	switch state {
 	case githubql.PullRequestStateOpen:
 		return changes.OpenState
@@ -615,11 +645,11 @@ func ghColor(hex string) issues.RGB {
 }
 
 type reactionGroups []struct {
 	Content githubql.ReactionContent
 	Users   struct {
-		Nodes      []githubqlActor
+		Nodes      []githubqlUser
 		TotalCount int
 	} `graphql:"users(first:10)"`
 	ViewerHasReacted bool
 }
 
@@ -634,11 +664,11 @@ func (s service) reactions(rgs reactionGroups) ([]reactions.Reaction, error) {
 		// Only return the details of first few users and authed user.
 		var us []users.User
 		addedAuthedUser := false
 		for i := 0; i < rg.Users.TotalCount; i++ {
 			if i < len(rg.Users.Nodes) {
-				actor := ghActor(rg.Users.Nodes[i])
+				actor := ghUser(rg.Users.Nodes[i])
 				us = append(us, actor)
 				if s.currentUser.ID != 0 && actor.UserSpec == s.currentUser.UserSpec {
 					addedAuthedUser = true
 				}
 			} else if i == len(rg.Users.Nodes) {