github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/externals/proof_service_reddit.go (about)

     1  // Copyright 2015 Keybase, Inc. All rights reserved. Use of
     2  // this source code is governed by the included BSD license.
     3  
     4  package externals
     5  
     6  import (
     7  	"net/url"
     8  	"regexp"
     9  	"strings"
    10  
    11  	libkb "github.com/keybase/client/go/libkb"
    12  	keybase1 "github.com/keybase/client/go/protocol/keybase1"
    13  	jsonw "github.com/keybase/go-jsonw"
    14  )
    15  
    16  // =============================================================================
    17  // Reddit
    18  //
    19  
    20  type RedditChecker struct {
    21  	proof libkb.RemoteProofChainLink
    22  }
    23  
    24  var _ libkb.ProofChecker = (*RedditChecker)(nil)
    25  
    26  const (
    27  	RedditPrefix = "https://www.reddit.com"
    28  	RedditSub    = RedditPrefix + "/r/keybaseproofs"
    29  )
    30  
    31  func NewRedditChecker(p libkb.RemoteProofChainLink) (*RedditChecker, libkb.ProofError) {
    32  	return &RedditChecker{p}, nil
    33  }
    34  
    35  func (rc *RedditChecker) GetTorError() libkb.ProofError { return nil }
    36  
    37  func (rc *RedditChecker) CheckStatus(mctx libkb.MetaContext, h libkb.SigHint, _ libkb.ProofCheckerMode,
    38  	pvlU keybase1.MerkleStoreEntry) (*libkb.SigHint, libkb.ProofError) {
    39  	// TODO CORE-8951 see if we can populate verifiedHint with anything useful.
    40  	return nil, CheckProofPvl(mctx, keybase1.ProofType_REDDIT, rc.proof, h, pvlU)
    41  }
    42  
    43  //
    44  // =============================================================================
    45  
    46  func urlReencode(s string) string {
    47  	// Reddit interprets plusses in the query string differently depending
    48  	// on whether the user is using the old or new (2018) design, and
    49  	// on whether it's the title or body of the post.
    50  	// old,     *, '+'   -> ' '
    51  	// old,     *, '%2B' -> '+'
    52  	// new, title, '+'   -> ' '
    53  	// new, title, '%2B' -> ' '
    54  	// new,  body, '+'   -> '+'
    55  	// new,  body, '%2B' -> '+'
    56  
    57  	// Examples:
    58  	// https://www.reddit.com/r/test/submit?text=content+fmt%0Aplus+x%2By%20z&title=title+fmt%0Aplus+x%2By%20z
    59  	// old: https://www.reddit.com/r/test/comments/8eee0e/title_fmt_plus_xy_z/
    60  	// new: https://www.reddit.com/r/test/comments/8eelwf/title_fmt_plus_x_y_z/
    61  
    62  	// Replace '(', ")" and "'" so that URL-detection works in Linux
    63  	// Padding is not needed now, but might be in the future depending on
    64  	// changes we make
    65  	rxx := regexp.MustCompile(`[()'+]`)
    66  	s = rxx.ReplaceAllStringFunc(s, func(r string) string {
    67  		switch r {
    68  		case `(`:
    69  			return `%28`
    70  		case `)`:
    71  			return `%29`
    72  		case `'`:
    73  			return `%27`
    74  		case `+`:
    75  			// HTTPArgs.EncodeToString has encoded ' ' -> '+'
    76  			// we recode '+' -> '%20'.
    77  			return `%20`
    78  		default:
    79  			return r
    80  		}
    81  	})
    82  	return s
    83  }
    84  
    85  type RedditServiceType struct{ libkb.BaseServiceType }
    86  
    87  func (t *RedditServiceType) Key() string { return t.GetTypeName() }
    88  
    89  var redditUsernameRegexp = regexp.MustCompile(`^(?i:[a-z0-9_-]{3,20})$`)
    90  
    91  func (t *RedditServiceType) NormalizeUsername(s string) (string, error) {
    92  	if !redditUsernameRegexp.MatchString(s) {
    93  		return "", libkb.NewBadUsernameError(s)
    94  	}
    95  	return strings.ToLower(s), nil
    96  }
    97  
    98  func (t *RedditServiceType) NormalizeRemoteName(mctx libkb.MetaContext, s string) (ret string, err error) {
    99  	return t.NormalizeUsername(s)
   100  }
   101  
   102  func (t *RedditServiceType) GetTypeName() string   { return "reddit" }
   103  func (t *RedditServiceType) PickerSubtext() string { return "reddit.com" }
   104  
   105  func (t *RedditServiceType) GetPrompt() string { return "Your username on Reddit" }
   106  
   107  func (t *RedditServiceType) ToServiceJSON(un string) *jsonw.Wrapper {
   108  	return t.BaseToServiceJSON(t, un)
   109  }
   110  
   111  func (t *RedditServiceType) PostInstructions(un string) *libkb.Markup {
   112  	return libkb.FmtMarkup(`Please click on the following link to post to Reddit:`)
   113  }
   114  
   115  func (t *RedditServiceType) FormatProofText(mctx libkb.MetaContext, ppr *libkb.PostProofRes,
   116  	kbUsername, remoteUsername string, sigID keybase1.SigID) (res string, err error) {
   117  	var title string
   118  	if title, err = ppr.Metadata.AtKey("title").GetString(); err != nil {
   119  		return
   120  	}
   121  
   122  	urlPre := libkb.HTTPArgs{"title": libkb.S{Val: title}, "text": libkb.S{Val: ppr.Text}}.EncodeToString()
   123  	q := urlReencode(urlPre)
   124  
   125  	chooseHost := func(untrustedHint, trustedDefault string) string {
   126  		allowedHosts := []string{
   127  			"reddit.com",
   128  			"www.reddit.com",
   129  			// 2017-04-18: The new reddit mobile site doesn't respect the post-pre-populate query parameters.
   130  			//             The i.reddit.com site may be better.
   131  			"i.reddit.com",
   132  			"old.reddit.com",
   133  		}
   134  		if untrustedHint == "" {
   135  			return trustedDefault
   136  		}
   137  		for _, h := range allowedHosts {
   138  			if untrustedHint == h {
   139  				return h
   140  			}
   141  		}
   142  		return trustedDefault
   143  	}
   144  	var host string
   145  	if mctx.G().IsMobileAppType() {
   146  		hostHint, err := ppr.Metadata.AtKey("mobile_host").GetString()
   147  		if err != nil {
   148  			hostHint = ""
   149  		}
   150  		host = chooseHost(hostHint, "old.reddit.com")
   151  	} else {
   152  		// Note that GetAppType() often returns libkb.NoAppType. Don't assume that we get
   153  		// libkb.DesktopAppType in the non-mobile case.
   154  		// Use the old reddit design until this bug is fixed:
   155  		// https://www.reddit.com/r/redesign/comments/8evfap/bug_post_contents_gets_dropped_when_using/
   156  		hostHint, err := ppr.Metadata.AtKey("other_host").GetString()
   157  		if err != nil {
   158  			hostHint = ""
   159  		}
   160  		host = chooseHost(hostHint, "old.reddit.com")
   161  	}
   162  
   163  	u := url.URL{
   164  		Scheme:   "https",
   165  		Host:     host,
   166  		Path:     "/r/KeybaseProofs/submit",
   167  		RawQuery: q,
   168  	}
   169  
   170  	res = u.String()
   171  	return
   172  }
   173  
   174  func (t *RedditServiceType) DisplayName() string { return "Reddit" }
   175  
   176  func (t *RedditServiceType) RecheckProofPosting(tryNumber int, status keybase1.ProofStatus, _ string) (warning *libkb.Markup, err error) {
   177  	warning, err = t.BaseRecheckProofPosting(tryNumber, status)
   178  	return
   179  }
   180  
   181  func (t *RedditServiceType) GetProofType() string { return t.BaseGetProofType(t) }
   182  
   183  func (t *RedditServiceType) CheckProofText(text string, id keybase1.SigID, sig string) (err error) {
   184  	// Anything is fine. We might get rid of the body later.
   185  	return nil
   186  }
   187  
   188  func (t *RedditServiceType) MakeProofChecker(l libkb.RemoteProofChainLink) libkb.ProofChecker {
   189  	return &RedditChecker{l}
   190  }