func (s *Service) fetchPullRequestMerged(ctx context.Context, prURL string) (bool, error) {
	req, err := s.clV3.NewRequest("GET", prURL+"/merge", nil)
	if err != nil {
		return false, err
	}
	resp, err := s.clV3.Do(ctx, req, nil)
	switch e, ok := err.(*githubv3.ErrorResponse); {
	case err == nil && resp.StatusCode == http.StatusNoContent:
		return true, nil
	case ok && e.Response.StatusCode == http.StatusNotFound:
		return false, nil
	case err != nil:
		return false, err
	default:
		body, _ := ioutil.ReadAll(resp.Body)
		return false, fmt.Errorf("unexpected status code: %v body: %q", resp.Status, body)
	}
}
fetchPullRequestMerged fetches whether the Pull Request at the API URL is merged
at current time.
package github
import (
	"bytes"
	"context"
	"encoding/base64"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"runtime/debug"
	"strconv"
	"strings"
	"time"
	"dmitri.shuralyov.com/go/prefixtitle"
	"dmitri.shuralyov.com/route/github"
	"dmitri.shuralyov.com/state"
	githubv3 "github.com/google/go-github/github"
	"github.com/shurcooL/events/event"
	"github.com/shurcooL/githubv4"
	"github.com/shurcooL/users"
	"golang.org/x/mod/modfile"
)
func (s *Service) pollList() (err error) {
	defer func() {
		if e := recover(); e != nil {
			err = fmt.Errorf("internal panic: %v\n\n%s", e, debug.Stack())
		}
	}()
	for {
		s.list.mu.Lock()
		repos := make(map[int64]repository, len(s.list.repos))
		for id, r := range s.list.repos {
			repos[id] = r
		}
		commits := make(map[string]event.Commit, len(s.list.commits))
		for sha, c := range s.list.commits {
			commits[sha] = c
		}
		s.list.mu.Unlock()
		events, repos, commits, prs, eventIDs, pollInterval, fetchError := s.fetchEvents(context.Background(), repos, commits)
		if fetchError != nil {
			log.Println("fetchEvents:", fetchError)
		}
		s.list.mu.Lock()
		if fetchError == nil {
			s.list.events, s.list.repos, s.list.commits, s.list.prs, s.list.eventIDs = events, repos, commits, prs, eventIDs
		}
		s.list.fetchError = fetchError
		s.list.mu.Unlock()
		if pollInterval < time.Minute {
			pollInterval = time.Minute
		}
		time.Sleep(pollInterval)
	}
}
func (s *Service) fetchEvents(
	ctx context.Context,
	repos map[int64]repository,  
	commits map[string]event.Commit,  
) (
	events []*githubv3.Event,
	_ map[int64]repository,  
	_ map[string]event.Commit,  
	prs map[string]bool,  
	eventIDs map[*githubv3.Event]uint64,  
	pollInterval time.Duration,
	err error,
) {
	events, resp, err := s.clV3.Activity.ListEventsPerformedByUser(ctx, s.user.Login, true, &githubv3.ListOptions{PerPage: 100})
	if err != nil {
		return nil, nil, nil, nil, nil, 0, err
	}
	if pi, err := strconv.Atoi(resp.Header.Get("X-Poll-Interval")); err == nil {
		pollInterval = time.Duration(pi) * time.Second
	}
	prs = make(map[string]bool)
	eventIDs = make(map[*githubv3.Event]uint64)
	usedRepos := make(map[int64]bool)     
	usedCommits := make(map[string]bool)  
	for _, e := range events {
		payload, err := e.ParsePayload()
		if err != nil {
			return nil, nil, nil, nil, nil, 0, fmt.Errorf("fetchEvents: ParsePayload failed: %v", err)
		}
		usedRepos[*e.Repo.ID] = true
		if _, ok := repos[*e.Repo.ID]; !ok {
			modulePath, err := s.fetchModulePath(ctx, *e.Repo.ID, "github.com/"+*e.Repo.Name)
			if err != nil && strings.HasPrefix(err.Error(), "Could not resolve to a node ") {  
				log.Printf("fetchModulePath: repository id=%d name=%q was not found: %v\n", *e.Repo.ID, *e.Repo.Name, err)
				modulePath = "github.com/" + *e.Repo.Name
			} else if err != nil {
				return nil, nil, nil, nil, nil, 0, fmt.Errorf("fetchModulePath: %v", err)
			}
			repos[*e.Repo.ID] = repository{ModulePath: modulePath}
		}
		switch p := payload.(type) {
		case *githubv3.PushEvent:
			for _, c := range p.Commits {
				usedCommits[*c.SHA] = true
				if _, ok := commits[*c.SHA]; ok {
					continue
				}
				commit, err := s.fetchCommit(ctx, *e.Repo.ID, *c.SHA)
				if err != nil && strings.HasPrefix(err.Error(), "Could not resolve to a node ") {  
					log.Printf("fetchEvents: commit %s@%s was not found: %v\n", *e.Repo.Name, *c.SHA, err)
					avatarURL := "https://secure.gravatar.com/avatar?d=mm&f=y&s=96"
					if *c.Author.Email == s.user.Email {
						avatarURL = s.user.AvatarURL
					}
					commit = event.Commit{
						SHA:             *c.SHA,
						Message:         *c.Message,
						AuthorAvatarURL: avatarURL,
					}
				} else if err != nil {
					return nil, nil, nil, nil, nil, 0, fmt.Errorf("fetchCommit: %v", err)
				}
				commits[*c.SHA] = commit
			}
		case *githubv3.CommitCommentEvent:
			usedCommits[*p.Comment.CommitID] = true
			if _, ok := commits[*p.Comment.CommitID]; ok {
				continue
			}
			commit, err := s.fetchCommit(ctx, *e.Repo.ID, *p.Comment.CommitID)
			if err != nil && strings.HasPrefix(err.Error(), "Could not resolve to a node ") {  
				log.Printf("fetchEvents: commit %s@%s was not found: %v\n", *e.Repo.Name, *p.Comment.CommitID, err)
				commit = event.Commit{
					SHA:             *p.Comment.CommitID,
					AuthorAvatarURL: "https://secure.gravatar.com/avatar?d=mm&f=y&s=96",
				}
			} else if err != nil {
				return nil, nil, nil, nil, nil, 0, fmt.Errorf("fetchCommit: %v", err)
			}
			commits[*p.Comment.CommitID] = commit
		case *githubv3.IssueCommentEvent:
			if p.Issue.PullRequestLinks == nil {
				continue
			}
			if _, ok := prs[*p.Issue.PullRequestLinks.URL]; ok {
				continue
			}
			merged, err := s.fetchPullRequestMerged(ctx, *p.Issue.PullRequestLinks.URL)
			if err != nil {
				return nil, nil, nil, nil, nil, 0, fmt.Errorf("fetchPullRequestMerged: %v", err)
			}
			prs[*p.Issue.PullRequestLinks.URL] = merged
		case *githubv3.IssuesEvent:
			if *p.Action == "closed" || *p.Action == "reopened" {
				id, err := s.fetchEventID(ctx, e)
				if err != nil {
					log.Printf("fetchEvents: fetchEventID: %v\n", err)
				} else {
					eventIDs[e] = id
				}
			}
		case *githubv3.PullRequestEvent:
			if *p.Action == "closed" || *p.Action == "reopened" {
				id, err := s.fetchEventID(ctx, e)
				if err != nil {
					log.Printf("fetchEvents: fetchEventID: %v\n", err)
				} else {
					eventIDs[e] = id
				}
			}
		}
	}
	for id := range repos {
		if !usedRepos[id] {
			delete(repos, id)
		}
	}
	for sha := range commits {
		if !usedCommits[sha] {
			delete(commits, sha)
		}
	}
	return events, repos, commits, prs, eventIDs, pollInterval, nil
}
const goRepoID = 23096959
func (s *Service) fetchModulePath(ctx context.Context, repoID int64, repoPath string) (modulePath string, _ error) {
	if repoID == goRepoID {
		return "", nil
	}
	var q struct {
		Node struct {
			Repository struct {
				Object *struct {
					Blob struct {
						Text string
					} `graphql:"...on Blob"`
				} `graphql:"object(expression:\"HEAD:go.mod\")"`
			} `graphql:"...on Repository"`
		} `graphql:"node(id:$repoID)"`
	}
	variables := map[string]interface{}{
		"repoID": githubv4.ID(base64.StdEncoding.EncodeToString([]byte(fmt.Sprintf("010:Repository%d", repoID)))),  
	}
	err := s.clV4.Query(ctx, &q, variables)
	if err != nil {
		return "", err
	}
	if q.Node.Repository.Object == nil {
		return repoPath, nil
	}
	modulePath = modfile.ModulePath([]byte(q.Node.Repository.Object.Blob.Text))
	if modulePath == "" {
		return repoPath, nil
	}
	return modulePath, nil
}
func (s *Service) fetchCommit(ctx context.Context, repoID int64, sha string) (event.Commit, error) {
	commitID := fmt.Sprintf("06:Commit%d:%s", repoID, sha)
	var q struct {
		Node struct {
			Commit struct {
				OID     string
				Message string
				Author  struct {
					AvatarURL string `graphql:"avatarUrl(size:96)"`
				}
				URL string
			} `graphql:"...on Commit"`
		} `graphql:"node(id:$commitID)"`
	}
	variables := map[string]interface{}{
		"commitID": githubv4.ID(base64.StdEncoding.EncodeToString([]byte(commitID))),  
	}
	err := s.clV4.Query(ctx, &q, variables)
	if err != nil {
		return event.Commit{}, err
	}
	return event.Commit{
		SHA:             q.Node.Commit.OID,
		Message:         q.Node.Commit.Message,
		AuthorAvatarURL: q.Node.Commit.Author.AvatarURL,
		HTMLURL:         q.Node.Commit.URL,
	}, nil
}